Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: a3d7db6f4be8546a0bcd8a76243672043c711641 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * This is an implementation of an early-draft specification developed under the Java
 * Community Process (JCP) and is made available for testing and evaluation purposes
 * only. The code is not compatible with any specification of the JCP.
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contributions for
 *								bug 349326 - [1.7] new warning for missing try-with-resources
 *								bug 186342 - [compiler][null] Using annotations for null checking
 *								bug 365519 - editorial cleanup after bug 186342 and bug 365387
 *								bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK
 *								bug 382353 - [1.8][compiler] Implementation property modifiers should be accepted on default methods.
 *								bug 383368 - [compiler][null] syntactic null analysis for field references
 *								Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
 *								Bug 392238 - [1.8][compiler][null] Detect semantically invalid null type annotations
 *								Bug 416176 - [1.8][compiler][null] null type annotations cause grief on type variables
 *								Bug 438012 - [1.8][null] Bogus Warning: The nullness annotation is redundant with a default that applies to this location
 *								Bug 435805 - [1.8][compiler][null] Java 8 compiler does not recognize declaration style null annotations
 *								Bug 466713 - Null Annotations: NullPointerException using <int @Nullable []> as Type Param
 *     Jesper S Moller <jesper@selskabet.org> - Contributions for
 *								bug 378674 - "The method can be declared as static" is wrong
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import java.util.List;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.flow.ExceptionHandlingFlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.MemberTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.AbortMethod;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationCollector;
import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationPosition;

public class MethodDeclaration extends AbstractMethodDeclaration {

	public TypeReference returnType;
	public TypeParameter[] typeParameters;
	public boolean isImplicit; // used in Java 14 Records

	/**
	 * MethodDeclaration constructor comment.
	 */
	public MethodDeclaration(CompilationResult compilationResult) {
		super(compilationResult);
		this.bits |= ASTNode.CanBeStatic; // Start with this assumption, will course correct during resolve and analyseCode.
	}

	public void analyseCode(ClassScope classScope, FlowContext flowContext, FlowInfo flowInfo) {
		// starting of the code analysis for methods
		if (this.ignoreFurtherInvestigation)
			return;
		try {
			if (this.binding == null)
				return;

			if (!this.binding.isUsed() && !this.binding.isAbstract()) {
				if (this.binding.isPrivate()
					|| (((this.binding.modifiers & (ExtraCompilerModifiers.AccOverriding|ExtraCompilerModifiers.AccImplementing)) == 0)
						&& this.binding.isOrEnclosedByPrivateType())) {
					if (!classScope.referenceCompilationUnit().compilationResult.hasSyntaxError) {
						this.scope.problemReporter().unusedPrivateMethod(this);
					}
				}
			}

			// skip enum implicit methods
			if (this.binding.declaringClass.isEnum() && (this.selector == TypeConstants.VALUES || this.selector == TypeConstants.VALUEOF))
				return;

			// may be in a non necessary <clinit> for innerclass with static final constant fields
			if (this.binding.isAbstract() || this.binding.isNative())
				return;
			
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=385780
			if (this.typeParameters != null &&
					!this.scope.referenceCompilationUnit().compilationResult.hasSyntaxError) {
				for (int i = 0, length = this.typeParameters.length; i < length; ++i) {
					TypeParameter typeParameter = this.typeParameters[i];
					if ((typeParameter.binding.modifiers  & ExtraCompilerModifiers.AccLocallyUsed) == 0) {
						this.scope.problemReporter().unusedTypeParameter(typeParameter);						
					}
				}
			}
			ExceptionHandlingFlowContext methodContext =
				new ExceptionHandlingFlowContext(
					flowContext,
					this,
					this.binding.thrownExceptions,
					null,
					this.scope,
					FlowInfo.DEAD_END);

			// nullity and mark as assigned
			analyseArguments(classScope.environment(), flowInfo, this.arguments, this.binding);

			if (this.binding.declaringClass instanceof MemberTypeBinding && !this.binding.declaringClass.isStatic()) {
				// method of a non-static member type can't be static.
				this.bits &= ~ASTNode.CanBeStatic;
			}
			// propagate to statements
			if (this.statements != null) {
				boolean enableSyntacticNullAnalysisForFields = this.scope.compilerOptions().enableSyntacticNullAnalysisForFields;
				int complaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) == 0 ? Statement.NOT_COMPLAINED : Statement.COMPLAINED_FAKE_REACHABLE;
				for (int i = 0, count = this.statements.length; i < count; i++) {
					Statement stat = this.statements[i];
					if ((complaintLevel = stat.complainIfUnreachable(flowInfo, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) {
						flowInfo = stat.analyseCode(this.scope, methodContext, flowInfo);
					}
					if (enableSyntacticNullAnalysisForFields) {
						methodContext.expireNullCheckedFieldInfo();
					}
				}
			} else {
				// method with empty body should not be flagged as static.
				this.bits &= ~ASTNode.CanBeStatic;
			}
			// check for missing returning path
			TypeBinding returnTypeBinding = this.binding.returnType;
			if ((returnTypeBinding == TypeBinding.VOID) || isAbstract()) {
				if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) {
					this.bits |= ASTNode.NeedFreeReturn;
				}
			} else {
				if (flowInfo != FlowInfo.DEAD_END) {
					this.scope.problemReporter().shouldReturn(returnTypeBinding, this);
				}
			}
			// check unreachable catch blocks
			methodContext.complainIfUnusedExceptionHandlers(this);
			// check unused parameters
			this.scope.checkUnusedParameters(this.binding);
			// check if the method could have been static
			if (!this.binding.isStatic() && (this.bits & ASTNode.CanBeStatic) != 0 && !this.isDefaultMethod()) {
				if(!this.binding.isOverriding() && !this.binding.isImplementing()) {
					if (this.binding.isPrivate() || this.binding.isFinal() || this.binding.declaringClass.isFinal()) {
						this.scope.problemReporter().methodCanBeDeclaredStatic(this);
					} else {
						this.scope.problemReporter().methodCanBePotentiallyDeclaredStatic(this);
					}
				}
					
			}
			this.scope.checkUnclosedCloseables(flowInfo, null, null/*don't report against a specific location*/, null);
		} catch (AbortMethod e) {
			this.ignoreFurtherInvestigation = true;
		}
	}

