| /******************************************************************************* |
| * Copyright (c) 2013, 2019 GK Software AG 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: |
| * Stephan Herrmann - initial API and implementation |
| * Till Brychcy - Contributions for |
| * Bug 467482 - TYPE_USE null annotations: Incorrect "Redundant null check"-warning |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import org.eclipse.jdt.core.compiler.IProblem; |
| 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.AnnotationBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| 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.LookupEnvironment; |
| 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.Substitution; |
| 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; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; |
| |
| /** |
| * 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(Severity.OK, FlowInfo.UNKNOWN, null); |
| public static final NullAnnotationMatching NULL_ANNOTATIONS_OK_NONNULL = new NullAnnotationMatching(Severity.OK, FlowInfo.NON_NULL, null); |
| public static final NullAnnotationMatching NULL_ANNOTATIONS_UNCHECKED = new NullAnnotationMatching(Severity.UNCHECKED, FlowInfo.UNKNOWN, null); |
| public static final NullAnnotationMatching NULL_ANNOTATIONS_MISMATCH = new NullAnnotationMatching(Severity.MISMATCH, FlowInfo.UNKNOWN, null); |
| |
| public enum CheckMode { |
| /** in this mode we check normal assignment compatibility. */ |
| COMPATIBLE { |
| @Override boolean requiredNullableMatchesAll() { |
| return true; |
| } |
| }, |
| /** in this mode we check similar to isTypeArgumentContained. */ |
| EXACT, |
| /** in this mode we check compatibility of a type argument against the corresponding type parameter. */ |
| BOUND_CHECK, |
| /** similar to COMPATIBLE, but for type variables we look for instantiations, rather than treating them as "free type variables". */ |
| BOUND_SUPER_CHECK, |
| /** allow covariant return types, but no other deviations. */ |
| OVERRIDE_RETURN { |
| @Override CheckMode toDetail() { |
| return OVERRIDE; |
| } |
| }, |
| /** in this mode we do not tolerate incompatibly missing annotations on type parameters (for overriding analysis) */ |
| OVERRIDE { |
| @Override boolean requiredNullableMatchesAll() { |
| return true; |
| } |
| @Override CheckMode toDetail() { |
| return OVERRIDE; |
| } |
| }; |
| |
| boolean requiredNullableMatchesAll() { |
| return false; |
| } |
| CheckMode toDetail() { |
| return CheckMode.EXACT; |
| } |
| } |
| |
| private enum Severity { |
| /** No problem detected. */ |
| OK, |
| /** No real problem, but could issue an {@link IProblem#NonNullTypeVariableFromLegacyMethod} or similar. */ |
| LEGACY_WARNING, |
| /** Need unchecked conversion from unannotated to annotated. */ |
| UNCHECKED, |
| /** Definite nullity mismatch. */ |
| MISMATCH; |
| |
| public Severity max(Severity severity) { |
| if (compareTo(severity) < 0) |
| return severity; |
| return this; |
| } |
| |
| public boolean isAnyMismatch() { |
| return compareTo(LEGACY_WARNING) > 0; |
| } |
| } |
| |
| private final Severity severity; |
| |
| /** If non-null this field holds the supertype of the provided type which was used for direct matching. */ |
| public final TypeBinding superTypeHint; |
| public final int nullStatus; |
| |
| NullAnnotationMatching(Severity severity, int nullStatus, TypeBinding superTypeHint) { |
| this.severity = severity; |
| this.superTypeHint = superTypeHint; |
| this.nullStatus = nullStatus; |
| } |
| |
| public boolean isAnyMismatch() { return this.severity.isAnyMismatch(); } |
| public boolean isUnchecked() { return this.severity == Severity.UNCHECKED; } |
| public boolean isDefiniteMismatch() { return this.severity == Severity.MISMATCH; } |
| public boolean wantToReport() { return this.severity == Severity.LEGACY_WARNING; } |
| |
| public boolean isPotentiallyNullMismatch() { |
| return !isDefiniteMismatch() && this.nullStatus != -1 && (this.nullStatus & FlowInfo.POTENTIALLY_NULL) != 0; |
| } |
| |
| 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, FlowInfo flowInfo, int nullStatus, Expression expression, TypeBinding providedType) |
| { |
| if (providedType == null) return FlowInfo.UNKNOWN; // assume we already reported an error |
| long lhsTagBits = 0L; |
| boolean hasReported = false; |
| boolean usesNullTypeAnnotations = currentScope.environment().usesNullTypeAnnotations(); |
| if (!usesNullTypeAnnotations) { |
| 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, flowInfo, ce.ifTrueNullStatus, ce.valueIfTrue, ce.valueIfTrue.resolvedType); |
| int status2 = NullAnnotationMatching.checkAssignment(currentScope, flowContext, var, flowInfo, ce.ifFalseNullStatus, ce.valueIfFalse, ce.valueIfFalse.resolvedType); |
| if (status1 == status2) |
| return status1; |
| return nullStatus; // if both branches disagree use the precomputed & merged nullStatus |
| } else if (expression instanceof SwitchExpression && expression.isPolyExpression()) { |
| // drill into all the branches: |
| SwitchExpression se = ((SwitchExpression) expression); |
| Expression[] resExprs = se.resultExpressions.toArray(new Expression[0]); |
| Expression re = resExprs[0]; |
| int status0 = NullAnnotationMatching.checkAssignment(currentScope, flowContext, var, flowInfo, re.nullStatus(flowInfo, flowContext), re, re.resolvedType); |
| boolean identicalStatus = true; |
| for (int i = 1, l = resExprs.length; i < l; ++i) { |
| re = resExprs[i]; |
| int otherStatus = NullAnnotationMatching.checkAssignment(currentScope, flowContext, var, flowInfo, re.nullStatus(flowInfo, flowContext), re, re.resolvedType); |
| identicalStatus &= status0 == otherStatus; |
| } |
| return identicalStatus ? status0 : nullStatus; // if not all branches agree use the precomputed & merged nullStatus |
| } |
| lhsTagBits = var.type.tagBits & TagBits.AnnotationNullMASK; |
| NullAnnotationMatching annotationStatus = analyse(var.type, providedType, null, null, nullStatus, expression, CheckMode.COMPATIBLE); |
| if (annotationStatus.isAnyMismatch()) { |
| flowContext.recordNullityMismatch(currentScope, expression, providedType, var.type, flowInfo, nullStatus, annotationStatus); |
| hasReported = true; |
| } else { |
| if (annotationStatus.wantToReport()) |
| annotationStatus.report(currentScope); |
| if (annotationStatus.nullStatus != FlowInfo.UNKNOWN) { |
| return annotationStatus.nullStatus; |
| } |
| } |
| } |
| if (lhsTagBits == TagBits.AnnotationNonNull && nullStatus != FlowInfo.NON_NULL) { |
| if (!hasReported) |
| flowContext.recordNullityMismatch(currentScope, expression, providedType, var.type, flowInfo, nullStatus, null); |
| return FlowInfo.NON_NULL; |
| } else if (lhsTagBits == TagBits.AnnotationNullable && nullStatus == FlowInfo.UNKNOWN) { // provided a legacy type? |
| if (usesNullTypeAnnotations && providedType.isTypeVariable() && (providedType.tagBits & TagBits.AnnotationNullMASK) == 0) |
| return FlowInfo.POTENTIALLY_NULL | FlowInfo.POTENTIALLY_NON_NULL; // -> free type variable can mean either nullable or nonnull |
| return FlowInfo.POTENTIALLY_NULL | FlowInfo.POTENTIALLY_UNKNOWN; // -> combine info from lhs & rhs |
| } |
| 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, null, null, nullStatus, null, CheckMode.COMPATIBLE); |
| } |
| /** |
| * Find any mismatches between the two given types, which are caused by null type annotations. |
| * @param requiredType |
| * @param providedType |
| * @param providedSubstitute in inheritance situations this maps the providedType into the realm of the subclass, needed for TVB identity checks. |
| * Pass null if not interested in these added checks. |
| * @param substitution TODO |
| * @param nullStatus we are only interested in NULL or NON_NULL, -1 indicates that we are in a recursion, where flow info is ignored |
| * @param providedExpression optionally holds the provided expression of type 'providedType' |
| * @param mode controls the kind of check performed (see {@link CheckMode}). |
| * @return a status object representing the severity of mismatching plus optionally a supertype hint |
| */ |
| public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, TypeBinding providedSubstitute, Substitution substitution, |
| int nullStatus, Expression providedExpression, CheckMode mode) |
| { |
| if (!requiredType.enterRecursiveFunction()) |
| return NullAnnotationMatching.NULL_ANNOTATIONS_OK; |
| try { |
| Severity severity = Severity.OK; |
| TypeBinding superTypeHint = null; |
| NullAnnotationMatching okStatus = NullAnnotationMatching.NULL_ANNOTATIONS_OK; |
| if (areSameTypes(requiredType, providedType, providedSubstitute)) { |
| if ((requiredType.tagBits & TagBits.AnnotationNonNull) != 0) |
| return okNonNullStatus(providedExpression); |
| return okStatus; |
| } |
| if (requiredType instanceof TypeVariableBinding && substitution != null && (mode == CheckMode.EXACT || mode == CheckMode.COMPATIBLE || mode == CheckMode.BOUND_SUPER_CHECK)) { |
| requiredType.exitRecursiveFunction(); |
| requiredType = Scope.substitute(substitution, requiredType); |
| if (!requiredType.enterRecursiveFunction()) |
| return NullAnnotationMatching.NULL_ANNOTATIONS_OK; |
| if (areSameTypes(requiredType, providedType, providedSubstitute)) { |
| if ((requiredType.tagBits & TagBits.AnnotationNonNull) != 0) |
| return okNonNullStatus(providedExpression); |
| return okStatus; |
| } |
| } |
| if (mode == CheckMode.BOUND_CHECK && requiredType instanceof TypeVariableBinding) { |
| boolean passedBoundCheck = (substitution instanceof ParameterizedTypeBinding) && (((ParameterizedTypeBinding) substitution).tagBits & TagBits.PassedBoundCheck) != 0; |
| if (!passedBoundCheck) { |
| // during bound check against a type variable check the provided type against all upper bounds: |
| TypeBinding superClass = requiredType.superclass(); |
| if (superClass != null && (superClass.hasNullTypeAnnotations() || substitution != null)) { // annotations may enter when substituting a nested type variable |
| NullAnnotationMatching status = analyse(superClass, providedType, null, substitution, nullStatus, providedExpression, CheckMode.BOUND_SUPER_CHECK); |
| severity = severity.max(status.severity); |
| if (severity == Severity.MISMATCH) |
| return new NullAnnotationMatching(severity, nullStatus, superTypeHint); |
| } |
| TypeBinding[] superInterfaces = requiredType.superInterfaces(); |
| if (superInterfaces != null) { |
| for (int i = 0; i < superInterfaces.length; i++) { |
| if (superInterfaces[i].hasNullTypeAnnotations() || substitution != null) { // annotations may enter when substituting a nested type variable |
| NullAnnotationMatching status = analyse(superInterfaces[i], providedType, null, substitution, nullStatus, providedExpression, CheckMode.BOUND_SUPER_CHECK); |
| severity = severity.max(status.severity); |
| if (severity == Severity.MISMATCH) |
| return new NullAnnotationMatching(severity, nullStatus, superTypeHint); |
| } |
| } |
| } |
| } |
| } |
| 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) |
| providedDimsTagBits = new long[dims+1]; // set to unspec'd at all dimensions |
| int currentNullStatus = nullStatus; |
| for (int i=0; i<=dims; i++) { |
| long requiredBits = validNullTagBits(requiredDimsTagBits[i]); |
| long providedBits = validNullTagBits(providedDimsTagBits[i]); |
| if (i == 0 && requiredBits == TagBits.AnnotationNullable && nullStatus != -1 && mode.requiredNullableMatchesAll()) { |
| // toplevel nullable array: no need to check |
| if (nullStatus == FlowInfo.NULL) |
| break; // null value has no details |
| } else { |
| if (i > 0) |
| currentNullStatus = -1; // don't use beyond the outermost dimension |
| Severity dimSeverity = computeNullProblemSeverity(requiredBits, providedBits, currentNullStatus, i == 0 ? mode : mode.toDetail(), false); |
| if (i > 0 && dimSeverity == Severity.UNCHECKED |
| && providedExpression instanceof ArrayAllocationExpression |
| && providedBits == 0 && requiredBits != 0) |
| { |
| Expression[] dimensions = ((ArrayAllocationExpression) providedExpression).dimensions; |
| Expression previousDim = dimensions[i-1]; |
| if (previousDim instanceof IntLiteral && previousDim.constant.intValue() == 0) { |
| dimSeverity = Severity.OK; // element of empty dimension matches anything |
| nullStatus = -1; |
| break; |
| } |
| } |
| severity = severity.max(dimSeverity); |
| if (severity == Severity.MISMATCH) |
| return NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH; |
| } |
| if (severity == Severity.OK) |
| nullStatus = -1; |
| } |
| } 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() || requiredType.isTypeVariable()) { |
| long requiredBits = requiredNullTagBits(requiredType, mode); |
| if (requiredBits == TagBits.AnnotationNullable && nullStatus != -1 && mode.requiredNullableMatchesAll()) { |
| // at toplevel (having a nullStatus) nullable matches all |
| } else { |
| long providedBits = providedNullTagBits(providedType); |
| Severity s = computeNullProblemSeverity(requiredBits, providedBits, nullStatus, mode, requiredType.isTypeVariable()); |
| if (s.isAnyMismatch() && requiredType.isWildcard() && requiredBits != 0) { |
| if (((WildcardBinding) requiredType).determineNullBitsFromDeclaration(null, null) == 0) { |
| // wildcard has its nullBits from the type variable: avoid redundant warning. |
| s = Severity.OK; |
| } |
| } |
| severity = severity.max(s); |
| if (!severity.isAnyMismatch() && (providedBits & TagBits.AnnotationNullMASK) == TagBits.AnnotationNonNull) |
| okStatus = okNonNullStatus(providedExpression); |
| } |
| if (severity != Severity.MISMATCH && nullStatus != FlowInfo.NULL) { // null value has no details |
| TypeBinding providedSuper = providedType.findSuperTypeOriginatingFrom(requiredType); |
| TypeBinding providedSubstituteSuper = providedSubstitute != null ? providedSubstitute.findSuperTypeOriginatingFrom(requiredType) : null; |
| if (severity == Severity.UNCHECKED && requiredType.isTypeVariable() && providedType.isTypeVariable() && (providedSuper == requiredType || providedSubstituteSuper == requiredType)) { //$IDENTITY-COMPARISON$ |
| severity = Severity.OK; |
| } |
| 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; |
| TypeBinding[] providedSubstitutes = (providedSubstituteSuper instanceof ParameterizedTypeBinding) ? ((ParameterizedTypeBinding)providedSubstituteSuper).arguments : null; |
| if (requiredArguments != null && providedArguments != null && requiredArguments.length == providedArguments.length) { |
| for (int i = 0; i < requiredArguments.length; i++) { |
| TypeBinding providedArgSubstitute = providedSubstitutes != null ? providedSubstitutes[i] : null; |
| NullAnnotationMatching status = analyse(requiredArguments[i], providedArguments[i], providedArgSubstitute, substitution, -1, providedExpression, mode.toDetail()); |
| severity = severity.max(status.severity); |
| if (severity == Severity.MISMATCH) |
| return new NullAnnotationMatching(severity, nullStatus, superTypeHint); |
| } |
| } |
| } |
| TypeBinding requiredEnclosing = requiredType.enclosingType(); |
| TypeBinding providedEnclosing = providedType.enclosingType(); |
| if (requiredEnclosing != null && providedEnclosing != null) { |
| TypeBinding providedEnclSubstitute = providedSubstitute != null ? providedSubstitute.enclosingType() : null; |
| NullAnnotationMatching status = analyse(requiredEnclosing, providedEnclosing, providedEnclSubstitute, substitution, -1, providedExpression, mode); |
| severity = severity.max(status.severity); |
| } |
| } |
| } |
| if (!severity.isAnyMismatch()) |
| return okStatus; |
| return new NullAnnotationMatching(severity, nullStatus, superTypeHint); |
| } finally { |
| requiredType.exitRecursiveFunction(); |
| } |
| } |
| public void report(Scope scope) { |
| // nop |
| } |
| public static NullAnnotationMatching okNonNullStatus(final Expression providedExpression) { |
| if (providedExpression instanceof MessageSend) { |
| final MethodBinding method = ((MessageSend) providedExpression).binding; |
| if (method != null && method.isValidBinding()) { |
| MethodBinding originalMethod = method.original(); |
| TypeBinding originalDeclaringClass = originalMethod.declaringClass; |
| if (originalDeclaringClass instanceof BinaryTypeBinding |
| && ((BinaryTypeBinding) originalDeclaringClass).externalAnnotationStatus.isPotentiallyUnannotatedLib() |
| && originalMethod.returnType.isTypeVariable() |
| && (originalMethod.returnType.tagBits & TagBits.AnnotationNullMASK) == 0) |
| { |
| final int severity = ((BinaryTypeBinding) originalDeclaringClass).externalAnnotationStatus == ExternalAnnotationStatus.NO_EEA_FILE |
| ? ProblemSeverities.Warning : ProblemSeverities.Info; // reduce severity if not configured to for external annotations |
| return new NullAnnotationMatching(Severity.LEGACY_WARNING, FlowInfo.UNKNOWN, null) { |
| @Override |
| public void report(Scope scope) { |
| scope.problemReporter().nonNullTypeVariableInUnannotatedBinary(scope.environment(), method, providedExpression, severity); |
| } |
| }; |
| } |
| } |
| } |
| return NullAnnotationMatching.NULL_ANNOTATIONS_OK_NONNULL; |
| } |
| |
| /** Are both types identical wrt the unannotated type and any null type annotations? Only unstructured types and captures are considered. */ |
| protected static boolean areSameTypes(TypeBinding requiredType, TypeBinding providedType, TypeBinding providedSubstitute) { |
| if (requiredType == providedType) //$IDENTITY-COMPARISON$ // short cut for really-really-same types |
| return true; |
| if (requiredType.isParameterizedType() || requiredType.isArrayType()) |
| return false; // not analysing details here |
| if (TypeBinding.notEquals(requiredType, providedType)) { |
| if (requiredType instanceof CaptureBinding) { |
| // when providing exactly the lower bound of the required type we're definitely fine: |
| TypeBinding lowerBound = ((CaptureBinding)requiredType).lowerBound; |
| if (lowerBound != null && areSameTypes(lowerBound, providedType, providedSubstitute)) |
| return (requiredType.tagBits & TagBits.AnnotationNullMASK) == (providedType.tagBits & TagBits.AnnotationNullMASK); |
| } else if (requiredType.kind() == Binding.TYPE_PARAMETER && requiredType == providedSubstitute) { //$IDENTITY-COMPARISON$ |
| return true; |
| } else if (providedType instanceof CaptureBinding) { |
| // when requiring exactly the upper bound of the provided type we're fine, too: |
| TypeBinding upperBound = ((CaptureBinding)providedType).upperBound(); |
| if (upperBound != null && areSameTypes(requiredType, upperBound, providedSubstitute)) |
| return (requiredType.tagBits & TagBits.AnnotationNullMASK) == (providedType.tagBits & TagBits.AnnotationNullMASK); |
| } |
| return false; |
| } |
| return (requiredType.tagBits & TagBits.AnnotationNullMASK) == (providedType.tagBits & TagBits.AnnotationNullMASK); |
| } |
| |
| // 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, CheckMode mode) { |
| |
| long tagBits = type.tagBits & TagBits.AnnotationNullMASK; |
| if (tagBits != 0) |
| return validNullTagBits(tagBits); |
| |
| if (type.isWildcard()) { |
| return TagBits.AnnotationNullMASK; |
| } |
| |
| if (type.isTypeVariable()) { |
| // assume we must require @NonNull, unless lower @Nullable bound |
| // (annotation directly on the TV has already been checked above) |
| if (type.isCapture()) { |
| TypeBinding lowerBound = ((CaptureBinding) type).lowerBound; |
| if (lowerBound != null) { |
| tagBits = lowerBound.tagBits & TagBits.AnnotationNullMASK; |
| if (tagBits == TagBits.AnnotationNullable) |
| return TagBits.AnnotationNullable; // type cannot require @NonNull |
| } |
| } |
| switch (mode) { |
| case BOUND_CHECK: // no pessimistic checks during boundcheck (we *have* the instantiation) |
| case BOUND_SUPER_CHECK: |
| case OVERRIDE: // no pessimistic checks during override check (comparing two *declarations*) |
| case OVERRIDE_RETURN: |
| break; |
| default: |
| return TagBits.AnnotationNonNull; // instantiation could require @NonNull |
| } |
| } |
| |
| 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 |
| return TagBits.AnnotationNullMASK; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Use only if no suitable flowInfo is available. |
| */ |
| public static int nullStatusFromExpressionType(TypeBinding type) { |
| if (type.isFreeTypeVariable()) |
| return FlowInfo.FREE_TYPEVARIABLE; |
| long bits = type.tagBits & TagBits.AnnotationNullMASK; |
| if (bits == 0) |
| return FlowInfo.UNKNOWN; |
| if (bits == TagBits.AnnotationNonNull) |
| return FlowInfo.NON_NULL; |
| return FlowInfo.POTENTIALLY_NON_NULL | FlowInfo.POTENTIALLY_NULL; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Evaluate problem severity from the given details: |
| * @param requiredBits null tagBits of the required type |
| * @param providedBits null tagBits of the provided type |
| * @param nullStatus -1 means: don't use, other values see constants in FlowInfo |
| * @param mode check mode (see {@link CheckMode}) |
| * @param requiredIsTypeVariable is the required type a type variable (possibly: "free type variable")? |
| * @return see {@link #severity} for interpretation of values |
| */ |
| private static Severity computeNullProblemSeverity(long requiredBits, long providedBits, int nullStatus, CheckMode mode, boolean requiredIsTypeVariable) { |
| if (requiredBits == providedBits) |
| return Severity.OK; |
| if (requiredBits == 0) { |
| switch (mode) { |
| case COMPATIBLE: |
| case BOUND_CHECK: |
| case BOUND_SUPER_CHECK: |
| case EXACT: |
| return Severity.OK; |
| case OVERRIDE_RETURN: |
| if (providedBits == TagBits.AnnotationNonNull) |
| return Severity.OK; // covariant redefinition to nonnull is good |
| if (!requiredIsTypeVariable) |
| return Severity.OK; // refining an unconstrained non-TVB return to nullable is also legal |
| return Severity.UNCHECKED; |
| case OVERRIDE: |
| return Severity.UNCHECKED; // warn about dropped annotation |
| } |
| } else if (requiredBits == TagBits.AnnotationNullMASK) { |
| return Severity.OK; // OK since LHS accepts either |
| } else if (requiredBits == TagBits.AnnotationNonNull) { |
| switch (mode) { |
| case COMPATIBLE: |
| case BOUND_SUPER_CHECK: |
| if (nullStatus == FlowInfo.NON_NULL) |
| return Severity.OK; // OK by flow analysis |
| //$FALL-THROUGH$ |
| case BOUND_CHECK: |
| case EXACT: |
| case OVERRIDE_RETURN: |
| case OVERRIDE: |
| if (providedBits == 0) |
| return Severity.UNCHECKED; |
| return Severity.MISMATCH; |
| } |
| |
| } else if (requiredBits == TagBits.AnnotationNullable) { |
| switch (mode) { |
| case COMPATIBLE: |
| case OVERRIDE_RETURN: |
| case BOUND_SUPER_CHECK: |
| return Severity.OK; // in these modes everything is compatible to nullable |
| case BOUND_CHECK: |
| case EXACT: |
| if (providedBits == 0) |
| return Severity.UNCHECKED; |
| return Severity.MISMATCH; |
| case OVERRIDE: |
| return Severity.MISMATCH; |
| } |
| } |
| return Severity.OK; // shouldn't get here, requiredBits should be one of the listed cases |
| } |
| |
| static 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) { |
| if (!visit((ReferenceBinding)typeVariable)) |
| return false; |
| long allNullBits = typeVariable.tagBits & TagBits.AnnotationNullMASK; |
| if (typeVariable.firstBound != null) |
| allNullBits = typeVariable.firstBound.tagBits & TagBits.AnnotationNullMASK; |
| for (TypeBinding otherBound : typeVariable.otherUpperBounds()) |
| allNullBits |= otherBound.tagBits & TagBits.AnnotationNullMASK; |
| if (allNullBits == TagBits.AnnotationNullMASK) { |
| this.typeWithContradiction = typeVariable; |
| return false; |
| } |
| return true; |
| } |
| @Override |
| public boolean visit(RawTypeBinding rawType) { |
| return visit((ReferenceBinding)rawType); |
| } |
| @Override |
| public boolean visit(WildcardBinding wildcardBinding) { |
| long allNullBits = wildcardBinding.tagBits & TagBits.AnnotationNullMASK; |
| switch (wildcardBinding.boundKind) { |
| case Wildcard.EXTENDS: |
| allNullBits |= wildcardBinding.bound.tagBits & TagBits.AnnotationNonNull; |
| break; |
| case Wildcard.SUPER: |
| allNullBits |= wildcardBinding.bound.tagBits & TagBits.AnnotationNullable; |
| break; |
| } |
| if (allNullBits == TagBits.AnnotationNullMASK) { |
| this.typeWithContradiction = wildcardBinding; |
| return false; |
| } |
| return true; |
| } |
| @Override |
| public boolean visit(ParameterizedTypeBinding parameterizedTypeBinding) { |
| if (!visit((ReferenceBinding) parameterizedTypeBinding)) |
| return false; |
| return super.visit(parameterizedTypeBinding); |
| } |
| } |
| |
| /** |
| * 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 checkForContradictions(MethodBinding method, Object location, Scope scope) { |
| |
| int start = 0, end = 0; |
| if (location instanceof InvocationSite) { |
| start = ((InvocationSite) location).sourceStart(); |
| end = ((InvocationSite) location).sourceEnd(); |
| } else if (location instanceof ASTNode) { |
| start = ((ASTNode) location).sourceStart; |
| end = ((ASTNode) location).sourceEnd; |
| } |
| 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, start, end, location instanceof FunctionalExpression); |
| // note: if needed, we might want to update the method by removing the contradictory annotations?? |
| return method; |
| } |
| |
| Expression[] arguments = null; |
| if (location instanceof Invocation) |
| arguments = ((Invocation)location).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, start, end, location instanceof FunctionalExpression); |
| return method; |
| } |
| } |
| return method; |
| } |
| |
| public static boolean hasContradictions(TypeBinding type) { |
| SearchContradictions searchContradiction = new SearchContradictions(); |
| TypeBindingVisitor.visit(searchContradiction, type); |
| return searchContradiction.typeWithContradiction != null; |
| } |
| |
| public static TypeBinding strongerType(TypeBinding type1, TypeBinding type2, LookupEnvironment environment) { |
| if ((type1.tagBits & TagBits.AnnotationNonNull) != 0) |
| return mergeTypeAnnotations(type1, type2, true, environment); |
| return mergeTypeAnnotations(type2, type1, true, environment); // don't bother to distinguish unannotated vs. @Nullable, since both can accept null |
| } |
| |
| public static TypeBinding[] weakerTypes(TypeBinding[] parameters1, TypeBinding[] parameters2, LookupEnvironment environment) { |
| TypeBinding[] newParameters = new TypeBinding[parameters1.length]; |
| for (int i = 0; i < newParameters.length; i++) { |
| long tagBits1 = parameters1[i].tagBits; |
| long tagBits2 = parameters2[i].tagBits; |
| if ((tagBits1 & TagBits.AnnotationNullable) != 0) |
| newParameters[i] = mergeTypeAnnotations(parameters1[i], parameters2[i], true, environment); // @Nullable must be preserved |
| else if ((tagBits2 & TagBits.AnnotationNullable) != 0) |
| newParameters[i] = mergeTypeAnnotations(parameters2[i], parameters1[i], true, environment); // @Nullable must be preserved |
| else if ((tagBits1 & TagBits.AnnotationNonNull) == 0) |
| newParameters[i] = mergeTypeAnnotations(parameters1[i], parameters2[i], true, environment); // unannotated must be preserved |
| else |
| newParameters[i] = mergeTypeAnnotations(parameters2[i], parameters1[i], true, environment); // either unannotated, or both are @NonNull |
| } |
| return newParameters; |
| } |
| private static TypeBinding mergeTypeAnnotations(TypeBinding type, TypeBinding otherType, boolean top, LookupEnvironment environment) { |
| TypeBinding mainType = type; |
| if (!top) { |
| // for all but the top level type superimpose other's type annotation onto type |
| AnnotationBinding[] otherAnnotations = otherType.getTypeAnnotations(); |
| if (otherAnnotations != Binding.NO_ANNOTATIONS) |
| mainType = environment.createAnnotatedType(type, otherAnnotations); |
| } |
| if (mainType.isParameterizedType() && otherType.isParameterizedType()) { |
| ParameterizedTypeBinding ptb = (ParameterizedTypeBinding) type, otherPTB = (ParameterizedTypeBinding) otherType; |
| TypeBinding[] typeArguments = ptb.arguments; |
| TypeBinding[] otherTypeArguments = otherPTB.arguments; |
| TypeBinding[] newTypeArguments = new TypeBinding[typeArguments.length]; |
| for (int i = 0; i < typeArguments.length; i++) { |
| newTypeArguments[i] = mergeTypeAnnotations(typeArguments[i], otherTypeArguments[i], false, environment); |
| } |
| return environment.createParameterizedType(ptb.genericType(), newTypeArguments, ptb.enclosingType()); |
| } |
| return mainType; |
| } |
| |
| @SuppressWarnings("nls") |
| @Override |
| public String toString() { |
| if (this == NULL_ANNOTATIONS_OK) return "OK"; |
| if (this == NULL_ANNOTATIONS_MISMATCH) return "MISMATCH"; |
| if (this == NULL_ANNOTATIONS_OK_NONNULL) return "OK NonNull"; |
| if (this == NULL_ANNOTATIONS_UNCHECKED) return "UNCHECKED"; |
| StringBuilder buf = new StringBuilder(); |
| buf.append("Analysis result: severity="+this.severity); |
| buf.append(" nullStatus="+this.nullStatus); |
| return buf.toString(); |
| } |
| } |