All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2013, 2014 GK Software AG 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.ast;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
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.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.CaptureBinding;
import org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBindingVisitor;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;

/**
 * Performs matching of null type annotations.
 * Instances are used to encode result from this analysis.
 * @since 3.10
 */
public class NullAnnotationMatching {
	
	public static final NullAnnotationMatching NULL_ANNOTATIONS_OK = new NullAnnotationMatching(0, null);
	public static final NullAnnotationMatching NULL_ANNOTATIONS_UNCHECKED = new NullAnnotationMatching(1, null);
	public static final NullAnnotationMatching NULL_ANNOTATIONS_MISMATCH = new NullAnnotationMatching(2, null);

	/** 0 = OK, 1 = unchecked, 2 = definite mismatch */
	public final int severity;
	
	/** If non-null this field holds the supertype of the provided type which was used for direct matching. */
	public final TypeBinding superTypeHint;
	
	public NullAnnotationMatching(int severity, TypeBinding superTypeHint) {
		this.severity = severity;
		this.superTypeHint = superTypeHint;
	}

	public boolean isAnyMismatch()      { return this.severity != 0; }
	public boolean isUnchecked()        { return this.severity == 1; }
	public boolean isDefiniteMismatch() { return this.severity == 2; }
	
	public String superTypeHintName(CompilerOptions options, boolean shortNames) {
		return String.valueOf(this.superTypeHint.nullAnnotatedReadableName(options, shortNames));
	}
	
	/** Check null-ness of 'var' against a possible null annotation */
	public static int checkAssignment(BlockScope currentScope, FlowContext flowContext,
									   VariableBinding var, int nullStatus, Expression expression, TypeBinding providedType)
	{
		long lhsTagBits = 0L;
		boolean hasReported = false;
		if (currentScope.compilerOptions().sourceLevel < ClassFileConstants.JDK1_8) {
			lhsTagBits = var.tagBits & TagBits.AnnotationNullMASK;
		} else {
			if (expression instanceof ConditionalExpression && expression.isPolyExpression()) {
				// drill into both branches:
				ConditionalExpression ce = ((ConditionalExpression) expression);
				int status1 = NullAnnotationMatching.checkAssignment(currentScope, flowContext, var, ce.ifTrueNullStatus, ce.valueIfTrue, ce.valueIfTrue.resolvedType);
				int status2 = NullAnnotationMatching.checkAssignment(currentScope, flowContext, var, ce.ifFalseNullStatus, ce.valueIfFalse, ce.valueIfFalse.resolvedType);
				if (status1 == status2)
					return status1;
				return nullStatus; // if both branches disagree use the precomputed & merged nullStatus
			}
			lhsTagBits = var.type.tagBits & TagBits.AnnotationNullMASK;
			NullAnnotationMatching annotationStatus = analyse(var.type, providedType, nullStatus);
			if (annotationStatus.isDefiniteMismatch()) {
				currentScope.problemReporter().nullityMismatchingTypeAnnotation(expression, providedType, var.type, annotationStatus);
				hasReported = true;
			} else if (annotationStatus.isUnchecked()) {
				flowContext.recordNullityMismatch(currentScope, expression, providedType, var.type, nullStatus);
				hasReported = true;
			}
		}
		if (lhsTagBits == TagBits.AnnotationNonNull && nullStatus != FlowInfo.NON_NULL) {
			if (!hasReported)
				flowContext.recordNullityMismatch(currentScope, expression, providedType, var.type, nullStatus);
			return FlowInfo.NON_NULL;
		} else if (lhsTagBits == TagBits.AnnotationNullable && nullStatus == FlowInfo.UNKNOWN) {	// provided a legacy type?
			return FlowInfo.POTENTIALLY_NULL;			// -> use more specific info from the annotation
		}
		return nullStatus;
	}