	@Override
	public void getAllAnnotationContexts(int targetType, List allAnnotationContexts) {
		AnnotationCollector collector = new AnnotationCollector(this.returnType, targetType, allAnnotationContexts);
		for (int i = 0, max = this.annotations.length; i < max; i++) {
			Annotation annotation = this.annotations[i];
			annotation.traverse(collector, (BlockScope) null);
		}
	}
	
	public boolean hasNullTypeAnnotation(AnnotationPosition position) {
		// parser associates SE8 annotations to the declaration
		return TypeReference.containsNullAnnotation(this.annotations) || 
				(this.returnType != null && this.returnType.hasNullTypeAnnotation(position)); // just in case
	}

	@Override
	public boolean isDefaultMethod() {
		return (this.modifiers & ExtraCompilerModifiers.AccDefaultMethod) != 0;
	}

	@Override
	public boolean isMethod() {
		return true;
	}

	@Override
	public Argument getRecordComponent() {
		if (this.arguments != null && this.arguments.length != 0)
			return null;
		ClassScope skope = this.scope.classScope();
		if (!(skope.referenceContext instanceof RecordDeclaration))
			return null;
		RecordDeclaration rd = (RecordDeclaration) skope.referenceContext;
		Argument[] args = rd.getArgs();
		if (args == null || args.length == 0)
			return null;
		for (Argument arg : rd.getArgs()) {
			if (arg == null || arg.name == null)
				continue;
			if (CharOperation.equals(this.selector, arg.name)) {
				return arg;
			}
		}
		return null;
	}

	@Override
	public void parseStatements(Parser parser, CompilationUnitDeclaration unit) {
		//fill up the method body with statement
		parser.parse(this, unit);
	}

	@Override
	public StringBuffer printReturnType(int indent, StringBuffer output) {
		if (this.returnType == null) return output;
		return this.returnType.printExpression(0, output).append(' ');
	}

