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 spotless-ext-greclipse Show documentation
Show all versions of spotless-ext-greclipse Show documentation
Groovy Eclipse's formatter bundled for Spotless
The newest version!
/*******************************************************************************
* Copyright (c) 2012, 2014 GK Software AG, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.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.classfmt.ClassFileConstants;
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 {
/**
* 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;
}
long sourceLevel = scope.compilerOptions().sourceLevel;
boolean needToApplyNonNullDefault = currentMethod.hasNonNullDefaultFor(Binding.DefaultLocationParameter|Binding.DefaultLocationReturnType,
sourceLevel >= ClassFileConstants.JDK1_8);
// 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, needToApplyNonNullDefault, complain, currentSuper, 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 (sourceLevel < ClassFileConstants.JDK1_8) {
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= ClassFileConstants.JDK1_8;
long inheritedNullnessBits = getReturnTypeNullnessTagBits(inheritedMethod, useTypeAnnotations);
long currentNullnessBits = getReturnTypeNullnessTagBits(currentMethod, useTypeAnnotations);
boolean shouldInherit = this.inheritNullAnnotations;
// return type:
returnType: {
if (currentMethod.returnType == null || currentMethod.returnType.isBaseType())
break returnType; // no nullness for primitive types
if (currentNullnessBits == 0) {
// unspecified, may fill in either from super or from default
if (shouldInherit) {
if (inheritedNullnessBits != 0) {
if (hasNonNullDefault) {
// both inheritance and default: check for conflict?
if (shouldComplain && inheritedNullnessBits == TagBits.AnnotationNullable)
scope.problemReporter().conflictingNullAnnotations(currentMethod, ((MethodDeclaration) srcMethod).returnType, inheritedMethod);
// still use the inherited bits to avoid incompatibility
}
if (inheritedNonNullnessInfos != null && srcMethod != null) {
recordDeferredInheritedNullness(scope, ((MethodDeclaration) srcMethod).returnType,
inheritedMethod, Boolean.valueOf(inheritedNullnessBits == TagBits.AnnotationNonNull), inheritedNonNullnessInfos[0]);
} else {
// no need to defer, record this info now:
applyReturnNullBits(currentMethod, inheritedNullnessBits);
}
break returnType; // compatible by construction, skip complain phase below
}
}
if (hasNonNullDefault) { // conflict with inheritance already checked
currentNullnessBits = TagBits.AnnotationNonNull;
applyReturnNullBits(currentMethod, currentNullnessBits);
}
}
if (shouldComplain) {
if ((inheritedNullnessBits & TagBits.AnnotationNonNull) != 0
&& currentNullnessBits != TagBits.AnnotationNonNull)
{
if (srcMethod != null) {
scope.problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod,
this.environment.getNonNullAnnotationName());
break returnType;
} else {
scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, useTypeAnnotations);
return;
}
}
if (useTypeAnnotations) {
TypeBinding substituteReturnType = null; // for TVB identity checks inside NullAnnotationMatching.analyze()
TypeVariableBinding[] typeVariables = inheritedMethod.original().typeVariables;
if (typeVariables != null && currentMethod.returnType.id != TypeIds.T_void) {
ParameterizedGenericMethodBinding substitute = this.environment.createParameterizedGenericMethod(currentMethod, typeVariables);
substituteReturnType = substitute.returnType;
}
if (NullAnnotationMatching.analyse(inheritedMethod.returnType, currentMethod.returnType, substituteReturnType, 0, CheckMode.OVERRIDE).isAnyMismatch()) {
scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, useTypeAnnotations);
return;
}
}
}
}
// parameters:
TypeBinding[] substituteParameters = null; // for TVB identity checks inside NullAnnotationMatching.analyze()
if (shouldComplain) {
TypeVariableBinding[] typeVariables = currentMethod.original().typeVariables;
if (typeVariables != Binding.NO_TYPE_VARIABLES) {
ParameterizedGenericMethodBinding substitute = this.environment.createParameterizedGenericMethod(inheritedMethod, typeVariables);
substituteParameters = substitute.parameters;
}
}
Argument[] currentArguments = srcMethod == null ? null : srcMethod.arguments;
int length = 0;
if (currentArguments != null)
length = currentArguments.length;
if (useTypeAnnotations) // need to look for type annotations on all parameters:
length = currentMethod.parameters.length;
else if (inheritedMethod.parameterNonNullness != null)
length = inheritedMethod.parameterNonNullness.length;
else if (currentMethod.parameterNonNullness != null)
length = currentMethod.parameterNonNullness.length;
for (int i = 0; i < length; i++) {
if (currentMethod.parameters[i].isBaseType()) continue;
Argument currentArgument = currentArguments == null
? null : currentArguments[i];
Boolean inheritedNonNullNess = getParameterNonNullness(inheritedMethod, i, useTypeAnnotations);
Boolean currentNonNullNess = getParameterNonNullness(currentMethod, i, useTypeAnnotations);
if (currentNonNullNess == null) {
// unspecified, may fill in either from super or from default
if (inheritedNonNullNess != null) {
if (shouldInherit) {
if (hasNonNullDefault) {
// both inheritance and default: check for conflict?
if (shouldComplain
&& inheritedNonNullNess == Boolean.FALSE
&& currentArgument != null)
{
scope.problemReporter().conflictingNullAnnotations(currentMethod, currentArgument, inheritedMethod);
}
// still use the inherited info to avoid incompatibility
}
if (inheritedNonNullnessInfos != null && srcMethod != null) {
recordDeferredInheritedNullness(scope, srcMethod.arguments[i].type,
inheritedMethod, inheritedNonNullNess, inheritedNonNullnessInfos[i+1]);
} else {
// no need to defer, record this info now:
if (!useTypeAnnotations)
recordArgNonNullness(currentMethod, length, i, currentArgument, inheritedNonNullNess);
else
recordArgNonNullness18(currentMethod, i, currentArgument, inheritedNonNullNess, this.environment);
}
continue; // compatible by construction, skip complain phase below
}
}
if (hasNonNullDefault) { // conflict with inheritance already checked
currentNonNullNess = Boolean.TRUE;
if (!useTypeAnnotations)
recordArgNonNullness(currentMethod, length, i, currentArgument, Boolean.TRUE);
else
recordArgNonNullness18(currentMethod, i, currentArgument, Boolean.TRUE, this.environment);
}
}
if (shouldComplain) {
char[][] annotationName;
if (inheritedNonNullNess == Boolean.TRUE) {
annotationName = this.environment.getNonNullAnnotationName();
} else {
annotationName = this.environment.getNullableAnnotationName();
}
if (inheritedNonNullNess != Boolean.TRUE // super parameter is not restricted to @NonNull
&& currentNonNullNess == Boolean.TRUE) // current parameter is restricted to @NonNull
{
// incompatible
if (currentArgument != null) {
scope.problemReporter().illegalRedefinitionToNonNullParameter(
currentArgument,
inheritedMethod.declaringClass,
(inheritedNonNullNess == null) ? null : this.environment.getNullableAnnotationName());
} else {
scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
}
continue;
} else if (currentNonNullNess == null)
{
// unannotated strictly conflicts only with inherited @Nullable
if (inheritedNonNullNess == Boolean.FALSE) {
if (currentArgument != null) {
scope.problemReporter().parameterLackingNullableAnnotation(
currentArgument,
inheritedMethod.declaringClass,
annotationName);
} else {
scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
}
continue;
} else if (inheritedNonNullNess == Boolean.TRUE) {
// not strictly a conflict, but a configurable warning is given anyway:
scope.problemReporter().parameterLackingNonnullAnnotation(
currentArgument,
inheritedMethod.declaringClass,
annotationName);
continue;
}
}
if (useTypeAnnotations) {
TypeBinding substituteParameter = substituteParameters != null ? substituteParameters[i] : null;
if (NullAnnotationMatching.analyse(currentMethod.parameters[i], inheritedMethod.parameters[i], substituteParameter, 0, CheckMode.OVERRIDE).isAnyMismatch()) {
scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
}
}
}
}
}
void applyReturnNullBits(MethodBinding method, long nullnessBits) {
if (this.environment.globalOptions.sourceLevel < ClassFileConstants.JDK1_8) {
method.tagBits |= nullnessBits;
} else {
if (!method.returnType.isBaseType()) {
method.returnType = this.environment.createAnnotatedType(method.returnType, this.environment.nullAnnotationsFromTagBits(nullnessBits));
}
}
}
private Boolean getParameterNonNullness(MethodBinding method, int i, boolean useTypeAnnotations) {
if (useTypeAnnotations) {
TypeBinding parameter = method.parameters[i];
if (parameter != null) {
long nullBits = NullAnnotationMatching.validNullTagBits(parameter.tagBits);
if (nullBits != 0L)
return Boolean.valueOf(nullBits == TagBits.AnnotationNonNull);
}
return null;
}
return (method.parameterNonNullness == null)
? null : method.parameterNonNullness[i];
}
private long getReturnTypeNullnessTagBits(MethodBinding method, boolean useTypeAnnotations) {
if (useTypeAnnotations) {
if (method.returnType == null)
return 0L;
return NullAnnotationMatching.validNullTagBits(method.returnType.tagBits);
}
return method.tagBits & TagBits.AnnotationNullMASK;
}
/* check for conflicting annotations and record here the info 'inheritedNonNullness' found in 'inheritedMethod'. */
protected void recordDeferredInheritedNullness(Scope scope, ASTNode location,
MethodBinding inheritedMethod, Boolean inheritedNonNullness,
InheritedNonNullnessInfo nullnessInfo)
{
if (nullnessInfo.inheritedNonNullness != null && nullnessInfo.inheritedNonNullness != inheritedNonNullness) {
scope.problemReporter().conflictingInheritedNullAnnotations(location,
nullnessInfo.inheritedNonNullness.booleanValue(), nullnessInfo.annotationOrigin,
inheritedNonNullness.booleanValue(), inheritedMethod);
nullnessInfo.complained = true;
// leave previous info intact, so subsequent errors are reported against the same first method
} else {
nullnessInfo.inheritedNonNullness = inheritedNonNullness;
nullnessInfo.annotationOrigin = inheritedMethod;
}
}
/* record declared nullness of a parameter into the method and into the argument (if present). */
void recordArgNonNullness(MethodBinding method, int paramCount, int paramIdx, Argument currentArgument, Boolean nonNullNess) {
if (method.parameterNonNullness == null)
method.parameterNonNullness = new Boolean[paramCount];
method.parameterNonNullness[paramIdx] = nonNullNess;
if (currentArgument != null) {
currentArgument.binding.tagBits |= nonNullNess.booleanValue() ?
TagBits.AnnotationNonNull : TagBits.AnnotationNullable;
}
}
void recordArgNonNullness18(MethodBinding method, int paramIdx, Argument currentArgument, Boolean nonNullNess, LookupEnvironment env) {
AnnotationBinding annotationBinding = nonNullNess.booleanValue() ? env.getNonNullAnnotation() : env.getNullableAnnotation();
method.parameters[paramIdx] = env.createAnnotatedType(method.parameters[paramIdx], new AnnotationBinding[]{ annotationBinding});
if (currentArgument != null) {
currentArgument.binding.type = method.parameters[paramIdx];
}
}
// ==== minimal set of utility methods previously from MethodVerifier15: ====
static boolean areParametersEqual(MethodBinding one, MethodBinding two) {
TypeBinding[] oneArgs = one.parameters;
TypeBinding[] twoArgs = two.parameters;
if (oneArgs == twoArgs) return true;
int length = oneArgs.length;
if (length != twoArgs.length) return false;
// methods with raw parameters are considered equal to inherited methods
// with parameterized parameters for backwards compatibility, need a more complex check
int i;
foundRAW: for (i = 0; i < length; i++) {
if (!areTypesEqual(oneArgs[i], twoArgs[i])) {
if (oneArgs[i].leafComponentType().isRawType()) {
if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType())) {
// raw mode does not apply if the method defines its own type variables
if (one.typeVariables != Binding.NO_TYPE_VARIABLES)
return false;
// one parameter type is raw, hence all parameters types must be raw or non generic
// otherwise we have a mismatch check backwards
for (int j = 0; j < i; j++)
if (oneArgs[j].leafComponentType().isParameterizedTypeWithActualArguments())
return false;
// switch to all raw mode
break foundRAW;
}
}
return false;
}
}
// all raw mode for remaining parameters (if any)
for (i++; i < length; i++) {
if (!areTypesEqual(oneArgs[i], twoArgs[i])) {
if (oneArgs[i].leafComponentType().isRawType())
if (oneArgs[i].dimensions() == twoArgs[i].dimensions() && oneArgs[i].leafComponentType().isEquivalentTo(twoArgs[i].leafComponentType()))
continue;
return false;
} else if (oneArgs[i].leafComponentType().isParameterizedTypeWithActualArguments()) {
return false; // no remaining parameter can be a Parameterized type (if one has been converted then all RAW types must be converted)
}
}
return true;
}
static boolean areTypesEqual(TypeBinding one, TypeBinding two) {
if (TypeBinding.equalsEquals(one, two)) return true;
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=329584
switch(one.kind()) {
case Binding.TYPE:
switch (two.kind()) {
case Binding.PARAMETERIZED_TYPE:
case Binding.RAW_TYPE:
if (TypeBinding.equalsEquals(one, two.erasure()))
return true;
}
break;
case Binding.RAW_TYPE:
case Binding.PARAMETERIZED_TYPE:
switch(two.kind()) {
case Binding.TYPE:
if (TypeBinding.equalsEquals(one.erasure(), two))
return true;
}
}
// need to consider X> 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
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy