org.eclipse.jdt.internal.compiler.lookup.ImplicitNullAnnotationVerifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
Eclipse Compiler for Java(TM)
/*******************************************************************************
* Copyright (c) 2012, 2020 GK Software SE, 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
*
* Contributors:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching;
import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching.CheckMode;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
/**
* Extracted slice from MethodVerifier15, which is responsible only for implicit null annotations.
* First, if enabled, it detects overridden methods from which null annotations are inherited.
* Next, also default nullness is filled into remaining empty slots.
* After all implicit annotations have been filled in compatibility is checked and problems are complained.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ImplicitNullAnnotationVerifier {
public static void ensureNullnessIsKnown(MethodBinding methodBinding, Scope scope) {
if ((methodBinding.tagBits & TagBits.IsNullnessKnown) == 0) {
LookupEnvironment environment2 = scope.environment();
// ensure nullness of methodBinding is known (but we are not interested in reporting problems against methodBinding)
new ImplicitNullAnnotationVerifier(environment2, environment2.globalOptions.inheritNullAnnotations)
.checkImplicitNullAnnotations(methodBinding, null/*srcMethod*/, false, scope);
}
}
/**
* Simple record to store nullness info for one argument or return type
* while iterating over a set of overridden methods.
*/
static class InheritedNonNullnessInfo {
Boolean inheritedNonNullness;
MethodBinding annotationOrigin;
boolean complained;
}
// delegate which to ask for recursive analysis of super methods
// can be 'this', but is never a MethodVerifier (to avoid infinite recursion).
ImplicitNullAnnotationVerifier buddyImplicitNullAnnotationsVerifier;
private boolean inheritNullAnnotations;
protected LookupEnvironment environment;
public ImplicitNullAnnotationVerifier(LookupEnvironment environment, boolean inheritNullAnnotations) {
this.buddyImplicitNullAnnotationsVerifier = this;
this.inheritNullAnnotations = inheritNullAnnotations;
this.environment = environment;
}
// for sub-classes:
ImplicitNullAnnotationVerifier(LookupEnvironment environment) {
CompilerOptions options = environment.globalOptions;
this.buddyImplicitNullAnnotationsVerifier = new ImplicitNullAnnotationVerifier(environment, options.inheritNullAnnotations);
this.inheritNullAnnotations = options.inheritNullAnnotations;
this.environment = environment;
}
/**
* Check and fill in implicit annotations from overridden methods and from default.
* Precondition: caller has checked whether annotation-based null analysis is enabled.
*/
public void checkImplicitNullAnnotations(MethodBinding currentMethod, AbstractMethodDeclaration srcMethod, boolean complain, Scope scope) {
// check inherited nullness from superclass and superInterfaces
try {
ReferenceBinding currentType = currentMethod.declaringClass;
if (currentType.id == TypeIds.T_JavaLangObject) {
return;
}
boolean usesTypeAnnotations = scope.environment().usesNullTypeAnnotations();
boolean needToApplyReturnNonNullDefault =
currentMethod.hasNonNullDefaultForReturnType(srcMethod);
ParameterNonNullDefaultProvider needToApplyParameterNonNullDefault =
currentMethod.hasNonNullDefaultForParameter(srcMethod);
boolean needToApplyNonNullDefault = needToApplyReturnNonNullDefault | needToApplyParameterNonNullDefault.hasAnyNonNullDefault();
// compatibility & inheritance do not consider constructors / static methods:
boolean isInstanceMethod = !currentMethod.isConstructor() && !currentMethod.isStatic();
complain &= isInstanceMethod;
if (!needToApplyNonNullDefault
&& !complain
&& !(this.inheritNullAnnotations && isInstanceMethod)) {
return; // short cut, no work to be done
}
if (isInstanceMethod) {
List superMethodList = new ArrayList();
// need super types connected:
if (currentType instanceof SourceTypeBinding && !currentType.isHierarchyConnected() && !currentType.isAnonymousType()) {
((SourceTypeBinding) currentType).scope.connectTypeHierarchy();
}
int paramLen = currentMethod.parameters.length;
findAllOverriddenMethods(currentMethod.original(), currentMethod.selector, paramLen,
currentType, new HashSet(), superMethodList);
// prepare interim storage for nullness info so we don't pollute currentMethod before we know its conflict-free:
InheritedNonNullnessInfo[] inheritedNonNullnessInfos = new InheritedNonNullnessInfo[paramLen+1]; // index 0 is for the return type
for (int i=0; i= 0;) {
MethodBinding currentSuper = (MethodBinding) superMethodList.get(i);
if ((currentSuper.tagBits & TagBits.IsNullnessKnown) == 0) {
// recurse to prepare currentSuper
checkImplicitNullAnnotations(currentSuper, null, false, scope); // TODO (stephan) complain=true if currentSuper is source method??
}
checkNullSpecInheritance(currentMethod, srcMethod, needToApplyReturnNonNullDefault, needToApplyParameterNonNullDefault, complain, currentSuper, null, scope, inheritedNonNullnessInfos);
needToApplyNonNullDefault = false;
}
// transfer collected information into currentMethod:
InheritedNonNullnessInfo info = inheritedNonNullnessInfos[0];
if (!info.complained) {
long tagBits = 0;
if (info.inheritedNonNullness == Boolean.TRUE) {
tagBits = TagBits.AnnotationNonNull;
} else if (info.inheritedNonNullness == Boolean.FALSE) {
tagBits = TagBits.AnnotationNullable;
}
if (tagBits != 0) {
if (!usesTypeAnnotations) {
currentMethod.tagBits |= tagBits;
} else {
if (!currentMethod.returnType.isBaseType()) {
LookupEnvironment env = scope.environment();
currentMethod.returnType = env.createAnnotatedType(currentMethod.returnType, env.nullAnnotationsFromTagBits(tagBits));
}
}
}
}
for (int i=0; i and X extends Object> as the same 'type'
if (one.isParameterizedType() && two.isParameterizedType())
return one.isEquivalentTo(two) && two.isEquivalentTo(one);
// Can skip this since we resolved each method before comparing it, see computeSubstituteMethod()
// if (one instanceof UnresolvedReferenceBinding)
// return ((UnresolvedReferenceBinding) one).resolvedType == two;
// if (two instanceof UnresolvedReferenceBinding)
// return ((UnresolvedReferenceBinding) two).resolvedType == one;
return false; // all other type bindings are identical
}
}