	@Override
	public void resolveStatements() {
		// ========= abort on fatal error =============
		if (this.returnType != null && this.binding != null) {
			this.bits |= (this.returnType.bits & ASTNode.HasTypeAnnotations);
			this.returnType.resolvedType = this.binding.returnType;
			// record the return type binding
		}
		Argument recordComponent = getRecordComponent();
		if (recordComponent != null) {
			/* JLS 14 Records Sec 8.10.3 */
			if (TypeBinding.notEquals(this.returnType.resolvedType, recordComponent.type.resolvedType))
				this.scope.problemReporter().recordIllegalAccessorReturnType(this.returnType, recordComponent.type.resolvedType);
			if (this.typeParameters != null)
				this.scope.problemReporter().recordAccessorMethodShouldNotBeGeneric(this);
			if ((this.binding.modifiers & ClassFileConstants.AccPublic) == 0)
				this.scope.problemReporter().recordAccessorMethodShouldBePublic(this);
			if ((this.binding.modifiers & ClassFileConstants.AccStatic) != 0)
				this.scope.problemReporter().recordAccessorMethodShouldNotBeStatic(this);
			if (this.thrownExceptions != null)
				this.scope.problemReporter().recordAccessorMethodHasThrowsClause(this);
		}
		// check if method with constructor name
		if (CharOperation.equals(this.scope.enclosingSourceType().sourceName, this.selector)) {
			this.scope.problemReporter().methodWithConstructorName(this);
		}
		// to check whether the method returns a type parameter not declared by it.
		boolean returnsUndeclTypeVar = false;
		if (this.returnType != null && this.returnType.resolvedType instanceof TypeVariableBinding) {
			returnsUndeclTypeVar = true;
		}
		if (this.typeParameters != null) {
			for (int i = 0, length = this.typeParameters.length; i < length; i++) {
				TypeParameter typeParameter = this.typeParameters[i];
				this.bits |= (typeParameter.bits & ASTNode.HasTypeAnnotations);
				// typeParameter is already resolved from Scope#connectTypeVariables()
				if (returnsUndeclTypeVar && TypeBinding.equalsEquals(this.typeParameters[i].binding, this.returnType.resolvedType)) {
					returnsUndeclTypeVar = false;
				}
			}
		}

		// check @Override annotation
		final CompilerOptions compilerOptions = this.scope.compilerOptions();
		checkOverride: {
			if (this.binding == null) break checkOverride;
			long complianceLevel = compilerOptions.complianceLevel;
			if (complianceLevel < ClassFileConstants.JDK1_5) break checkOverride;
			int bindingModifiers = this.binding.modifiers;
			boolean hasOverrideAnnotation = (this.binding.tagBits & TagBits.AnnotationOverride) != 0;
			boolean hasUnresolvedArguments = (this.binding.tagBits & TagBits.HasUnresolvedArguments) != 0;
			if (hasOverrideAnnotation  && !hasUnresolvedArguments) {
				// no static method is considered overriding
				if ((bindingModifiers & (ClassFileConstants.AccStatic|ExtraCompilerModifiers.AccOverriding)) == ExtraCompilerModifiers.AccOverriding)
					break checkOverride;
				//	in 1.5, strictly for overriding superclass method
				//	in 1.6 and above, also tolerate implementing interface method
				if (complianceLevel >= ClassFileConstants.JDK1_6
						&& ((bindingModifiers & (ClassFileConstants.AccStatic|ExtraCompilerModifiers.AccImplementing)) == ExtraCompilerModifiers.AccImplementing))
					break checkOverride;
				// claims to override, and doesn't actually do so
				this.scope.problemReporter().methodMustOverride(this, complianceLevel);
			} else {
				//In case of  a concrete class method, we have to check if it overrides(in 1.5 and above) OR implements a method(1.6 and above).
				//Also check if the method has a signature that is override-equivalent to that of any public method declared in Object.
				if (!this.binding.declaringClass.isInterface()){
						if((bindingModifiers & (ClassFileConstants.AccStatic|ExtraCompilerModifiers.AccOverriding)) == ExtraCompilerModifiers.AccOverriding) {
							this.scope.problemReporter().missingOverrideAnnotation(this);
						} else {
							if(complianceLevel >= ClassFileConstants.JDK1_6
								&& compilerOptions.reportMissingOverrideAnnotationForInterfaceMethodImplementation
								&& this.binding.isImplementing()) {
									// actually overrides, but did not claim to do so
									this.scope.problemReporter().missingOverrideAnnotationForInterfaceMethodImplementation(this);
							}
							
						}
				}
				else {	//For 1.6 and above only
					//In case of a interface class method, we have to check if it overrides a method (isImplementing returns true in case it overrides)
					//Also check if the method has a signature that is override-equivalent to that of any public method declared in Object.
					if(complianceLevel >= ClassFileConstants.JDK1_6
							&& compilerOptions.reportMissingOverrideAnnotationForInterfaceMethodImplementation
							&& (((bindingModifiers & (ClassFileConstants.AccStatic|ExtraCompilerModifiers.AccOverriding)) == ExtraCompilerModifiers.AccOverriding) || this.binding.isImplementing())){
						// actually overrides, but did not claim to do so
						this.scope.problemReporter().missingOverrideAnnotationForInterfaceMethodImplementation(this);
					}
				}
			}
		}

		switch (TypeDeclaration.kind(this.scope.referenceType().modifiers)) {
			case TypeDeclaration.ENUM_DECL :
				if (this.selector == TypeConstants.VALUES) break;
				if (this.selector == TypeConstants.VALUEOF) break;
				//$FALL-THROUGH$
			case TypeDeclaration.CLASS_DECL :
				// if a method has an semicolon body and is not declared as abstract==>error
				// native methods may have a semicolon body
				if ((this.modifiers & ExtraCompilerModifiers.AccSemicolonBody) != 0) {
					if ((this.modifiers & ClassFileConstants.AccNative) == 0)
						if ((this.modifiers & ClassFileConstants.AccAbstract) == 0)
							this.scope.problemReporter().methodNeedBody(this);
				} else {
					// the method HAS a body --> abstract native modifiers are forbidden
					if (((this.modifiers & ClassFileConstants.AccNative) != 0) || ((this.modifiers & ClassFileConstants.AccAbstract) != 0))
						this.scope.problemReporter().methodNeedingNoBody(this);
					else if (this.binding == null || this.binding.isStatic() || (this.binding.declaringClass instanceof LocalTypeBinding) || returnsUndeclTypeVar) {
						// Cannot be static for one of the reasons stated above
						this.bits &= ~ASTNode.CanBeStatic;
					}
				}
				break;
			case TypeDeclaration.INTERFACE_DECL :
				if (compilerOptions.sourceLevel >= ClassFileConstants.JDK1_8
						&& (this.modifiers & (ExtraCompilerModifiers.AccSemicolonBody | ClassFileConstants.AccAbstract)) == ExtraCompilerModifiers.AccSemicolonBody) {
					boolean isPrivateMethod = compilerOptions.sourceLevel >= ClassFileConstants.JDK9 && (this.modifiers & ClassFileConstants.AccPrivate) != 0;
					if (isPrivateMethod || ((this.modifiers & (ClassFileConstants.AccStatic | ExtraCompilerModifiers.AccDefaultMethod)) != 0)) {
							this.scope.problemReporter().methodNeedBody(this);
					}
				}
				break;
		}
		super.resolveStatements();

		// TagBits.OverridingMethodWithSupercall is set during the resolveStatements() call
		if (compilerOptions.getSeverity(CompilerOptions.OverridingMethodWithoutSuperInvocation) != ProblemSeverities.Ignore) {
			if (this.binding != null) {
        		int bindingModifiers = this.binding.modifiers;
        		if ((bindingModifiers & (ExtraCompilerModifiers.AccOverriding|ExtraCompilerModifiers.AccImplementing)) == ExtraCompilerModifiers.AccOverriding
        				&& (this.bits & ASTNode.OverridingMethodWithSupercall) == 0) {
        			this.scope.problemReporter().overridesMethodWithoutSuperInvocation(this.binding);
        		}
			}
		}
	}

