Bug 560310 - [null] Bogus error message in loop
Change-Id: I9f28022b7c8af9dc08f4d7b3962b5b0f9b0fd013
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
index a89da88..ae901e2 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
@@ -9961,7 +9961,7 @@
"2. ERROR in x\\C.java (at line 8)\n" +
" @NonNull Object x = t; // error, should warn?\n" +
" ^\n" +
- "Null type mismatch (type annotations): required \'@NonNull Object\' but this expression has type \'T1 extends @Nullable Number\'\n" +
+ "Null type safety: required \'@NonNull\' but this expression has type \'T1\', a free type variable that may represent a \'@Nullable\' type\n" +
"----------\n" +
"3. ERROR in x\\C.java (at line 10)\n" +
" return t.toString(); // legal???\n" +
@@ -18062,4 +18062,73 @@
};
runner.runConformTest();
}
+public void testBug560310() {
+ runConformTestWithLibs(
+ new String[] {
+ "confusing/Confusing.java",
+ "package confusing;\n" +
+ "\n" +
+ "import java.util.ArrayList;\n" +
+ "\n" +
+ "import org.eclipse.jdt.annotation.NonNullByDefault;\n" +
+ "\n" +
+ "public abstract class Confusing {\n" +
+ " abstract int unannotated(ArrayList<String> list);\n" +
+ "\n" +
+ " @NonNullByDefault\n" +
+ " public void f(boolean x) {\n" +
+ " ArrayList<String> list = x ? null : new ArrayList<>();\n" +
+ "\n" +
+ " while (true) {\n" +
+ " unannotated(list);\n" +
+ " }\n" +
+ " }\n" +
+ "}\n"
+ },
+ getCompilerOptions(),
+ "----------\n" +
+ "1. INFO in confusing\\Confusing.java (at line 15)\n" +
+ " unannotated(list);\n" +
+ " ^^^^\n" +
+ "Unsafe null type conversion (type annotations): The value of type \'ArrayList<@NonNull String>\' is made accessible using the less-annotated type \'ArrayList<String>\'\n" +
+ "----------\n");
+}
+public void testBug560310try_finally() {
+ runConformTestWithLibs(
+ new String[] {
+ "confusing/Confusing.java",
+ "package confusing;\n" +
+ "\n" +
+ "import java.util.ArrayList;\n" +
+ "\n" +
+ "import org.eclipse.jdt.annotation.NonNullByDefault;\n" +
+ "\n" +
+ "public abstract class Confusing {\n" +
+ " abstract int unannotated(ArrayList<String> list);\n" +
+ "\n" +
+ " @NonNullByDefault\n" +
+ " public void f(boolean x) {\n" +
+ " ArrayList<String> list = x ? null : new ArrayList<>();\n" +
+ "\n" +
+ " try {\n" +
+ " unannotated(list);\n" +
+ " } finally {\n" +
+ " unannotated(list);\n" +
+ " }\n" +
+ " }\n" +
+ "}\n"
+ },
+ getCompilerOptions(),
+ "----------\n" +
+ "1. INFO in confusing\\Confusing.java (at line 15)\n" +
+ " unannotated(list);\n" +
+ " ^^^^\n" +
+ "Unsafe null type conversion (type annotations): The value of type \'ArrayList<@NonNull String>\' is made accessible using the less-annotated type \'ArrayList<String>\'\n" +
+ "----------\n" +
+ "2. INFO in confusing\\Confusing.java (at line 17)\n" +
+ " unannotated(list);\n" +
+ " ^^^^\n" +
+ "Unsafe null type conversion (type annotations): The value of type \'ArrayList<@NonNull String>\' is made accessible using the less-annotated type \'ArrayList<String>\'\n" +
+ "----------\n");
+}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
index 2be11ef..1054469 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
@@ -129,6 +129,14 @@
this.nullStatus = nullStatus;
}
+ /**
+ * For creating updated status during *FlowContext.complainOnDeferred*Checks() once the actual nullStatus is known
+ */
+ public NullAnnotationMatching withNullStatus(int updatedNullStatus) {
+ return updatedNullStatus == this.nullStatus ? this
+ : new NullAnnotationMatching(this.severity, updatedNullStatus, this.superTypeHint);
+ }
+
public boolean isAnyMismatch() { return this.severity.isAnyMismatch(); }
public boolean isUnchecked() { return this.severity == Severity.UNCHECKED || this.severity == Severity.UNCHECKED_TO_UNANNOTATED; }
public boolean isAnnotatedToUnannotated() { return this.severity == Severity.UNCHECKED_TO_UNANNOTATED; }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
index e763088..5cd6819 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -27,6 +27,7 @@
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching;
import org.eclipse.jdt.internal.compiler.ast.Reference;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
@@ -52,6 +53,7 @@
ASTNode[] nullReferences; // Expressions for null checking, Statements for resource analysis
// cast to Expression is safe if corresponding nullCheckType != EXIT_RESOURCE
int[] nullCheckTypes;
+ NullAnnotationMatching[] nullAnnotationStatuses;
int nullCount;
// see also the related field FlowContext#expectedTypes
@@ -210,7 +212,15 @@
int nullStatus = flowInfo.nullStatus(local);
if (nullStatus != FlowInfo.NON_NULL) {
char[][] annotationName = scope.environment().getNonNullAnnotationName();
- scope.problemReporter().nullityMismatch((Expression) location, this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], nullStatus, annotationName);
+ TypeBinding providedType = this.providedExpectedTypes[i][0];
+ TypeBinding expectedType = this.providedExpectedTypes[i][1];
+ Expression expression2 = (Expression) location;
+ if (this.nullAnnotationStatuses[i] != null) {
+ this.nullAnnotationStatuses[i] = this.nullAnnotationStatuses[i].withNullStatus(nullStatus);
+ scope.problemReporter().nullityMismatchingTypeAnnotation(expression2, providedType, expectedType, this.nullAnnotationStatuses[i]);
+ } else {
+ scope.problemReporter().nullityMismatch(expression2, providedType, expectedType, nullStatus, annotationName);
+ }
}
break;
case IN_UNBOXING:
@@ -449,12 +459,13 @@
}
@Override
-protected void recordNullReference(LocalVariableBinding local,
- ASTNode expression, int checkType, FlowInfo nullInfo) {
+protected void recordNullReferenceWithAnnotationStatus(LocalVariableBinding local,
+ ASTNode expression, int checkType, FlowInfo nullInfo, NullAnnotationMatching nullAnnotationStatus) {
if (this.nullCount == 0) {
this.nullLocals = new LocalVariableBinding[5];
this.nullReferences = new ASTNode[5];
this.nullCheckTypes = new int[5];
+ this.nullAnnotationStatuses = new NullAnnotationMatching[5];
}
else if (this.nullCount == this.nullLocals.length) {
int newLength = this.nullCount * 2;
@@ -467,9 +478,13 @@
System.arraycopy(this.nullCheckTypes, 0,
this.nullCheckTypes = new int[newLength], 0,
this.nullCount);
+ System.arraycopy(this.nullAnnotationStatuses, 0,
+ this.nullAnnotationStatuses = new NullAnnotationMatching[this.nullCount * 2], 0,
+ this.nullCount);
}
this.nullLocals[this.nullCount] = local;
this.nullReferences[this.nullCount] = expression;
+ this.nullAnnotationStatuses[this.nullCount] = nullAnnotationStatus;
this.nullCheckTypes[this.nullCount++] = checkType;
}
@Override
@@ -480,12 +495,12 @@
recordNullReference(null, expression, IN_UNBOXING, flowInfo);
}
@Override
-protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, TypeBinding expectedType, int checkType) {
+protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, NullAnnotationMatching nullAnnotationStatus, TypeBinding expectedType, int checkType) {
// cf. decision structure inside FinallyFlowContext.recordUsingNullReference(..)
if (nullStatus == FlowInfo.UNKNOWN ||
((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0 && nullStatus != FlowInfo.NULL)) {
recordProvidedExpectedTypes(providedType, expectedType, this.nullCount);
- recordNullReference(expression.localVariableBinding(), expression, checkType, flowInfo);
+ recordNullReferenceWithAnnotationStatus(expression.localVariableBinding(), expression, checkType, flowInfo, nullAnnotationStatus);
return true;
}
return false;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
index 3d73bad..8fb978d 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -820,8 +820,33 @@
* Alternatively, a {@link #IN_UNBOXING} check can e requested.
* @param nullInfo the null flow info observed at this first visit of location.
*/
-protected void recordNullReference(LocalVariableBinding local,
+protected final void recordNullReference(LocalVariableBinding local,
ASTNode location, int checkType, FlowInfo nullInfo) {
+ recordNullReferenceWithAnnotationStatus(local, location, checkType, nullInfo, null);
+}
+
+/**
+ * Record a null reference for use by deferred checks. Only looping or
+ * finally contexts really record that information. Other contexts
+ * immediately check for unboxing.
+ * @param local the local variable involved in the check
+ * @param location the location triggering the analysis, for normal null dereference
+ * this is an expression resolving to 'local', for resource leaks it is an
+ * early exit statement.
+ * @param checkType the checkType against which the check must be performed; one of
+ * {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL
+ * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL},
+ * {@link #CAN_ONLY_NON_NULL CAN_ONLY_NON_NULL}, potentially
+ * combined with a context indicator (one of {@link #IN_COMPARISON_NULL},
+ * {@link #IN_COMPARISON_NON_NULL}, {@link #IN_ASSIGNMENT} or {@link #IN_INSTANCEOF}).
+ * <br>
+ * Alternatively, a {@link #IN_UNBOXING} check can e requested.
+ * @param nullInfo the null flow info observed at this first visit of location.
+ * @param nullAnnotationStatus if null annotations are analysed this may hold more information
+ * about the exact kind of problem, can be <code>null</code>
+ */
+protected void recordNullReferenceWithAnnotationStatus(LocalVariableBinding local,
+ ASTNode location, int checkType, FlowInfo nullInfo, NullAnnotationMatching nullAnnotationStatus) {
// default implementation: do nothing
}
@@ -1054,7 +1079,7 @@
if ((this.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) {
isInsideAssert = FlowContext.HIDE_NULL_COMPARISON_WARNING;
}
- if (currentContext.internalRecordNullityMismatch(expression, providedType, flowInfo, nullStatus, expectedType, ASSIGN_TO_NONNULL | isInsideAssert))
+ if (currentContext.internalRecordNullityMismatch(expression, providedType, flowInfo, nullStatus, annotationStatus, expectedType, ASSIGN_TO_NONNULL | isInsideAssert))
return;
currentContext = currentContext.parent;
}
@@ -1066,7 +1091,7 @@
currentScope.problemReporter().nullityMismatch(expression, providedType, expectedType, nullStatus,
currentScope.environment().getNonNullAnnotationName());
}
-protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, TypeBinding expectedType, int checkType) {
+protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, NullAnnotationMatching nullAnnotationStatus, TypeBinding expectedType, int checkType) {
// nop, to be overridden in subclasses
return false; // not recorded
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
index 82f40c8..203f568 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -37,6 +37,7 @@
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FakedTrackingVariable;
+import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching;
import org.eclipse.jdt.internal.compiler.ast.Reference;
import org.eclipse.jdt.internal.compiler.codegen.BranchLabel;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
@@ -75,6 +76,7 @@
// cast to Expression is safe if corresponding nullCheckType != EXIT_RESOURCE
int[] nullCheckTypes;
UnconditionalFlowInfo[] nullInfos; // detailed null info observed during the first visit of nullReferences[i], or null
+ NullAnnotationMatching[] nullAnnotationStatuses;
int nullCount;
// see also the related field FlowContext#expectedTypes
@@ -408,7 +410,15 @@
int nullStatus = flowInfo.nullStatus(local);
if (nullStatus != FlowInfo.NON_NULL) {
char[][] annotationName = scope.environment().getNonNullAnnotationName();
- scope.problemReporter().nullityMismatch((Expression) location, this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], nullStatus, annotationName);
+ TypeBinding providedType = this.providedExpectedTypes[i][0];
+ TypeBinding expectedType = this.providedExpectedTypes[i][1];
+ Expression expression2 = (Expression) location;
+ if (this.nullAnnotationStatuses[i] != null) {
+ this.nullAnnotationStatuses[i] = this.nullAnnotationStatuses[i].withNullStatus(nullStatus);
+ scope.problemReporter().nullityMismatchingTypeAnnotation(expression2, providedType, expectedType, this.nullAnnotationStatuses[i]);
+ } else {
+ scope.problemReporter().nullityMismatch(expression2, providedType, expectedType, nullStatus, annotationName);
+ }
}
break;
case EXIT_RESOURCE:
@@ -563,13 +573,14 @@
}
@Override
-protected void recordNullReference(LocalVariableBinding local,
- ASTNode expression, int checkType, FlowInfo nullInfo) {
+protected void recordNullReferenceWithAnnotationStatus(LocalVariableBinding local,
+ ASTNode expression, int checkType, FlowInfo nullInfo, NullAnnotationMatching nullAnnotationStatus) {
if (this.nullCount == 0) {
this.nullLocals = new LocalVariableBinding[5];
this.nullReferences = new ASTNode[5];
this.nullCheckTypes = new int[5];
this.nullInfos = new UnconditionalFlowInfo[5];
+ this.nullAnnotationStatuses = new NullAnnotationMatching[5];
}
else if (this.nullCount == this.nullLocals.length) {
System.arraycopy(this.nullLocals, 0,
@@ -580,10 +591,13 @@
this.nullCheckTypes = new int[this.nullCount * 2], 0, this.nullCount);
System.arraycopy(this.nullInfos, 0,
this.nullInfos = new UnconditionalFlowInfo[this.nullCount * 2], 0, this.nullCount);
+ System.arraycopy(this.nullAnnotationStatuses, 0,
+ this.nullAnnotationStatuses = new NullAnnotationMatching[this.nullCount * 2], 0, this.nullCount);
}
this.nullLocals[this.nullCount] = local;
this.nullReferences[this.nullCount] = expression;
this.nullCheckTypes[this.nullCount] = checkType;
+ this.nullAnnotationStatuses[this.nullCount] = nullAnnotationStatus;
this.nullInfos[this.nullCount++] = nullInfo != null ? nullInfo.unconditionalCopy() : null;
}
@Override
@@ -781,9 +795,9 @@
}
@Override
- protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, TypeBinding expectedType, int checkType) {
+ protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, NullAnnotationMatching nullAnnotationStatus, TypeBinding expectedType, int checkType) {
recordProvidedExpectedTypes(providedType, expectedType, this.nullCount);
- recordNullReference(expression.localVariableBinding(), expression, checkType, flowInfo);
+ recordNullReferenceWithAnnotationStatus(expression.localVariableBinding(), expression, checkType, flowInfo, nullAnnotationStatus);
return true;
}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
index e82038d..fd1f596 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
@@ -10602,19 +10602,29 @@
nullityMismatchIsNull(expression, requiredType);
return;
}
- // try to improve nonnull vs. nullable:
- if (status.isPotentiallyNullMismatch()
- && (requiredType.tagBits & TagBits.AnnotationNonNull) != 0
- && (providedType.tagBits & TagBits.AnnotationNullable) == 0)
- {
- if(this.options.pessimisticNullAnalysisForFreeTypeVariablesEnabled && providedType.isTypeVariable() && !providedType.hasNullTypeAnnotations()) {
- nullityMismatchIsFreeTypeVariable(providedType, expression.sourceStart, expression.sourceEnd);
+ if ((requiredType.tagBits & TagBits.AnnotationNonNull) != 0) { // some problems need a closer look to report the best possible message:
+ // try to improve nonnull vs. nullable:
+ if (status.isPotentiallyNullMismatch()
+ && (providedType.tagBits & TagBits.AnnotationNullable) == 0)
+ {
+ if(this.options.pessimisticNullAnalysisForFreeTypeVariablesEnabled && providedType.isTypeVariable() && !providedType.hasNullTypeAnnotations()) {
+ nullityMismatchIsFreeTypeVariable(providedType, expression.sourceStart, expression.sourceEnd);
+ return;
+ }
+
+ nullityMismatchPotentiallyNull(expression, requiredType, this.options.nonNullAnnotationName);
return;
}
-
- nullityMismatchPotentiallyNull(expression, requiredType, this.options.nonNullAnnotationName);
- return;
+ VariableBinding var = expression.localVariableBinding();
+ if (var == null && expression instanceof Reference) {
+ var = ((Reference)expression).lastFieldBinding();
+ }
+ if(var != null && var.type.isFreeTypeVariable()) {
+ nullityMismatchVariableIsFreeTypeVariable(var, expression);
+ return;
+ }
}
+
String[] arguments;
String[] shortArguments;