Expand Up @@ -22,6 +22,8 @@
* bug 376053 - [compiler][resource] Strange potential resource leak problems
* bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated
* bug 393719 - [compiler] inconsistent warnings on iteration variables
* bug 331649 - [compiler][null] consider null annotations for fields
* bug 382789 - [compiler][null] warn when syntactically-nonnull expression is compared against null
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.problem;

Expand All @@ -46,6 +48,7 @@
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
Expand All @@ -55,6 +58,7 @@
import org.eclipse.jdt.internal.compiler.ast.BranchStatement;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
Expand All @@ -75,6 +79,7 @@
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
Expand Down Expand Up @@ -119,6 +124,7 @@
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
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.parser.JavadocTagConstants;
import org.eclipse.jdt.internal.compiler.parser.Parser;
Expand Down Expand Up @@ -298,6 +304,7 @@ public static int getIrritant(int problemID) {
return CompilerOptions.VarargsArgumentNeedCast;

case IProblem.NullLocalVariableReference:
case IProblem.NullableFieldReference:
return CompilerOptions.NullReference;

case IProblem.PotentialNullLocalVariableReference:
Expand All @@ -310,9 +317,14 @@ public static int getIrritant(int problemID) {
case IProblem.NonNullLocalVariableComparisonYieldsFalse:
case IProblem.NullLocalVariableComparisonYieldsFalse:
case IProblem.NullLocalVariableInstanceofYieldsFalse:
case IProblem.RedundantNullCheckOnNonNullExpression:
case IProblem.NonNullExpressionComparisonYieldsFalse:
case IProblem.RedundantNullCheckOnNonNullMessageSend:
case IProblem.RedundantNullCheckOnSpecdNonNullLocalVariable:
case IProblem.SpecdNonNullLocalVariableComparisonYieldsFalse:
case IProblem.NonNullMessageSendComparisonYieldsFalse:
case IProblem.RedundantNullCheckOnNonNullSpecdField:
case IProblem.NonNullSpecdFieldComparisonYieldsFalse:
return CompilerOptions.RedundantNullCheck;

case IProblem.RequiredNonNullButProvidedNull:
Expand All @@ -322,6 +334,8 @@ public static int getIrritant(int problemID) {
case IProblem.IllegalDefinitionToNonNullParameter:
case IProblem.ParameterLackingNullableAnnotation:
case IProblem.CannotImplementIncompatibleNullness:
case IProblem.UninitializedNonNullField:
case IProblem.UninitializedNonNullFieldHintMissingDefault:
case IProblem.ConflictingNullAnnotations:
case IProblem.ConflictingInheritedNullAnnotations:
return CompilerOptions.NullSpecViolation;
Expand Down Expand Up @@ -5162,6 +5176,92 @@ public void localVariableNullComparedToNonNull(LocalVariableBinding local, ASTNo
nodeSourceEnd(local, location));
}

/**
* @param expr expression being compared for null or nonnull
* @param checkForNull true if checking for null, false if checking for nonnull
*/
public boolean expressionNonNullComparison(Expression expr, boolean checkForNull) {
int problemId = 0;
Binding binding = null;
String[] arguments = null;
int start = 0, end = 0;

Expression location = expr;
// unwrap uninteresting nodes:
while (true) {
if (expr instanceof Assignment)
return false; // don't report against the assignment, but the variable
else if (expr instanceof CastExpression)
expr = ((CastExpression) expr).expression;
else
break;
}
// check all those kinds of expressions that can possible answer NON_NULL from nullStatus():
if (expr instanceof MessageSend) {
problemId = checkForNull
? IProblem.NonNullMessageSendComparisonYieldsFalse
: IProblem.RedundantNullCheckOnNonNullMessageSend;
MethodBinding method = ((MessageSend)expr).binding;
binding = method;
arguments = new String[] { new String(method.shortReadableName()) };
start = location.sourceStart;
end = location.sourceEnd;
} else if (expr instanceof Reference && !(expr instanceof ThisReference) && !(expr instanceof ArrayReference)) {
FieldBinding field = ((Reference)expr).lastFieldBinding();
if (field == null) {
return false;
}
if (field.isNonNull()) {
problemId = checkForNull
? IProblem.NonNullSpecdFieldComparisonYieldsFalse
: IProblem.RedundantNullCheckOnNonNullSpecdField;
char[][] nonNullName = this.options.nonNullAnnotationName;
arguments = new String[] { new String(field.name),
new String(nonNullName[nonNullName.length-1]) };
}
binding = field;
start = nodeSourceStart(binding, location);
end = nodeSourceEnd(binding, location);
} else if (expr instanceof AllocationExpression
|| expr instanceof ArrayAllocationExpression
|| expr instanceof ArrayInitializer
|| expr instanceof ClassLiteralAccess
|| expr instanceof ThisReference) {
// fall through to bottom
} else if (expr instanceof Literal
|| expr instanceof ConditionalExpression) {
if (expr instanceof NullLiteral) {
needImplementation(location); // reported as nonnull??
return false;
}
if (expr.resolvedType != null && expr.resolvedType.isBaseType()) {
// false alarm, auto(un)boxing is involved
return false;
}
// fall through to bottom
} else if (expr instanceof BinaryExpression) {
if ((expr.bits & ASTNode.ReturnTypeIDMASK) != TypeIds.T_JavaLangString) {
// false alarm, primitive types involved, must be auto(un)boxing?
return false;
}
// fall through to bottom
} else {
needImplementation(expr); // want to see if we get here
return false;
}
if (problemId == 0) {
// standard case, fill in details now
problemId = checkForNull
? IProblem.NonNullExpressionComparisonYieldsFalse
: IProblem.RedundantNullCheckOnNonNullExpression;
start = location.sourceStart;
end = location.sourceEnd;
arguments = NoArgument;
}
this.handle(problemId, arguments, arguments, start, end);
return true;
}

public void localVariableNullInstanceof(LocalVariableBinding local, ASTNode location) {
int severity = computeSeverity(IProblem.NullLocalVariableInstanceofYieldsFalse);
if (severity == ProblemSeverities.Ignore) return;
Expand Down Expand Up @@ -5201,6 +5301,18 @@ public void localVariablePotentialNullReference(LocalVariableBinding local, ASTN
nodeSourceEnd(local, location));
}

public void nullableFieldDereference(VariableBinding variable, long position) {
String[] arguments = new String[] {new String(variable.name)};
char[][] nullableName = this.options.nullableAnnotationName;
arguments = new String[] {new String(variable.name), new String(nullableName[nullableName.length-1])};
this.handle(
IProblem.NullableFieldReference,
arguments,
arguments,
(int)(position >>> 32),
(int)(position));
}

public void localVariableRedundantCheckOnNonNull(LocalVariableBinding local, ASTNode location) {
int severity = computeSeverity(IProblem.RedundantNullCheckOnNonNullLocalVariable);
if (severity == ProblemSeverities.Ignore) return;
Expand Down Expand Up @@ -7223,6 +7335,19 @@ public void uninitializedBlankFinalField(FieldBinding field, ASTNode location) {
nodeSourceStart(field, location),
nodeSourceEnd(field, location));
}
public void uninitializedNonNullField(FieldBinding field, ASTNode location) {
char[][] nonNullAnnotationName = this.options.nonNullAnnotationName;
String[] arguments = new String[] {
new String(nonNullAnnotationName[nonNullAnnotationName.length-1]),
new String(field.readableName())
};
this.handle(
methodHasMissingSwitchDefault() ? IProblem.UninitializedNonNullFieldHintMissingDefault : IProblem.UninitializedNonNullField,
arguments,
arguments,
nodeSourceStart(field, location),
nodeSourceEnd(field, location));
}
public void uninitializedLocalVariable(LocalVariableBinding binding, ASTNode location) {
binding.tagBits |= TagBits.NotInitialized;
String[] arguments = new String[] {new String(binding.readableName())};
Expand Down Expand Up @@ -8236,14 +8361,13 @@ public void nullityMismatch(Expression expression, TypeBinding providedType, Typ
return;
}
if ((nullStatus & FlowInfo.POTENTIALLY_NULL) != 0) {
if (expression instanceof SingleNameReference) {
SingleNameReference snr = (SingleNameReference) expression;
if (snr.binding instanceof LocalVariableBinding) {
if (((LocalVariableBinding)snr.binding).isNullable()) {
nullityMismatchSpecdNullable(expression, requiredType, annotationName);
return;
}
}
VariableBinding var = expression.localVariableBinding();
if (var == null && expression instanceof Reference) {
var = ((Reference)expression).lastFieldBinding();
}
if (var != null && var.isNullable()) {
nullityMismatchSpecdNullable(expression, requiredType, annotationName);
return;
}
nullityMismatchPotentiallyNull(expression, requiredType, annotationName);
return;
Expand Down Expand Up @@ -8443,6 +8567,13 @@ public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, in
this.handle(IProblem.RedundantNullAnnotation, ProblemHandler.NoArgument, ProblemHandler.NoArgument, sourceStart, sourceEnd);
}

public void nullAnnotationIsRedundant(FieldDeclaration sourceField) {
Annotation annotation = findAnnotation(sourceField.annotations, TypeIds.T_ConfiguredAnnotationNonNull);
int sourceStart = annotation != null ? annotation.sourceStart : sourceField.type.sourceStart;
int sourceEnd = sourceField.type.sourceEnd;
this.handle(IProblem.RedundantNullAnnotation, ProblemHandler.NoArgument, ProblemHandler.NoArgument, sourceStart, sourceEnd);
}

public void nullDefaultAnnotationIsRedundant(ASTNode location, Annotation[] annotations, Binding outer) {
Annotation annotation = findAnnotation(annotations, TypeIds.T_ConfiguredAnnotationNonNullByDefault);
int start = annotation != null ? annotation.sourceStart : location.sourceStart;
Expand Down
Expand Up @@ -19,6 +19,8 @@
# bug 388281 - [compiler][null] inheritance of null annotations as an option
# bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated
# bug 393719 - [compiler] inconsistent warnings on iteration variables
# bug 331649 - [compiler][null] consider null annotations for fields
# bug 382789 - [compiler][null] warn when syntactically-nonnull expression is compared against null
###############################################################################
0 = {0}
1 = super cannot be used in java.lang.Object
Expand Down Expand Up @@ -593,6 +595,10 @@
### MORE TYPE RELATED
662 = Illegal attempt to create arrays of union types

### NULL ANALYSIS FOR OTHER EXPRESSIONS
670 = Null comparison always yields false: this expression cannot be null
671 = Redundant null check: this expression cannot be null

### CORRUPTED BINARIES
700 = The class file {0} contains a signature ''{1}'' ill-formed at position {2}

Expand Down Expand Up @@ -689,6 +695,7 @@
921 = The method {0} from {1} cannot implement the corresponding method from {2} due to incompatible nullness constraints
922 = The nullness annotation is redundant with a default that applies to this location
923 = The nullness annotation @{0} is not applicable for the primitive type {1}
924 = Potential null pointer access: The field {0} is declared as @{1}
925 = Nullness default is redundant with the global default
926 = Nullness default is redundant with a default specified for the enclosing package {0}
927 = Nullness default is redundant with a default specified for the enclosing type {0}
Expand All @@ -698,6 +705,11 @@
931 = Redundant null check: The variable {0} is specified as @{1}
932 = Null comparison always yields false: The variable {0} is specified as @{1}
933 = Null type mismatch: required ''@{0} {1}'' but the provided value is specified as @{2}
934 = The @{0} field {1} may not have been initialized
935 = The @{0} field {1} may not have been initialized. Note that a problem regarding missing ''default:'' on ''switch'' has been suppressed, which is perhaps related to this problem
936 = Null comparison always yields false: The method {0} cannot return null
937 = Redundant null check: The field {0} is declared as @{1}
938 = Null comparison always yields false: The field {0} is declared as @{1}
939 = The default ''@{0}'' conflicts with the inherited ''@{1}'' annotation in the overridden method from {2}
940 = Conflict between inherited null annotations ''@{0}'' declared in {1} versus ''@{2}'' declared in {3}

Expand Down
28 changes: 23 additions & 5 deletions org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
Expand Up @@ -97,6 +97,7 @@
* COMPILER_PB_SWITCH_MISSING_DEFAULT_CASE
* COMPILER_INHERIT_NULL_ANNOTATIONS
* COMPILER_PB_NONNULL_PARAMETER_ANNOTATION_DROPPED
* COMPILER_PB_SYNTACTIC_NULL_ANALYSIS_FOR_FIELDS
*******************************************************************************/

package org.eclipse.jdt.core;
Expand Down Expand Up @@ -1490,7 +1491,7 @@ public final class JavaCore extends Plugin {
* <p>If the annotation specified by this option is applied to a type in a method
* signature or variable declaration, this will be interpreted as a specification
* that <code>null</code> is a legal value in that position. Currently supported
* positions are: method parameters, method return type and local variables.</p>
* positions are: method parameters, method return type, fields and local variables.</p>
* <p>If a value whose type
* is annotated with this annotation is dereferenced without checking for null,
* the compiler will trigger a diagnostic as further controlled by
Expand All @@ -1516,7 +1517,7 @@ public final class JavaCore extends Plugin {
* <p>If the annotation specified by this option is applied to a type in a method
* signature or variable declaration, this will be interpreted as a specification
* that <code>null</code> is <b>not</b> a legal value in that position. Currently
* supported positions are: method parameters, method return type and local variables.</p>
* supported positions are: method parameters, method return type, fields and local variables.</p>
* <p>For values declared with this annotation, the compiler will never trigger a null
* reference diagnostic (as controlled by {@link #COMPILER_PB_POTENTIAL_NULL_REFERENCE}
* and {@link #COMPILER_PB_NULL_REFERENCE}), because the assumption is made that null
Expand All @@ -1540,8 +1541,8 @@ public final class JavaCore extends Plugin {
* <p>This option defines a fully qualified Java type name that the compiler may use
* to perform special null analysis.</p>
* <p>If the annotation is applied without an argument, all unannotated types in method signatures
* within the annotated element will be treated as if they were specified with the non-null annotation
* (see {@link #COMPILER_NONNULL_ANNOTATION_NAME}).</p>
* and field declarations within the annotated element will be treated as if they were specified
* with the non-null annotation (see {@link #COMPILER_NONNULL_ANNOTATION_NAME}).</p>
* <p>If the annotation is applied with the constant <code>false</code> as its argument
* all corresponding defaults at outer scopes will be canceled for the annotated element.</p>
* <p>This option only has an effect if the option {@link #COMPILER_ANNOTATION_NULL_ANALYSIS} is enabled.</p>
Expand Down Expand Up @@ -1592,6 +1593,9 @@ public final class JavaCore extends Plugin {
* for at least one of its parameters, tries to tighten that null contract by
* specifying a nonnull annotation for its corresponding parameter
* (prohibition of covariant parameters).</li>
* <li>A non-static field with a nonnull annotation is not definitely assigned at
* the end of each constructor.</li>
* <li>A static field with a nonnull annotation is not definitely assigned in static initializers.</li>
* </ol>
* In the above an expression is considered as <em>nullable</em> if
* either it is statically known to evaluate to the value <code>null</code>, or if it is
Expand Down Expand Up @@ -1687,6 +1691,20 @@ public final class JavaCore extends Plugin {
* @category CompilerOptionID
*/
public static final String COMPILER_PB_REDUNDANT_NULL_ANNOTATION = PLUGIN_ID + ".compiler.problem.redundantNullAnnotation"; //$NON-NLS-1$
/**
* Compiler option ID: Perform syntactic null analysis for fields.
* <p>When enabled, the compiler will detect certain syntactic constellations where a null
* related warning against a field reference would normally be raised but can be suppressed
* at low risk given that the same field reference was known to be non-null immediately before.</p>
* <dl>
* <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields"</code></dd>
* <dt>Possible values:</dt><dd><code>{ "disabled", "enabled" }</code></dd>
* <dt>Default:</dt><dd><code>"disabled"</code></dd>
* </dl>
* @since 3.9
* @category CompilerOptionID
*/
public static final String COMPILER_PB_SYNTACTIC_NULL_ANALYSIS_FOR_FIELDS = JavaCore.PLUGIN_ID+".compiler.problem.syntacticNullAnalysisForFields"; //$NON-NLS-1$
/**
* Compiler option ID: Inheritance of null annotations.
* <p>When enabled, the compiler will check for each method without any explicit null annotations:
Expand Down Expand Up @@ -5597,4 +5615,4 @@ public void start(BundleContext context) throws Exception {
super.start(context);
JavaModelManager.getJavaModelManager().startup();
}
}
}