	@Override
	public void traverse(
		ASTVisitor visitor,
		ClassScope classScope) {

		if (visitor.visit(this, classScope)) {
			if (this.javadoc != null) {
				this.javadoc.traverse(visitor, this.scope);
			}
			if (this.annotations != null) {
				int annotationsLength = this.annotations.length;
				for (int i = 0; i < annotationsLength; i++)
					this.annotations[i].traverse(visitor, this.scope);
			}
			if (this.typeParameters != null) {
				int typeParametersLength = this.typeParameters.length;
				for (int i = 0; i < typeParametersLength; i++) {
					this.typeParameters[i].traverse(visitor, this.scope);
				}
			}
			if (this.returnType != null)
				this.returnType.traverse(visitor, this.scope);
			if (this.arguments != null) {
				int argumentLength = this.arguments.length;
				for (int i = 0; i < argumentLength; i++)
					this.arguments[i].traverse(visitor, this.scope);
			}
			if (this.thrownExceptions != null) {
				int thrownExceptionsLength = this.thrownExceptions.length;
				for (int i = 0; i < thrownExceptionsLength; i++)
					this.thrownExceptions[i].traverse(visitor, this.scope);
			}
			if (this.statements != null) {
				int statementsLength = this.statements.length;
				for (int i = 0; i < statementsLength; i++)
					this.statements[i].traverse(visitor, this.scope);
			}
		}
		visitor.endVisit(this, classScope);
	}
	@Override
	public TypeParameter[] typeParameters() {
	    return this.typeParameters;
	}
}

Back to the top