	/**
	 * Find any mismatches between the two given types, which are caused by null type annotations.
	 * @param requiredType
	 * @param providedType
	 * @param nullStatus we are only interested in NULL or NON_NULL, -1 indicates that we are in a recursion, where flow info is ignored
	 * @return a status object representing the severity of mismatching plus optionally a supertype hint
	 */
	public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, int nullStatus) {
		return analyse(requiredType, providedType, nullStatus, false);
	}
	// additional parameter strict: if true we do not tolerate incompatibly missing annotations on type parameters (for overriding analysis)
	public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, int nullStatus, boolean strict) {
		int severity = 0;
		TypeBinding superTypeHint = null;
		if (requiredType instanceof ArrayBinding) {
			long[] requiredDimsTagBits = ((ArrayBinding)requiredType).nullTagBitsPerDimension;
			if (requiredDimsTagBits != null) {
				int dims = requiredType.dimensions();
				if (requiredType.dimensions() == providedType.dimensions()) {
					long[] providedDimsTagBits = ((ArrayBinding)providedType).nullTagBitsPerDimension;
					if (providedDimsTagBits == null) {
						severity = 1; // required is annotated, provided not, need unchecked conversion
					} else {
						for (int i=0; i<=dims; i++) {
							long requiredBits = validNullTagBits(requiredDimsTagBits[i]);
							long providedBits = validNullTagBits(providedDimsTagBits[i]);
							if (i > 0)
								nullStatus = -1; // don't use beyond the outermost dimension
							severity = Math.max(severity, computeNullProblemSeverity(requiredBits, providedBits, nullStatus, strict));
							if (severity == 2)
								return NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH;
						}
					}
				} else if (providedType.id == TypeIds.T_null) {
					if (dims > 0 && requiredDimsTagBits[0] == TagBits.AnnotationNonNull)
						return NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH;
				}
			}
		} else if (requiredType.hasNullTypeAnnotations() || providedType.hasNullTypeAnnotations()) {
			long requiredBits = requiredNullTagBits(requiredType);
			if (requiredBits != TagBits.AnnotationNullable // nullable lhs accepts everything, ...
					|| nullStatus == -1) // only at detail/recursion even nullable must be matched exactly
			{
				long providedBits = providedNullTagBits(providedType);
				severity = computeNullProblemSeverity(requiredBits, providedBits, nullStatus, strict && nullStatus == -1);
			}
			if (severity < 2) {
				TypeBinding providedSuper = providedType.findSuperTypeOriginatingFrom(requiredType);
				if (providedSuper != providedType) //$IDENTITY-COMPARISON$
					superTypeHint = providedSuper;
				if (requiredType.isParameterizedType()  && providedSuper instanceof ParameterizedTypeBinding) { // TODO(stephan): handle providedType.isRaw()
					TypeBinding[] requiredArguments = ((ParameterizedTypeBinding) requiredType).arguments;
					TypeBinding[] providedArguments = ((ParameterizedTypeBinding) providedSuper).arguments;
					if (requiredArguments != null && providedArguments != null && requiredArguments.length == providedArguments.length) {
						for (int i = 0; i < requiredArguments.length; i++) {
							NullAnnotationMatching status = analyse(requiredArguments[i], providedArguments[i], -1, strict);
							severity = Math.max(severity, status.severity);
							if (severity == 2)
								return new NullAnnotationMatching(severity, superTypeHint);
						}
					}
				}
				TypeBinding requiredEnclosing = requiredType.enclosingType();
				TypeBinding providedEnclosing = providedType.enclosingType();
				if (requiredEnclosing != null && providedEnclosing != null) {
					NullAnnotationMatching status = analyse(requiredEnclosing, providedEnclosing, -1, strict);
					severity = Math.max(severity, status.severity);
				}
			}
		}
		if (severity == 0)
			return NullAnnotationMatching.NULL_ANNOTATIONS_OK;
		return new NullAnnotationMatching(severity, superTypeHint);
	}

	// interpreting 'type' as a required type, compute the required null bits
	// we inspect the main type plus bounds of type variables and wildcards
	static long requiredNullTagBits(TypeBinding type) {

		long tagBits = type.tagBits & TagBits.AnnotationNullMASK;
		if (tagBits != 0)
			return validNullTagBits(tagBits);

		if (type.isWildcard()) {
			WildcardBinding wildcard = (WildcardBinding)type;
			if (wildcard.boundKind == Wildcard.UNBOUND)
				return 0;
			tagBits = wildcard.bound.tagBits & TagBits.AnnotationNullMASK;
			if (tagBits == 0)
				return 0;
			switch (wildcard.boundKind) {
				case Wildcard.EXTENDS :
					if (tagBits == TagBits.AnnotationNonNull)
						return TagBits.AnnotationNonNull;
					return TagBits.AnnotationNullMASK; // wildcard accepts @Nullable or better
				case Wildcard.SUPER :
					if (tagBits == TagBits.AnnotationNullable)
						return TagBits.AnnotationNullable;
					return TagBits.AnnotationNullMASK; // wildcard accepts @NonNull or worse
			}
			return 0;
		} 
		
		if (type.isTypeVariable()) {
			// assume we must require @NonNull, unless: (1) lower @Nullable bound, or (2) no nullness specified
			TypeVariableBinding typeVariable = (TypeVariableBinding)type;
			boolean haveNullBits = false;
			if (type.isCapture()) {
				TypeBinding lowerBound = ((CaptureBinding) type).lowerBound;
				if (lowerBound != null) {
					tagBits = lowerBound.tagBits & TagBits.AnnotationNullMASK;
					if (tagBits == TagBits.AnnotationNullable)
						return TagBits.AnnotationNullable; // (1) type cannot require @NonNull
					haveNullBits = tagBits != 0;
				}
			}
			if (typeVariable.firstBound != null)
				haveNullBits |= (typeVariable.firstBound.tagBits & TagBits.AnnotationNullMASK) != 0;
			if (haveNullBits)
				return TagBits.AnnotationNonNull; // could require @NonNull (unless (2) unspecified nullness)
		}

		return 0;
	}

	// interpreting 'type' as a provided type, compute the provide null bits
	// we inspect the main type plus bounds of type variables and wildcards
	static long providedNullTagBits(TypeBinding type) {

		long tagBits = type.tagBits & TagBits.AnnotationNullMASK;
		if (tagBits != 0)
			return validNullTagBits(tagBits);
		
		if (type.isWildcard()) { // wildcard can be 'provided' during inheritance checks
			WildcardBinding wildcard = (WildcardBinding)type;
			if (wildcard.boundKind == Wildcard.UNBOUND)
				return 0;
			tagBits = wildcard.bound.tagBits & TagBits.AnnotationNullMASK;
			if (tagBits == 0)
				return 0;
			switch (wildcard.boundKind) {
				case Wildcard.EXTENDS :
					if (tagBits == TagBits.AnnotationNonNull)
						return TagBits.AnnotationNonNull;
					return TagBits.AnnotationNullMASK; // @Nullable or better
				case Wildcard.SUPER :
					if (tagBits == TagBits.AnnotationNullable)
						return TagBits.AnnotationNullable;
					return TagBits.AnnotationNullMASK; // @NonNull or worse
			}
			return 0;
		}
	
		if (type.isTypeVariable()) { // incl. captures
			TypeVariableBinding typeVariable = (TypeVariableBinding)type;
			boolean haveNullBits = false;
			if (typeVariable.isCapture()) {
				TypeBinding lowerBound = ((CaptureBinding) typeVariable).lowerBound;
				if (lowerBound != null) {
					tagBits = lowerBound.tagBits & TagBits.AnnotationNullMASK;
					if (tagBits == TagBits.AnnotationNullable)
						return TagBits.AnnotationNullable; // cannot be @NonNull
					haveNullBits |= (tagBits != 0);
				}
			}
			if (typeVariable.firstBound != null) {
				long boundBits = typeVariable.firstBound.tagBits & TagBits.AnnotationNullMASK;
				if (boundBits == TagBits.AnnotationNonNull)
					return TagBits.AnnotationNonNull; // cannot be @Nullable
				haveNullBits |= (boundBits != 0);
			}
			if (haveNullBits)
				return TagBits.AnnotationNullMASK; // could be either, can only match to a wildcard accepting both
		}

		return 0;
	}

	public static long validNullTagBits(long bits) {
		bits &= TagBits.AnnotationNullMASK;
		return bits == TagBits.AnnotationNullMASK ? 0 : bits;
	}
	
	/** Provided that both types are {@link TypeBinding#equalsEquals}, return the one that is more likely to show null at runtime. */
	public static TypeBinding moreDangerousType(TypeBinding one, TypeBinding two) {
		if (one == null) return null;
		long oneNullBits = validNullTagBits(one.tagBits);
		long twoNullBits = validNullTagBits(two.tagBits);
		if (oneNullBits != twoNullBits) {
			if (oneNullBits == TagBits.AnnotationNullable)
				return one;			// nullable is dangerous
			if (twoNullBits == TagBits.AnnotationNullable)
				return two;			// nullable is dangerous
			// below this point we have unknown vs. nonnull, which is which?
			if (oneNullBits == 0)
				return one;			// unknown is more dangerous than nonnull
			return two;				// unknown is more dangerous than nonnull
		} else if (one != two) { //$IDENTITY-COMPARISON$
			if (analyse(one, two, -1).isAnyMismatch())
				return two;			// two doesn't snugly fit into one, so it must be more dangerous
		}
		return one;
	}

	private static int computeNullProblemSeverity(long requiredBits, long providedBits, int nullStatus, boolean strict) {
		if ((requiredBits != 0 || strict) && requiredBits != providedBits) {
			if (requiredBits == TagBits.AnnotationNonNull && nullStatus == FlowInfo.NON_NULL) {
				return 0; // OK by flow analysis
			}
			if (requiredBits == TagBits.AnnotationNullMASK)
				return 0; // OK since LHS accepts either
			if (providedBits != 0) {
				return 2; // mismatching annotations
			} else {
				return 1; // need unchecked conversion regarding type detail
			}
		}
		return 0; // OK by tagBits
	}

	/**
	 * After a method has substituted type parameters, check if this resulted in any contradictory null annotations.
	 * Problems are either reported directly (if scope != null) or by returning a ProblemMethodBinding.
	 */
	public static MethodBinding checkForContraditions(
			final MethodBinding method, final InvocationSite invocationSite, final Scope scope) {
		
		class SearchContradictions extends TypeBindingVisitor {
			ReferenceBinding typeWithContradiction;
			@Override
			public boolean visit(ReferenceBinding referenceBinding) {
				if ((referenceBinding.tagBits & TagBits.AnnotationNullMASK) == TagBits.AnnotationNullMASK) {
					this.typeWithContradiction = referenceBinding;
					return false;
				}
				return true;
			}
			@Override
			public boolean visit(TypeVariableBinding typeVariable) {
				return visit((ReferenceBinding)typeVariable);
			}
			@Override
			public boolean visit(RawTypeBinding rawType) {
				return visit((ReferenceBinding)rawType);
			}
		}

		SearchContradictions searchContradiction = new SearchContradictions();
		TypeBindingVisitor.visit(searchContradiction, method.returnType);
		if (searchContradiction.typeWithContradiction != null) {
			if (scope == null)
				return new ProblemMethodBinding(method, method.selector, method.parameters, ProblemReasons.ContradictoryNullAnnotations);
			scope.problemReporter().contradictoryNullAnnotationsInferred(method, invocationSite);
			// note: if needed, we might want to update the method by removing the contradictory annotations??
			return method;
		}

		Expression[] arguments = null;
		if (invocationSite instanceof Invocation)
			arguments = ((Invocation)invocationSite).arguments();
		for (int i = 0; i < method.parameters.length; i++) {
			TypeBindingVisitor.visit(searchContradiction, method.parameters[i]);
			if (searchContradiction.typeWithContradiction != null) {
				if (scope == null)
					return new ProblemMethodBinding(method, method.selector, method.parameters, ProblemReasons.ContradictoryNullAnnotations);
				if (arguments != null && i < arguments.length)
					scope.problemReporter().contradictoryNullAnnotationsInferred(method, arguments[i]);
				else
					scope.problemReporter().contradictoryNullAnnotationsInferred(method, invocationSite);
				return method;
			}
		}
		return method;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy