diff options
Diffstat (limited to 'org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal')
59 files changed, 1159 insertions, 276 deletions
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java index b07cdfd8d..b870f7f9d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2012 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -1960,6 +1960,9 @@ public class ClassFile implements TypeConstants, TypeIds { this.contentsOffset = startingContentsOffset; return; } + if (annotationTypeBinding.isMemberType()) { + this.recordInnerClasses(annotationTypeBinding); + } final int typeIndex = this.constantPool.literalIndex(annotationTypeBinding.signature()); this.contents[this.contentsOffset++] = (byte) (typeIndex >> 8); this.contents[this.contentsOffset++] = (byte) typeIndex; @@ -2173,6 +2176,12 @@ public class ClassFile implements TypeConstants, TypeIds { if (defaultValueBinding == null) { this.contentsOffset = attributeOffset; } else { + if (defaultValueBinding.isMemberType()) { + this.recordInnerClasses(defaultValueBinding); + } + if (memberValuePairReturnType.isMemberType()) { + this.recordInnerClasses(memberValuePairReturnType); + } if (memberValuePairReturnType.isArrayType() && !defaultValueBinding.isArrayType()) { // automatic wrapping if (this.contentsOffset + 3 >= this.contents.length) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index e55357865..2b634b2aa 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2012 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -17,6 +17,7 @@ * bug 365519 - editorial cleanup after bug 186342 and bug 365387 * bug 374605 - Unreasonable warning for enum-based switch statements * bug 384870 - [compiler] @Deprecated annotation not detected if preceded by other annotation + * bug 393719 - [compiler] inconsistent warnings on iteration variables *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -129,6 +130,7 @@ public abstract class ASTNode implements TypeConstants, TypeIds { // for local decls public static final int IsArgument = Bit3; + public static final int IsForeachElementVariable = Bit5; // for name refs or local decls public static final int FirstAssignmentToLocal = Bit4; @@ -541,8 +543,9 @@ public abstract class ASTNode implements TypeConstants, TypeIds { return false; ReferenceBinding refType = (ReferenceBinding) type; + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=397888 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=385780 - if (refType instanceof TypeVariableBinding) { + if ((this.bits & ASTNode.InsideJavadoc) == 0 && refType instanceof TypeVariableBinding) { refType.modifiers |= ExtraCompilerModifiers.AccLocallyUsed; } // ignore references insing Javadoc comments diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java index 859ee0515..47f3bd825 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java @@ -12,6 +12,7 @@ * Stephan Herrmann - Contributions for * bug 186342 - [compiler][null] Using annotations for null checking * bug 365662 - [compiler][null] warn on contradictory and redundant null annotations + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -433,6 +434,10 @@ public abstract class Annotation extends Expression { FieldDeclaration fieldDeclaration = sourceType.scope.referenceContext.declarationOf(sourceField); recordSuppressWarnings(scope, fieldDeclaration.declarationSourceStart, fieldDeclaration.declarationSourceEnd, scope.compilerOptions().suppressWarnings); } + if ((sourceField.tagBits & TAGBITS_NULLABLE_OR_NONNULL) == TAGBITS_NULLABLE_OR_NONNULL) { + scope.problemReporter().contradictoryNullAnnotations(this); + sourceField.tagBits &= ~TAGBITS_NULLABLE_OR_NONNULL; // avoid secondary problems + } break; case Binding.LOCAL : LocalVariableBinding variable = (LocalVariableBinding) this.recipient; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java index 55d4cac4e..21f7e7aea 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -167,7 +168,7 @@ public void generatePostIncrement(BlockScope currentScope, CodeStream codeStream codeStream.arrayAtPut(this.resolvedType.id, false); } -public int nullStatus(FlowInfo flowInfo) { +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { return FlowInfo.UNKNOWN; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java index da88a1264..75b294a6c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java @@ -22,12 +22,16 @@ * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" * bug 388996 - [compiler][resource] Incorrect 'potential resource leak' * bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.codegen.*; import org.eclipse.jdt.internal.compiler.flow.*; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.*; import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config; @@ -85,9 +89,10 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } FlowInfo preInitInfo = null; + CompilerOptions compilerOptions = currentScope.compilerOptions(); boolean shouldAnalyseResource = local != null && flowInfo.reachMode() == FlowInfo.REACHABLE - && currentScope.compilerOptions().analyseResourceLeaks + && compilerOptions.analyseResourceLeaks //{ObjectTeams: notably lift methods of Closeable roles would trigger warnings against synthetic code && !currentScope.isGeneratedScope() // SH} @@ -108,14 +113,29 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl else FakedTrackingVariable.cleanUpAfterAssignment(currentScope, this.lhs.bits, this.expression); - int nullStatus = this.expression.nullStatus(flowInfo); + int nullStatus = this.expression.nullStatus(flowInfo, flowContext); if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { if (nullStatus == FlowInfo.NULL) { flowContext.recordUsingNullReference(currentScope, local, this.lhs, FlowContext.CAN_ONLY_NULL | FlowContext.IN_ASSIGNMENT, flowInfo); } } - nullStatus = checkAssignmentAgainstNullAnnotation(currentScope, flowContext, local, nullStatus, this.expression, this.expression.resolvedType); + if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) { + VariableBinding var = this.lhs.nullAnnotatedVariableBinding(); + if (var != null) { + nullStatus = checkAssignmentAgainstNullAnnotation(currentScope, flowContext, var, nullStatus, this.expression, this.expression.resolvedType); + if (nullStatus == FlowInfo.NON_NULL + && var instanceof FieldBinding + && this.lhs instanceof Reference + && compilerOptions.enableSyntacticNullAnalysisForFields) + { + int timeToLive = (this.bits & InsideExpressionStatement) != 0 + ? 2 // assignment is statement: make info survives the end of this statement + : 1; // assignment is expression: expire on next event. + flowContext.recordNullCheckedFieldReference((Reference) this.lhs, timeToLive); + } + } + } if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { flowInfo.markNullStatus(local, nullStatus); if (flowContext.initsOnFinally != null) @@ -167,8 +187,8 @@ FieldBinding getLastField(Expression someExpression) { return null; } -public int nullStatus(FlowInfo flowInfo) { - return this.expression.nullStatus(flowInfo); +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { + return this.expression.nullStatus(flowInfo, flowContext); } public StringBuffer print(int indent, StringBuffer output) { @@ -227,7 +247,7 @@ public TypeBinding resolveType(BlockScope scope) { scope.compilationUnitScope().recordTypeConversion(lhsType, rhsType); } if (this.expression.isConstantValueOfTypeAssignableToType(rhsType, lhsType) - || rhsType.isCompatibleWith(lhsType)) { + || rhsType.isCompatibleWith(lhsType, scope)) { this.expression.computeConversion(scope, lhsType, rhsType); checkAssignment(scope, lhsType, rhsType); if (this.expression instanceof CastExpression diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java index 1a1a3674f..1c4eaa1c0 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java @@ -10,6 +10,7 @@ * Technical University Berlin - extended API and implementation * Stephan Herrmann - Contribution for * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -71,8 +72,15 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } else { this.left.checkNPE(currentScope, flowContext, flowInfo); flowInfo = this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) { + flowContext.expireNullCheckedFieldInfo(); + } this.right.checkNPE(currentScope, flowContext, flowInfo); - return this.right.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + flowInfo = this.right.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) { + flowContext.expireNullCheckedFieldInfo(); + } + return flowInfo; } } finally { // account for exception possibly thrown by arithmetics diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java index f9e073e5e..8cf78aa9c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java @@ -14,6 +14,7 @@ * bug 349326 - [1.7] new warning for missing try-with-resources * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -47,6 +48,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl // empty block if (this.statements == null) return flowInfo; int complaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; + boolean enableSyntacticNullAnalysisForFields = currentScope.compilerOptions().enableSyntacticNullAnalysisForFields; for (int i = 0, max = this.statements.length; i < max; i++) { Statement stat = this.statements[i]; if ((complaintLevel = stat.complainIfUnreachable(flowInfo, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) { @@ -55,6 +57,9 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl // record the effect of stat on the finally block of an enclosing try-finally, if any: if (flowContext.initsOnFinally != null) flowContext.mergeFinallyNullInfo(flowInfo); + if (enableSyntacticNullAnalysisForFields) { + flowContext.expireNullCheckedFieldInfo(); + } } if (this.explicitDeclarations > 0) { // if block has its own scope analyze tracking vars now: diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java index ca964cae8..5fe03b0b2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java @@ -14,6 +14,8 @@ * Stephan Herrmann - Contributions for * bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -165,7 +167,7 @@ public static void checkNeedForAssignedCast(BlockScope scope, TypeBinding expect // double d = (float) n; // cast to float is unnecessary if (castedExpressionType == null || rhs.resolvedType.isBaseType()) return; //if (castedExpressionType.id == T_null) return; // tolerate null expression cast - if (castedExpressionType.isCompatibleWith(expectedType)) { + if (castedExpressionType.isCompatibleWith(expectedType, scope)) { scope.problemReporter().unnecessaryCast(rhs); } } @@ -549,8 +551,8 @@ public LocalVariableBinding localVariableBinding() { return this.expression.localVariableBinding(); } -public int nullStatus(FlowInfo flowInfo) { - return this.expression.nullStatus(flowInfo); +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { + return this.expression.nullStatus(flowInfo, flowContext); } /** diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Clinit.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Clinit.java index fb166d1fc..e46fbaaf9 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Clinit.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Clinit.java @@ -9,6 +9,8 @@ * IBM Corporation - initial API and implementation * Technical University Berlin - extended API and implementation * Patrick Wienands <pwienands@abit.de> - Contribution for bug 393749 + * Stephan Herrmann - Contribution for + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -77,17 +79,23 @@ public class Clinit extends AbstractMethodDeclaration { flowInfo = flowInfo.mergedWith(staticInitializerFlowContext.initsOnReturn); FieldBinding[] fields = this.scope.enclosingSourceType().fields(); for (int i = 0, count = fields.length; i < count; i++) { - FieldBinding field; - if ((field = fields[i]).isStatic() - && field.isFinal() + FieldBinding field = fields[i]; //{ObjectTeams: don't check copied fields: - && (field.copyInheritanceSrc == null) + if (field.copyInheritanceSrc != null) continue; // SH} - && (!flowInfo.isDefinitelyAssigned(fields[i]))) { - this.scope.problemReporter().uninitializedBlankFinalField( - field, - this.scope.referenceType().declarationOf(field.original())); - // can complain against the field decl, since only one <clinit> + if (field.isStatic()) { + if (!flowInfo.isDefinitelyAssigned(field)) { + if (field.isFinal()) { + this.scope.problemReporter().uninitializedBlankFinalField( + field, + this.scope.referenceType().declarationOf(field.original())); + // can complain against the field decl, since only one <clinit> + } else if (field.isNonNull()) { + this.scope.problemReporter().uninitializedNonNullField( + field, + this.scope.referenceType().declarationOf(field.original())); + } + } } } // check static initializers thrown exceptions diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java index 11074ddfc..3ab010e5c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -77,7 +78,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, codeStream.recordPositionsFrom(pc, this.sourceStart); } -public int nullStatus(FlowInfo flowInfo) { +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { return FlowInfo.NON_NULL; // we may have complained on checkNPE, but we avoid duplicate error } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java index 3dc331b33..1f1c38f0d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java @@ -17,6 +17,7 @@ * bug 354554 - [null] conditional with redundant condition yields weak error message * bug 349326 - [1.7] new warning for missing try-with-resources * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -85,6 +86,14 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, this.trueInitStateIndex = currentScope.methodScope().recordInitializationStates(trueFlowInfo); trueFlowInfo = this.valueIfTrue.analyseCode(currentScope, flowContext, trueFlowInfo); + // may need to fetch this null status before expireNullCheckedFieldInfo(): + int preComputedTrueNullStatus = -1; + if (currentScope.compilerOptions().enableSyntacticNullAnalysisForFields) { + preComputedTrueNullStatus = this.valueIfTrue.nullStatus(trueFlowInfo, flowContext); + // wipe information that was meant only for valueIfTrue: + flowContext.expireNullCheckedFieldInfo(); + } + // process the if-false part FlowInfo falseFlowInfo = flowInfo.initsWhenFalse().copy(); if (isConditionOptimizedTrue) { @@ -104,10 +113,14 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo mergedInfo; if (isConditionOptimizedTrue){ mergedInfo = trueFlowInfo.addPotentialInitializationsFrom(falseFlowInfo); - this.nullStatus = this.valueIfTrue.nullStatus(trueFlowInfo); + if (preComputedTrueNullStatus != -1) { + this.nullStatus = preComputedTrueNullStatus; + } else { + this.nullStatus = this.valueIfTrue.nullStatus(trueFlowInfo, flowContext); + } } else if (isConditionOptimizedFalse) { mergedInfo = falseFlowInfo.addPotentialInitializationsFrom(trueFlowInfo); - this.nullStatus = this.valueIfFalse.nullStatus(falseFlowInfo); + this.nullStatus = this.valueIfFalse.nullStatus(falseFlowInfo, flowContext); } else { // this block must meet two conflicting requirements (see https://bugs.eclipse.org/324178): // (1) For null analysis of "Object o2 = (o1 != null) ? o1 : new Object();" we need to distinguish @@ -120,7 +133,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, // (regardless of the evaluation of the condition). // to support (1) use the infos of both branches originating from the condition for computing the nullStatus: - computeNullStatus(trueFlowInfo, falseFlowInfo); + computeNullStatus(preComputedTrueNullStatus, trueFlowInfo, falseFlowInfo, flowContext); // to support (2) we split the true/false branches according to their inner structure. Consider this: // if (b ? false : (true && (v = false))) return v; -- ok @@ -163,11 +176,13 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, return mergedInfo; } - private void computeNullStatus(FlowInfo trueBranchInfo, FlowInfo falseBranchInfo) { + private void computeNullStatus(int ifTrueNullStatus, FlowInfo trueBranchInfo, FlowInfo falseBranchInfo, FlowContext flowContext) { // given that the condition cannot be optimized to a constant // we now merge the nullStatus from both branches: - int ifTrueNullStatus = this.valueIfTrue.nullStatus(trueBranchInfo); - int ifFalseNullStatus = this.valueIfFalse.nullStatus(falseBranchInfo); + if (ifTrueNullStatus == -1) { // has this status been pre-computed? + ifTrueNullStatus = this.valueIfTrue.nullStatus(trueBranchInfo, flowContext); + } + int ifFalseNullStatus = this.valueIfFalse.nullStatus(falseBranchInfo, flowContext); if (ifTrueNullStatus == ifFalseNullStatus) { this.nullStatus = ifTrueNullStatus; @@ -384,7 +399,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, codeStream.recordPositionsFrom(pc, this.sourceEnd); } - public int nullStatus(FlowInfo flowInfo) { + public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { return this.nullStatus; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java index 63a797116..e56b23c7c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConstructorDeclaration.java @@ -16,6 +16,8 @@ * bug 361407 - Resource leak warning when resource is assigned to a field outside of constructor * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 383690 - [compiler] location of error re uninitialized final field should be aligned + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -275,12 +277,16 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial // propagate to statements if (this.statements != null) { + boolean enableSyntacticNullAnalysisForFields = this.scope.compilerOptions().enableSyntacticNullAnalysisForFields; int complaintLevel = (nonStaticFieldInfoReachMode & FlowInfo.UNREACHABLE) == 0 ? Statement.NOT_COMPLAINED : Statement.COMPLAINED_FAKE_REACHABLE; for (int i = 0, count = this.statements.length; i < count; i++) { Statement stat = this.statements[i]; if ((complaintLevel = stat.complainIfUnreachable(flowInfo, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) { flowInfo = stat.analyseCode(this.scope, constructorContext, flowInfo); } + if (enableSyntacticNullAnalysisForFields) { + constructorContext.expireNullCheckedFieldInfo(); + } } } // check for missing returning path @@ -293,7 +299,7 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=235781 // flowInfo.setReachMode(initialReachMode); - // check missing blank final field initializations + // check missing blank final field initializations (plus @NonNull) if ((this.constructorCall != null) //{ObjectTeams: no checking for some more cases: // don't need to check tsuper ctors: @@ -305,15 +311,21 @@ public void analyseCode(ClassScope classScope, InitializationFlowContext initial flowInfo = flowInfo.mergedWith(constructorContext.initsOnReturn); FieldBinding[] fields = this.binding.declaringClass.fields(); for (int i = 0, count = fields.length; i < count; i++) { - FieldBinding field; - if ((!(field = fields[i]).isStatic()) - && field.isFinal() - && (!flowInfo.isDefinitelyAssigned(fields[i]))) { - this.scope.problemReporter().uninitializedBlankFinalField( - field, - ((this.bits & ASTNode.IsDefaultConstructor) != 0) - ? (ASTNode) this.scope.referenceType().declarationOf(field.original()) - : this); + FieldBinding field = fields[i]; + if (!field.isStatic() && !flowInfo.isDefinitelyAssigned(field)) { + if (field.isFinal()) { + this.scope.problemReporter().uninitializedBlankFinalField( + field, + ((this.bits & ASTNode.IsDefaultConstructor) != 0) + ? (ASTNode) this.scope.referenceType().declarationOf(field.original()) + : this); + } else if (field.isNonNull()) { + this.scope.problemReporter().uninitializedNonNullField( + field, + ((this.bits & ASTNode.IsDefaultConstructor) != 0) + ? (ASTNode) this.scope.referenceType().declarationOf(field.original()) + : this); + } } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java index ef9274bc1..d3c33036f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java @@ -7,7 +7,10 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking + * Stephan Herrmann - Contributions for + * bug 186342 - [compiler][null] Using annotations for null checking + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -24,28 +27,67 @@ public class EqualExpression extends BinaryExpression { super(left,right,operator); } private void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { - int rightStatus = this.right.nullStatus(flowInfo); - int leftStatus = this.left.nullStatus(flowInfo); - // check if either is a method annotated @NonNull and compared to null: + + // collect null status of child nodes: + int rightStatus = this.right.nullStatus(flowInfo, flowContext); + int leftStatus = this.left.nullStatus(flowInfo, flowContext); + + boolean leftNonNullChecked = false; + boolean rightNonNullChecked = false; + + // check if either is a non-local expression known to be nonnull and compared to null, candidates are + // - method/field annotated @NonNull + // - allocation expression, some literals, this reference (see inside expressionNonNullComparison(..)) + // these checks do not leverage the flowInfo. + boolean checkEquality = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL; if (leftStatus == FlowInfo.NON_NULL && rightStatus == FlowInfo.NULL) { - if (this.left instanceof MessageSend) { - scope.problemReporter().messageSendRedundantCheckOnNonNull(((MessageSend) this.left).binding, this.left); - } - // TODO: handle all kinds of expressions (cf. also https://bugs.eclipse.org/364326) + leftNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.left, checkEquality); } else if (leftStatus == FlowInfo.NULL && rightStatus == FlowInfo.NON_NULL) { - if (this.right instanceof MessageSend) { - scope.problemReporter().messageSendRedundantCheckOnNonNull(((MessageSend) this.right).binding, this.right); + rightNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.right, checkEquality); + } + + // perform flowInfo-based checks for variables and record info for syntactic null analysis for fields: + if (!leftNonNullChecked) { + LocalVariableBinding local = this.left.localVariableBinding(); + if (local != null) { + if ((local.type.tagBits & TagBits.IsBaseType) == 0) { + checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, rightStatus, this.left); + } + } else if (this.left instanceof Reference + && ((!checkEquality && rightStatus == FlowInfo.NULL) || (checkEquality && rightStatus == FlowInfo.NON_NULL)) + && scope.compilerOptions().enableSyntacticNullAnalysisForFields) + { + FieldBinding field = ((Reference)this.left).lastFieldBinding(); + if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) { + flowContext.recordNullCheckedFieldReference((Reference) this.left, 1); + } } - // TODO: handle all kinds of expressions (cf. also https://bugs.eclipse.org/364326) } - - LocalVariableBinding local = this.left.localVariableBinding(); - if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { - checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, rightStatus, this.left); + if (!rightNonNullChecked) { + LocalVariableBinding local = this.right.localVariableBinding(); + if (local != null) { + if ((local.type.tagBits & TagBits.IsBaseType) == 0) { + checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, leftStatus, this.right); + } + } else if (this.right instanceof Reference + && ((!checkEquality && leftStatus == FlowInfo.NULL) || (checkEquality && leftStatus == FlowInfo.NON_NULL)) + && scope.compilerOptions().enableSyntacticNullAnalysisForFields) + { + FieldBinding field = ((Reference)this.right).lastFieldBinding(); + if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) { + flowContext.recordNullCheckedFieldReference((Reference) this.right, 1); + } + } } - local = this.right.localVariableBinding(); - if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { - checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, leftStatus, this.right); + + // handle reachability: + if (leftNonNullChecked || rightNonNullChecked) { + // above checks have not propagated unreachable into the corresponding branch, do it now: + if (checkEquality) { + initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } else { + initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } } } private void checkVariableComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse, LocalVariableBinding local, int nullStatus, Expression reference) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java index c9ea3768b..25b31e126 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java @@ -10,9 +10,11 @@ * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation - * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for * bug 292478 - Report potentially null across variable assignment * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -45,6 +47,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; 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.ShouldNotImplement; import org.eclipse.jdt.internal.compiler.util.Messages; @@ -704,14 +707,21 @@ boolean handledByGeneratedMethod(Scope scope, TypeBinding castType, TypeBinding { return false; } // SH} /** - * Check the local variable of this expression, if any, against potential NPEs - * given a flow context and an upstream flow info. If so, report the risk to - * the context. Marks the local as checked, which affects the flow info. + * Check this expression against potential NPEs, which may occur: + * <ul> + * <li>if the expression is the receiver in a field access, qualified allocation, array reference or message send + * incl. implicit message sends like it happens for the collection in a foreach statement.</li> + * <li>if the expression is subject to unboxing</li> + * <li>if the expression is the exception in a throw statement</li> + * </ul> + * If a risk of NPE is detected report it to the context. + * If the expression denotes a local variable, mark it as checked, which affects the flow info. * @param scope the scope of the analysis * @param flowContext the current flow context * @param flowInfo the upstream flow info; caveat: may get modified + * @return could this expression be checked by the current implementation? */ -public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { LocalVariableBinding local = localVariableBinding(); if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { @@ -728,7 +738,9 @@ public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInf if (flowContext.initsOnFinally != null) { flowContext.markFinallyNullStatus(local, FlowInfo.NON_NULL); } + return true; } + return false; // not checked } public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) { @@ -1050,11 +1062,11 @@ public void markAsNonNull() { this.bits |= ASTNode.IsNonNull; } -public int nullStatus(FlowInfo flowInfo) { +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { if (/* (this.bits & IsNonNull) != 0 || */ this.constant != null && this.constant != Constant.NotAConstant) - return FlowInfo.NON_NULL; // constant expression cannot be null + return FlowInfo.NON_NULL; // constant expression cannot be null LocalVariableBinding local = localVariableBinding(); if (local != null) @@ -1297,4 +1309,13 @@ public void traverse(ASTVisitor visitor, BlockScope scope) { public void traverse(ASTVisitor visitor, ClassScope scope) { // nothing to do } + +/** + * Used on the lhs of an assignment for detecting null spec violation. + * If this expression represents a null-annotated variable return the variable binding, + * otherwise null. +*/ +public VariableBinding nullAnnotatedVariableBinding() { + return null; +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java index 00d6a113d..dac84ffdf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java @@ -10,6 +10,9 @@ * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -112,6 +115,16 @@ public FlowInfo analyseCode(MethodScope initializationScope, FlowContext flowCon .unconditionalInits(); flowInfo.markAsDefinitelyAssigned(this.binding); } + if (this.initialization != null) { + if (this.binding.isNonNull()) { + int nullStatus = this.initialization.nullStatus(flowInfo, flowContext); + // check against annotation @NonNull: + if (nullStatus != FlowInfo.NON_NULL) { + char[][] annotationName = initializationScope.environment().getNonNullAnnotationName(); + initializationScope.problemReporter().nullityMismatch(this.initialization, this.initialization.resolvedType, this.binding.type, nullStatus, annotationName); + } + } + } return flowInfo; } @@ -270,7 +283,7 @@ public void resolve(MethodScope initializationScope) { if (fieldType != initializationType) // must call before computeConversion() and typeMismatchError() initializationScope.compilationUnitScope().recordTypeConversion(fieldType, initializationType); if (this.initialization.isConstantValueOfTypeAssignableToType(initializationType, fieldType) - || initializationType.isCompatibleWith(fieldType)) { + || initializationType.isCompatibleWith(fieldType, classScope)) { this.initialization.computeConversion(initializationScope, fieldType, initializationType); if (initializationType.needsUncheckedConversion(fieldType)) { initializationScope.problemReporter().unsafeTypeConversion(this.initialization, initializationType, fieldType); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java index 70fe09a84..506a1a1ba 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java @@ -8,9 +8,12 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 185682 - Increment/decrement operators mark local variables as read * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * bug 185682 - Increment/decrement operators mark local variables as read + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -27,6 +30,7 @@ import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.InvocationSite; +import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding; @@ -41,6 +45,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.ast.CalloutMappingDeclaration; import org.eclipse.objectteams.otdt.internal.core.compiler.ast.FieldAccessSpec; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding; @@ -154,6 +159,14 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte // assigning a final field outside an initializer or constructor or wrong reference currentScope.problemReporter().cannotAssignToFinalField(this.binding, this); } + } else if (this.binding.isNonNull()) { + // in a context where it can be assigned? + if ( !isCompound + && this.receiver.isThis() + && !(this.receiver instanceof QualifiedThisReference) + && ((this.receiver.bits & ASTNode.ParenthesizedMASK) == 0)) { // (this).x is forbidden + flowInfo.markAsDefinitelyAssigned(this.binding); + } } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 if (!this.binding.isStatic()) { @@ -196,6 +209,13 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + if (flowContext.isNullcheckedFieldAccess(this)) { + return true; // enough seen + } + return checkNullableFieldDereference(scope, this.binding, this.nameSourcePosition); +} + /** * @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.jdt.internal.compiler.lookup.TypeBinding) */ @@ -478,6 +498,58 @@ public void generatePostIncrement(BlockScope currentScope, CodeStream codeStream public TypeBinding[] genericTypeArguments() { return null; } + +public boolean isEquivalent(Reference reference) { + // only consider field references relative to "this": + if (this.receiver.isThis() && !(this.receiver instanceof QualifiedThisReference)) { + // current is a simple "this.f1" + char[] otherToken = null; + // matching 'reference' could be "f1" or "this.f1": + if (reference instanceof SingleNameReference) { + otherToken = ((SingleNameReference) reference).token; + } else if (reference instanceof FieldReference) { + FieldReference fr = (FieldReference) reference; + if (fr.receiver.isThis() && !(fr.receiver instanceof QualifiedThisReference)) { + otherToken = fr.token; + } + } + return otherToken != null && CharOperation.equals(this.token, otherToken); + } else { + // search deeper for "this" inside: + char[][] thisTokens = getThisFieldTokens(1); + if (thisTokens == null) { + return false; + } + // other can be "this.f1.f2", too, or "f1.f2": + char[][] otherTokens = null; + if (reference instanceof FieldReference) { + otherTokens = ((FieldReference) reference).getThisFieldTokens(1); + } else if (reference instanceof QualifiedNameReference) { + if (((QualifiedNameReference)reference).binding instanceof LocalVariableBinding) + return false; // initial variable mismatch: local (from f1.f2) vs. field (from this.f1.f2) + otherTokens = ((QualifiedNameReference) reference).tokens; + } + return CharOperation.equals(thisTokens, otherTokens); + } +} + +private char[][] getThisFieldTokens(int nestingCount) { + char[][] result = null; + if (this.receiver.isThis() && ! (this.receiver instanceof QualifiedThisReference)) { + // found an inner-most this-reference, start building the token array: + result = new char[nestingCount][]; + // fill it front to tail while traveling back out: + result[0] = this.token; + } else if (this.receiver instanceof FieldReference) { + result = ((FieldReference)this.receiver).getThisFieldTokens(nestingCount+1); + if (result != null) { + // front to tail: outermost is last: + result[result.length-nestingCount] = this.token; + } + } + return result; +} + public boolean isSuperAccess() { return this.receiver.isSuper(); } @@ -486,6 +558,10 @@ public boolean isTypeAccess() { return this.receiver != null && this.receiver.isTypeReference(); } +public FieldBinding lastFieldBinding() { + return this.binding; +} + /* * No need to emulate access to protected fields since not implicitly accessed */ @@ -573,9 +649,6 @@ private boolean isRemoteRoleFieldAccess() { && this.binding.declaringClass.isRole(); } // SH} -public int nullStatus(FlowInfo flowInfo) { - return FlowInfo.UNKNOWN; -} public Constant optimizedBooleanConstant() { switch (this.resolvedType.id) { @@ -832,4 +905,12 @@ public void traverse(ASTVisitor visitor, BlockScope scope) { } visitor.endVisit(this, scope); } + +public VariableBinding nullAnnotatedVariableBinding() { + if (this.binding != null + && ((this.binding.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) != 0)) { + return this.binding; + } + return null; +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java index 50632b616..cb28d425f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java @@ -13,6 +13,7 @@ * bug 370930 - NonNull annotation not considered for enhanced for loops * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 393719 - [compiler] inconsistent warnings on iteration variables *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -443,7 +444,7 @@ public class ForeachStatement extends Statement { && !this.scope.isBoxingCompatibleWith(this.collectionElementType, elementType)) { this.scope.problemReporter().notCompatibleTypesErrorInForeach(this.collection, this.collectionElementType, elementType); } else if (this.collectionElementType.needsUncheckedConversion(elementType)) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=321085 - this.scope.problemReporter().unsafeTypeConversion(this.collection, collectionType, upperScope.createArrayType(elementType, 1)); + this.scope.problemReporter().unsafeElementTypeConversion(this.collection, this.collectionElementType, elementType); } // :giro if (Config.getLoweringRequired()) @@ -534,6 +535,8 @@ public class ForeachStatement extends Statement { if (!this.collectionElementType.isCompatibleWith(elementType) && !this.scope.isBoxingCompatibleWith(this.collectionElementType, elementType)) { this.scope.problemReporter().notCompatibleTypesErrorInForeach(this.collection, this.collectionElementType, elementType); + } else if (this.collectionElementType.needsUncheckedConversion(elementType)) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=393719 + this.scope.problemReporter().unsafeElementTypeConversion(this.collection, this.collectionElementType, elementType); } // :giro if (Config.getLoweringRequired()) diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java index 858ed848c..60de061ff 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java @@ -11,6 +11,7 @@ * bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE * bug 349326 - [1.7] new warning for missing try-with-resources * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -104,6 +105,8 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } thenFlowInfo = this.thenStatement.analyseCode(currentScope, flowContext, thenFlowInfo); } + // any null check from the condition is now expired + flowContext.expireNullCheckedFieldInfo(); // code gen: optimizing the jump around the ELSE part if ((thenFlowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) { this.bits |= ASTNode.ThenExit; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java index 045210d7c..7054549fd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java @@ -10,6 +10,8 @@ * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -74,6 +76,12 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl // no impact upon enclosing try context return FlowInfo.conditional(initsWhenTrue, flowInfo.copy()); } + if (this.expression instanceof Reference && currentScope.compilerOptions().enableSyntacticNullAnalysisForFields) { + FieldBinding field = ((Reference)this.expression).lastFieldBinding(); + if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) { + flowContext.recordNullCheckedFieldReference((Reference) this.expression, 1); + } + } return this.expression.analyseCode(currentScope, flowContext, flowInfo). unconditionalInits(); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java index 51d7a75f3..96edbc149 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -660,6 +660,11 @@ public class Javadoc extends ASTNode { TypeBinding paramBindind = param.internalResolveType(scope); if (paramBindind != null && paramBindind.isValidBinding()) { if (paramBindind.isTypeVariable()) { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=397888 + if (scope.compilerOptions().reportUnusedParameterIncludeDocCommentReference) { + TypeVariableBinding typeVariableBinding = (TypeVariableBinding) paramBindind; + typeVariableBinding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed; + } // Verify duplicated tags boolean duplicate = false; for (int j = 0; j < i && !duplicate; j++) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java index 3e5af5e54..b21f46a1f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java @@ -21,6 +21,8 @@ * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations * bug 388996 - [compiler][resource] Incorrect 'potential resource leak' * bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -159,7 +161,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl else FakedTrackingVariable.cleanUpAfterAssignment(currentScope, Binding.LOCAL, this.initialization); - int nullStatus = this.initialization.nullStatus(flowInfo); + int nullStatus = this.initialization.nullStatus(flowInfo, flowContext); if (!flowInfo.isDefinitelyAssigned(this.binding)){// for local variable debug attributes this.bits |= FirstAssignmentToLocal; } else { @@ -328,7 +330,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl if (variableType != initializationType) // must call before computeConversion() and typeMismatchError() scope.compilationUnitScope().recordTypeConversion(variableType, initializationType); if (this.initialization.isConstantValueOfTypeAssignableToType(initializationType, variableType) - || initializationType.isCompatibleWith(variableType)) { + || initializationType.isCompatibleWith(variableType, scope)) { this.initialization.computeConversion(scope, variableType, initializationType); if (initializationType.needsUncheckedConversion(variableType)) { scope.problemReporter().unsafeTypeConversion(this.initialization, initializationType, variableType); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java index 9c3209ad5..4f2ef2b27 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java @@ -24,6 +24,8 @@ * bug 388281 - [compiler][null] inheritance of null annotations as an option * bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional * bug 381445 - [compiler][resource] Can the resource leak check be made aware of Closeables.closeQuietly? + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -313,10 +315,11 @@ protected FlowInfo checkBaseCallsIfSuper(BlockScope currentScope, FlowInfo flowI return flowInfo; } // SH} -public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { - super.checkNPE(scope, flowContext, flowInfo); - if ((nullStatus(flowInfo) & FlowInfo.POTENTIALLY_NULL) != 0) +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + // message send as a receiver + if ((nullStatus(flowInfo, flowContext) & FlowInfo.POTENTIALLY_NULL) != 0) scope.problemReporter().messageSendPotentialNullReference(this.binding, this); + return true; // done all possible checking } /** * @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.jdt.internal.compiler.lookup.TypeBinding) @@ -547,7 +550,7 @@ public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo f } // SH} } -public int nullStatus(FlowInfo flowInfo) { +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { if (this.binding.isValidBinding()) { // try to retrieve null status of this message send from an annotation of the called method: long tagBits = this.binding.tagBits; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java index 828b79f2c..885caa76e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java @@ -15,6 +15,7 @@ * bug 186342 - [compiler][null] Using annotations for null checking * bug 365519 - editorial cleanup after bug 186342 and bug 365387 * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -163,12 +164,16 @@ public class MethodDeclaration extends AbstractMethodDeclaration { } // propagate to statements if (this.statements != null) { + boolean enableSyntacticNullAnalysisForFields = this.scope.compilerOptions().enableSyntacticNullAnalysisForFields; int complaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) == 0 ? Statement.NOT_COMPLAINED : Statement.COMPLAINED_FAKE_REACHABLE; for (int i = 0, count = this.statements.length; i < count; i++) { Statement stat = this.statements[i]; if ((complaintLevel = stat.complainIfUnreachable(flowInfo, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) { flowInfo = stat.analyseCode(this.scope, methodContext, flowInfo); } + if (enableSyntacticNullAnalysisForFields) { + methodContext.expireNullCheckedFieldInfo(); + } } } else { // method with empty body should not be flagged as static. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NameReference.java index 8ee585d40..8a8aaea95 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NameReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NameReference.java @@ -10,6 +10,8 @@ * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -69,12 +71,23 @@ public NameReference() { this.bits |= Binding.TYPE | Binding.VARIABLE; // restrictiveFlag } +/** + * Use this method only when sure that the current reference is <strong>not</strong> + * a chain of several fields (QualifiedNameReference with more than one field). + * Otherwise use {@link #lastFieldBinding()}. + */ public FieldBinding fieldBinding() { //this method should be sent ONLY after a check against isFieldReference() //check its use doing senders......... return (FieldBinding) this.binding ; } +public FieldBinding lastFieldBinding() { + if ((this.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) + return fieldBinding(); // most subclasses only refer to one field anyway + return null; +} + public boolean isSuperAccess() { return false; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullLiteral.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullLiteral.java index aa77770d4..5e5bbbd5f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullLiteral.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullLiteral.java @@ -7,11 +7,14 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Stephan Herrmann - Contribution for + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.codegen.*; +import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.*; @@ -49,7 +52,7 @@ public class NullLiteral extends MagicLiteral { return TypeBinding.NULL; } - public int nullStatus(FlowInfo flowInfo) { + public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { return FlowInfo.NULL; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java index 443391d5c..111714a5d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java @@ -7,7 +7,9 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann - Contribution for bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE + * Stephan Herrmann - Contributions for + * bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -41,13 +43,16 @@ public class OR_OR_Expression extends BinaryExpression { // need to be careful of scenario: // (x || y) || !z, if passing the left info to the right, it would be swapped by the ! FlowInfo mergedInfo = this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + flowContext.expireNullCheckedFieldInfo(); mergedInfo = this.right.analyseCode(currentScope, flowContext, mergedInfo); + flowContext.expireNullCheckedFieldInfo(); this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } FlowInfo leftInfo = this.left.analyseCode(currentScope, flowContext, flowInfo); + flowContext.expireNullCheckedFieldInfo(); // need to be careful of scenario: // (x || y) || !z, if passing the left info to the right, it would be swapped by the ! @@ -63,6 +68,7 @@ public class OR_OR_Expression extends BinaryExpression { } } rightInfo = this.right.analyseCode(currentScope, flowContext, rightInfo); + flowContext.expireNullCheckedFieldInfo(); if ((this.left.implicitConversion & TypeIds.UNBOXING) != 0) { this.left.checkNPE(currentScope, flowContext, flowInfo); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OperatorExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OperatorExpression.java index f43174d10..8cd458422 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OperatorExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OperatorExpression.java @@ -8,9 +8,12 @@ * Contributors: * IBM Corporation - initial API and implementation * Perry James - nullStatus method improvement (165346) + * Stephan Herrmann - Contribution for + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; +import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.util.Util; @@ -1556,7 +1559,7 @@ public abstract class OperatorExpression extends Expression implements OperatorI return "unknown operator"; //$NON-NLS-1$ } - public int nullStatus(FlowInfo flowInfo) { + public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { return FlowInfo.NON_NULL; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java index 3d8cd1135..54ba6a86e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java @@ -16,6 +16,8 @@ * bug 365519 - editorial cleanup after bug 186342 and bug 365387 * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -140,7 +142,7 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte localBinding.useFlag = LocalVariableBinding.FAKE_USED; } if (needValue) { - checkNPE(currentScope, flowContext, flowInfo, true); + checkInternalNPE(currentScope, flowContext, flowInfo, true); } } @@ -201,6 +203,7 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte } } } + // note: not covering def.assign for @NonNull: QNR cannot provably refer to a variable of the current object manageSyntheticAccessIfNecessary(currentScope, lastFieldBinding, -1 /*write-access*/, flowInfo); return flowInfo; @@ -246,9 +249,9 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } else if (localBinding.useFlag == LocalVariableBinding.UNUSED) { localBinding.useFlag = LocalVariableBinding.FAKE_USED; } - if (needValue) { - checkNPE(currentScope, flowContext, flowInfo, true); - } + } + if (needValue) { + checkInternalNPE(currentScope, flowContext, flowInfo, true); } if (needValue) { manageEnclosingInstanceAccessIfNecessary(currentScope, flowInfo); @@ -265,9 +268,8 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } -public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, boolean checkString) { - // cannot override localVariableBinding because this would project o.m onto o when - // analyzing assignments +/* check if any dot in this QNR may trigger an NPE. */ +private void checkInternalNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, boolean checkString) { if ((this.bits & ASTNode.RestrictiveFlagMASK) == Binding.LOCAL) { LocalVariableBinding local = (LocalVariableBinding) this.binding; if (local != null && @@ -284,6 +286,38 @@ public void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInf } } } + if (this.otherBindings != null) { + if ((this.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) { + // is the first field dereferenced annotated Nullable? If so, report immediately + checkNullableFieldDereference(scope, (FieldBinding) this.binding, this.sourcePositions[0]); + } + // look for annotated fields, they do not depend on flow context -> check immediately: + int length = this.otherBindings.length - 1; // don't check the last binding + for (int i = 0; i < length; i++) { + checkNullableFieldDereference(scope, this.otherBindings[i], this.sourcePositions[i+1]); + } + } +} + +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + if (super.checkNPE(scope, flowContext, flowInfo)) { + return true; + } + FieldBinding fieldBinding = null; + long position = 0L; + if (this.otherBindings == null) { + if ((this.bits & RestrictiveFlagMASK) == Binding.FIELD) { + fieldBinding = (FieldBinding) this.binding; + position = this.sourcePositions[0]; + } + } else { + fieldBinding = this.otherBindings[this.otherBindings.length - 1]; + position = this.sourcePositions[this.sourcePositions.length - 1]; + } + if (fieldBinding != null) { + return checkNullableFieldDereference(scope, fieldBinding, position); + } + return false; } /** @@ -905,12 +939,45 @@ private void accessAsCalloutToField(ReferenceBinding enclosingReceiver, FieldBin setSyntheticAccessor(baseclassField, idx, new SyntheticMethodBinding(fakedAccessorBinding, SyntheticMethodBinding.InferredCalloutToField)); } // SH} + +public boolean isEquivalent(Reference reference) { + if (reference instanceof FieldReference) { + return reference.isEquivalent(this); // comparison FR <-> QNR is implemented only once + } + if (!(reference instanceof QualifiedNameReference)) return false; + // straight-forward test of equality of two QNRs: + QualifiedNameReference qualifiedReference = (QualifiedNameReference) reference; + if (this.tokens.length != qualifiedReference.tokens.length) return false; + if (this.binding != qualifiedReference.binding) return false; + if (this.otherBindings != null) { + if (qualifiedReference.otherBindings == null) return false; + int len = this.otherBindings.length; + if (len != qualifiedReference.otherBindings.length) return false; + for (int i=0; i<len; i++) { + if (this.otherBindings[i] != qualifiedReference.otherBindings[i]) return false; + } + } else if (qualifiedReference.otherBindings != null) { + return false; + } + return true; +} + public boolean isFieldAccess() { if (this.otherBindings != null) { return true; } return (this.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD; } + +public FieldBinding lastFieldBinding() { + if (this.otherBindings != null) { + return this.otherBindings[this.otherBindings.length - 1]; + } else if (this.binding != null && (this.bits & RestrictiveFlagMASK) == Binding.FIELD) { + return (FieldBinding) this.binding; + } + return null; +} + public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo) { //If inlinable field, forget the access emulation, the code gen will directly target it if (((this.bits & ASTNode.DepthMASK) == 0) || (this.constant != Constant.NotAConstant)) { @@ -1014,10 +1081,6 @@ public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FieldBindi } } -public int nullStatus(FlowInfo flowInfo) { - return FlowInfo.UNKNOWN; -} - public Constant optimizedBooleanConstant() { switch (this.resolvedType.id) { case T_boolean : @@ -1299,4 +1362,19 @@ public void traverse(ASTVisitor visitor, ClassScope scope) { public String unboundReferenceErrorName() { return new String(this.tokens[0]); } + +public VariableBinding nullAnnotatedVariableBinding() { + if (this.binding != null && isFieldAccess()) { + FieldBinding fieldBinding; + if (this.otherBindings == null) { + fieldBinding = (FieldBinding) this.binding; + } else { + fieldBinding = this.otherBindings[this.otherBindings.length - 1]; + } + if (fieldBinding.isNullable() || fieldBinding.isNonNull()) { + return fieldBinding; + } + } + return null; +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java index 72f3a744c..5714216e9 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java @@ -7,9 +7,12 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 185682 - Increment/decrement operators mark local variables as read * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * bug 185682 - Increment/decrement operators mark local variables as read + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -26,6 +29,7 @@ import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel; @@ -59,6 +63,21 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + if (flowContext.isNullcheckedFieldAccess(this)) { + return true; // enough seen + } + return super.checkNPE(scope, flowContext, flowInfo); +} + +protected boolean checkNullableFieldDereference(Scope scope, FieldBinding field, long sourcePosition) { + if ((field.tagBits & TagBits.AnnotationNullable) != 0) { + scope.problemReporter().nullableFieldDereference(field, sourcePosition); + return true; + } + return false; +} + public FieldBinding fieldBinding() { //this method should be sent one FIELD-tagged references // (ref.bits & BindingIds.FIELD != 0)() @@ -113,6 +132,34 @@ public abstract void generateCompoundAssignment(BlockScope currentScope, CodeStr public abstract void generatePostIncrement(BlockScope currentScope, CodeStream codeStream, CompoundAssignment postIncrement, boolean valueRequired); +/** + * Is the given reference equivalent to the receiver, + * meaning that both denote the same path of field reads? + * Used from {@link FlowContext#isNullcheckedFieldAccess(Reference)}. + */ +public boolean isEquivalent(Reference reference) { + return false; +} + +public FieldBinding lastFieldBinding() { + // override to answer the field designated by the entire reference + // (as opposed to fieldBinding() which answers the first field in a QNR) + return null; +} + +public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) { + FieldBinding fieldBinding = lastFieldBinding(); + if (fieldBinding != null) { + if (fieldBinding.isNonNull() || flowContext.isNullcheckedFieldAccess(this)) { + return FlowInfo.NON_NULL; + } else if (fieldBinding.isNullable()) { + return FlowInfo.POTENTIALLY_NULL; + } + return FlowInfo.UNKNOWN; + } + return super.nullStatus(flowInfo, flowContext); +} + /* report if a private field is only read from a 'special operator', * i.e., in a postIncrement expression or a compound assignment, * where the information is never flowing out off the field. */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java index 4c11965c9..d5e2ce9a4 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java @@ -22,6 +22,7 @@ * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" * bug 388996 - [compiler][resource] Incorrect 'potential resource leak' * bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -61,7 +62,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl this.expression.checkNPE(currentScope, flowContext, flowInfo); } if (flowInfo.reachMode() == FlowInfo.REACHABLE) - checkAgainstNullAnnotation(currentScope, flowContext, this.expression.nullStatus(flowInfo)); + checkAgainstNullAnnotation(currentScope, flowContext, this.expression.nullStatus(flowInfo, flowContext)); if (currentScope.compilerOptions().analyseResourceLeaks) { FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(this.expression, flowInfo, flowContext); if (trackingVariable != null) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java index be997933d..2afd2bfaf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java @@ -4,14 +4,16 @@ * 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 - * $Id: SingleNameReference.java 23404 2010-02-03 14:10:22Z stephan $ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 292478 - Report potentially null across variable assignment, - * Contribution for bug 185682 - Increment/decrement operators mark local variables as read * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * bug 292478 - Report potentially null across variable assignment, + * bug 185682 - Increment/decrement operators mark local variables as read + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -61,7 +63,6 @@ import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator; * * What: wrap role type in resolveType(). * - * @version $Id: SingleNameReference.java 23404 2010-02-03 14:10:22Z stephan $ */ public class SingleNameReference extends NameReference implements OperatorIds { @@ -139,6 +140,9 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte } else { currentScope.problemReporter().cannotAssignToFinalField(fieldBinding, this); } + } else if (!isCompound && fieldBinding.isNonNull()) { + // record assignment for detecting uninitialized non-null fields: + flowInfo.markAsDefinitelyAssigned(fieldBinding); } if (!fieldBinding.isStatic()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 @@ -264,6 +268,17 @@ public TypeBinding checkFieldAccess(BlockScope scope) { } +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + if (!super.checkNPE(scope, flowContext, flowInfo)) { + VariableBinding var = nullAnnotatedVariableBinding(); + if (var instanceof FieldBinding) { + checkNullableFieldDereference(scope, (FieldBinding) var, ((long)this.sourceStart<<32)+this.sourceEnd); + return true; + } + } + return false; +} + /** * @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.jdt.internal.compiler.lookup.TypeBinding) */ @@ -825,6 +840,19 @@ public TypeBinding[] genericTypeArguments() { return null; } +public boolean isEquivalent(Reference reference) { + char[] otherToken = null; + if (reference instanceof SingleNameReference) { + otherToken = ((SingleNameReference) reference).token; + } else if (reference instanceof FieldReference) { + // test for comparison "f1" vs. "this.f1": + FieldReference fr = (FieldReference) reference; + if (fr.receiver.isThis() && !(fr.receiver instanceof QualifiedThisReference)) + otherToken = fr.token; + } + return otherToken != null && CharOperation.equals(this.token, otherToken); +} + /** * Returns the local variable referenced by this node. Can be a direct reference (SingleNameReference) * or thru a cast expression etc... @@ -839,6 +867,16 @@ public LocalVariableBinding localVariableBinding() { return null; } +public VariableBinding nullAnnotatedVariableBinding() { + switch (this.bits & ASTNode.RestrictiveFlagMASK) { + case Binding.FIELD : // reading a field + case Binding.LOCAL : // reading a local variable + if ((((VariableBinding)this.binding).tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) != 0) + return (VariableBinding) this.binding; + } + return null; +} + public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo) { //If inlinable field, forget the access emulation, the code gen will directly target it if (((this.bits & ASTNode.DepthMASK) == 0) || (this.constant != Constant.NotAConstant)) { @@ -901,22 +939,7 @@ public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo f } } -public int nullStatus(FlowInfo flowInfo) { - if (this.constant != null && this.constant != Constant.NotAConstant) { - return FlowInfo.NON_NULL; // constant expression cannot be null - } - switch (this.bits & ASTNode.RestrictiveFlagMASK) { - case Binding.FIELD : // reading a field - return FlowInfo.UNKNOWN; - case Binding.LOCAL : // reading a local variable - LocalVariableBinding local = (LocalVariableBinding) this.binding; - if (local != null) - return flowInfo.nullStatus(local); - } - return FlowInfo.NON_NULL; // never get there -} - - /** +/** * @see org.eclipse.jdt.internal.compiler.ast.Expression#postConversionType(Scope) */ public TypeBinding postConversionType(Scope scope) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java index 9f79f10e6..588c2e5ec 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java @@ -18,6 +18,8 @@ * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 370930 - NonNull annotation not considered for enhanced for loops * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -103,7 +105,7 @@ protected void analyseArguments(BlockScope currentScope, FlowContext flowContext if (methodBinding.parameterNonNullness[i] == Boolean.TRUE) { TypeBinding expectedType = methodBinding.parameters[i]; Expression argument = arguments[i]; - int nullStatus = argument.nullStatus(flowInfo); // slight loss of precision: should also use the null info from the receiver. + int nullStatus = argument.nullStatus(flowInfo, flowContext); // slight loss of precision: should also use the null info from the receiver. if (nullStatus != FlowInfo.NON_NULL) // if required non-null is not provided flowContext.recordNullityMismatch(currentScope, argument, argument.resolvedType, expectedType, nullStatus); } @@ -111,19 +113,17 @@ protected void analyseArguments(BlockScope currentScope, FlowContext flowContext } } -/** Check null-ness of 'local' against a possible null annotation */ +/** Check null-ness of 'var' against a possible null annotation */ protected int checkAssignmentAgainstNullAnnotation(BlockScope currentScope, FlowContext flowContext, - LocalVariableBinding local, int nullStatus, Expression expression, TypeBinding providedType) + VariableBinding var, int nullStatus, Expression expression, TypeBinding providedType) { - if (local != null) { - if ((local.tagBits & TagBits.AnnotationNonNull) != 0 - && nullStatus != FlowInfo.NON_NULL) { - flowContext.recordNullityMismatch(currentScope, expression, providedType, local.type, nullStatus); - return FlowInfo.NON_NULL; - } else if ((local.tagBits & TagBits.AnnotationNullable) != 0 - && nullStatus == FlowInfo.UNKNOWN) { // provided a legacy type? - return FlowInfo.POTENTIALLY_NULL; // -> use more specific info from the annotation - } + if ((var.tagBits & TagBits.AnnotationNonNull) != 0 + && nullStatus != FlowInfo.NON_NULL) { + flowContext.recordNullityMismatch(currentScope, expression, providedType, var.type, nullStatus); + return FlowInfo.NON_NULL; + } else if ((var.tagBits & TagBits.AnnotationNullable) != 0 + && nullStatus == FlowInfo.UNKNOWN) { // provided a legacy type? + return FlowInfo.POTENTIALLY_NULL; // -> use more specific info from the annotation } return nullStatus; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java index 670959b9b..26327c452 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java @@ -4,12 +4,14 @@ * 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 - * $Id: ThisReference.java 23404 2010-02-03 14:10:22Z stephan $ * * Contributors: * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -25,8 +27,6 @@ import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator; * OTDT changes: * * What: if it's not an ImplicitThis, wrap the type if its a role. - * - * @version $Id: ThisReference.java 23404 2010-02-03 14:10:22Z stephan $ */ public class ThisReference extends Reference { @@ -67,6 +67,10 @@ public class ThisReference extends Reference { return true; } + public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + return true; // never problematic + } + /* * @see Reference#generateAssignment(...) */ @@ -109,10 +113,6 @@ public class ThisReference extends Reference { return true ; } - public int nullStatus(FlowInfo flowInfo) { - return FlowInfo.NON_NULL; - } - public StringBuffer printExpression(int indent, StringBuffer output){ if (isImplicitThis()) return output; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java index 06910c567..7a589e2af 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java @@ -778,7 +778,9 @@ public MethodBinding createDefaultConstructorWithBinding(MethodBinding inherited sourceType); //declaringClass constructor.binding.tagBits |= (inheritedConstructorBinding.tagBits & TagBits.HasMissingType); constructor.binding.modifiers |= ExtraCompilerModifiers.AccIsDefaultConstructor; - if (inheritedConstructorBinding.parameterNonNullness != null) { // this implies that annotation based null analysis is enabled + if (inheritedConstructorBinding.parameterNonNullness != null // this implies that annotation based null analysis is enabled + && argumentsLength > 0) + { // copy nullness info from inherited constructor to the new constructor: int len = inheritedConstructorBinding.parameterNonNullness.length; System.arraycopy(inheritedConstructorBinding.parameterNonNullness, 0, diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java index 261bf8ee7..81b932311 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java @@ -7,6 +7,8 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Stephan Herrmann - Contribution for + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -33,9 +35,11 @@ public FlowInfo analyseCode( FlowInfo flowInfo) { this.expression.checkNPE(currentScope, flowContext, flowInfo); if (((this.bits & OperatorMASK) >> OperatorSHIFT) == NOT) { - return this.expression. + flowInfo = this.expression. analyseCode(currentScope, flowContext, flowInfo). asNegatedCondition(); + flowContext.expireNullCheckedFieldInfo(); + return flowInfo; } else { return this.expression. analyseCode(currentScope, flowContext, flowInfo); 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 299809719..d8e5dddeb 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 @@ -13,6 +13,7 @@ * bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.flow; @@ -70,6 +71,12 @@ public class FlowContext implements TypeConstants { // array to store the provided and expected types from the potential error location (for display in error messages): public TypeBinding[][] providedExpectedTypes = null; + // record field references known to be non-null + // this array will never shrink, only grow. reset happens by nulling the first cell + // adding elements after reset ensures that the valid part of the array is always null-terminated + private Reference[] nullCheckedFieldReferences = null; + private int timeToLiveForNullCheckInfo = -1; + public static final int DEFER_NULL_DIAGNOSTIC = 0x1; public static final int PREEMPT_NULL_DIAGNOSTIC = 0x2; /** @@ -109,8 +116,73 @@ public FlowContext(FlowContext parent, ASTNode associatedNode) { } this.initsOnFinally = parent.initsOnFinally; this.conditionalLevel = parent.conditionalLevel; + this.nullCheckedFieldReferences = parent.nullCheckedFieldReferences; // re-use list if there is one + } +} + +/** + * Record that a reference to a field has been seen in a non-null state. + * + * @param reference Can be a SingleNameReference, a FieldReference or a QualifiedNameReference resolving to a field + * @param timeToLive control how many expire events are needed to expire this information + */ +public void recordNullCheckedFieldReference(Reference reference, int timeToLive) { + this.timeToLiveForNullCheckInfo = timeToLive; + if (this.nullCheckedFieldReferences == null) { + // first entry: + this.nullCheckedFieldReferences = new Reference[2]; + this.nullCheckedFieldReferences[0] = reference; + } else { + int len = this.nullCheckedFieldReferences.length; + // insert into first empty slot: + for (int i=0; i<len; i++) { + if (this.nullCheckedFieldReferences[i] == null) { + this.nullCheckedFieldReferences[i] = reference; + if (i+1 < len) { + this.nullCheckedFieldReferences[i+1] = null; // lazily mark next as empty + } + return; + } + } + // grow array: + System.arraycopy(this.nullCheckedFieldReferences, 0, this.nullCheckedFieldReferences=new Reference[len+2], 0, len); + this.nullCheckedFieldReferences[len] = reference; } } +/** + * Forget any information about fields that were previously known to be non-null. + * + * Will only cause any effect if CompilerOptions.enableSyntacticNullAnalysisForFields + * (implicitly by guards before calls to {@link #recordNullCheckedFieldReference(Reference, int)}). + */ +public void expireNullCheckedFieldInfo() { + if (this.nullCheckedFieldReferences != null) { + if (--this.timeToLiveForNullCheckInfo == 0) { + this.nullCheckedFieldReferences[0] = null; // lazily wipe + } + } +} + +/** + * Is the given field reference equivalent to a reference that is freshly known to be non-null? + * Can only return true if CompilerOptions.enableSyntacticNullAnalysisForFields + * (implicitly by guards before calls to {@link #recordNullCheckedFieldReference(Reference, int)}). + */ +public boolean isNullcheckedFieldAccess(Reference reference) { + if (this.nullCheckedFieldReferences == null) // always null unless CompilerOptions.enableSyntacticNullAnalysisForFields + return false; + int len = this.nullCheckedFieldReferences.length; + for (int i=0; i<len; i++) { + Reference checked = this.nullCheckedFieldReferences[i]; + if (checked == null) { + return false; + } + if (checked.isEquivalent(reference)) { + return true; + } + } + return false; +} public BranchLabel breakLabel() { return null; 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 5020e8aea..5dcf13ac5 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 @@ -15,6 +15,7 @@ * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations * bug 385626 - @NonNull fails across loop boundaries * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" + * bug 376263 - Bogus "Potential null pointer access" warning *******************************************************************************/ package org.eclipse.jdt.internal.compiler.flow; @@ -148,6 +149,7 @@ public void complainOnDeferredNullChecks(BlockScope scope, FlowInfo callerFlowIn addPotentialNullInfoFrom(this.innerFlowInfos[i]); } this.innerFlowContextsCount = 0; + FlowInfo upstreamCopy = this.upstreamNullFlowInfo.copy(); UnconditionalFlowInfo flowInfo = this.upstreamNullFlowInfo. addPotentialNullInfoFrom(callerFlowInfo.unconditionalInitsWithoutSideEffect()); if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { @@ -277,8 +279,12 @@ public void complainOnDeferredNullChecks(BlockScope scope, FlowInfo callerFlowIn default: // never happens } - this.parent.recordUsingNullReference(scope, local, location, - this.nullCheckTypes[i], flowInfo); + // https://bugs.eclipse.org/376263: avoid further deferring if the upstream info + // already has definite information (which might get lost for deferred checking). + if (!(this.nullCheckTypes[i] == MAY_NULL && upstreamCopy.isDefinitelyNonNull(local))) { + this.parent.recordUsingNullReference(scope, local, location, + this.nullCheckTypes[i], flowInfo); + } } } else { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java index 67fdcff78..06a761e4e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java @@ -19,6 +19,8 @@ * bug 366063 - Compiler should not add synthetic @NonNull annotations * bug 374605 - Unreasonable warning for enum-based switch statements * bug 388281 - [compiler][null] inheritance of null annotations as an option + * bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated + * bug 383368 - [compiler][null] syntactic null analysis for field references *******************************************************************************/ package org.eclipse.jdt.internal.compiler.impl; @@ -241,7 +243,9 @@ public class CompilerOptions { static final char[][] DEFAULT_NONNULL_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.NonNull".toCharArray()); //$NON-NLS-1$ static final char[][] DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.NonNullByDefault".toCharArray()); //$NON-NLS-1$ public static final String OPTION_ReportMissingNonNullByDefaultAnnotation = "org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation"; //$NON-NLS-1$ + public static final String OPTION_SyntacticNullAnalysisForFields = "org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields"; //$NON-NLS-1$ public static final String OPTION_InheritNullAnnotations = "org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations"; //$NON-NLS-1$ + public static final String OPTION_ReportNonnullParameterAnnotationDropped = "org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped"; //$NON-NLS-1$ /** * Possible values for configurable options */ @@ -355,6 +359,7 @@ public class CompilerOptions { public static final int MissingNonNullByDefaultAnnotation = IrritantSet.GROUP2 | ASTNode.Bit15; public static final int MissingDefaultCase = IrritantSet.GROUP2 | ASTNode.Bit16; public static final int UnusedTypeParameter = IrritantSet.GROUP2 | ASTNode.Bit17; + public static final int NonnullParameterAnnotationDropped = IrritantSet.GROUP2 | ASTNode.Bit18; //{ObjectTeams: OT/J specific problems/irritants: public static final int OTJFlag = IrritantSet.GROUP3; @@ -542,6 +547,9 @@ public class CompilerOptions { /** Should null annotations of overridden methods be inherited? */ public boolean inheritNullAnnotations; + /** Should immediate null-check for fields be considered during null analysis (syntactical match)? */ + public boolean enableSyntacticNullAnalysisForFields; + // keep in sync with warningTokenToIrritant and warningTokenFromIrritant public final static String[] warningTokens = { "all", //$NON-NLS-1$ @@ -819,6 +827,8 @@ public class CompilerOptions { return OPTION_ReportNullUncheckedConversion; case RedundantNullAnnotation : return OPTION_ReportRedundantNullAnnotation; + case NonnullParameterAnnotationDropped: + return OPTION_ReportNonnullParameterAnnotationDropped; } return null; } @@ -1019,8 +1029,10 @@ public class CompilerOptions { OPTION_ReportNullAnnotationInferenceConflict, OPTION_ReportNullUncheckedConversion, OPTION_ReportRedundantNullAnnotation, + OPTION_SyntacticNullAnalysisForFields, OPTION_ReportUnusedTypeParameter, - OPTION_InheritNullAnnotations + OPTION_InheritNullAnnotations, + OPTION_ReportNonnullParameterAnnotationDropped }; return result; } @@ -1090,6 +1102,7 @@ public class CompilerOptions { case NullUncheckedConversion : case RedundantNullAnnotation : case MissingNonNullByDefaultAnnotation: + case NonnullParameterAnnotationDropped: return "null"; //$NON-NLS-1$ case FallthroughCase : return "fallthrough"; //$NON-NLS-1$ @@ -1452,7 +1465,9 @@ public class CompilerOptions { optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.'))); optionsMap.put(OPTION_ReportMissingNonNullByDefaultAnnotation, getSeverityString(MissingNonNullByDefaultAnnotation)); optionsMap.put(OPTION_ReportUnusedTypeParameter, getSeverityString(UnusedTypeParameter)); + optionsMap.put(OPTION_SyntacticNullAnalysisForFields, this.enableSyntacticNullAnalysisForFields ? ENABLED : DISABLED); optionsMap.put(OPTION_InheritNullAnnotations, this.inheritNullAnnotations ? ENABLED : DISABLED); + optionsMap.put(OPTION_ReportNonnullParameterAnnotationDropped, getSeverityString(NonnullParameterAnnotationDropped)); return optionsMap; } @@ -1611,6 +1626,7 @@ public class CompilerOptions { this.nonNullAnnotationName = DEFAULT_NONNULL_ANNOTATION_NAME; this.nonNullByDefaultAnnotationName = DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME; this.intendedDefaultNonNullness = 0; + this.enableSyntacticNullAnalysisForFields = false; this.inheritNullAnnotations = false; this.analyseResourceLeaks = true; @@ -1989,9 +2005,13 @@ public class CompilerOptions { this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray()); } if ((optionValue = optionsMap.get(OPTION_ReportMissingNonNullByDefaultAnnotation)) != null) updateSeverity(MissingNonNullByDefaultAnnotation, optionValue); + if ((optionValue = optionsMap.get(OPTION_SyntacticNullAnalysisForFields)) != null) { + this.enableSyntacticNullAnalysisForFields = ENABLED.equals(optionValue); + } if ((optionValue = optionsMap.get(OPTION_InheritNullAnnotations)) != null) { this.inheritNullAnnotations = ENABLED.equals(optionValue); } + if ((optionValue = optionsMap.get(OPTION_ReportNonnullParameterAnnotationDropped)) != null) updateSeverity(NonnullParameterAnnotationDropped, optionValue); } // Javadoc options diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java index 8efc755c8..dba49a4c4 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java @@ -14,6 +14,7 @@ * bug 370639 - [compiler][resource] restore the default for resource leak warnings * bug 265744 - Enum switch should warn about missing default * bug 374605 - Unreasonable warning for enum-based switch statements + * bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated *******************************************************************************/ package org.eclipse.jdt.internal.compiler.impl; @@ -186,7 +187,8 @@ public class IrritantSet { |CompilerOptions.Tasks |CompilerOptions.UnclosedCloseable |CompilerOptions.NullUncheckedConversion - |CompilerOptions.RedundantNullAnnotation); + |CompilerOptions.RedundantNullAnnotation + |CompilerOptions.NonnullParameterAnnotationDropped); // default errors IF AnnotationBasedNullAnalysis is enabled: COMPILER_DEFAULT_ERRORS.set( CompilerOptions.NullSpecViolation @@ -203,7 +205,8 @@ public class IrritantSet { .set(CompilerOptions.NullSpecViolation) .set(CompilerOptions.NullAnnotationInferenceConflict) .set(CompilerOptions.NullUncheckedConversion) - .set(CompilerOptions.RedundantNullAnnotation); + .set(CompilerOptions.RedundantNullAnnotation) + .set(CompilerOptions.NonnullParameterAnnotationDropped); RESTRICTION.set(CompilerOptions.DiscouragedReference); STATIC_ACCESS.set(CompilerOptions.NonStaticAccessToStatic); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java index 16b38541b..22c138a29 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java @@ -8,6 +8,8 @@ * Contributors: * IBM Corporation - initial API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -166,7 +168,7 @@ public int hashCode() { /* Answer true if the receiver type can be assigned to the argument type (right) */ -public boolean isCompatibleWith(TypeBinding otherType) { +public boolean isCompatibleWith(TypeBinding otherType, Scope captureScope) { if (this == otherType) return true; @@ -193,7 +195,7 @@ public boolean isCompatibleWith(TypeBinding otherType) { TypeBinding otherLowerBound; if ((otherLowerBound = otherCapture.lowerBound) != null) { if (!otherLowerBound.isArrayType()) return false; - return isCompatibleWith(otherLowerBound); + return isCompatibleWith(otherLowerBound, captureScope); } } return false; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java index 40c907583..1e1178296 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BaseTypeBinding.java @@ -7,6 +7,8 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Stephan Herrmann - Contribution for + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -150,7 +152,7 @@ public final class BaseTypeBinding extends TypeBinding { /* Answer true if the receiver type can be assigned to the argument type (right) */ - public final boolean isCompatibleWith(TypeBinding left) { + public final boolean isCompatibleWith(TypeBinding left, Scope captureScope) { if (this == left) return true; int right2left = this.id + (left.id<<4); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java index d823335f2..e496d9988 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java @@ -17,6 +17,7 @@ * bug 358903 - Filter practically unimportant resource leak warnings * bug 365531 - [compiler][null] investigate alternative strategy for internally encoding nullness defaults * bug 388281 - [compiler][null] inheritance of null annotations as an option + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -650,6 +651,12 @@ private void createFields(IBinaryField[] iFields, long sourceLevel, char[][][] m this.fields[i].setAnnotations(createAnnotations(binaryField.getAnnotations(), this.environment, missingTypeNames)); } } + if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { + for (int i = 0; i <size; i++) { + IBinaryField binaryField = iFields[i]; + scanFieldForNullAnnotation(binaryField, this.fields[i]); + } + } } } } @@ -1576,6 +1583,42 @@ SimpleLookupTable storedAnnotations(boolean forceInitialize) { } return this.storedAnnotations; } + +void scanFieldForNullAnnotation(IBinaryField field, FieldBinding fieldBinding) { + // global option is checked by caller + char[][] nullableAnnotationName = this.environment.getNullableAnnotationName(); + char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName(); + if (nullableAnnotationName == null || nonNullAnnotationName == null) + return; // not well-configured to use null annotations + + if (fieldBinding.type == null || fieldBinding.type.isBaseType()) + return; // null annotations are only applied to reference types + + boolean explicitNullness = false; + IBinaryAnnotation[] annotations = field.getAnnotations(); + if (annotations != null) { + for (int i = 0; i < annotations.length; i++) { + char[] annotationTypeName = annotations[i].getTypeName(); + if (annotationTypeName[0] != Util.C_RESOLVED) + continue; + char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';' + if (CharOperation.equals(typeName, nonNullAnnotationName)) { + fieldBinding.tagBits |= TagBits.AnnotationNonNull; + explicitNullness = true; + break; + } + if (CharOperation.equals(typeName, nullableAnnotationName)) { + fieldBinding.tagBits |= TagBits.AnnotationNullable; + explicitNullness = true; + break; + } + } + } + if (!explicitNullness && (this.tagBits & TagBits.AnnotationNonNullByDefault) != 0) { + fieldBinding.tagBits |= TagBits.AnnotationNonNull; + } +} + void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding methodBinding) { if (!this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) return; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java index 51b3c5676..4c6d589ba 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java @@ -16,6 +16,7 @@ * Bug 349326 - [1.7] new warning for missing try-with-resources * Bug 358903 - Filter practically unimportant resource leak warnings * Bug 395977 - [compiler][resource] Resource leak warning behavior possibly incorrect for anonymous inner class + * Bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -1634,7 +1635,7 @@ public class ClassScope extends Scope { sourceType.tagBits |= (superType.tagBits & TagBits.HierarchyHasProblems); // propagate if missing supertpye sourceType.superclass = superType; // bound check (in case of bogus definition of Enum type) - if (refTypeVariables[0].boundCheck(superType, sourceType) != TypeConstants.OK) { + if (refTypeVariables[0].boundCheck(superType, sourceType, this) != TypeConstants.OK) { problemReporter().typeMismatchError(rootEnumType, refTypeVariables[0], sourceType, null); } return !foundCycle; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java index cb5b676be..428844362 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java @@ -8,9 +8,11 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 185682 - Increment/decrement operators mark local variables as read * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * bug 185682 - Increment/decrement operators mark local variables as read + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -299,6 +301,17 @@ public Constant constant() { return fieldConstant; } +public void fillInDefaultNonNullness(FieldDeclaration sourceField, Scope scope) { + if ( this.type != null + && !this.type.isBaseType() + && (this.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) == 0) + { + this.tagBits |= TagBits.AnnotationNonNull; + } else if ((this.tagBits & TagBits.AnnotationNonNull) != 0) { + scope.problemReporter().nullAnnotationIsRedundant(sourceField); + } +} + /** * X<T> t --> LX<TT;>; */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java index 81466c334..be1b76594 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java @@ -309,10 +309,8 @@ public class ImplicitNullAnnotationVerifier { } } if (shouldComplain) { - boolean needNonNull = false; char[][] annotationName; if (inheritedNonNullNess == Boolean.TRUE) { - needNonNull = true; annotationName = environment.getNonNullAnnotationName(); } else { annotationName = environment.getNullableAnnotationName(); @@ -329,17 +327,24 @@ public class ImplicitNullAnnotationVerifier { } else { scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); } - } else if (inheritedNonNullNess == Boolean.FALSE // unannotated conflics only with inherited @Nullable - && currentNonNullNess == null) + } else if (currentNonNullNess == null) { - if (currentArgument != null) { - scope.problemReporter().parameterLackingNullAnnotation( + // unannotated strictly conflicts only with inherited @Nullable + if (inheritedNonNullNess == Boolean.FALSE) { + if (currentArgument != null) { + scope.problemReporter().parameterLackingNullableAnnotation( + currentArgument, + inheritedMethod.declaringClass, + annotationName); + } else { + scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); + } + } else if (inheritedNonNullNess == Boolean.TRUE) { + // not strictly a conflict, but a configurable warning is given anyway: + scope.problemReporter().parameterLackingNonnullAnnotation( currentArgument, inheritedMethod.declaringClass, - needNonNull, annotationName); - } else { - scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod); } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java index 1d2b9dd46..e23a0b9b8 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LocalVariableBinding.java @@ -14,6 +14,7 @@ * bug 349326 - [1.7] new warning for missing try-with-resources * bug 186342 - [compiler][null] Using annotations for null checking * bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -196,14 +197,6 @@ public class LocalVariableBinding extends VariableBinding { } } - public boolean isNonNull() { - return (this.tagBits & TagBits.AnnotationNonNull) != 0; - } - - public boolean isNullable() { - return (this.tagBits & TagBits.AnnotationNullable) != 0; - } - // Answer whether the variable binding is a secret variable added for code gen purposes public boolean isSecret() { //{ObjectTeams: _OT$ variables are secret, too. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java index 7b461c313..9bc43539a 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier15.java @@ -15,6 +15,7 @@ * bug 365519 - editorial cleanup after bug 186342 and bug 365387 * bug 388281 - [compiler][null] inheritance of null annotations as an option * bug 388795 - [compiler] detection of name clash depends on order of super interfaces + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -781,7 +782,7 @@ MethodBinding computeSubstituteMethod(MethodBinding inheritedMethod, MethodBindi continue next; return inheritedMethod; // not a match } - } else if (inheritedTypeVariable.boundCheck(substitute, argument) != TypeConstants.OK) { + } else if (inheritedTypeVariable.boundCheck(substitute, argument, this.type.scope) != TypeConstants.OK) { return inheritedMethod; } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java index 8531ba806..7dccd2cf3 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java @@ -8,7 +8,9 @@ * Contributors: * IBM Corporation - initial API and implementation * Technical University Berlin - extended API and implementation - * Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking + * Stephan Herrmann - Contributions for + * bug 186342 - [compiler][null] Using annotations for null checking + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -132,9 +134,9 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin if (actualReceiverType instanceof ReferenceBinding) actualReceiverRefType = (ReferenceBinding) actualReceiverType; } - switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType)) { + switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType, scope)) { /* orig: - switch (typeVariable.boundCheck(substitution, substituteForChecks)) { + switch (typeVariable.boundCheck(substitution, substituteForChecks, scope)) { :giro */ // SH} case TypeConstants.MISMATCH : @@ -288,7 +290,7 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin if (substitute != null) continue nextTypeParameter; // already inferred previously TypeBinding [] bounds = inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_EXTENDS); if (bounds == null) continue nextTypeParameter; - TypeBinding[] glb = Scope.greaterLowerBound(bounds); + TypeBinding[] glb = Scope.greaterLowerBound(bounds, scope); TypeBinding mostSpecificSubstitute = null; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=341795 - Per 15.12.2.8, we should fully apply glb if (glb != null) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java index 56b001244..96a37ad85 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java @@ -8,9 +8,11 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephan Herrmann - Contribution for bug 349326 - [1.7] new warning for missing try-with-resources * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contributions for + * bug 349326 - [1.7] new warning for missing try-with-resources + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -111,7 +113,7 @@ public class ParameterizedTypeBinding extends ReferenceBinding implements Substi TypeVariableBinding[] typeVariables = this.type.typeVariables(); if (this.arguments != null && typeVariables != null) { // arguments may be null in error cases for (int i = 0, length = typeVariables.length; i < length; i++) { - if (typeVariables[i].boundCheck(this, this.arguments[i]) != TypeConstants.OK) { + if (typeVariables[i].boundCheck(this, this.arguments[i], scope) != TypeConstants.OK) { hasErrors = true; if ((this.arguments[i].tagBits & TagBits.HasMissingType) == 0) { // do not report secondary error, if type reference already got complained against diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 6404a8a3c..9b3f81188 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -16,6 +16,7 @@ * bug 358903 - Filter practically unimportant resource leak warnings * bug 365531 - [compiler][null] investigate alternative strategy for internally encoding nullness defaults * bug 388281 - [compiler][null] inheritance of null annotations as an option + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -1481,15 +1482,15 @@ public ReferenceBinding transferTypeArguments(ReferenceBinding other) { * In addition to improving performance, caching also ensures there is no infinite regression * since per nature, the compatibility check is recursive through parameterized type arguments (122775) */ -public boolean isCompatibleWith(TypeBinding otherType) { +public boolean isCompatibleWith(TypeBinding otherType, /*@Nullable*/ Scope captureScope) { //{ObjectTeams: behind the facade introduce new parameter useObjectShortcut. - return isCompatibleWith(otherType, true); + return isCompatibleWith(otherType, true, captureScope); } // version which does not consider everything conform to Object: -public boolean isStrictlyCompatibleWith(TypeBinding otherType) { - return isCompatibleWith(otherType, false); +public boolean isStrictlyCompatibleWith(TypeBinding otherType, /*@Nullable*/ Scope captureScope) { + return isCompatibleWith(otherType, false, captureScope); } -public boolean isCompatibleWith(TypeBinding otherType, boolean useObjectShortcut) { +public boolean isCompatibleWith(TypeBinding otherType, boolean useObjectShortcut, /*@Nullable*/ Scope captureScope) { // SH} if (otherType == this) return true; @@ -1513,13 +1514,21 @@ public boolean isCompatibleWith(TypeBinding otherType, boolean useObjectShortcut this.compatibleCache.put(otherType, Boolean.FALSE); // protect from recursive call //{ObjectTeams: propagate: /* orig: - if (isCompatibleWith0(otherType)) { + if (isCompatibleWith0(otherType, captureScope)) { :giro */ - if (isCompatibleWith0(otherType, useObjectShortcut)) { + if (isCompatibleWith0(otherType, useObjectShortcut, captureScope)) { // SH} this.compatibleCache.put(otherType, Boolean.TRUE); return true; } + if (captureScope == null + && this instanceof TypeVariableBinding + && ((TypeVariableBinding)this).firstBound instanceof ParameterizedTypeBinding) { + // see https://bugs.eclipse.org/395002#c9 + // in this case a subsequent check with captureScope != null may actually get + // a better result, reset this info to ensure we're not blocking that re-check. + this.compatibleCache.put(otherType, null); + } return false; } @@ -1538,7 +1547,7 @@ public void resetIncompatibleTypes() { * Answer true if the receiver type can be assigned to the argument type (right) */ //{ObjectTeams: new parameter useObjectShortcut -private boolean isCompatibleWith0(TypeBinding otherType, boolean useObjectShortcut) { +private boolean isCompatibleWith0(TypeBinding otherType, boolean useObjectShortcut, /*@Nullable*/ Scope captureScope) { // SH} if (otherType == this) return true; @@ -1582,8 +1591,17 @@ private boolean isCompatibleWith0(TypeBinding otherType, boolean useObjectShortc // above if same erasure } ReferenceBinding otherReferenceType = (ReferenceBinding) otherType; - if (otherReferenceType.isInterface()) // could be annotation type - return implementsInterface(otherReferenceType, true); + if (otherReferenceType.isInterface()) { // could be annotation type + if (implementsInterface(otherReferenceType, true)) + return true; + if (this instanceof TypeVariableBinding && captureScope != null) { + TypeVariableBinding typeVariable = (TypeVariableBinding) this; + if (typeVariable.firstBound instanceof ParameterizedTypeBinding) { + TypeBinding bound = typeVariable.firstBound.capture(captureScope, -1); // no position needed as this capture will never escape this context + return bound.isCompatibleWith(otherReferenceType); + } + } + } //{ObjectTeams: only leave if we have checked for java.lang.Object above: /* orig: if (isInterface()) // Explicit conversion from an interface diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java index 882f065f4..d27ed0ec8 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java @@ -13,6 +13,7 @@ * Stephan Herrmann - Contributions for * bug 186342 - [compiler][null] Using annotations for null checking * bug 387612 - Unreachable catch block...exception is never thrown from the try + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -353,7 +354,7 @@ public abstract class Scope { } // 5.1.10 - public static TypeBinding[] greaterLowerBound(TypeBinding[] types) { + public static TypeBinding[] greaterLowerBound(TypeBinding[] types, /*@Nullable*/ Scope scope) { if (types == null) return null; int length = types.length; if (length == 0) return null; @@ -366,7 +367,7 @@ public abstract class Scope { if (i == j) continue; TypeBinding jType = result[j]; if (jType == null) continue; - if (iType.isCompatibleWith(jType)) { // if Vi <: Vj, Vj is removed + if (iType.isCompatibleWith(jType, scope)) { // if Vi <: Vj, Vj is removed if (result == types) { // defensive copy System.arraycopy(result, 0, result = new TypeBinding[length], 0, length); } @@ -488,7 +489,7 @@ public abstract class Scope { TypeBinding [] bounds = new TypeBinding[1 + substitutedOtherBounds.length]; bounds[0] = substitutedBound; System.arraycopy(substitutedOtherBounds, 0, bounds, 1, substitutedOtherBounds.length); - TypeBinding[] glb = Scope.greaterLowerBound(bounds); // re-evaluate + TypeBinding[] glb = Scope.greaterLowerBound(bounds, null); // re-evaluate if (glb != null && glb != bounds) { substitutedBound = glb[0]; if (glb.length == 1) { @@ -3748,7 +3749,7 @@ public abstract class Scope { case Wildcard.SUPER : // ? super U, ? super V if (wildU.boundKind == Wildcard.SUPER) { - TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound,wildV.bound}); + TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound,wildV.bound}, this); if (glb == null) return null; return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER); // TODO (philippe) need to capture entire bounds } @@ -3764,7 +3765,7 @@ public abstract class Scope { return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS); // U, ? super V case Wildcard.SUPER : - TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{u,wildV.bound}); + TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{u,wildV.bound}, this); if (glb == null) return null; return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER); // TODO (philippe) need to capture entire bounds case Wildcard.UNBOUND : @@ -3782,7 +3783,7 @@ public abstract class Scope { return environment().createWildcard(genericType, rank, lub, null /*no extra bound*/, Wildcard.EXTENDS); // U, ? super V case Wildcard.SUPER : - TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound, v}); + TypeBinding[] glb = greaterLowerBound(new TypeBinding[]{wildU.bound, v}, this); if (glb == null) return null; return environment().createWildcard(genericType, rank, glb[0], null /*no extra bound*/, Wildcard.SUPER); // TODO (philippe) need to capture entire bounds case Wildcard.UNBOUND : @@ -4581,7 +4582,7 @@ public abstract class Scope { private int parameterCompatibilityLevel(TypeBinding arg, TypeBinding param, LookupEnvironment env, boolean tieBreakingVarargsMethods) { // only called if env.options.sourceLevel >= ClassFileConstants.JDK1_5 - if (arg.isCompatibleWith(param)) + if (arg.isCompatibleWith(param, this)) return COMPATIBLE; if (tieBreakingVarargsMethods && (this.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_7 || !CompilerOptions.tolerateIllegalAmbiguousVarargsInvocation)) { /* 15.12.2.5 Choosing the Most Specific Method, ... One variable arity member method named m is more specific than diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index 6994dabda..6673003fb 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -21,6 +21,8 @@ * bug 384663 - Package Based Annotation Compilation Error in JDT 3.8/4.2 (works in 3.7.2) * bug 386356 - Type mismatch error with annotations and generics * bug 388281 - [compiler][null] inheritance of null annotations as an option + * bug 331649 - [compiler][null] consider null annotations for fields + * bug 380896 - [compiler][null] Enum constants not recognised as being NonNull. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -1990,75 +1992,91 @@ public FieldBinding resolveTypeFor(FieldBinding field) { if (fieldDecls[f].binding != field) continue; - MethodScope initializationScope = field.isStatic() - ? this.scope.referenceContext.staticInitializerScope - : this.scope.referenceContext.initializerScope; - FieldBinding previousField = initializationScope.initializedField; - try { - initializationScope.initializedField = field; - FieldDeclaration fieldDecl = fieldDecls[f]; - TypeBinding fieldType = - fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT - ? initializationScope.environment().convertToRawType(this, false /*do not force conversion of enclosing types*/) // enum constant is implicitly of declaring enum type - : fieldDecl.type.resolveType(initializationScope, true /* check bounds*/); - field.type = fieldType; - field.modifiers &= ~ExtraCompilerModifiers.AccUnresolved; - if (fieldType == null) { - fieldDecl.binding = null; - return null; - } - if (fieldType == TypeBinding.VOID) { - this.scope.problemReporter().variableTypeCannotBeVoid(fieldDecl); - fieldDecl.binding = null; - return null; - } - if (fieldType.isArrayType() && ((ArrayBinding) fieldType).leafComponentType == TypeBinding.VOID) { - this.scope.problemReporter().variableTypeCannotBeVoidArray(fieldDecl); - fieldDecl.binding = null; - return null; - } - if ((fieldType.tagBits & TagBits.HasMissingType) != 0) { - field.tagBits |= TagBits.HasMissingType; - } - TypeBinding leafType = fieldType.leafComponentType(); - if (leafType instanceof ReferenceBinding && (((ReferenceBinding)leafType).modifiers & ExtraCompilerModifiers.AccGenericSignature) != 0) { - field.modifiers |= ExtraCompilerModifiers.AccGenericSignature; + MethodScope initializationScope = field.isStatic() + ? this.scope.referenceContext.staticInitializerScope + : this.scope.referenceContext.initializerScope; + FieldBinding previousField = initializationScope.initializedField; + try { + initializationScope.initializedField = field; + FieldDeclaration fieldDecl = fieldDecls[f]; + TypeBinding fieldType = + fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT + ? initializationScope.environment().convertToRawType(this, false /*do not force conversion of enclosing types*/) // enum constant is implicitly of declaring enum type + : fieldDecl.type.resolveType(initializationScope, true /* check bounds*/); + field.type = fieldType; + field.modifiers &= ~ExtraCompilerModifiers.AccUnresolved; + if (fieldType == null) { + fieldDecl.binding = null; + return null; + } + if (fieldType == TypeBinding.VOID) { + this.scope.problemReporter().variableTypeCannotBeVoid(fieldDecl); + fieldDecl.binding = null; + return null; + } + if (fieldType.isArrayType() && ((ArrayBinding) fieldType).leafComponentType == TypeBinding.VOID) { + this.scope.problemReporter().variableTypeCannotBeVoidArray(fieldDecl); + fieldDecl.binding = null; + return null; + } + if ((fieldType.tagBits & TagBits.HasMissingType) != 0) { + field.tagBits |= TagBits.HasMissingType; + } + TypeBinding leafType = fieldType.leafComponentType(); + if (leafType instanceof ReferenceBinding && (((ReferenceBinding)leafType).modifiers & ExtraCompilerModifiers.AccGenericSignature) != 0) { + field.modifiers |= ExtraCompilerModifiers.AccGenericSignature; + } + + // apply null default: + LookupEnvironment environment = this.scope.environment(); + if (environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { + if (fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) { + // enum constants neither have a type declaration nor can they be null + field.tagBits |= TagBits.AnnotationNonNull; + } else { + initializeNullDefault(); + if (hasNonNullDefault()) { + field.fillInDefaultNonNullness(fieldDecl, initializationScope); + } + // validate null annotation: + this.scope.validateNullAnnotation(field.tagBits, fieldDecl.type, fieldDecl.annotations); } - } finally { - initializationScope.initializedField = previousField; } + } finally { + initializationScope.initializedField = previousField; + } //{ObjectTeams: copy-inherited fields and anchored types: - if (fieldDecls[f].getKind() != AbstractVariableDeclaration.ENUM_CONSTANT) { - if (fieldDecls[f].type == null) // should not happen for non-enum types - throw new InternalCompilerError("Field "+fieldDecls[f]+" has no type in "+this); + if (fieldDecls[f].getKind() != AbstractVariableDeclaration.ENUM_CONSTANT) { + if (fieldDecls[f].type == null) // should not happen for non-enum types + throw new InternalCompilerError("Field "+fieldDecls[f]+" has no type in "+this); - field.copyInheritanceSrc = fieldDecls[f].copyInheritanceSrc; - field.maybeSetFieldTypeAnchorAttribute(); - // anchored to tthis? - field.type = RoleTypeCreator.maybeWrapUnqualifiedRoleType(this.scope, field.type, fieldDecls[f].type); - if (field.couldBeTeamAnchor()) { - // link decl and binding via model - // for early resolving from TeamAnchor.hasSameBestNameAs() - FieldModel.getModel(fieldDecls[f]).setBinding(field); - } + field.copyInheritanceSrc = fieldDecls[f].copyInheritanceSrc; + field.maybeSetFieldTypeAnchorAttribute(); + // anchored to tthis? + field.type = RoleTypeCreator.maybeWrapUnqualifiedRoleType(this.scope, field.type, fieldDecls[f].type); + if (field.couldBeTeamAnchor()) { + // link decl and binding via model + // for early resolving from TeamAnchor.hasSameBestNameAs() + FieldModel.getModel(fieldDecls[f]).setBinding(field); } - // need role field bridges? - if ( isRole() - && ((field.modifiers & ClassFileConstants.AccPrivate) != 0) - && !CharOperation.prefixEquals(IOTConstants.OT_DOLLAR_NAME, field.name)) - { - MethodBinding inner; - ReferenceBinding originalRole = field.declaringClass; - if (field.copyInheritanceSrc != null) - originalRole = field.copyInheritanceSrc.declaringClass; - inner = FieldModel.getDecapsulatingFieldAccessor(this, field, true/*isGetter*/); + } + // need role field bridges? + if ( isRole() + && ((field.modifiers & ClassFileConstants.AccPrivate) != 0) + && !CharOperation.prefixEquals(IOTConstants.OT_DOLLAR_NAME, field.name)) + { + MethodBinding inner; + ReferenceBinding originalRole = field.declaringClass; + if (field.copyInheritanceSrc != null) + originalRole = field.copyInheritanceSrc.declaringClass; + inner = FieldModel.getDecapsulatingFieldAccessor(this, field, true/*isGetter*/); + ((SourceTypeBinding) enclosingType()).addSyntheticRoleMethodBridge(this, originalRole, inner, SyntheticMethodBinding.RoleMethodBridgeOuter); + if (!field.isFinal()) { // no setter for final (includes all static role fields) + // otherwise we would have to handle different signatures (w/ w/o role arg), which we currently don't + inner = FieldModel.getDecapsulatingFieldAccessor(this, field, false/*isGetter*/); ((SourceTypeBinding) enclosingType()).addSyntheticRoleMethodBridge(this, originalRole, inner, SyntheticMethodBinding.RoleMethodBridgeOuter); - if (!field.isFinal()) { // no setter for final (includes all static role fields) - // otherwise we would have to handle different signatures (w/ w/o role arg), which we currently don't - inner = FieldModel.getDecapsulatingFieldAccessor(this, field, false/*isGetter*/); - ((SourceTypeBinding) enclosingType()).addSyntheticRoleMethodBridge(this, originalRole, inner, SyntheticMethodBinding.RoleMethodBridgeOuter); - } } + } // SH} return field; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java index b45fb89f1..ac089680e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java @@ -8,9 +8,11 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephen Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 317046 * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephen Herrmann <stephan@cs.tu-berlin.de> - Contributions for + * bug 317046 - Exception during debugging when hover mouse over a field + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -476,7 +478,11 @@ public boolean isClass() { /* Answer true if the receiver type can be assigned to the argument type (right) */ -public abstract boolean isCompatibleWith(TypeBinding right); +public boolean isCompatibleWith(TypeBinding right) { + return isCompatibleWith(right, null); // delegate from the old signature to the new implementation: +} +// version that allows to capture a type bound using 'scope': +public abstract boolean isCompatibleWith(TypeBinding right, /*@Nullable*/ Scope scope); public boolean isEnum() { return false; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java index c5cd45102..71c27915a 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java @@ -13,6 +13,7 @@ * bug 349326 - [1.7] new warning for missing try-with-resources * bug 359362 - FUP of bug 349326: Resource leak on non-Closeable resource * bug 358903 - Filter practically unimportant resource leak warnings + * bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation. *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -64,12 +65,26 @@ public class TypeVariableBinding extends ReferenceBinding { * Returns true if the argument type satisfies all bounds of the type parameter */ //{ObjectTeams: added optional argument actualReceiverType: - public int boundCheck(Substitution substitution, TypeBinding argumentType) { - return boundCheck(substitution, argumentType, null); + public int boundCheck(Substitution substitution, TypeBinding argumentType, Scope scope) { + return boundCheck(substitution, argumentType, null, scope); } - public int boundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType) { + public int boundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope) { + int code = internalBoundCheck(substitution, argumentType, actualReceiverType, scope); +// SH} + if (code == TypeConstants.MISMATCH) { + if (argumentType instanceof TypeVariableBinding && scope != null) { + TypeBinding bound = ((TypeVariableBinding)argumentType).firstBound; + if (bound instanceof ParameterizedTypeBinding) { + int code2 = boundCheck(substitution, bound.capture(scope, -1), scope); // no position needed as this capture will never escape this context + return Math.min(code, code2); + } + } + } + return code; + } +//{ObjectTeams: added optional argument actualReceiverType: + private int internalBoundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope) { // SH} - if (argumentType == TypeBinding.NULL || argumentType == this) { return TypeConstants.OK; } @@ -93,7 +108,7 @@ public class TypeVariableBinding extends ReferenceBinding { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass; if (substitutedSuperType.id != TypeIds.T_JavaLangObject) { if (isArrayBound) { - if (!wildcardBound.isCompatibleWith(substitutedSuperType)) + if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope)) return TypeConstants.MISMATCH; } else { TypeBinding match = wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType); @@ -120,7 +135,7 @@ public class TypeVariableBinding extends ReferenceBinding { for (int i = 0, length = this.superInterfaces.length; i < length; i++) { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superInterfaces[i]) : this.superInterfaces[i]; if (isArrayBound) { - if (!wildcardBound.isCompatibleWith(substitutedSuperType)) + if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope)) return TypeConstants.MISMATCH; } else { TypeBinding match = wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType); @@ -140,7 +155,7 @@ public class TypeVariableBinding extends ReferenceBinding { // if the wildcard is lower-bounded by a type variable that has no relevant upper bound there's nothing to check here (bug 282152): if (wildcard.bound.isTypeVariable() && ((TypeVariableBinding)wildcard.bound).superclass.id == TypeIds.T_JavaLangObject) break; - return boundCheck(substitution, wildcard.bound); + return boundCheck(substitution, wildcard.bound, scope); case Wildcard.UNBOUND : break; @@ -151,7 +166,7 @@ public class TypeVariableBinding extends ReferenceBinding { if (this.superclass.id != TypeIds.T_JavaLangObject) { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass; if (substitutedSuperType != argumentType) { - if (!argumentType.isCompatibleWith(substitutedSuperType)) { + if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) { return TypeConstants.MISMATCH; } TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType); @@ -206,7 +221,7 @@ public class TypeVariableBinding extends ReferenceBinding { for (int i = 0, length = this.superInterfaces.length; i < length; i++) { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superInterfaces[i]) : this.superInterfaces[i]; if (substitutedSuperType != argumentType) { - if (!argumentType.isCompatibleWith(substitutedSuperType)) { + if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) { return TypeConstants.MISMATCH; } TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedAnnotationBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedAnnotationBinding.java index fe6d35db7..57952eaa7 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedAnnotationBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedAnnotationBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. + * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -21,8 +21,14 @@ UnresolvedAnnotationBinding(ReferenceBinding type, ElementValuePair[] pairs, Loo public ReferenceBinding getAnnotationType() { if (this.typeUnresolved) { // the type is resolved when requested - this.type = (ReferenceBinding) BinaryTypeBinding.resolveType(this.type, this.env, false /* no raw conversion for now */); - // annotation type are never parameterized + boolean wasToleratingMissingTypeProcessingAnnotations = this.env.mayTolerateMissingType; + this.env.mayTolerateMissingType = true; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388042 + try { + this.type = (ReferenceBinding) BinaryTypeBinding.resolveType(this.type, this.env, false /* no raw conversion for now */); + // annotation types are never parameterized + } finally { + this.env.mayTolerateMissingType = wasToleratingMissingTypeProcessingAnnotations; + } this.typeUnresolved = false; } return this.type; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/VariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/VariableBinding.java index 002af3139..217afd82f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/VariableBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/VariableBinding.java @@ -9,6 +9,8 @@ * IBM Corporation - initial API and implementation * Fraunhofer FIRST - extended API and implementation * Technical University Berlin - extended API and implementation + * Stephan Herrmann - Contribution for + * bug 331649 - [compiler][null] consider null annotations for fields *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -75,7 +77,17 @@ public abstract class VariableBinding public final boolean isEffectivelyFinal() { return (this.tagBits & TagBits.IsEffectivelyFinal) != 0; } - + + /** Answer true if null annotations are enabled and this field is specified @NonNull */ + public boolean isNonNull() { + return (this.tagBits & TagBits.AnnotationNonNull) != 0; + } + + /** Answer true if null annotations are enabled and this field is specified @Nullable */ + public boolean isNullable() { + return (this.tagBits & TagBits.AnnotationNullable) != 0; + } + public char[] readableName() { //{ObjectTeams: pretty printing for generated names: if (CharOperation.prefixEquals(IOTConstants.OT_DOLLAR_NAME, this.name)) diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java index 8bcd33809..0ab0f2d51 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java @@ -13,6 +13,7 @@ * Stephan Herrmann - Contributions for * bug 366003 - CCE in ASTNode.resolveAnnotations(ASTNode.java:639) * bug 374605 - Unreasonable warning for enum-based switch statements + * bug 393719 - [compiler] inconsistent warnings on iteration variables *******************************************************************************/ package org.eclipse.jdt.internal.compiler.parser; @@ -3951,6 +3952,10 @@ protected void consumeEnhancedForStatementHeader(){ this.expressionLengthPtr--; final Expression collection = this.expressionStack[this.expressionPtr--]; statement.collection = collection; + // https://bugs.eclipse.org/393719 - [compiler] inconsistent warnings on iteration variables + // let declaration(Source)End include the collection to achieve that @SuppressWarnings affects this part, too: + statement.elementVariable.declarationSourceEnd = collection.sourceEnd; + statement.elementVariable.declarationEnd = collection.sourceEnd; statement.sourceEnd = this.rParenPos; if(!this.statementRecoveryActivated && @@ -3967,6 +3972,7 @@ protected void consumeEnhancedForStatementHeaderInit(boolean hasModifiers) { LocalDeclaration localDeclaration = createLocalDeclaration(identifierName, (int) (namePosition >>> 32), (int) namePosition); localDeclaration.declarationSourceEnd = localDeclaration.declarationEnd; + localDeclaration.bits |= ASTNode.IsForeachElementVariable; int extraDims = this.intStack[this.intPtr--]; this.identifierPtr--; 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 5765e0bcf..9afa1200f 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 @@ -22,6 +22,10 @@ * bug 374605 - Unreasonable warning for enum-based switch statements * bug 388281 - [compiler][null] inheritance of null annotations as an option * 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; @@ -50,6 +54,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; 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; @@ -81,6 +86,7 @@ import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; 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; @@ -318,6 +324,7 @@ public static int getIrritant(int problemID) { case IProblem.UnsafeRawConstructorInvocation: case IProblem.UnsafeRawMethodInvocation: case IProblem.UnsafeTypeConversion: + case IProblem.UnsafeElementTypeConversion: case IProblem.UnsafeRawFieldAssignment: case IProblem.UnsafeGenericCast: case IProblem.UnsafeReturnTypeOverride: @@ -356,6 +363,7 @@ public static int getIrritant(int problemID) { return CompilerOptions.VarargsArgumentNeedCast; case IProblem.NullLocalVariableReference: + case IProblem.NullableFieldReference: return CompilerOptions.NullReference; case IProblem.PotentialNullLocalVariableReference: @@ -368,9 +376,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: @@ -378,13 +391,17 @@ public static int getIrritant(int problemID) { case IProblem.IllegalReturnNullityRedefinition: case IProblem.IllegalRedefinitionToNonNullParameter: case IProblem.IllegalDefinitionToNonNullParameter: - case IProblem.ParameterLackingNonNullAnnotation: case IProblem.ParameterLackingNullableAnnotation: case IProblem.CannotImplementIncompatibleNullness: + case IProblem.UninitializedNonNullField: + case IProblem.UninitializedNonNullFieldHintMissingDefault: case IProblem.ConflictingNullAnnotations: case IProblem.ConflictingInheritedNullAnnotations: return CompilerOptions.NullSpecViolation; + case IProblem.ParameterLackingNonNullAnnotation: + return CompilerOptions.NonnullParameterAnnotationDropped; + case IProblem.RequiredNonNullButProvidedPotentialNull: return CompilerOptions.NullAnnotationInferenceConflict; case IProblem.RequiredNonNullButProvidedUnknown: @@ -787,6 +804,7 @@ public static int getProblemCategory(int severity, int problemID) { case CompilerOptions.NullAnnotationInferenceConflict : case CompilerOptions.NullUncheckedConversion : case CompilerOptions.MissingNonNullByDefaultAnnotation: + case CompilerOptions.NonnullParameterAnnotationDropped: return CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM; case CompilerOptions.RedundantNullAnnotation : return CategorizedProblem.CAT_UNNECESSARY_CODE; @@ -5744,6 +5762,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; @@ -5799,6 +5903,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; @@ -7947,6 +8063,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())}; @@ -8334,6 +8463,21 @@ public void unsafeTypeConversion(Expression expression, TypeBinding expressionTy expression.sourceStart, expression.sourceEnd); } +public void unsafeElementTypeConversion(Expression expression, TypeBinding expressionType, TypeBinding expectedType) { + if (this.options.sourceLevel < ClassFileConstants.JDK1_5) return; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=305259 + int severity = computeSeverity(IProblem.UnsafeElementTypeConversion); + if (severity == ProblemSeverities.Ignore) return; + if (!this.options.reportUnavoidableGenericTypeProblems && expression.forcedToBeRaw(this.referenceContext)) { + return; + } + this.handle( + IProblem.UnsafeElementTypeConversion, + new String[] { new String(expressionType.readableName()), new String(expectedType.readableName()), new String(expectedType.erasure().readableName()) }, + new String[] { new String(expressionType.shortReadableName()), new String(expectedType.shortReadableName()), new String(expectedType.erasure().shortReadableName()) }, + severity, + expression.sourceStart, + expression.sourceEnd); +} public void unusedArgument(LocalDeclaration localDecl) { int severity = computeSeverity(IProblem.ArgumentIsNeverUsed); if (severity == ProblemSeverities.Ignore) return; @@ -8970,14 +9114,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; @@ -12666,14 +12809,30 @@ public void illegalRedefinitionToNonNullParameter(Argument argument, ReferenceBi argument.type.sourceEnd); } } -public void parameterLackingNullAnnotation(Argument argument, ReferenceBinding declaringClass, boolean needNonNull, char[][] inheritedAnnotationName) { +public void parameterLackingNullableAnnotation(Argument argument, ReferenceBinding declaringClass, char[][] inheritedAnnotationName) { this.handle( - needNonNull ? IProblem.ParameterLackingNonNullAnnotation : IProblem.ParameterLackingNullableAnnotation, + IProblem.ParameterLackingNullableAnnotation, new String[] { new String(argument.name), new String(declaringClass.readableName()), CharOperation.toString(inheritedAnnotationName)}, new String[] { new String(argument.name), new String(declaringClass.shortReadableName()), new String(inheritedAnnotationName[inheritedAnnotationName.length-1])}, argument.type.sourceStart, argument.type.sourceEnd); } +public void parameterLackingNonnullAnnotation(Argument argument, ReferenceBinding declaringClass, char[][] inheritedAnnotationName) { + int sourceStart = 0, sourceEnd = 0; + if (argument != null) { + sourceStart = argument.type.sourceStart; + sourceEnd = argument.type.sourceEnd; + } else if (this.referenceContext instanceof TypeDeclaration) { + sourceStart = ((TypeDeclaration) this.referenceContext).sourceStart; + sourceEnd = ((TypeDeclaration) this.referenceContext).sourceEnd; + } + this.handle( + IProblem.ParameterLackingNonNullAnnotation, + new String[] { new String(declaringClass.readableName()), CharOperation.toString(inheritedAnnotationName)}, + new String[] { new String(declaringClass.shortReadableName()), new String(inheritedAnnotationName[inheritedAnnotationName.length-1])}, + sourceStart, + sourceEnd); +} public void illegalReturnRedefinition(AbstractMethodDeclaration abstractMethodDecl, MethodBinding inheritedMethod, char[][] nonNullAnnotationName) { MethodDeclaration methodDecl = (MethodDeclaration) abstractMethodDecl; StringBuffer methodSignature = new StringBuffer(); @@ -12758,6 +12917,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; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties index 60f8ffaff..33aa94105 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -19,6 +19,10 @@ # bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations # bug 374605 - Unreasonable warning for enum-based switch statements # 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 @@ -533,6 +537,7 @@ 580 = Type mismatch: cannot convert from element type {0} to {1} 581 = Can only iterate over an array or an instance of java.lang.Iterable 582 = Can only iterate over an array or an instance of java.util.Collection +585 = Type safety: Elements of type {0} need unchecked conversion to conform to {1} ### SOURCE LEVEL 590 = Syntax error, type parameters are only available if source level is 1.5 or greater @@ -592,6 +597,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} @@ -681,13 +690,14 @@ 914 = The return type is incompatible with the @{1} return from {0} 915 = Illegal redefinition of parameter {0}, inherited method from {1} declares this parameter as @{2} 916 = Illegal redefinition of parameter {0}, inherited method from {1} does not constrain this parameter -917 = Missing non-null annotation: inherited method from {1} declares this parameter as @{2} +917 = Missing non-null annotation: inherited method from {0} declares this parameter as @{1} 918 = Missing nullable annotation: inherited method from {1} declares this parameter as @{2} 919 = Potential null pointer access: The method {0} may return null 920 = Redundant null check: The method {0} cannot return null 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} @@ -697,6 +707,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} |