Index: compiler/org/eclipse/jdt/core/compiler/IProblem.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java,v retrieving revision 1.161 diff -u -r1.161 IProblem.java --- compiler/org/eclipse/jdt/core/compiler/IProblem.java 21 Oct 2005 07:19:17 -0000 1.161 +++ compiler/org/eclipse/jdt/core/compiler/IProblem.java 28 Nov 2005 12:08:04 -0000 @@ -684,6 +684,8 @@ int LocalVariableCannotBeNull = MethodRelated + 397; /** @since 3.1 */ int LocalVariableCanOnlyBeNull = MethodRelated + 398; + /** @since 3.2 */ + int LocalVariableMayBeNull = MethodRelated + 399; // method verifier problems int AbstractMethodMustBeImplemented = MethodRelated + 400; Index: compiler/org/eclipse/jdt/internal/compiler/ast/AND_AND_Expression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AND_AND_Expression.java,v retrieving revision 1.31 diff -u -r1.31 AND_AND_Expression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/AND_AND_Expression.java 18 Nov 2005 16:46:22 -0000 1.31 +++ compiler/org/eclipse/jdt/internal/compiler/ast/AND_AND_Expression.java 28 Nov 2005 12:08:04 -0000 @@ -49,7 +49,7 @@ // need to be careful of scenario: // (x && y) && !z, if passing the left info to the right, it would be // swapped by the ! - FlowInfo rightInfo = leftInfo.initsWhenTrue().unconditionalInits().copy(); + FlowInfo rightInfo = leftInfo.initsWhenTrue().unconditionalCopy(); rightInitStateIndex = currentScope.methodScope().recordInitializationStates(rightInfo); int previousMode = rightInfo.reachMode(); @@ -57,12 +57,11 @@ rightInfo.setReachMode(FlowInfo.UNREACHABLE); } rightInfo = right.analyseCode(currentScope, flowContext, rightInfo); - FlowInfo trueMergedInfo = rightInfo.initsWhenTrue().copy(); - rightInfo.setReachMode(previousMode); // reset after trueMergedInfo got extracted FlowInfo mergedInfo = FlowInfo.conditional( - trueMergedInfo, - leftInfo.initsWhenFalse().copy().unconditionalInits().mergedWith( - rightInfo.initsWhenFalse().copy().unconditionalInits())); + rightInfo.safeInitsWhenTrue(), + leftInfo.initsWhenFalse().unconditionalInits().mergedWith( + rightInfo.initsWhenFalse().setReachMode(previousMode).unconditionalInits())); + // reset after trueMergedInfo got extracted mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } Index: compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java,v retrieving revision 1.38 diff -u -r1.38 ArrayReference.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java 10 Oct 2005 10:29:38 -0000 1.38 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java 28 Nov 2005 12:08:04 -0000 @@ -27,34 +27,32 @@ sourceStart = rec.sourceStart; } - public FlowInfo analyseAssignment( +public FlowInfo analyseAssignment( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, Assignment assignment, boolean compoundAssignment) { + // unconditionalInits is applied to all existing calls -> we remove extraneous ones here + if (assignment.expression == null) { + return analyseCode(currentScope, flowContext, flowInfo); + } + return assignment + .expression + .analyseCode( + currentScope, + flowContext, + analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()); +} - if (assignment.expression == null) { - return analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); - } - return assignment - .expression - .analyseCode( - currentScope, - flowContext, - analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()) - .unconditionalInits(); - } - - public FlowInfo analyseCode( +public FlowInfo analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - - flowInfo = receiver.analyseCode(currentScope, flowContext, flowInfo); - receiver.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); - return position.analyseCode(currentScope, flowContext, flowInfo); - } + receiver.checkNPE(currentScope, flowContext, flowInfo, true); + flowInfo = receiver.analyseCode(currentScope, flowContext, flowInfo); + return position.analyseCode(currentScope, flowContext, flowInfo); +} public void generateAssignment( BlockScope currentScope, @@ -179,6 +177,10 @@ codeStream.arrayAtPut(this.resolvedType.id, false); } +public int nullStatus(FlowInfo flowInfo) { + return FlowInfo.UNKNOWN; +} + public StringBuffer printExpression(int indent, StringBuffer output) { receiver.printExpression(0, output).append('['); Index: compiler/org/eclipse/jdt/internal/compiler/ast/AssertStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AssertStatement.java,v retrieving revision 1.44 diff -u -r1.44 AssertStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/AssertStatement.java 18 Nov 2005 16:46:22 -0000 1.44 +++ compiler/org/eclipse/jdt/internal/compiler/ast/AssertStatement.java 28 Nov 2005 12:08:04 -0000 @@ -54,11 +54,12 @@ boolean isOptimizedTrueAssertion = cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isOptimizedFalseAssertion = cst != Constant.NotAConstant && cst.booleanValue() == false; - FlowInfo assertInfo = flowInfo.copy(); + UnconditionalFlowInfo assertInfo = assertExpression. + analyseCode(currentScope, flowContext, flowInfo.copy()). + unconditionalInits(); if (isOptimizedTrueAssertion) { assertInfo.setReachMode(FlowInfo.UNREACHABLE); } - assertInfo = assertExpression.analyseCode(currentScope, flowContext, assertInfo).unconditionalInits(); if (exceptionArgument != null) { // only gets evaluated when escaping - results are not taken into account @@ -80,7 +81,7 @@ if (isOptimizedFalseAssertion) { return flowInfo; // if assertions are enabled, the following code will be unreachable } else { - return flowInfo.mergedWith(assertInfo.unconditionalInits()); + return flowInfo.mergedWith(assertInfo); } } Index: compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java,v retrieving revision 1.66 diff -u -r1.66 Assignment.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java 18 Nov 2005 16:46:22 -0000 1.66 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java 28 Nov 2005 12:08:04 -0000 @@ -36,34 +36,38 @@ this.sourceEnd = sourceEnd; } - public FlowInfo analyseCode( - BlockScope currentScope, - FlowContext flowContext, - FlowInfo flowInfo) { - // record setting a variable: various scenarii are possible, setting an array reference, - // a field reference, a blank final field reference, a field of an enclosing instance or - // just a local variable. - - LocalVariableBinding local = this.lhs.localVariableBinding(); - int nullStatus = this.expression.nullStatus(flowInfo); - if (local != null && nullStatus == FlowInfo.NULL) { - flowContext.recordUsingNullReference(currentScope, local, this.lhs, FlowInfo.NON_NULL, flowInfo); - } - flowInfo = ((Reference) lhs) - .analyseAssignment(currentScope, flowContext, flowInfo, this, false) - .unconditionalInits(); - if (local != null) { - switch(nullStatus) { - case FlowInfo.NULL : - flowInfo.markAsDefinitelyNull(local); - break; - case FlowInfo.NON_NULL : - flowInfo.markAsDefinitelyNonNull(local); - break; - } - } - return flowInfo; - } +public FlowInfo analyseCode( + BlockScope currentScope, + FlowContext flowContext, + FlowInfo flowInfo) { + // record setting a variable: various scenarii are possible, setting an array reference, + // a field reference, a blank final field reference, a field of an enclosing instance or + // just a local variable. + LocalVariableBinding local = this.lhs.localVariableBinding(); + int nullStatus = this.expression.nullStatus(flowInfo); + if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { + if (nullStatus == FlowInfo.NULL) { + flowContext.recordUsingNullReference(currentScope, local, this.lhs, + FlowContext.CAN_ONLY_NULL, flowInfo); + } + } + flowInfo = ((Reference) lhs) + .analyseAssignment(currentScope, flowContext, flowInfo, this, false) + .unconditionalInits(); + if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { + switch(nullStatus) { + case FlowInfo.NULL : + flowInfo.markAsDefinitelyNull(local); + break; + case FlowInfo.NON_NULL : + flowInfo.markAsDefinitelyNonNull(local); + break; + default: + flowInfo.markAsDefinitelyUnknown(local); + } + } + return flowInfo; +} void checkAssignmentEffect(BlockScope scope) { @@ -250,4 +254,8 @@ } visitor.endVisit(this, scope); } + +public LocalVariableBinding localVariableBinding() { + return lhs.localVariableBinding(); +} } Index: compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java,v retrieving revision 1.52 diff -u -r1.52 BinaryExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java 18 Nov 2005 16:46:21 -0000 1.52 +++ compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java 28 Nov 2005 12:08:05 -0000 @@ -31,18 +31,17 @@ this.sourceEnd = right.sourceEnd; } - public FlowInfo analyseCode( +public FlowInfo analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - - return right - .analyseCode( - currentScope, - flowContext, - left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()) - .unconditionalInits(); - } + left.checkNPE(currentScope, flowContext, flowInfo, false /* skip String */); + flowInfo = left.analyseCode(currentScope, flowContext, flowInfo). + unconditionalInits(); + right.checkNPE(currentScope, flowContext, flowInfo, false /* skip String */); + return right.analyseCode(currentScope, flowContext, flowInfo). + unconditionalInits(); +} public void computeConstant(BlockScope scope, int leftId, int rightId) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java,v retrieving revision 1.51 diff -u -r1.51 CompoundAssignment.java --- compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java 18 Nov 2005 16:46:21 -0000 1.51 +++ compiler/org/eclipse/jdt/internal/compiler/ast/CompoundAssignment.java 28 Nov 2005 12:08:05 -0000 @@ -34,13 +34,14 @@ this.operator = operator ; } - public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - // record setting a variable: various scenarii are possible, setting an array reference, - // a field reference, a blank final field reference, a field of an enclosing instance or - // just a local variable. - - return ((Reference) lhs).analyseAssignment(currentScope, flowContext, flowInfo, this, true).unconditionalInits(); - } +public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, + FlowInfo flowInfo) { + // record setting a variable: various scenarii are possible, setting an array reference, + // a field reference, a blank final field reference, a field of an enclosing instance or + // just a local variable. + lhs.checkNPE(currentScope, flowContext, flowInfo, false /* skip String */); + return ((Reference) lhs).analyseAssignment(currentScope, flowContext, flowInfo, this, true).unconditionalInits(); +} public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { @@ -56,9 +57,10 @@ codeStream.recordPositionsFrom(pc, this.sourceStart); } - public int nullStatus(FlowInfo flowInfo) { - return FlowInfo.NON_NULL; - } +public int nullStatus(FlowInfo flowInfo) { + return FlowInfo.NON_NULL; + // we may have complained on checkNPE, but we avoid duplicate error +} public String operatorToString() { switch (operator) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java,v retrieving revision 1.75 diff -u -r1.75 ConditionalExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java 18 Nov 2005 16:46:21 -0000 1.75 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java 28 Nov 2005 12:08:05 -0000 @@ -40,11 +40,8 @@ sourceEnd = valueIfFalse.sourceEnd; } - public FlowInfo analyseCode( - BlockScope currentScope, - FlowContext flowContext, +public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - Constant cst = this.condition.optimizedBooleanConstant(); boolean isConditionOptimizedTrue = cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isConditionOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false; @@ -84,16 +81,13 @@ boolean isValueIfFalseOptimizedTrue = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isValueIfFalseOptimizedFalse = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; - UnconditionalFlowInfo trueInfoWhenTrue = trueFlowInfo.initsWhenTrue().copy().unconditionalInits(); + UnconditionalFlowInfo trueInfoWhenTrue = trueFlowInfo.initsWhenTrue().unconditionalCopy(); + UnconditionalFlowInfo falseInfoWhenTrue = falseFlowInfo.initsWhenTrue().unconditionalCopy(); + UnconditionalFlowInfo trueInfoWhenFalse = trueFlowInfo.initsWhenFalse().unconditionalInits(); + UnconditionalFlowInfo falseInfoWhenFalse = falseFlowInfo.initsWhenFalse().unconditionalInits(); if (isValueIfTrueOptimizedFalse) trueInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); - - UnconditionalFlowInfo falseInfoWhenTrue = falseFlowInfo.initsWhenTrue().copy().unconditionalInits(); if (isValueIfFalseOptimizedFalse) falseInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); - - UnconditionalFlowInfo trueInfoWhenFalse = trueFlowInfo.initsWhenFalse().copy().unconditionalInits(); if (isValueIfTrueOptimizedTrue) trueInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); - - UnconditionalFlowInfo falseInfoWhenFalse = falseFlowInfo.initsWhenFalse().copy().unconditionalInits(); if (isValueIfFalseOptimizedTrue) falseInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); mergedInfo = @@ -262,6 +256,23 @@ // no implicit conversion for boolean values codeStream.updateLastRecordedEndPC(currentScope, codeStream.position); } + +public int nullStatus(FlowInfo flowInfo) { + Constant cst = this.condition.optimizedBooleanConstant(); + if (cst != Constant.NotAConstant) { + if (cst.booleanValue()) { + return valueIfTrue.nullStatus(flowInfo); + } + return valueIfFalse.nullStatus(flowInfo); + } + int ifTrueNullStatus = valueIfTrue.nullStatus(flowInfo), + ifFalseNullStatus = valueIfFalse.nullStatus(flowInfo); + if (ifTrueNullStatus == ifFalseNullStatus) { + return ifTrueNullStatus; + } + return FlowInfo.UNKNOWN; + // cannot decide which branch to take, and they disagree +} public Constant optimizedBooleanConstant() { Index: compiler/org/eclipse/jdt/internal/compiler/ast/ContinueStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ContinueStatement.java,v retrieving revision 1.6 diff -u -r1.6 ContinueStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ContinueStatement.java 14 Oct 2005 22:26:02 -0000 1.6 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ContinueStatement.java 28 Nov 2005 12:08:05 -0000 @@ -71,7 +71,7 @@ flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); // collect inits } else if (traversedContext == targetContext) { // only record continue info once accumulated through subroutines, and only against target context - targetContext.recordContinueFrom(flowInfo); + targetContext.recordContinueFrom(flowContext, flowInfo); break; } } while ((traversedContext = traversedContext.parent) != null); Index: compiler/org/eclipse/jdt/internal/compiler/ast/DoStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/DoStatement.java,v retrieving revision 1.42 diff -u -r1.42 DoStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/DoStatement.java 18 Nov 2005 16:46:21 -0000 1.42 +++ compiler/org/eclipse/jdt/internal/compiler/ast/DoStatement.java 28 Nov 2005 12:08:05 -0000 @@ -46,6 +46,7 @@ LoopingFlowContext loopingContext = new LoopingFlowContext( flowContext, + flowInfo, this, breakLabel, continueLabel, @@ -59,9 +60,14 @@ int previousMode = flowInfo.reachMode(); - FlowInfo actionInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations(); + UnconditionalFlowInfo actionInfo = flowInfo.nullInfoLessUnconditionalCopy(); + // we need to collect the contribution to nulls of the coming paths through the + // loop, be they falling through normally or branched to break, continue labels + // or catch blocks if ((action != null) && !action.isEmptyBlock()) { - actionInfo = action.analyseCode(currentScope, loopingContext, actionInfo); + actionInfo = action. + analyseCode(currentScope, loopingContext, actionInfo). + unconditionalInits(); // code generation can be optimized when no need to continue in the loop if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) { @@ -75,22 +81,34 @@ */ actionInfo.setReachMode(previousMode); - actionInfo = + LoopingFlowContext condLoopContext; + FlowInfo condInfo = condition.analyseCode( currentScope, - loopingContext, + (condLoopContext = + new LoopingFlowContext(flowContext, flowInfo, this, null, + null, currentScope)), (action == null ? actionInfo - : (actionInfo.mergedWith(loopingContext.initsOnContinue)))); + : (actionInfo.mergedWith(loopingContext.initsOnContinue))).copy()); if (!isConditionOptimizedFalse && continueLabel != null) { - loopingContext.complainOnDeferredChecks(currentScope, actionInfo); + loopingContext.complainOnDeferredFinalChecks(currentScope, condInfo); + condLoopContext.complainOnDeferredFinalChecks(currentScope, condInfo); + UnconditionalFlowInfo checkFlowInfo; + loopingContext.complainOnDeferredNullChecks(currentScope, + checkFlowInfo = actionInfo. + addPotentialNullInfoFrom( + condInfo.initsWhenTrue().unconditionalInits())); + condLoopContext.complainOnDeferredNullChecks(currentScope, + checkFlowInfo); } // end of loop FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranches( loopingContext.initsOnBreak, - isConditionOptimizedTrue, - actionInfo.initsWhenFalse().addInitializationsFrom(flowInfo), // recover null inits from before condition analysis + isConditionOptimizedTrue, + condInfo.isReachable() ? flowInfo.addInitializationsFrom(condInfo.initsWhenFalse()) : condInfo, + // recover null inits from before condition analysis false, // never consider opt false case for DO loop, since break can always occur (47776) !isConditionTrue /*do{}while(true); unreachable(); */); mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); Index: compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java,v retrieving revision 1.57 diff -u -r1.57 EqualExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java 18 Nov 2005 16:46:22 -0000 1.57 +++ compiler/org/eclipse/jdt/internal/compiler/ast/EqualExpression.java 28 Nov 2005 12:08:05 -0000 @@ -22,84 +22,98 @@ public EqualExpression(Expression left, Expression right,int operator) { super(left,right,operator); } - public void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { + private void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { LocalVariableBinding local = this.left.localVariableBinding(); - if (local != null) { + if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, right.nullStatus(flowInfo), this.left); } local = this.right.localVariableBinding(); - if (local != null) { + if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, left.nullStatus(flowInfo), this.right); } } private void checkVariableComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse, LocalVariableBinding local, int nullStatus, Expression reference) { switch (nullStatus) { case FlowInfo.NULL : - flowContext.recordUsingNullReference(scope, local, reference, FlowInfo.NULL, flowInfo); + flowContext.recordUsingNullReference(scope, local, reference, + FlowContext.CAN_ONLY_NULL_NON_NULL, flowInfo); if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) { - initsWhenTrue.markAsDefinitelyNull(local); // from thereon it is set - initsWhenFalse.markAsDefinitelyNonNull(local); // from thereon it is set + initsWhenTrue.markAsComparedEqualToNull(local); // from thereon it is set + initsWhenFalse.markAsComparedEqualToNonNull(local); // from thereon it is set } else { - initsWhenTrue.markAsDefinitelyNonNull(local); // from thereon it is set - initsWhenFalse.markAsDefinitelyNull(local); // from thereon it is set + initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set + initsWhenFalse.markAsComparedEqualToNull(local); // from thereon it is set } break; case FlowInfo.NON_NULL : - flowContext.recordUsingNullReference(scope, local, reference, FlowInfo.NON_NULL, flowInfo); + flowContext.recordUsingNullReference(scope, local, reference, + FlowContext.CAN_ONLY_NULL, flowInfo); if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) { - initsWhenTrue.markAsDefinitelyNonNull(local); // from thereon it is set + initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set } break; } } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { + FlowInfo result; if (((bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) { if ((left.constant != Constant.NotAConstant) && (left.constant.typeID() == T_boolean)) { if (left.constant.booleanValue()) { // true == anything // this is equivalent to the right argument inits - return right.analyseCode(currentScope, flowContext, flowInfo); + result = right.analyseCode(currentScope, flowContext, flowInfo); } else { // false == anything // this is equivalent to the right argument inits negated - return right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); + result = right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); } - } - if ((right.constant != Constant.NotAConstant) && (right.constant.typeID() == T_boolean)) { + } + else if ((right.constant != Constant.NotAConstant) && (right.constant.typeID() == T_boolean)) { if (right.constant.booleanValue()) { // anything == true - // this is equivalent to the right argument inits - return left.analyseCode(currentScope, flowContext, flowInfo); + // this is equivalent to the left argument inits + result = left.analyseCode(currentScope, flowContext, flowInfo); } else { // anything == false // this is equivalent to the right argument inits negated - return left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); + result = left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); } + } + else { + result = right.analyseCode( + currentScope, flowContext, + left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).unconditionalInits(); } - return right.analyseCode( - currentScope, flowContext, - left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).unconditionalInits(); } else { //NOT_EQUAL : if ((left.constant != Constant.NotAConstant) && (left.constant.typeID() == T_boolean)) { if (!left.constant.booleanValue()) { // false != anything // this is equivalent to the right argument inits - return right.analyseCode(currentScope, flowContext, flowInfo); + result = right.analyseCode(currentScope, flowContext, flowInfo); } else { // true != anything // this is equivalent to the right argument inits negated - return right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); + result = right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); } } - if ((right.constant != Constant.NotAConstant) && (right.constant.typeID() == T_boolean)) { + else if ((right.constant != Constant.NotAConstant) && (right.constant.typeID() == T_boolean)) { if (!right.constant.booleanValue()) { // anything != false // this is equivalent to the right argument inits - return left.analyseCode(currentScope, flowContext, flowInfo); + result = left.analyseCode(currentScope, flowContext, flowInfo); } else { // anything != true // this is equivalent to the right argument inits negated - return left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); + result = left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition(); } + } + else { + result = right.analyseCode( + currentScope, flowContext, + left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()). + /* unneeded since we flatten it: asNegatedCondition(). */ + unconditionalInits(); } - return right.analyseCode( - currentScope, flowContext, - left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).asNegatedCondition().unconditionalInits(); } + if (result instanceof UnconditionalFlowInfo && result.isReachable()) { // the flow info is flat + result = FlowInfo.conditional(result, result.copy()); + } + checkNullComparison(currentScope, flowContext, result, result.initsWhenTrue(), result.initsWhenFalse()); + return result; } public final void computeConstant(TypeBinding leftType, TypeBinding rightType) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java,v retrieving revision 1.88 diff -u -r1.88 Expression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java 18 Nov 2005 16:46:23 -0000 1.88 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java 28 Nov 2005 12:08:05 -0000 @@ -479,25 +479,28 @@ } } - public FlowInfo checkNullStatus(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int nullStatus) { - - LocalVariableBinding local = this.localVariableBinding(); - if (local != null) { - switch(nullStatus) { - case FlowInfo.NULL : - flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NULL, flowInfo); - flowInfo.markAsDefinitelyNull(local); // from thereon it is set - break; - case FlowInfo.NON_NULL : - flowContext.recordUsingNullReference(scope, local, this, FlowInfo.NON_NULL, flowInfo); - flowInfo.markAsDefinitelyNonNull(local); // from thereon it is set - break; - case FlowInfo.UNKNOWN : - break; - } - } - return flowInfo; +/** + * 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. + * @param scope the scope of the analysis + * @param flowContext the current flow context + * @param flowInfo the upstream flow info; caveat: may get modified + * @param checkString if true, a local variable of type String is checked; else + * it is skipped + */ +public void checkNPE(BlockScope scope, FlowContext flowContext, + FlowInfo flowInfo, boolean checkString) { + LocalVariableBinding local = this.localVariableBinding(); + if (local != null && + (local.type.tagBits & TagBits.IsBaseType) == 0 && + (checkString || local.type.id != T_JavaLangString)) { + flowContext.recordUsingNullReference(scope, local, this, + FlowContext.MAY_NULL, flowInfo); + flowInfo.markAsComparedEqualToNonNull(local); + // from thereon it is set } +} private MethodBinding[] getAllInheritedMethods(ReferenceBinding binding) { ArrayList collector = new ArrayList(); @@ -516,9 +519,6 @@ getAllInheritedMethods0(superInterfaces[i], collector); } } - public void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) { - // do nothing by default - see EqualExpression - } public boolean checkUnsafeCast(Scope scope, TypeBinding castType, TypeBinding expressionType, TypeBinding match, boolean isNarrowing) { if (match == castType) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java,v retrieving revision 1.92 diff -u -r1.92 FieldReference.java --- compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java 18 Nov 2005 16:46:23 -0000 1.92 +++ compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java 28 Nov 2005 12:08:05 -0000 @@ -98,7 +98,9 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) { boolean nonStatic = !binding.isStatic(); receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic); - if (nonStatic) receiver.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); + if (nonStatic) { + receiver.checkNPE(currentScope, flowContext, flowInfo, true); + } if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) { manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/); @@ -407,6 +409,10 @@ } } +public int nullStatus(FlowInfo flowInfo) { + return FlowInfo.UNKNOWN; +} + public Constant optimizedBooleanConstant() { switch (this.resolvedType.id) { case T_boolean : Index: compiler/org/eclipse/jdt/internal/compiler/ast/ForStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForStatement.java,v retrieving revision 1.51 diff -u -r1.51 ForStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ForStatement.java 18 Nov 2005 16:46:21 -0000 1.51 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ForStatement.java 28 Nov 2005 12:08:05 -0000 @@ -83,38 +83,46 @@ // process the condition LoopingFlowContext condLoopContext = null; - FlowInfo condInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations(); + FlowInfo condInfo = flowInfo.nullInfoLessUnconditionalCopy(); if (condition != null) { if (!isConditionTrue) { condInfo = condition.analyseCode( scope, (condLoopContext = - new LoopingFlowContext(flowContext, this, null, null, scope)), + new LoopingFlowContext(flowContext, flowInfo, this, null, + null, scope)), condInfo); } } // process the action LoopingFlowContext loopingContext; - FlowInfo actionInfo; + UnconditionalFlowInfo actionInfo; if (action == null || (action.isEmptyBlock() && currentScope.compilerOptions().complianceLevel <= ClassFileConstants.JDK1_3)) { if (condLoopContext != null) - condLoopContext.complainOnDeferredChecks(scope, condInfo); + condLoopContext.complainOnDeferredFinalChecks(scope, condInfo); if (isConditionTrue) { + if (condLoopContext != null) { + condLoopContext.complainOnDeferredNullChecks(currentScope, + condInfo); + } return FlowInfo.DEAD_END; } else { if (isConditionFalse){ continueLabel = null; // for(;false;p()); } - actionInfo = condInfo.initsWhenTrue().copy().unconditionalInits().discardNullRelatedInitializations(); + actionInfo = condInfo.initsWhenTrue().unconditionalCopy(); loopingContext = - new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope); + new LoopingFlowContext(flowContext, flowInfo, this, + breakLabel, continueLabel, scope); } - } else { + } + else { loopingContext = - new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope); + new LoopingFlowContext(flowContext, flowInfo, this, breakLabel, + continueLabel, scope); FlowInfo initsWhenTrue = condInfo.initsWhenTrue(); condIfTrueInitStateIndex = currentScope.methodScope().recordInitializationStates(initsWhenTrue); @@ -122,38 +130,63 @@ if (isConditionFalse) { actionInfo = FlowInfo.DEAD_END; } else { - actionInfo = initsWhenTrue.copy().unconditionalInits().discardNullRelatedInitializations(); + actionInfo = initsWhenTrue.unconditionalCopy(); if (isConditionOptimizedFalse){ actionInfo.setReachMode(FlowInfo.UNREACHABLE); } } if (!this.action.complainIfUnreachable(actionInfo, scope, false)) { - actionInfo = action.analyseCode(scope, loopingContext, actionInfo); + actionInfo = action.analyseCode(scope, loopingContext, actionInfo). + unconditionalInits(); } // code generation can be optimized when no need to continue in the loop if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) { continueLabel = null; - } else { - if (condLoopContext != null) - condLoopContext.complainOnDeferredChecks(scope, condInfo); - actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits()); - loopingContext.complainOnDeferredChecks(scope, actionInfo); + } + else { + if (condLoopContext != null) { + condLoopContext.complainOnDeferredFinalChecks(scope, + condInfo); + } + actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue); + loopingContext.complainOnDeferredFinalChecks(scope, + actionInfo); } } // for increments - FlowInfo exitBranch = condInfo.initsWhenFalse(); - exitBranch.addInitializationsFrom(flowInfo); // recover null inits from before condition analysis + FlowInfo exitBranch = flowInfo.copy(); + // recover null inits from before condition analysis + LoopingFlowContext incrementContext = null; if (continueLabel != null) { if (increments != null) { - LoopingFlowContext loopContext = - new LoopingFlowContext(flowContext, this, null, null, scope); + incrementContext = + new LoopingFlowContext(flowContext, flowInfo, this, null, + null, scope); + FlowInfo incrementInfo = actionInfo; for (int i = 0, count = increments.length; i < count; i++) { - actionInfo = increments[i].analyseCode(scope, loopContext, actionInfo); + incrementInfo = increments[i]. + analyseCode(scope, incrementContext, incrementInfo); } - loopContext.complainOnDeferredChecks(scope, actionInfo); + incrementContext.complainOnDeferredFinalChecks(scope, + actionInfo = incrementInfo.unconditionalInits()); } - exitBranch.addPotentialInitializationsFrom(actionInfo.unconditionalInits()); + exitBranch.addPotentialInitializationsFrom(actionInfo). + addInitializationsFrom(condInfo.initsWhenFalse()); + } + else { + exitBranch.addInitializationsFrom(condInfo.initsWhenFalse()); + } + // nulls checks + if (condLoopContext != null) { + condLoopContext.complainOnDeferredNullChecks(currentScope, + actionInfo); + } + loopingContext.complainOnDeferredNullChecks(currentScope, + actionInfo); + if (incrementContext != null) { + incrementContext.complainOnDeferredNullChecks(currentScope, + actionInfo); } //end of loop Index: compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java,v retrieving revision 1.25 diff -u -r1.25 ForeachStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java 18 Nov 2005 16:46:22 -0000 1.25 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java 28 Nov 2005 12:08:05 -0000 @@ -17,6 +17,7 @@ import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext; +import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; @@ -81,9 +82,9 @@ continueLabel = new Label(); // process the element variable and collection + this.collection.checkNPE(currentScope, flowContext, flowInfo, true); flowInfo = this.elementVariable.analyseCode(scope, flowContext, flowInfo); - FlowInfo condInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations(); - condInfo = this.collection.analyseCode(scope, flowContext, condInfo); + FlowInfo condInfo = this.collection.analyseCode(scope, flowContext, flowInfo.copy()); // element variable will be assigned when iterating condInfo.markAsDefinitelyAssigned(this.elementVariable.binding); @@ -91,25 +92,30 @@ this.postCollectionInitStateIndex = currentScope.methodScope().recordInitializationStates(condInfo); // process the action - LoopingFlowContext loopingContext = new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope); - FlowInfo actionInfo = condInfo.initsWhenTrue().copy(); + LoopingFlowContext loopingContext = + new LoopingFlowContext(flowContext, flowInfo, this, breakLabel, + continueLabel, scope); + UnconditionalFlowInfo actionInfo = + condInfo.nullInfoLessUnconditionalCopy(); FlowInfo exitBranch; if (!(action == null || (action.isEmptyBlock() && currentScope.compilerOptions().complianceLevel <= ClassFileConstants.JDK1_3))) { if (!this.action.complainIfUnreachable(actionInfo, scope, false)) { - actionInfo = action.analyseCode(scope, loopingContext, actionInfo); + actionInfo = action. + analyseCode(scope, loopingContext, actionInfo). + unconditionalCopy(); } // code generation can be optimized when no need to continue in the loop - exitBranch = condInfo.initsWhenFalse(); - exitBranch.addInitializationsFrom(flowInfo); // recover null inits from before condition analysis + exitBranch = flowInfo.unconditionalCopy(). + addInitializationsFrom(condInfo.initsWhenFalse()); if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) { continueLabel = null; } else { - actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits()); - loopingContext.complainOnDeferredChecks(scope, actionInfo); - exitBranch.addPotentialInitializationsFrom(actionInfo.unconditionalInits()); + actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue); + loopingContext.complainOnDeferredFinalChecks(scope, actionInfo); + exitBranch.addPotentialInitializationsFrom(actionInfo); } } else { exitBranch = condInfo.initsWhenFalse(); @@ -133,6 +139,8 @@ } } //end of loop + loopingContext.complainOnDeferredNullChecks(currentScope, actionInfo); + FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranches( loopingContext.initsOnBreak, false, Index: compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java,v retrieving revision 1.50 diff -u -r1.50 IfStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java 18 Nov 2005 16:46:21 -0000 1.50 +++ compiler/org/eclipse/jdt/internal/compiler/ast/IfStatement.java 28 Nov 2005 12:08:05 -0000 @@ -60,22 +60,22 @@ FlowInfo flowInfo) { // process the condition - flowInfo = condition.analyseCode(currentScope, flowContext, flowInfo); + FlowInfo conditionFlowInfo = + condition.analyseCode(currentScope, flowContext, flowInfo); Constant cst = this.condition.optimizedBooleanConstant(); boolean isConditionOptimizedTrue = cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isConditionOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false; // process the THEN part - FlowInfo thenFlowInfo = flowInfo.initsWhenTrue().copy(); + FlowInfo thenFlowInfo = conditionFlowInfo.safeInitsWhenTrue(); if (isConditionOptimizedFalse) { thenFlowInfo.setReachMode(FlowInfo.UNREACHABLE); } - FlowInfo elseFlowInfo = flowInfo.initsWhenFalse().copy(); + FlowInfo elseFlowInfo = conditionFlowInfo.initsWhenFalse(); if (isConditionOptimizedTrue) { elseFlowInfo.setReachMode(FlowInfo.UNREACHABLE); } - this.condition.checkNullComparison(currentScope, flowContext, flowInfo, thenFlowInfo, elseFlowInfo); if (this.thenStatement != null) { // Save info for code gen thenInitStateIndex = @@ -107,11 +107,11 @@ // merge THEN & ELSE initializations FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranches( - thenFlowInfo, - isConditionOptimizedTrue, - elseFlowInfo, - isConditionOptimizedFalse, - true /*if(true){ return; } fake-reachable(); */); + thenFlowInfo, + isConditionOptimizedTrue, + elseFlowInfo, + isConditionOptimizedFalse, + true /*if(true){ return; } fake-reachable(); */); mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } Index: compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java,v retrieving revision 1.44 diff -u -r1.44 InstanceOfExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java 18 Nov 2005 16:46:21 -0000 1.44 +++ compiler/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java 28 Nov 2005 12:08:05 -0000 @@ -31,17 +31,18 @@ this.sourceEnd = type.sourceEnd; } - public FlowInfo analyseCode( +public FlowInfo analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - - flowInfo = expression - .analyseCode(currentScope, flowContext, flowInfo) - .unconditionalInits(); - expression.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); - return flowInfo; + LocalVariableBinding local = this.expression.localVariableBinding(); + if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { + flowContext.recordUsingNullReference(currentScope, local, + this.expression, FlowContext.CAN_ONLY_NULL, flowInfo); } + return expression.analyseCode(currentScope, flowContext, flowInfo). + unconditionalInits(); +} /** * Code generation for instanceOfExpression Index: compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java,v retrieving revision 1.52 diff -u -r1.52 LocalDeclaration.java --- compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java 18 Nov 2005 16:46:23 -0000 1.52 +++ compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java 28 Nov 2005 12:08:05 -0000 @@ -32,25 +32,22 @@ this.declarationEnd = sourceEnd; } - public FlowInfo analyseCode( - BlockScope currentScope, - FlowContext flowContext, +public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - - // record variable initialization if any - if (flowInfo.isReachable()) { - bits |= IsLocalDeclarationReachable; // only set if actually reached - } - if (this.initialization == null) - return flowInfo; - - int nullStatus = this.initialization.nullStatus(flowInfo); - flowInfo = - this.initialization - .analyseCode(currentScope, flowContext, flowInfo) - .unconditionalInits(); - - flowInfo.markAsDefinitelyAssigned(binding); + // record variable initialization if any + if (flowInfo.isReachable()) { + bits |= IsLocalDeclarationReachable; // only set if actually reached + } + if (this.initialization == null) { + return flowInfo; + } + int nullStatus = this.initialization.nullStatus(flowInfo); + flowInfo = + this.initialization + .analyseCode(currentScope, flowContext, flowInfo) + .unconditionalInits(); + flowInfo.markAsDefinitelyAssigned(binding); + if ((this.binding.type.tagBits & TagBits.IsBaseType) == 0) { switch(nullStatus) { case FlowInfo.NULL : flowInfo.markAsDefinitelyNull(this.binding); @@ -58,9 +55,12 @@ case FlowInfo.NON_NULL : flowInfo.markAsDefinitelyNonNull(this.binding); break; + default: + flowInfo.markAsDefinitelyUnknown(this.binding); } - return flowInfo; } + return flowInfo; +} public void checkModifiers() { Index: compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java,v retrieving revision 1.103 diff -u -r1.103 MessageSend.java --- compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java 18 Nov 2005 20:26:12 -0000 1.103 +++ compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java 28 Nov 2005 12:08:05 -0000 @@ -42,7 +42,9 @@ boolean nonStatic = !binding.isStatic(); flowInfo = receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic).unconditionalInits(); - if (nonStatic) receiver.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); + if (nonStatic) { + receiver.checkNPE(currentScope, flowContext, flowInfo, true); + } if (arguments != null) { int length = arguments.length; @@ -53,7 +55,10 @@ ReferenceBinding[] thrownExceptions; if ((thrownExceptions = binding.thrownExceptions) != NoExceptions) { // must verify that exceptions potentially thrown by this expression are caught in the method - flowContext.checkExceptionHandlers(thrownExceptions, this, flowInfo, currentScope); + flowContext.checkExceptionHandlers(thrownExceptions, this, flowInfo.copy(), currentScope); + // TODO (maxime) the copy above is needed because of a side effect into + // checkExceptionHandlers; consider protecting there instead of here; + // NullReferenceTest#test0510 } manageSyntheticAccessIfNecessary(currentScope, flowInfo); return flowInfo; Index: compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java,v retrieving revision 1.30 diff -u -r1.30 OR_OR_Expression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java 18 Nov 2005 16:46:21 -0000 1.30 +++ compiler/org/eclipse/jdt/internal/compiler/ast/OR_OR_Expression.java 28 Nov 2005 12:08:05 -0000 @@ -50,7 +50,7 @@ // need to be careful of scenario: // (x || y) || !z, if passing the left info to the right, it would be swapped by the ! - FlowInfo rightInfo = leftInfo.initsWhenFalse().unconditionalInits().copy(); + FlowInfo rightInfo = leftInfo.initsWhenFalse().unconditionalCopy(); rightInitStateIndex = currentScope.methodScope().recordInitializationStates(rightInfo); @@ -59,14 +59,11 @@ rightInfo.setReachMode(FlowInfo.UNREACHABLE); } rightInfo = right.analyseCode(currentScope, flowContext, rightInfo); - FlowInfo falseMergedInfo = rightInfo.initsWhenFalse().copy(); - rightInfo.setReachMode(previousMode); // reset after falseMergedInfo got extracted - FlowInfo mergedInfo = FlowInfo.conditional( // merging two true initInfos for such a negative case: if ((t && (b = t)) || f) r = b; // b may not have been initialized - leftInfo.initsWhenTrue().copy().unconditionalInits().mergedWith( - rightInfo.initsWhenTrue().copy().unconditionalInits()), - falseMergedInfo); + leftInfo.initsWhenTrue().unconditionalInits().mergedWith( + rightInfo.safeInitsWhenTrue().setReachMode(previousMode).unconditionalInits()), + rightInfo.initsWhenFalse()); mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; Index: compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java,v retrieving revision 1.98 diff -u -r1.98 QualifiedNameReference.java --- compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java 18 Nov 2005 16:46:21 -0000 1.98 +++ compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java 28 Nov 2005 12:08:06 -0000 @@ -98,7 +98,7 @@ } else if (localBinding.useFlag == LocalVariableBinding.UNUSED) { localBinding.useFlag = LocalVariableBinding.FAKE_USED; } - this.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); + checkNPE(currentScope, flowContext, flowInfo, true); } if (needValue) { @@ -257,7 +257,7 @@ } else if (localBinding.useFlag == LocalVariableBinding.UNUSED) { localBinding.useFlag = LocalVariableBinding.FAKE_USED; } - this.checkNullStatus(currentScope, flowContext, flowInfo, FlowInfo.NON_NULL); + checkNPE(currentScope, flowContext, flowInfo, true); } if (needValue) { manageEnclosingInstanceAccessIfNecessary(currentScope, flowInfo); @@ -304,7 +304,23 @@ bits |= Binding.FIELD; return getOtherFieldBindings(scope); } - + +public void checkNPE(BlockScope scope, FlowContext flowContext, + FlowInfo flowInfo, boolean checkString) { + // cannot override localVariableBinding because this would project o.m onto o when + // analysing assignments + if ((bits & RestrictiveFlagMASK) == Binding.LOCAL) { + LocalVariableBinding local = (LocalVariableBinding) this.binding; + if (local != null && + (local.type.tagBits & TagBits.IsBaseType) == 0 && + (checkString || local.type.id != T_JavaLangString)) { + flowContext.recordUsingNullReference(scope, local, this, + FlowContext.MAY_NULL, flowInfo); + flowInfo.markAsComparedEqualToNonNull(local); + // from thereon it is set + } + } +} /** * @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) */ @@ -773,6 +789,7 @@ ? type.capture(scope, this.sourceEnd) : type; } + public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo) { if (!flowInfo.isReachable()) return; //If inlinable field, forget the access emulation, the code gen will directly target it @@ -849,6 +866,10 @@ } } +public int nullStatus(FlowInfo flowInfo) { + return FlowInfo.UNKNOWN; +} + public Constant optimizedBooleanConstant() { switch (this.resolvedType.id) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java,v retrieving revision 1.25 diff -u -r1.25 Reference.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java 18 Nov 2005 16:46:21 -0000 1.25 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java 28 Nov 2005 12:08:06 -0000 @@ -68,20 +68,4 @@ public abstract void generateCompoundAssignment(BlockScope currentScope, CodeStream codeStream, Expression expression, int operator, int assignmentImplicitConversion, boolean valueRequired); public abstract void generatePostIncrement(BlockScope currentScope, CodeStream codeStream, CompoundAssignment postIncrement, boolean valueRequired); - -public int nullStatus(FlowInfo flowInfo) { - - if (this.constant != null && this.constant != Constant.NotAConstant) - return FlowInfo.NON_NULL; // constant expression cannot be null - - LocalVariableBinding local = localVariableBinding(); - if (local != null) { - if (flowInfo.isDefinitelyNull(local)) - return FlowInfo.NULL; - if (flowInfo.isDefinitelyNonNull(local)) - return FlowInfo.NON_NULL; - } - return FlowInfo.UNKNOWN; -} - } Index: compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java,v retrieving revision 1.80 diff -u -r1.80 SingleNameReference.java --- compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java 18 Nov 2005 16:46:21 -0000 1.80 +++ compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java 28 Nov 2005 12:08:06 -0000 @@ -676,6 +676,27 @@ } } } + +public int nullStatus(FlowInfo flowInfo) { + if (this.constant != null && this.constant != Constant.NotAConstant) { + return FlowInfo.NON_NULL; // constant expression cannot be null + } + switch (bits & 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) { + if (flowInfo.isDefinitelyNull(local)) + return FlowInfo.NULL; + if (flowInfo.isDefinitelyNonNull(local)) + return FlowInfo.NON_NULL; + return FlowInfo.UNKNOWN; + } + } + return FlowInfo.NON_NULL; +} + public StringBuffer printExpression(int indent, StringBuffer output){ return output.append(token); Index: compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java,v retrieving revision 1.59 diff -u -r1.59 SwitchStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java 20 Oct 2005 13:26:45 -0000 1.59 +++ compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java 28 Nov 2005 12:08:06 -0000 @@ -62,11 +62,11 @@ if ((caseIndex < caseCount) && (statement == cases[caseIndex])) { // statement is a case this.scope.enclosingCase = cases[caseIndex]; // record entering in a switch case block caseIndex++; - caseInits = caseInits.mergedWith(flowInfo.copy().unconditionalInits()); + caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); didAlreadyComplain = false; // reset complaint } else if (statement == defaultCase) { // statement is the default case this.scope.enclosingCase = defaultCase; // record entering in a switch case block - caseInits = caseInits.mergedWith(flowInfo.copy().unconditionalInits()); + caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); didAlreadyComplain = false; // reset complaint } if (!statement.complainIfUnreachable(caseInits, scope, didAlreadyComplain)) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java,v retrieving revision 1.85 diff -u -r1.85 TryStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java 18 Nov 2005 16:46:21 -0000 1.85 +++ compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java 28 Nov 2005 12:08:06 -0000 @@ -85,7 +85,7 @@ .analyseCode( currentScope, finallyContext = new FinallyFlowContext(flowContext, finallyBlock), - flowInfo.copy().unconditionalInits().discardNullRelatedInitializations()) + flowInfo.nullInfoLessUnconditionalCopy()) .unconditionalInits(); if (subInfo == FlowInfo.DEAD_END) { isSubRoutineEscaping = true; @@ -121,13 +121,15 @@ for (int i = 0; i < catchCount; i++) { // keep track of the inits that could potentially have led to this exception handler (for final assignments diagnosis) FlowInfo catchInfo = - flowInfo - .copy() - .unconditionalInits() + flowInfo.unconditionalCopy(). + addPotentialInitializationsFrom( + handlingContext.initsOnException( + caughtExceptionTypes[i])) .addPotentialInitializationsFrom( - handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits()) - .addPotentialInitializationsFrom(tryInfo.unconditionalInits()) - .addPotentialInitializationsFrom(handlingContext.initsOnReturn); + tryInfo.unconditionalInits()) + .addPotentialInitializationsFrom( + handlingContext.initsOnReturn); + // WORK this one is wrong -- too permissive for test case NullReferenceTest#test0560 // catch var is always set LocalVariableBinding catchArg = catchArguments[i].binding; @@ -162,10 +164,11 @@ // we also need to check potential multiple assignments of final variables inside the finally block // need to include potential inits from returns inside the try/catch parts - 1GK2AOF - finallyContext.complainOnDeferredChecks( + finallyContext.complainOnDeferredChecks( //$NON-NULL-1$ null with subRoutineStartLabel, which returns tryInfo.isReachable() - ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn)) - : insideSubContext.initsOnReturn, + ? (tryInfo.addPotentialInitializationsFrom( + insideSubContext.initsOnReturn)) //$NON-NULL-1$ null with subRoutineStartLabel, which returns + : insideSubContext.initsOnReturn, //$NON-NULL-1$ null with subRoutineStartLabel, which returns currentScope); if (subInfo == FlowInfo.DEAD_END) { mergedInitStateIndex = Index: compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java,v retrieving revision 1.109 diff -u -r1.109 TypeDeclaration.java --- compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java 20 Oct 2005 13:26:45 -0000 1.109 +++ compiler/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java 28 Nov 2005 12:08:07 -0000 @@ -634,7 +634,7 @@ * Common flow analysis for all types * */ - public void internalAnalyseCode(FlowContext flowContext, FlowInfo flowInfo) { + private void internalAnalyseCode(FlowContext flowContext, FlowInfo flowInfo) { if ((this.binding.isPrivate()/* || (this.binding.tagBits & (TagBits.IsAnonymousType|TagBits.IsLocalType)) == TagBits.IsLocalType*/) && !this.binding.isUsed()) { if (!scope.referenceCompilationUnit().compilationResult.hasSyntaxError) { @@ -644,8 +644,8 @@ InitializationFlowContext initializerContext = new InitializationFlowContext(null, this, initializerScope); InitializationFlowContext staticInitializerContext = new InitializationFlowContext(null, this, staticInitializerScope); - FlowInfo nonStaticFieldInfo = flowInfo.copy().unconditionalInits().discardFieldInitializations(); - FlowInfo staticFieldInfo = flowInfo.copy().unconditionalInits().discardFieldInitializations(); + FlowInfo nonStaticFieldInfo = flowInfo.unconditionalFieldLessCopy(); + FlowInfo staticFieldInfo = flowInfo.unconditionalFieldLessCopy(); if (fields != null) { for (int i = 0, count = fields.length; i < count; i++) { FieldDeclaration field = fields[i]; @@ -699,7 +699,7 @@ } } if (methods != null) { - UnconditionalFlowInfo outerInfo = flowInfo.copy().unconditionalInits().discardFieldInitializations(); + UnconditionalFlowInfo outerInfo = flowInfo.unconditionalFieldLessCopy(); FlowInfo constructorInfo = nonStaticFieldInfo.unconditionalInits().discardNonFieldInitializations().addInitializationsFrom(outerInfo); for (int i = 0, count = methods.length; i < count; i++) { AbstractMethodDeclaration method = methods[i]; Index: compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java,v retrieving revision 1.38 diff -u -r1.38 UnaryExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java 18 Nov 2005 16:46:22 -0000 1.38 +++ compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java 28 Nov 2005 12:08:07 -0000 @@ -27,19 +27,20 @@ this.bits |= operator << OperatorSHIFT; // encode operator } - public FlowInfo analyseCode( +public FlowInfo analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - - if (((bits & OperatorMASK) >> OperatorSHIFT) == NOT) { - return this.expression - .analyseCode(currentScope, flowContext, flowInfo) - .asNegatedCondition(); - } else { - return this.expression.analyseCode(currentScope, flowContext, flowInfo); - } + this.expression.checkNPE(currentScope, flowContext, flowInfo, true); + if (((bits & OperatorMASK) >> OperatorSHIFT) == NOT) { + return this.expression. + analyseCode(currentScope, flowContext, flowInfo). + asNegatedCondition(); + } else { + return this.expression. + analyseCode(currentScope, flowContext, flowInfo); } +} public Constant optimizedBooleanConstant() { Index: compiler/org/eclipse/jdt/internal/compiler/ast/WhileStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/WhileStatement.java,v retrieving revision 1.51 diff -u -r1.51 WhileStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/WhileStatement.java 18 Nov 2005 16:46:21 -0000 1.51 +++ compiler/org/eclipse/jdt/internal/compiler/ast/WhileStatement.java 28 Nov 2005 12:08:07 -0000 @@ -55,11 +55,15 @@ preCondInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); LoopingFlowContext condLoopContext; - FlowInfo condInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations(); + FlowInfo condInfo = flowInfo.nullInfoLessUnconditionalCopy(); + // we need to collect the contribution to nulls of the coming paths through the + // loop, be they falling through normally or branched to break, continue labels + // or catch blocks condInfo = this.condition.analyseCode( currentScope, (condLoopContext = - new LoopingFlowContext(flowContext, this, null, null, currentScope)), + new LoopingFlowContext(flowContext, flowInfo, this, null, + null, currentScope)), condInfo); LoopingFlowContext loopingContext; @@ -67,17 +71,22 @@ FlowInfo exitBranch; if (action == null || (action.isEmptyBlock() && currentScope.compilerOptions().complianceLevel <= ClassFileConstants.JDK1_3)) { - condLoopContext.complainOnDeferredChecks(currentScope, condInfo); + condLoopContext.complainOnDeferredFinalChecks(currentScope, + condInfo); + condLoopContext.complainOnDeferredNullChecks(currentScope, + condInfo.unconditionalInits()); if (isConditionTrue) { return FlowInfo.DEAD_END; } else { - FlowInfo mergedInfo = condInfo.initsWhenFalse().unconditionalInits(); + FlowInfo mergedInfo = condInfo.initsWhenFalse(); if (isConditionOptimizedTrue){ mergedInfo.setReachMode(FlowInfo.UNREACHABLE); } mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); - return mergedInfo; + return flowInfo.unconditionalInits(). + addPotentialNullInfoFrom( + condInfo.initsWhenFalse().unconditionalInits()); } } else { // in case the condition was inlined to false, record the fact that there is no way to reach any @@ -85,6 +94,7 @@ loopingContext = new LoopingFlowContext( flowContext, + flowInfo, this, breakLabel, continueLabel, @@ -108,15 +118,26 @@ } // code generation can be optimized when no need to continue in the loop - exitBranch = condInfo.initsWhenFalse(); - exitBranch.addInitializationsFrom(flowInfo); // recover null inits from before condition analysis + exitBranch = flowInfo.copy(); + // need to start over from flowInfo so as to get null inits + if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) { continueLabel = null; + exitBranch.addInitializationsFrom(condInfo.initsWhenFalse()); } else { - condLoopContext.complainOnDeferredChecks(currentScope, condInfo); + condLoopContext.complainOnDeferredFinalChecks(currentScope, + condInfo); actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits()); - loopingContext.complainOnDeferredChecks(currentScope, actionInfo); - exitBranch.addPotentialInitializationsFrom(actionInfo.unconditionalInits()); + condLoopContext.complainOnDeferredNullChecks(currentScope, + actionInfo); + loopingContext.complainOnDeferredFinalChecks(currentScope, + actionInfo); + loopingContext.complainOnDeferredNullChecks(currentScope, + actionInfo); + exitBranch. + addPotentialInitializationsFrom( + actionInfo.unconditionalInits()). + addInitializationsFrom(condInfo.initsWhenFalse()); } } Index: compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java,v retrieving revision 1.23 diff -u -r1.23 ConditionalFlowInfo.java --- compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java 23 Feb 2005 02:47:29 -0000 1.23 +++ compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java 28 Nov 2005 12:08:07 -0000 @@ -83,52 +83,20 @@ && initsWhenFalse.isDefinitelyAssigned(local); } - /** - * Check status of definite non-null assignment for a field. - */ - public boolean isDefinitelyNonNull(FieldBinding field) { - - return initsWhenTrue.isDefinitelyNonNull(field) - && initsWhenFalse.isDefinitelyNonNull(field); - } - - /** - * Check status of definite non-null assignment for a local variable. - */ - public boolean isDefinitelyNonNull(LocalVariableBinding local) { - - return initsWhenTrue.isDefinitelyNonNull(local) - && initsWhenFalse.isDefinitelyNonNull(local); - } +public boolean isDefinitelyNonNull(LocalVariableBinding local) { + return initsWhenTrue.isDefinitelyNonNull(local) + && initsWhenFalse.isDefinitelyNonNull(local); +} - /** - * Check status of definite null assignment for a field. - */ - public boolean isDefinitelyNull(FieldBinding field) { - - return initsWhenTrue.isDefinitelyNull(field) - && initsWhenFalse.isDefinitelyNull(field); - } - - /** - * Check status of definite null assignment for a local variable. - */ - public boolean isDefinitelyNull(LocalVariableBinding local) { - - return initsWhenTrue.isDefinitelyNull(local) - && initsWhenFalse.isDefinitelyNull(local); - } +public boolean isDefinitelyNull(LocalVariableBinding local) { + return initsWhenTrue.isDefinitelyNull(local) + && initsWhenFalse.isDefinitelyNull(local); +} - public int reachMode(){ - return unconditionalInits().reachMode(); - } - - public boolean isReachable(){ - - return unconditionalInits().isReachable(); - //should maybe directly be: false - } - +public boolean isDefinitelyUnknown(LocalVariableBinding local) { + return initsWhenTrue.isDefinitelyUnknown(local) + && initsWhenFalse.isDefinitelyUnknown(local); +} /** * Check status of potential assignment for a field. */ @@ -147,6 +115,36 @@ || initsWhenFalse.isPotentiallyAssigned(local); } +public boolean isPotentiallyNull(LocalVariableBinding local) { + return initsWhenTrue.isPotentiallyNull(local) + || initsWhenFalse.isPotentiallyNull(local); +} + +public boolean isPotentiallyUnknown(LocalVariableBinding local) { + return initsWhenTrue.isPotentiallyUnknown(local) + || initsWhenFalse.isPotentiallyUnknown(local); +} + +public boolean isProtectedNonNull(LocalVariableBinding local) { + return initsWhenTrue.isProtectedNonNull(local) + && initsWhenFalse.isProtectedNonNull(local); +} + +public boolean isProtectedNull(LocalVariableBinding local) { + return initsWhenTrue.isProtectedNull(local) + && initsWhenFalse.isProtectedNull(local); +} + +public void markAsComparedEqualToNonNull(LocalVariableBinding local) { + initsWhenTrue.markAsComparedEqualToNonNull(local); + initsWhenFalse.markAsComparedEqualToNonNull(local); +} + +public void markAsComparedEqualToNull(LocalVariableBinding local) { + initsWhenTrue.markAsComparedEqualToNull(local); + initsWhenFalse.markAsComparedEqualToNull(local); +} + /** * Record a field got definitely assigned. */ @@ -201,50 +199,60 @@ initsWhenFalse.markAsDefinitelyNull(local); } - /** - * Clear the initialization info for a field - */ - public void markAsDefinitelyNotAssigned(FieldBinding field) { - - initsWhenTrue.markAsDefinitelyNotAssigned(field); - initsWhenFalse.markAsDefinitelyNotAssigned(field); - } - - /** - * Clear the initialization info for a local variable - */ - public void markAsDefinitelyNotAssigned(LocalVariableBinding local) { - - initsWhenTrue.markAsDefinitelyNotAssigned(local); - initsWhenFalse.markAsDefinitelyNotAssigned(local); - } - - public FlowInfo setReachMode(int reachMode) { - - initsWhenTrue.setReachMode(reachMode); - initsWhenFalse.setReachMode(reachMode); - return this; - } - - /** - * Converts conditional receiver into inconditional one, updated in the following way: - */ - public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) { - - return unconditionalInits().mergedWith(otherInits); - } +public void markAsDefinitelyUnknown(LocalVariableBinding local) { + initsWhenTrue.markAsDefinitelyUnknown(local); + initsWhenFalse.markAsDefinitelyUnknown(local); +} + +public FlowInfo setReachMode(int reachMode) { + if (reachMode == REACHABLE) { + this.tagBits &= ~UNREACHABLE; + } + else { + this.tagBits |= UNREACHABLE; + } + initsWhenTrue.setReachMode(reachMode); + initsWhenFalse.setReachMode(reachMode); + return this; +} +public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) { + return unconditionalInits().mergedWith(otherInits); +} + +public UnconditionalFlowInfo nullInfoLessUnconditionalCopy() { + return unconditionalInitsWithoutSideEffect(). + nullInfoLessUnconditionalCopy(); +} public String toString() { return "FlowInfo"; //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-2$ } - - public UnconditionalFlowInfo unconditionalInits() { - - return initsWhenTrue.unconditionalInits().copy() - .mergedWith(initsWhenFalse.unconditionalInits()); - } + +public FlowInfo safeInitsWhenTrue() { + return initsWhenTrue; +} + +public UnconditionalFlowInfo unconditionalCopy() { + return initsWhenTrue.unconditionalCopy(). + mergedWith(initsWhenFalse.unconditionalInits()); +} + +public UnconditionalFlowInfo unconditionalFieldLessCopy() { + return initsWhenTrue.unconditionalFieldLessCopy(). + mergedWith(initsWhenFalse.unconditionalFieldLessCopy()); + // should never happen, hence suboptimal does not hurt +} + +public UnconditionalFlowInfo unconditionalInits() { + return initsWhenTrue.unconditionalInits(). + mergedWith(initsWhenFalse.unconditionalInits()); +} + +public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect() { + // cannot do better here than unconditionalCopy - but still a different + // operation for UnconditionalFlowInfo + return initsWhenTrue.unconditionalCopy(). + mergedWith(initsWhenFalse.unconditionalInits()); +} } Index: compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java,v retrieving revision 1.33 diff -u -r1.33 ExceptionHandlingFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java 20 Oct 2005 13:26:46 -0000 1.33 +++ compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -62,7 +62,7 @@ int cacheIndex = i / BitCacheSize, bitMask = 1 << (i % BitCacheSize); if (handledExceptions[i].isUncheckedException(true)) { isReached[cacheIndex] |= bitMask; - this.initsOnExceptions[i] = flowInfo.copy().unconditionalInits(); + this.initsOnExceptions[i] = flowInfo.unconditionalCopy(); } else { this.initsOnExceptions[i] = FlowInfo.DEAD_END; } @@ -168,20 +168,22 @@ this.isReached[cacheIndex] |= bitMask; initsOnExceptions[index] = - initsOnExceptions[index] == FlowInfo.DEAD_END - ? flowInfo.copy().unconditionalInits() - : initsOnExceptions[index].mergedWith(flowInfo.copy().unconditionalInits()); + initsOnExceptions[index].isReachable() ? + initsOnExceptions[index].mergedWith(flowInfo): + flowInfo.unconditionalCopy(); } - public void recordReturnFrom(FlowInfo flowInfo) { - - if (!flowInfo.isReachable()) return; - if (initsOnReturn == FlowInfo.DEAD_END) { - initsOnReturn = flowInfo.copy().unconditionalInits(); - } else { - initsOnReturn = initsOnReturn.mergedWith(flowInfo.copy().unconditionalInits()); - } +public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { + if (!flowInfo.isReachable()) { + return; + } + if (initsOnReturn.isReachable()) { + initsOnReturn = initsOnReturn.mergedWith(flowInfo); + } + else { + initsOnReturn = (UnconditionalFlowInfo) flowInfo.copy(); } +} /* * Compute a merged list of unhandled exception types (keeping only the most generic ones). Index: compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java,v retrieving revision 1.17 diff -u -r1.17 FinallyFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java 23 Feb 2005 02:47:29 -0000 1.17 +++ compiler/org/eclipse/jdt/internal/compiler/flow/FinallyFlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -16,6 +16,7 @@ import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; /** @@ -28,77 +29,124 @@ VariableBinding[] finalVariables; int assignCount; + LocalVariableBinding[] nullLocals; Expression[] nullReferences; - int[] nullStatus; + int[] nullCheckTypes; int nullCount; public FinallyFlowContext(FlowContext parent, ASTNode associatedNode) { super(parent, associatedNode); } - /** - * Given some contextual initialization info (derived from a try block or a catch block), this - * code will check that the subroutine context does not also initialize a final variable potentially set - * redundantly. - */ - public void complainOnDeferredChecks(FlowInfo flowInfo, BlockScope scope) { +/** + * Given some contextual initialization info (derived from a try block or a catch block), this + * code will check that the subroutine context does not also initialize a final variable potentially set + * redundantly. + */ +public void complainOnDeferredChecks(FlowInfo flowInfo, BlockScope scope) { + + // check redundant final assignments + for (int i = 0; i < this.assignCount; i++) { + VariableBinding variable = this.finalVariables[i]; + if (variable == null) continue; - // check redundant final assignments - for (int i = 0; i < assignCount; i++) { - VariableBinding variable = finalVariables[i]; - if (variable == null) continue; - - boolean complained = false; // remember if have complained on this final assignment - if (variable instanceof FieldBinding) { - // final field - if (flowInfo.isPotentiallyAssigned((FieldBinding)variable)) { - complained = true; - scope.problemReporter().duplicateInitializationOfBlankFinalField((FieldBinding)variable, finalAssignments[i]); - } - } else { - // final local variable - if (flowInfo.isPotentiallyAssigned((LocalVariableBinding) variable)) { - complained = true; - scope.problemReporter().duplicateInitializationOfFinalLocal( - (LocalVariableBinding) variable, - finalAssignments[i]); - } - } - // any reference reported at this level is removed from the parent context - // where it could also be reported again - if (complained) { - FlowContext currentContext = parent; - while (currentContext != null) { - //if (currentContext.isSubRoutine()) { - currentContext.removeFinalAssignmentIfAny(finalAssignments[i]); - //} - currentContext = currentContext.parent; - } + boolean complained = false; // remember if have complained on this final assignment + if (variable instanceof FieldBinding) { + // final field + if (flowInfo.isPotentiallyAssigned((FieldBinding)variable)) { + complained = true; + scope.problemReporter().duplicateInitializationOfBlankFinalField((FieldBinding)variable, finalAssignments[i]); + } + } else { + // final local variable + if (flowInfo.isPotentiallyAssigned((LocalVariableBinding) variable)) { + complained = true; + scope.problemReporter().duplicateInitializationOfFinalLocal( + (LocalVariableBinding) variable, + this.finalAssignments[i]); } } - - // check inconsistent null checks - for (int i = 0; i < nullCount; i++) { - Expression expression = nullReferences[i]; - if (expression == null) continue; + // any reference reported at this level is removed from the parent context + // where it could also be reported again + if (complained) { + FlowContext currentContext = this.parent; + while (currentContext != null) { + //if (currentContext.isSubRoutine()) { + currentContext.removeFinalAssignmentIfAny(this.finalAssignments[i]); + //} + currentContext = currentContext.parent; + } + } + } + + // check inconsistent null checks + if (this.deferNullDiagnostic) { // within an enclosing loop, be conservative + for (int i = 0; i < this.nullCount; i++) { + Expression expression = this.nullReferences[i]; + LocalVariableBinding local = this.nullLocals[i]; + switch (this.nullCheckTypes[i]) { + case CAN_ONLY_NULL_NON_NULL : + case CAN_ONLY_NULL: + if (flowInfo.isProtectedNonNull(local)) { + if (nullCheckTypes[i] == CAN_ONLY_NULL_NON_NULL) { + scope.problemReporter().localVariableCannotBeNull(local, expression); + } + return; + } + if (flowInfo.isProtectedNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + return; + } + break; + case MAY_NULL : + if (flowInfo.isProtectedNonNull(local)) { + return; + } + if (flowInfo.isProtectedNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + return; + } + break; + default: + // never happens + } + this.parent.recordUsingNullReference(scope, local, expression, + this.nullCheckTypes[i], flowInfo); + } + } + else { // no enclosing loop, be as precise as possible right now + for (int i = 0; i < this.nullCount; i++) { + Expression expression = this.nullReferences[i]; // final local variable - LocalVariableBinding local = expression.localVariableBinding(); - switch (nullStatus[i]) { - case FlowInfo.NULL : + LocalVariableBinding local = this.nullLocals[i]; + switch (this.nullCheckTypes[i]) { + case CAN_ONLY_NULL_NON_NULL : + if (flowInfo.isDefinitelyNonNull(local)) { + scope.problemReporter().localVariableCannotBeNull(local, expression); + return; + } + case CAN_ONLY_NULL: if (flowInfo.isDefinitelyNull(local)) { - nullReferences[i] = null; - this.parent.recordUsingNullReference(scope, local, expression, nullStatus[i], flowInfo); + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + return; } break; - case FlowInfo.NON_NULL : - if (flowInfo.isDefinitelyNonNull(local)) { - nullReferences[i] = null; - this.parent.recordUsingNullReference(scope, local, expression, nullStatus[i], flowInfo); + case MAY_NULL : + if (flowInfo.isDefinitelyNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + return; + } + if (flowInfo.isPotentiallyNull(local)) { + scope.problemReporter().localVariableMayBeNull(local, expression); + return; } break; + default: + // should not happen } } } +} public String individualToString() { @@ -138,6 +186,73 @@ return true; } + public void recordUsingNullReference(Scope scope, LocalVariableBinding local, + Expression reference, int checkType, FlowInfo flowInfo) { + if (!flowInfo.isReachable()) { + return; + } + if (deferNullDiagnostic) { // within an enclosing loop, be conservative + switch (checkType) { + case CAN_ONLY_NULL_NON_NULL : + case CAN_ONLY_NULL: + if (flowInfo.isProtectedNonNull(local)) { + if (checkType == CAN_ONLY_NULL_NON_NULL) { + scope.problemReporter().localVariableCannotBeNull(local, reference); + } + return; + } + if (flowInfo.isProtectedNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + break; + case MAY_NULL : + if (flowInfo.isProtectedNonNull(local)) { + return; + } + if (flowInfo.isProtectedNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + break; + default: + // never happens + } + } + else { // no enclosing loop, be as precise as possible right now + switch (checkType) { + case CAN_ONLY_NULL_NON_NULL : + if (flowInfo.isDefinitelyNonNull(local)) { + scope.problemReporter().localVariableCannotBeNull(local, reference); + return; + } + case CAN_ONLY_NULL: + if (flowInfo.isDefinitelyNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + break; + case MAY_NULL : + if (flowInfo.isDefinitelyNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + if (flowInfo.isPotentiallyNull(local)) { + scope.problemReporter().localVariableMayBeNull(local, reference); + return; + } + if (flowInfo.isDefinitelyNonNull(local)) { + return; // shortcut: cannot be null + } + break; + default: + // never happens + } + } + recordNullReference(local, reference, checkType); + // prepare to re-check with try/catch flow info + } + void removeFinalAssignmentIfAny(Reference reference) { for (int i = 0; i < assignCount; i++) { if (finalAssignments[i] == reference) { @@ -148,18 +263,27 @@ } } - protected boolean recordNullReference(Expression expression, int status) { - if (nullCount == 0) { - nullReferences = new Expression[5]; - nullStatus = new int[5]; - } else { - if (nullCount == nullReferences.length) { - System.arraycopy(nullReferences, 0, nullReferences = new Expression[nullCount * 2], 0, nullCount); - System.arraycopy(nullStatus, 0, nullStatus = new int[nullCount * 2], 0, nullCount); - } - } - nullReferences[nullCount] = expression; - nullStatus[nullCount++] = status; - return true; +protected void recordNullReference(LocalVariableBinding local, + Expression expression, int status) { + if (this.nullCount == 0) { + this.nullLocals = new LocalVariableBinding[5]; + this.nullReferences = new Expression[5]; + this.nullCheckTypes = new int[5]; + } + else if (this.nullCount == this.nullLocals.length) { + int newLength = this.nullCount * 2; + System.arraycopy(this.nullLocals, 0, + this.nullLocals = new LocalVariableBinding[newLength], 0, + this.nullCount); + System.arraycopy(this.nullReferences, 0, + this.nullReferences = new Expression[newLength], 0, + this.nullCount); + System.arraycopy(this.nullCheckTypes, 0, + this.nullCheckTypes = new int[newLength], 0, + this.nullCount); } + this.nullLocals[this.nullCount] = local; + this.nullReferences[this.nullCount] = expression; + this.nullCheckTypes[this.nullCount++] = status; +} } Index: compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java,v retrieving revision 1.45 diff -u -r1.45 FlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java 14 Oct 2005 22:43:00 -0000 1.45 +++ compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -35,12 +35,15 @@ public ASTNode associatedNode; public FlowContext parent; - + boolean deferNullDiagnostic, preemptNullDiagnostic; + // preempt marks looping contexts public final static FlowContext NotContinuableContext = new FlowContext(null, null); public FlowContext(FlowContext parent, ASTNode associatedNode) { this.parent = parent; this.associatedNode = associatedNode; + deferNullDiagnostic = parent != null && + (parent.deferNullDiagnostic || parent.preemptNullDiagnostic); } public Label breakLabel() { @@ -163,7 +166,7 @@ traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); if (traversedContext.associatedNode instanceof TryStatement){ TryStatement tryStatement = (TryStatement) traversedContext.associatedNode; - flowInfo = flowInfo.copy().addInitializationsFrom(tryStatement.subRoutineInits); + flowInfo = flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); } traversedContext = traversedContext.parent; } @@ -256,7 +259,7 @@ traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); if (traversedContext.associatedNode instanceof TryStatement){ TryStatement tryStatement = (TryStatement) traversedContext.associatedNode; - flowInfo = flowInfo.copy().addInitializationsFrom(tryStatement.subRoutineInits); + flowInfo = flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); } traversedContext = traversedContext.parent; } @@ -403,7 +406,7 @@ // default implementation: do nothing } -public void recordContinueFrom(FlowInfo flowInfo) { +public void recordContinueFrom(FlowContext innerFlowContext, FlowInfo flowInfo) { // default implementation: do nothing } @@ -411,11 +414,21 @@ return true; // keep going } -protected boolean recordNullReference(Expression expression, int status) { - return false; // keep going +/** + * Record a null reference for use by deferred checks. Only looping or + * finally contexts really record that information. + * @param local the local variable involved in the check + * @param expression the expression within which local lays + * @param status the status against which the check must be performed; one of + * {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL + * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL} + */ +protected void recordNullReference(LocalVariableBinding local, + Expression expression, int status) { + // default implementation: do nothing } -public void recordReturnFrom(FlowInfo flowInfo) { +public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { // default implementation: do nothing } @@ -432,34 +445,72 @@ } } -public void recordUsingNullReference(Scope scope, LocalVariableBinding local, Expression reference, int status, FlowInfo flowInfo) { - if (!flowInfo.isReachable()) return; - - switch (status) { - case FlowInfo.NULL : +public static final int + CAN_ONLY_NULL_NON_NULL = 20, + // check against null and non null, with definite values -- comparisons + CAN_ONLY_NULL = 21, + // check against null, with definite values -- assignment to null + MAY_NULL = 22; + // check against null, with potential values -- NPE guard + +/** + * Record a null reference for use by deferred checks. Only looping or + * finally contexts really record that information. The context may + * emit an error immediately depending on the status of local against + * flowInfo and its nature (only looping of finally contexts defer part + * of the checks; nonetheless, contexts that are nested into a looping or a + * finally context get affected and delegate some checks to their enclosing + * context). + * @param scope the scope into which the check is performed + * @param local the local variable involved in the check + * @param reference the expression within which local lays + * @param checkType the status against which the check must be performed; one + * of {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL + * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL} + * @param flowInfo the flow info at the check point; deferring contexts will + * perform supplementary checks against flow info instances that cannot + * be known at the time of calling this method (they are influenced by + * code that follows the current point) + */ +public void recordUsingNullReference(Scope scope, LocalVariableBinding local, + Expression reference, int checkType, FlowInfo flowInfo) { + if (!flowInfo.isReachable() || flowInfo.isDefinitelyUnknown(local)) { + return; + } + switch (checkType) { + case CAN_ONLY_NULL_NON_NULL : + if (flowInfo.isDefinitelyNonNull(local)) { + scope.problemReporter().localVariableCannotBeNull(local, reference); + return; + } + else if (flowInfo.isPotentiallyUnknown(local)) { + return; + } + case CAN_ONLY_NULL: if (flowInfo.isDefinitelyNull(local)) { scope.problemReporter().localVariableCanOnlyBeNull(local, reference); return; - } else if (flowInfo.isDefinitelyNonNull(local)) { - scope.problemReporter().localVariableCannotBeNull(local, reference); + } + else if (flowInfo.isPotentiallyUnknown(local)) { return; } break; - case FlowInfo.NON_NULL : + case MAY_NULL : if (flowInfo.isDefinitelyNull(local)) { - scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + if (flowInfo.isPotentiallyNull(local)) { + scope.problemReporter().localVariableMayBeNull(local, reference); return; } break; + default: + // never happens } - - // for initialization inside looping statement that effectively loops - FlowContext context = this; - while (context != null) { - if (context.recordNullReference(reference, status)) { - return; // no need to keep going - } - context = context.parent; + if (parent != null) { + parent.recordUsingNullReference(scope, local, reference, checkType, + flowInfo); } } Index: compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java,v retrieving revision 1.27 diff -u -r1.27 FlowInfo.java --- compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java 23 Feb 2005 02:47:29 -0000 1.27 +++ compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java 28 Nov 2005 12:08:07 -0000 @@ -15,8 +15,10 @@ public abstract class FlowInfo { + public int tagBits; // REACHABLE by default public final static int REACHABLE = 0; public final static int UNREACHABLE = 1; + public final static int NULL_FLAG_MASK = 2; public final static int UNKNOWN = 0; public final static int NULL = 1; @@ -25,11 +27,30 @@ public static final UnconditionalFlowInfo DEAD_END; // Represents a dead branch status of initialization static { DEAD_END = new UnconditionalFlowInfo(); - DEAD_END.reachMode = UNREACHABLE; + DEAD_END.tagBits = UNREACHABLE; } - abstract public FlowInfo addInitializationsFrom(FlowInfo otherInits); - abstract public FlowInfo addPotentialInitializationsFrom(FlowInfo otherInits); +/** + * Add other inits to this flow info, then return this. The operation semantics + * are to match as closely as possible the application to this flow info of all + * the operations that resulted into otherInits. + * @param otherInits other inits to add to this + * @return this, modified according to otherInits information + */ +abstract public FlowInfo addInitializationsFrom(FlowInfo otherInits); + + +/** + * Compose other inits over this flow info, then return this. The operation + * semantics are to wave into this flow info the consequences of a possible + * path into the operations that resulted into otherInits. The fact that this + * path may be left unexecuted under peculiar conditions results into less + * specific results than {@link #addInitializationsFrom(FlowInfo) + * addInitializationsFrom}. + * @param otherInits other inits to compose over this + * @return this, modified according to otherInits information + */ +abstract public FlowInfo addPotentialInitializationsFrom(FlowInfo otherInits); public FlowInfo asNegatedCondition() { @@ -42,6 +63,10 @@ return new ConditionalFlowInfo(initsWhenTrue, initsWhenFalse); } +/** + * Return a deep copy of the current instance. + * @return a deep copy of this flow info + */ abstract public FlowInfo copy(); public static UnconditionalFlowInfo initial(int maxFieldCount) { @@ -49,9 +74,27 @@ info.maxFieldCount = maxFieldCount; return info; } - - abstract public FlowInfo initsWhenFalse(); - + +/** + * Return the flow info that would result from the path associated to the + * value false for the condition expression that generated this flow info. + * May be this flow info if it is not an instance of {@link + * ConditionalFlowInfo}. May have a side effect on subparts of this flow + * info (subtrees get merged). + * @return the flow info associated to the false branch of the condition + * that generated this flow info + */ +abstract public FlowInfo initsWhenFalse(); + +/** + * Return the flow info that would result from the path associated to the + * value true for the condition expression that generated this flow info. + * May be this flow info if it is not an instance of {@link + * ConditionalFlowInfo}. May have a side effect on subparts of this flow + * info (subtrees get merged). + * @return the flow info associated to the true branch of the condition + * that generated this flow info + */ abstract public FlowInfo initsWhenTrue(); /** @@ -64,25 +107,26 @@ */ public abstract boolean isDefinitelyAssigned(LocalVariableBinding local); - /** - * Check status of definite null assignment for a field. - */ - abstract public boolean isDefinitelyNonNull(FieldBinding field); - - /** - * Check status of definite null assignment for a local. - */ +/** + * Check status of definite non-null value for a given local variable. + * @param local the variable to ckeck + * @return true iff local is definitely non null for this flow info + */ public abstract boolean isDefinitelyNonNull(LocalVariableBinding local); - /** - * Check status of definite null assignment for a field. - */ - abstract public boolean isDefinitelyNull(FieldBinding field); - - /** - * Check status of definite null assignment for a local. - */ - public abstract boolean isDefinitelyNull(LocalVariableBinding local); +/** + * Check status of definite null value for a given local variable. + * @param local the variable to ckeck + * @return true iff local is definitely null for this flow info + */ +public abstract boolean isDefinitelyNull(LocalVariableBinding local); + +/** + * Check status of definite unknown value for a given local variable. + * @param local the variable to ckeck + * @return true iff local is definitely unknown for this flow info + */ +public abstract boolean isDefinitelyUnknown(LocalVariableBinding local); /** * Check status of potential assignment for a field. @@ -95,7 +139,48 @@ abstract public boolean isPotentiallyAssigned(LocalVariableBinding field); - abstract public boolean isReachable(); +/** + * Check status of potential null assignment for a local. + */ +public abstract boolean isPotentiallyNull(LocalVariableBinding local); + +/** + * Return true if the given local may have been assigned to an unknown value. + * @param local the local to check + * @return true if the given local may have been assigned to an unknown value + */ +public abstract boolean isPotentiallyUnknown(LocalVariableBinding local); + +/** + * Return true if the given local is protected by a test against a non null + * value. + * @param local the local to check + * @return true if the given local is protected by a test against a non null + */ +public abstract boolean isProtectedNonNull(LocalVariableBinding local); + +/** + * Return true if the given local is protected by a test against null. + * @param local the local to check + * @return true if the given local is protected by a test against null + */ +public abstract boolean isProtectedNull(LocalVariableBinding local); + +public boolean isReachable() { + return (this.tagBits & UNREACHABLE) == 0; +} + +/** + * Record that a local variable got checked to be non null. + * @param local the checked local variable + */ +abstract public void markAsComparedEqualToNonNull(LocalVariableBinding local); + +/** + * Record that a local variable got checked to be null. + * @param local the checked local variable + */ +abstract public void markAsComparedEqualToNull(LocalVariableBinding local); /** * Record a field got definitely assigned. @@ -118,7 +203,7 @@ abstract public void markAsDefinitelyNull(LocalVariableBinding local); /** - * Record a field got definitely assigned. + * Record a field got definitely assigned to null. */ abstract public void markAsDefinitelyNull(FieldBinding field); @@ -127,52 +212,99 @@ */ abstract public void markAsDefinitelyAssigned(LocalVariableBinding local); - /** - * Clear the initialization info for a field - */ - abstract public void markAsDefinitelyNotAssigned(FieldBinding field); - - /** - * Clear the initialization info for a local variable - */ - abstract public void markAsDefinitelyNotAssigned(LocalVariableBinding local); - - /** - * Merge branches using optimized boolean conditions - */ - public static FlowInfo mergedOptimizedBranches(FlowInfo initsWhenTrue, boolean isOptimizedTrue, FlowInfo initsWhenFalse, boolean isOptimizedFalse, boolean allowFakeDeadBranch) { - FlowInfo mergedInfo; - if (isOptimizedTrue){ - if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) { - mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE); - } else { - mergedInfo = initsWhenTrue.addPotentialInitializationsFrom(initsWhenFalse); - } - - } else if (isOptimizedFalse) { - if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) { - mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE); - } else { - mergedInfo = initsWhenFalse.addPotentialInitializationsFrom(initsWhenTrue); - } - - } else { - mergedInfo = initsWhenTrue.unconditionalInits().mergedWith(initsWhenFalse.unconditionalInits()); +/** + * Record a local got definitely assigned to an unknown value. + */ +abstract public void markAsDefinitelyUnknown(LocalVariableBinding local); + +/** + * Merge branches using optimized boolean conditions + */ +public static UnconditionalFlowInfo mergedOptimizedBranches( + FlowInfo initsWhenTrue, boolean isOptimizedTrue, + FlowInfo initsWhenFalse, boolean isOptimizedFalse, + boolean allowFakeDeadBranch) { + UnconditionalFlowInfo mergedInfo; + if (isOptimizedTrue){ + if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) { + mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE). + unconditionalInits(); + } + else { + mergedInfo = + initsWhenTrue.addPotentialInitializationsFrom(initsWhenFalse. + nullInfoLessUnconditionalCopy()). + unconditionalInits(); + } + } + else if (isOptimizedFalse) { + if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) { + mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE). + unconditionalInits(); + } + else { + mergedInfo = + initsWhenFalse.addPotentialInitializationsFrom(initsWhenTrue. + nullInfoLessUnconditionalCopy()). + unconditionalInits(); } - return mergedInfo; + } + else { + mergedInfo = initsWhenTrue. + mergedWith(initsWhenFalse.unconditionalInits()); } + return mergedInfo; +} - abstract public int reachMode(); - - abstract public FlowInfo setReachMode(int reachMode); - - /** - * Returns the receiver updated in the following way: - */ - abstract public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits); +/** + * Return REACHABLE if this flow info is reachable, UNREACHABLE + * else. + * @return REACHABLE if this flow info is reachable, UNREACHABLE + * else + */ +public int reachMode() { + return this.tagBits & UNREACHABLE; +} + +/** + * Return a flow info that carries the same information as the result of + * {@link #initsWhenTrue() initsWhenTrue}, but warrantied to be different + * from this.
+ * Caveat: side effects on the result may affect components of this. + * @return the result of initsWhenTrue or a copy of it + */ +abstract public FlowInfo safeInitsWhenTrue(); + +/** + * Set this flow info reach mode and return this. + * @param reachMode one of {@link #REACHABLE REACHABLE} or {@link #UNREACHABLE UNREACHABLE} + * @return this, with the reach mode set to reachMode + */ +abstract public FlowInfo setReachMode(int reachMode); + +/** + * Return the intersection of this and otherInits, that is + * one of: + * otherInits is not affected, and is not returned either (no + * need to protect the result). + * @param otherInits the flow info to merge with this + * @return the intersection of this and otherInits. + */ +abstract public UnconditionalFlowInfo mergedWith( + UnconditionalFlowInfo otherInits); + +/** + * Return a copy of this unconditional flow info, deprived from its null + * info. {@link #DEAD_END DEAD_END} is returned unmodified. + * @return a copy of this unconditional flow info deprived from its null info + */ +abstract public UnconditionalFlowInfo nullInfoLessUnconditionalCopy(); public String toString(){ @@ -182,5 +314,38 @@ return super.toString(); } - abstract public UnconditionalFlowInfo unconditionalInits(); +/** + * Return a new flow info that holds the same information as this would after + * a call to unconditionalInits, but leaving this info unaffected. Moreover, + * the result can be modified without affecting this. + * @return a new flow info carrying this unconditional flow info + */ +abstract public UnconditionalFlowInfo unconditionalCopy(); + +/** + * Return a new flow info that holds the same information as this would after + * a call to {@link #unconditionalInits() unconditionalInits} followed by the + * erasure of fields specific information, but leaving this flow info unaffected. + * @return a new flow info carrying the unconditional flow info for local variables + */ +abstract public UnconditionalFlowInfo unconditionalFieldLessCopy(); + +/** + * Return a flow info that merges the possible paths of execution described by + * this flow info. In case of an unconditional flow info, return this. In case + * of a conditional flow info, merge branches recursively. Caveat: this may + * be affected, and modifying the result may affect this. + * @return a flow info that merges the possible paths of execution described by + * this + */ +abstract public UnconditionalFlowInfo unconditionalInits(); + +/** + * Return a new flow info that holds the same information as this would after + * a call to {@link #unconditionalInits() unconditionalInits}, but leaving + * this info unaffected. Side effects on the result might affect this though + * (consider it as read only). + * @return a flow info carrying this unconditional flow info + */ +abstract public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect(); } Index: compiler/org/eclipse/jdt/internal/compiler/flow/InsideSubRoutineFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/InsideSubRoutineFlowContext.java,v retrieving revision 1.15 diff -u -r1.15 InsideSubRoutineFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/InsideSubRoutineFlowContext.java 23 Feb 2005 02:47:29 -0000 1.15 +++ compiler/org/eclipse/jdt/internal/compiler/flow/InsideSubRoutineFlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -47,13 +47,14 @@ return (SubRoutineStatement)associatedNode; } - public void recordReturnFrom(FlowInfo flowInfo) { - - if (!flowInfo.isReachable()) return; - if (initsOnReturn == FlowInfo.DEAD_END) { - initsOnReturn = flowInfo.copy().unconditionalInits(); - } else { - initsOnReturn = initsOnReturn.mergedWith(flowInfo.copy().unconditionalInits()); - } +public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { + if (!flowInfo.isReachable()) { + return; } + if (initsOnReturn == FlowInfo.DEAD_END) { + initsOnReturn = (UnconditionalFlowInfo) flowInfo.copy(); + } else { + initsOnReturn = initsOnReturn.mergedWith(flowInfo); + } +} } Index: compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java,v retrieving revision 1.30 diff -u -r1.30 LoopingFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java 23 Feb 2005 02:47:29 -0000 1.30 +++ compiler/org/eclipse/jdt/internal/compiler/flow/LoopingFlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -28,82 +28,165 @@ public Label continueLabel; public UnconditionalFlowInfo initsOnContinue = FlowInfo.DEAD_END; + private UnconditionalFlowInfo upstreamNullFlowInfo; + private LoopingFlowContext innerFlowContexts[] = null; + private UnconditionalFlowInfo innerFlowInfos[] = null; + private int innerFlowContextsNb = 0; + Reference finalAssignments[]; VariableBinding finalVariables[]; int assignCount = 0; + LocalVariableBinding[] nullLocals; Expression[] nullReferences; - int[] nullStatus; + int[] nullCheckTypes; int nullCount; Scope associatedScope; public LoopingFlowContext( FlowContext parent, + FlowInfo upstreamNullFlowInfo, ASTNode associatedNode, Label breakLabel, Label continueLabel, Scope associatedScope) { super(parent, associatedNode, breakLabel); + preemptNullDiagnostic = true; + // children will defer to this, which may defer to its own parent this.continueLabel = continueLabel; this.associatedScope = associatedScope; + this.upstreamNullFlowInfo = upstreamNullFlowInfo.unconditionalCopy(); } - - public void complainOnDeferredChecks(BlockScope scope, FlowInfo flowInfo) { - - // complain on final assignments in loops - for (int i = 0; i < assignCount; i++) { - VariableBinding variable = finalVariables[i]; - if (variable == null) continue; - boolean complained = false; // remember if have complained on this final assignment - if (variable instanceof FieldBinding) { - if (flowInfo.isPotentiallyAssigned((FieldBinding) variable)) { - complained = true; - scope.problemReporter().duplicateInitializationOfBlankFinalField( - (FieldBinding) variable, - finalAssignments[i]); - } - } else { - if (flowInfo.isPotentiallyAssigned((LocalVariableBinding) variable)) { - complained = true; - scope.problemReporter().duplicateInitializationOfFinalLocal( - (LocalVariableBinding) variable, - finalAssignments[i]); - } + +/** + * Perform deferred checks relative to final variables duplicate initialization + * of lack of initialization. + * @param scope the scope to which this context is associated + * @param flowInfo the flow info against which checks must be performed + */ +public void complainOnDeferredFinalChecks(BlockScope scope, FlowInfo flowInfo) { + // complain on final assignments in loops + for (int i = 0; i < assignCount; i++) { + VariableBinding variable = finalVariables[i]; + if (variable == null) continue; + boolean complained = false; // remember if have complained on this final assignment + if (variable instanceof FieldBinding) { + if (flowInfo.isPotentiallyAssigned((FieldBinding) variable)) { + complained = true; + scope.problemReporter().duplicateInitializationOfBlankFinalField( + (FieldBinding) variable, + finalAssignments[i]); } - // any reference reported at this level is removed from the parent context where it - // could also be reported again - if (complained) { - FlowContext context = parent; - while (context != null) { - context.removeFinalAssignmentIfAny(finalAssignments[i]); - context = context.parent; - } + } else { + if (flowInfo.isPotentiallyAssigned((LocalVariableBinding) variable)) { + complained = true; + scope.problemReporter().duplicateInitializationOfFinalLocal( + (LocalVariableBinding) variable, + finalAssignments[i]); } } - // check inconsistent null checks - for (int i = 0; i < nullCount; i++) { - Expression expression = nullReferences[i]; - if (expression == null) continue; + // any reference reported at this level is removed from the parent context where it + // could also be reported again + if (complained) { + FlowContext context = parent; + while (context != null) { + context.removeFinalAssignmentIfAny(finalAssignments[i]); + context = context.parent; + } + } + } +} + +/** + * Perform deferred checks relative to the null status of local variables. + * @param scope the scope to which this context is associated + * @param flowInfo the flow info against which checks must be performed + */ +public void complainOnDeferredNullChecks(BlockScope scope, FlowInfo flowInfo) { + for (int i = 0 ; i < this.innerFlowContextsNb ; i++) { + this.upstreamNullFlowInfo. + addPotentialNullInfoFrom( + this.innerFlowContexts[i].upstreamNullFlowInfo). + addPotentialNullInfoFrom(this.innerFlowInfos[i]); + } + this.innerFlowContextsNb = 0; + flowInfo = this.upstreamNullFlowInfo. + addPotentialNullInfoFrom( + flowInfo.unconditionalInitsWithoutSideEffect()); + if (this.deferNullDiagnostic) { + // check only immutable null checks on innermost looping context + for (int i = 0; i < this.nullCount; i++) { + LocalVariableBinding local = this.nullLocals[i]; + Expression expression = this.nullReferences[i]; // final local variable - LocalVariableBinding local = expression.localVariableBinding(); - switch (nullStatus[i]) { - case FlowInfo.NULL : + switch (this.nullCheckTypes[i]) { + case CAN_ONLY_NULL_NON_NULL : + if (flowInfo.isDefinitelyNonNull(local)) { + this.nullReferences[i] = null; + scope.problemReporter().localVariableCannotBeNull(local, expression); + continue; + } + case CAN_ONLY_NULL : if (flowInfo.isDefinitelyNull(local)) { - nullReferences[i] = null; - this.parent.recordUsingNullReference(scope, local, expression, nullStatus[i], flowInfo); + this.nullReferences[i] = null; + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + continue; } break; - case FlowInfo.NON_NULL : + case MAY_NULL: + if (flowInfo.isDefinitelyNull(local)) { + this.nullReferences[i] = null; + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + continue; + } + break; + default: + // never happens + } + this.parent.recordUsingNullReference(scope, local, expression, + this.nullCheckTypes[i], flowInfo); + } + } + else { + // check inconsistent null checks on outermost looping context + for (int i = 0; i < this.nullCount; i++) { + Expression expression = this.nullReferences[i]; + // final local variable + LocalVariableBinding local = this.nullLocals[i]; + switch (this.nullCheckTypes[i]) { + case CAN_ONLY_NULL_NON_NULL : if (flowInfo.isDefinitelyNonNull(local)) { - nullReferences[i] = null; - this.parent.recordUsingNullReference(scope, local, expression, nullStatus[i], flowInfo); + this.nullReferences[i] = null; + scope.problemReporter().localVariableCannotBeNull(local, expression); + continue; + } + case CAN_ONLY_NULL : + if (flowInfo.isDefinitelyNull(local)) { + this.nullReferences[i] = null; + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + continue; } break; + case MAY_NULL: + if (flowInfo.isDefinitelyNull(local)) { + this.nullReferences[i] = null; + scope.problemReporter().localVariableCanOnlyBeNull(local, expression); + continue; + } + if (flowInfo.isPotentiallyNull(local)) { + this.nullReferences[i] = null; + scope.problemReporter().localVariableMayBeNull(local, expression); + continue; + } + break; + default: + // never happens } - } + } } - +} + public Label continueLabel() { return continueLabel; } @@ -125,15 +208,46 @@ return initsOnContinue != FlowInfo.DEAD_END; } - public void recordContinueFrom(FlowInfo flowInfo) { - - if (!flowInfo.isReachable()) return; - if (initsOnContinue == FlowInfo.DEAD_END) { - initsOnContinue = flowInfo.copy().unconditionalInits(); - } else { - initsOnContinue = initsOnContinue.mergedWith(flowInfo.copy().unconditionalInits()); +public void recordContinueFrom(FlowContext innerFlowContext, FlowInfo flowInfo) { + if (!flowInfo.isReachable()) { + return; + } + if (initsOnContinue.isReachable()) { + initsOnContinue = initsOnContinue. + mergedWith(flowInfo.unconditionalInitsWithoutSideEffect()); + } + else { + initsOnContinue = flowInfo.unconditionalCopy(); + } + FlowContext inner = innerFlowContext; + while (inner != this && !(inner instanceof LoopingFlowContext)) { + inner = inner.parent; + } + if (inner == this) { + this.upstreamNullFlowInfo. + addPotentialNullInfoFrom( + flowInfo.unconditionalInitsWithoutSideEffect()); + } + else { + int length = 0; + if (this.innerFlowContexts == null) { + this.innerFlowContexts = new LoopingFlowContext[5]; + this.innerFlowInfos = new UnconditionalFlowInfo[5]; } + else if (this.innerFlowContextsNb == + (length = this.innerFlowContexts.length) - 1) { + System.arraycopy(this.innerFlowContexts, 0, + (this.innerFlowContexts = new LoopingFlowContext[length + 5]), + 0, length); + System.arraycopy(this.innerFlowInfos, 0, + (this.innerFlowInfos= new UnconditionalFlowInfo[length + 5]), + 0, length); + } + this.innerFlowContexts[this.innerFlowContextsNb] = (LoopingFlowContext) inner; + this.innerFlowInfos[this.innerFlowContextsNb++] = + flowInfo.unconditionalInitsWithoutSideEffect(); } +} protected boolean recordFinalAssignment( VariableBinding binding, @@ -170,20 +284,67 @@ return true; } - protected boolean recordNullReference(Expression expression, int status) { - if (nullCount == 0) { - nullReferences = new Expression[5]; - nullStatus = new int[5]; - } else { - if (nullCount == nullReferences.length) { - System.arraycopy(nullReferences, 0, nullReferences = new Expression[nullCount * 2], 0, nullCount); - System.arraycopy(nullStatus, 0, nullStatus = new int[nullCount * 2], 0, nullCount); +protected void recordNullReference(LocalVariableBinding local, + Expression expression, int status) { + if (nullCount == 0) { + nullLocals = new LocalVariableBinding[5]; + nullReferences = new Expression[5]; + nullCheckTypes = new int[5]; + } + else if (nullCount == nullLocals.length) { + System.arraycopy(nullLocals, 0, + nullLocals = new LocalVariableBinding[nullCount * 2], 0, nullCount); + System.arraycopy(nullReferences, 0, + nullReferences = new Expression[nullCount * 2], 0, nullCount); + System.arraycopy(nullCheckTypes, 0, + nullCheckTypes = new int[nullCount * 2], 0, nullCount); + } + nullLocals[nullCount] = local; + nullReferences[nullCount] = expression; + nullCheckTypes[nullCount++] = status; +} + +public void recordUsingNullReference(Scope scope, LocalVariableBinding local, + Expression reference, int checkType, FlowInfo flowInfo) { + if (!flowInfo.isReachable() || flowInfo.isDefinitelyUnknown(local)) { + return; + } + switch (checkType) { + case CAN_ONLY_NULL_NON_NULL : + case CAN_ONLY_NULL: + if (flowInfo.isDefinitelyNonNull(local)) { + if (checkType == CAN_ONLY_NULL_NON_NULL) { + scope.problemReporter().localVariableCannotBeNull(local, reference); + } + return; } - } - nullReferences[nullCount] = expression; - nullStatus[nullCount++] = status; - return true; - } + if (flowInfo.isDefinitelyNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + if (flowInfo.isPotentiallyUnknown(local)) { + return; + } + recordNullReference(local, reference, checkType); + return; + case MAY_NULL : + if (flowInfo.isDefinitelyNonNull(local)) { + return; + } + if (flowInfo.isDefinitelyNull(local)) { + scope.problemReporter().localVariableCanOnlyBeNull(local, reference); + return; + } + if (flowInfo.isPotentiallyNull(local)) { + scope.problemReporter().localVariableMayBeNull(local, reference); + return; + } + recordNullReference(local, reference, checkType); + return; + default: + // never happens + } +} void removeFinalAssignmentIfAny(Reference reference) { for (int i = 0; i < assignCount; i++) { Index: compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java,v retrieving revision 1.26 diff -u -r1.26 SwitchFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java 14 Oct 2005 22:43:00 -0000 1.26 +++ compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java 28 Nov 2005 12:08:07 -0000 @@ -42,10 +42,11 @@ } public void recordBreakFrom(FlowInfo flowInfo) { - if (initsOnBreak == FlowInfo.DEAD_END) { - initsOnBreak = flowInfo.copy().unconditionalInits(); - } else { - initsOnBreak = initsOnBreak.mergedWith(flowInfo.copy().unconditionalInits()); + if (initsOnBreak.isReachable()) { + initsOnBreak = initsOnBreak.mergedWith(flowInfo.unconditionalInits()); + } + else { + initsOnBreak = flowInfo.unconditionalCopy(); } } } Index: compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java,v retrieving revision 1.44 diff -u -r1.44 UnconditionalFlowInfo.java --- compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java 18 Nov 2005 16:46:23 -0000 1.44 +++ compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java 28 Nov 2005 12:08:08 -0000 @@ -14,6 +14,8 @@ import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TagBits; + /** * Record initialization status during definite assignment analysis @@ -22,807 +24,1524 @@ */ public class UnconditionalFlowInfo extends FlowInfo { - public long definiteInits; public long potentialInits; - public long extraDefiniteInits[]; - public long extraPotentialInits[]; - public long definiteNulls; - public long definiteNonNulls; - public long extraDefiniteNulls[]; - public long extraDefiniteNonNulls[]; - - public int reachMode; // by default + public long nullAssignmentStatusBit1; + public long nullAssignmentStatusBit2; + // 0 0 is potential (bit 1 is leftmost here) + // 1 0 is assigned + // 0 1 is protected null (aka if (o == null) { // here o protected null...) + // 1 1 is protected non null + public long nullAssignmentValueBit1; + public long nullAssignmentValueBit2; + // information only relevant for potential and assigned + // 0 0 is start -- nothing known at all + // 0 1 is assigned non null or potential anything but null + // 1 0 is assigned null or potential null + // 1 1 is potential null and potential anything but null or definite unknown + + public static final int extraLength = 6; + public long extra[][]; + // extra bit fields for larger numbers of fields/variables + // extra[0] holds definiteInits values, extra[1] potentialInits, etc. + // lifecycle is extra == null or else all extra[]'s are allocated + // arrays which have the same size - public int maxFieldCount; + public int maxFieldCount; // limit between fields and locals // Constants public static final int BitCacheSize = 64; // 64 bits in a long. - UnconditionalFlowInfo() { - this.reachMode = REACHABLE; - } - - // unions of both sets of initialization - used for try/finally - public FlowInfo addInitializationsFrom(FlowInfo inits) { +public FlowInfo addInitializationsFrom(FlowInfo inits) { + if (this == DEAD_END) + return this; + if (inits == DEAD_END) + return this; + UnconditionalFlowInfo otherInits = inits.unconditionalInits(); - if (this == DEAD_END) - return this; - - UnconditionalFlowInfo otherInits = inits.unconditionalInits(); - if (otherInits == DEAD_END) - return this; - - // union of definitely assigned variables, - definiteInits |= otherInits.definiteInits; - // union of potentially set ones - potentialInits |= otherInits.potentialInits; - - // union of definitely null variables, - definiteNulls = (definiteNulls | otherInits.definiteNulls) & ~otherInits.definiteNonNulls; - // union of definitely non null variables, - definiteNonNulls = (definiteNonNulls | otherInits.definiteNonNulls) & ~otherInits.definiteNulls; - // fix-up null/non-null infos since cannot overlap: + --> - - // treating extra storage - if (extraDefiniteInits != null) { - if (otherInits.extraDefiniteInits != null) { + // union of definitely assigned variables, + this.definiteInits |= otherInits.definiteInits; + // union of potentially set ones + this.potentialInits |= otherInits.potentialInits; + // combine null information + // note: we may have both forms of protection (null and non null) + // coming with otherInits, because of loops + boolean considerNulls = (otherInits.tagBits & NULL_FLAG_MASK) != 0; + long a1, na1, a2, na2, a3, a4, na4, b1, b2, nb2, b3, nb3, b4, nb4; + if (considerNulls) { + if ((this.tagBits & NULL_FLAG_MASK) == 0) { + this.nullAssignmentStatusBit1 = otherInits.nullAssignmentStatusBit1; + this.nullAssignmentStatusBit2 = otherInits.nullAssignmentStatusBit2; + this.nullAssignmentValueBit1 = otherInits.nullAssignmentValueBit1; + this.nullAssignmentValueBit2 = otherInits.nullAssignmentValueBit2; + } + else { + this.nullAssignmentStatusBit1 = + (b1 = otherInits.nullAssignmentStatusBit1) | + (a1 = this.nullAssignmentStatusBit1) & + ((nb2 = ~(b2 = otherInits.nullAssignmentStatusBit2)) & + (nb3 = ~(b3 = otherInits.nullAssignmentValueBit1)) & + ((nb4 = ~(b4 = otherInits.nullAssignmentValueBit2)) | + ((a2 = this.nullAssignmentStatusBit2) ^ + (a4 = this.nullAssignmentValueBit2))) | + nb4 & (na2 = ~a2) & (na4 = ~a4)); + this.nullAssignmentStatusBit2 = + b1 & b2 | + ~b1 & (((na1 = ~a1) | a4) & b2 | + a2 & (b2 | + a1 & (na4 = ~a4) & nb2 & nb3 | + (~(a3 = this.nullAssignmentValueBit1) & nb3 | na1 & na4) & nb4)); + this.nullAssignmentValueBit1 = + nb2 & b3 | + ~b1 & ((a1 & na2 & na4 | na1 & a3) & (nb2 | nb4) | + a1 & na2 & a3 & nb2 | + (a1 | a2 | na4) & b3); + this.nullAssignmentValueBit2 = + b4 | + a4 & (nb2 & nb3 | ~(b1 ^ b2)); + } + this.tagBits |= NULL_FLAG_MASK; // in all cases - avoid forgetting extras + } + // treating extra storage + if (this.extra != null || otherInits.extra != null) { + int mergeLimit = 0, copyLimit = 0; + if (this.extra != null) { + if (otherInits.extra != null) { // both sides have extra storage - int i = 0, length, otherLength; - if ((length = extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { + int length, otherLength; + if ((length = this.extra[0].length) < + (otherLength = otherInits.extra[0].length)) { // current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) - System.arraycopy(extraDefiniteInits, 0, (extraDefiniteInits = new long[otherLength]), 0, length); - System.arraycopy(extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, length); - System.arraycopy(extraDefiniteNulls, 0, (extraDefiniteNulls = new long[otherLength]), 0, length); - System.arraycopy(extraDefiniteNonNulls, 0, (extraDefiniteNonNulls = new long[otherLength]), 0, length); - for (; i < length; i++) { - extraDefiniteInits[i] |= otherInits.extraDefiniteInits[i]; - extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - extraDefiniteNulls[i] = (extraDefiniteNulls[i] | otherInits.extraDefiniteNulls[i]) & ~otherInits.extraDefiniteNonNulls[i]; - extraDefiniteNonNulls[i] = (extraDefiniteNonNulls[i] | otherInits.extraDefiniteNonNulls[i]) & ~otherInits.extraDefiniteNulls[i]; - } - for (; i < otherLength; i++) { - extraPotentialInits[i] = otherInits.extraPotentialInits[i]; + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[otherLength]), 0, length); } + mergeLimit = length; + copyLimit = otherLength; } else { // current storage is longer - for (; i < otherLength; i++) { - extraDefiniteInits[i] |= otherInits.extraDefiniteInits[i]; - extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - extraDefiniteNulls[i] = (extraDefiniteNulls[i] | otherInits.extraDefiniteNulls[i]) & ~otherInits.extraDefiniteNonNulls[i]; - extraDefiniteNonNulls[i] = (extraDefiniteNonNulls[i] | otherInits.extraDefiniteNonNulls[i]) & ~otherInits.extraDefiniteNulls[i]; - } - for (; i < length; i++) { - extraDefiniteInits[i] = 0; - extraDefiniteNulls[i] = 0; - extraDefiniteNonNulls[i] = 0; + mergeLimit = otherLength; + } + } + } + else if (otherInits.extra != null) { + // no storage here, but other has extra storage. + // shortcut regular copy because array copy is better + int otherLength; + this.extra = new long[extraLength][]; + System.arraycopy(otherInits.extra[0], 0, + (this.extra[0] = new long[otherLength = + otherInits.extra[0].length]), 0, otherLength); + System.arraycopy(otherInits.extra[1], 0, + (this.extra[1] = new long[otherLength]), 0, otherLength); + if (considerNulls) { + for (int j = 2; j < extraLength; j++) { + System.arraycopy(otherInits.extra[j], 0, + (this.extra[j] = new long[otherLength]), 0, otherLength); + } + } + else { + for (int j = 2; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; + } + } + } + int i = 0; + for (; i < mergeLimit; i++) { + this.extra[0][i] |= otherInits.extra[0][i]; + this.extra[1][i] |= otherInits.extra[1][i]; + if (considerNulls) { // could consider pushing the test outside the loop + if (this.extra[2][i] == 0 && + this.extra[3][i] == 0 && + this.extra[4][i] == 0 && + this.extra[5][i] == 0) { + for (int j = 2; j < extraLength; j++) { + this.extra[j][i] = otherInits.extra[j][i]; } } - } else { - // no extra storage on otherInits + else { + this.extra[2][i] = + (b1 = otherInits.extra[2][i]) | + (a1 = this.extra[2][i]) & + ((nb2 = ~(b2 = otherInits.extra[3][i])) & + (nb3 = ~(b3 = otherInits.extra[4][i])) & + ((nb4 = ~(b4 = otherInits.extra[5][i])) | + ((a2 = this.extra[3][i]) ^ + (a4 = this.extra[5][i]))) | + nb4 & (na2 = ~a2) & (na4 = ~a4)); + this.extra[3][i] = + b1 & b2 | + ~b1 & (((na1 = ~a1) | a4) & b2 | + a2 & (b2 | + a1 & (na4 = ~a4) & nb2 & nb3 | + (~(a3 = this.extra[4][i]) & nb3 | na1 & na4) & nb4)); + this.extra[4][i] = + nb2 & b3 | + ~b1 & ((a1 & na2 & na4 | na1 & a3) & (nb2 | nb4) | + a1 & na2 & a3 & nb2 | + (a1 | a2 | na4) & b3); + this.extra[5][i] = + b4 | + a4 & (nb2 & nb3 | ~(b1 ^ b2)); + } } - } else - if (otherInits.extraDefiniteInits != null) { - // no storage here, but other has extra storage. - int otherLength; - System.arraycopy(otherInits.extraDefiniteInits, 0, (extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]), 0, otherLength); - System.arraycopy(otherInits.extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, otherLength); - System.arraycopy(otherInits.extraDefiniteNulls, 0, (extraDefiniteNulls = new long[otherLength]), 0, otherLength); - System.arraycopy(otherInits.extraDefiniteNonNulls, 0, (extraDefiniteNonNulls = new long[otherLength]), 0, otherLength); + } + for (; i < copyLimit; i++) { + this.extra[0][i] = otherInits.extra[0][i]; + this.extra[1][i] = otherInits.extra[1][i]; + if (considerNulls) { + for (int j = 2; j < extraLength; j++) { + this.extra[j][i] = otherInits.extra[j][i]; + } } - return this; + } } + return this; +} - // unions of both sets of initialization - used for try/finally - public FlowInfo addPotentialInitializationsFrom(FlowInfo inits) { - - if (this == DEAD_END){ - return this; +public FlowInfo addPotentialInitializationsFrom(FlowInfo inits) { + if (this == DEAD_END){ + return this; + } + if (inits == DEAD_END){ + return this; + } + UnconditionalFlowInfo otherInits = inits.unconditionalInits(); + // union of potentially set ones + this.potentialInits |= otherInits.potentialInits; + // treating extra storage + if (this.extra != null) { + if (otherInits.extra != null) { + // both sides have extra storage + int i = 0, length, otherLength; + if ((length = this.extra[0].length) < (otherLength = otherInits.extra[0].length)) { + // current storage is shorter -> grow current + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[otherLength]), 0, length); + } + for (; i < length; i++) { + this.extra[1][i] |= otherInits.extra[1][i]; + } + for (; i < otherLength; i++) { + this.extra[1][i] = otherInits.extra[1][i]; + } + } + else { + // current storage is longer + for (; i < otherLength; i++) { + this.extra[1][i] |= otherInits.extra[1][i]; + } + } + } + } + else if (otherInits.extra != null) { + // no storage here, but other has extra storage. + int otherLength = otherInits.extra[0].length; + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; } + System.arraycopy(otherInits.extra[1], 0, this.extra[1], 0, + otherLength); + } + this.addPotentialNullInfoFrom(otherInits); + return this; +} - UnconditionalFlowInfo otherInits = inits.unconditionalInits(); - if (otherInits == DEAD_END){ - return this; - } - // union of potentially set ones - this.potentialInits |= otherInits.potentialInits; - // also merge null check information (affected by potential inits) - this.definiteNulls &= otherInits.definiteNulls; - this.definiteNonNulls &= otherInits.definiteNonNulls; - - // treating extra storage - if (this.extraDefiniteInits != null) { - if (otherInits.extraDefiniteInits != null) { - // both sides have extra storage - int i = 0, length, otherLength; - if ((length = this.extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { - // current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) - System.arraycopy(this.extraDefiniteInits, 0, (this.extraDefiniteInits = new long[otherLength]), 0, length); - System.arraycopy(this.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, length); - System.arraycopy(this.extraDefiniteNulls, 0, (this.extraDefiniteNulls = new long[otherLength]), 0, length); - System.arraycopy(this.extraDefiniteNonNulls, 0, (this.extraDefiniteNonNulls = new long[otherLength]), 0, length); - while (i < length) { - this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - this.extraDefiniteNulls[i] &= otherInits.extraDefiniteNulls[i]; - this.extraDefiniteNonNulls[i] &= otherInits.extraDefiniteNonNulls[i++]; +/** + * Compose other inits over this flow info, then return this. The operation + * semantics are to wave into this flow info the consequences upon null + * information of a possible path into the operations that resulted into + * otherInits. The fact that this path may be left unexecuted under peculiar + * conditions results into less specific results than + * {@link #addInitializationsFrom(FlowInfo) addInitializationsFrom}; moreover, + * only the null information is affected. + * @param otherInits other null inits to compose over this + * @return this, modified according to otherInits information + */ +public UnconditionalFlowInfo addPotentialNullInfoFrom( + UnconditionalFlowInfo otherInits) { + if ((this.tagBits & UNREACHABLE) != 0 || + (otherInits.tagBits & UNREACHABLE) != 0 || + (otherInits.tagBits & NULL_FLAG_MASK) == 0) { + return this; + } + // if we get here, otherInits has some null info + boolean thisHasNulls = (this.tagBits & NULL_FLAG_MASK) != 0; + if (thisHasNulls) { + long a1, a2, na2, a3, na3, a4, na4, b1, nb1, b2, nb2, b3, nb3, b4, nb4; + this.nullAssignmentStatusBit1 = + ((a1 = this.nullAssignmentStatusBit1) & + (na4 = ~(a4 = this.nullAssignmentValueBit2)) & + ((na3 = ~(a3 = this.nullAssignmentValueBit1)) | + (a2 = this.nullAssignmentStatusBit2)) | + a2 & na3 & a4) & + (nb3 = ~(b3 = otherInits.nullAssignmentValueBit1)) & + ((b2 = otherInits.nullAssignmentStatusBit2) | + (nb4 = ~(b4 = otherInits.nullAssignmentValueBit2))) | + a1 & (na2 = ~a2) & + (a4 & ((nb1 = ~(b1 = otherInits.nullAssignmentStatusBit1)) & + nb3 | b1 & + (b4 | b2)) | + na4 & (nb1 & (((nb2 = ~b2) & nb4 | b2) & nb3 | b3 & nb4) | + b1 & nb4 & (nb2 | nb3))); + this.nullAssignmentStatusBit2 = + a2 & (~a1 & na4 & nb4 | + a1 & na3 & nb3 & (nb1 & (nb2 & nb4 | b2) | + b1 & (nb4 |b2 & b4))); + this.nullAssignmentValueBit1 = + a3 | + b1 & nb2 & nb4 | + nb1 & b3 | + a1 & na2 & (b1 & b3 | nb1 & b4); +// b1 & (~b2 & ~b4 | a1 & ~a2 & b3) | +// ~b1 & (b3 | a1 & ~a2 & b4); -- same op nb + this.nullAssignmentValueBit2 = + a4 & (na2 | a2 & na3) | + b4 & (nb2 | b2 & nb3); + // extra storage management + if (otherInits.extra != null) { + int mergeLimit = 0, copyLimit = 0; + int otherLength = otherInits.extra[0].length; + if (this.extra == null) { + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; + } + copyLimit = otherLength; + } + else { + mergeLimit = otherLength; + if (mergeLimit > this.extra[0].length) { + copyLimit = mergeLimit; + mergeLimit = this.extra[0].length; + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + this.extra[j] = new long[otherLength], 0, + mergeLimit); } - while (i < otherLength) { - this.extraPotentialInits[i] = otherInits.extraPotentialInits[i]; - this.extraDefiniteNulls[i] &= otherInits.extraDefiniteNulls[i]; - this.extraDefiniteNonNulls[i] &= otherInits.extraDefiniteNonNulls[i++]; + } + int i; + for (i = 0; i < mergeLimit; i++) { + this.extra[2][i] = + ((a1 = this.extra[2][i]) & + (na4 = ~(a4 = this.extra[5][i])) & + ((na3 = ~(a3 = this.extra[4][i])) | + (a2 = this.extra[3][i])) | + a2 & na3 & a4) & + (nb3 = ~(b3 = otherInits.extra[4][i])) & + ((b2 = otherInits.extra[3][i]) | + (nb4 = ~(b4 = otherInits.extra[5][i]))) | + a1 & (na2 = ~a2) & + (a4 & ((nb1 = ~(b1 = otherInits.extra[2][i])) & + nb3 | b1 & + (b4 | b2)) | + na4 & (nb1 & (((nb2 = ~b2) & nb4 | b2) & nb3 | b3 & nb4) | + b1 & nb4 & (nb2 | nb3))); + this.extra[3][i] = + a2 & (~a1 & na4 & nb4 | + a1 & na3 & nb3 & (nb1 & (nb2 & nb4 | b2) | + b1 & (nb4 |b2 & b4))); + this.extra[4][i] = + a3 | + b1 & nb2 & nb4 | + nb1 & b3 | + a1 & na2 & (b1 & b3 | nb1 & b4); + this.extra[5][i] = + a4 & (na2 | a2 & na3) | + b4 & (nb2 | b2 & nb3); + } + for (; i < copyLimit; i++) { + if (otherInits.extra[4][i] != 0 || + otherInits.extra[5][i] != 0) { + this.tagBits |= NULL_FLAG_MASK; + this.extra[4][i] = + otherInits.extra[4][i] & + ~(otherInits.extra[2][i] & + ~otherInits.extra[3][i] & + otherInits.extra[5][i]); + this.extra[5][i] = + otherInits.extra[5][i]; } - } else { - // current storage is longer - while (i < otherLength) { - this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - this.extraDefiniteNulls[i] &= otherInits.extraDefiniteNulls[i]; - this.extraDefiniteNonNulls[i] &= otherInits.extraDefiniteNonNulls[i++]; + } + } + } + } + else { + if (otherInits.nullAssignmentValueBit1 != 0 || + otherInits.nullAssignmentValueBit2 != 0) { + // add potential values + this.nullAssignmentValueBit1 = + otherInits.nullAssignmentValueBit1 & + ~(otherInits.nullAssignmentStatusBit1 & + ~otherInits.nullAssignmentStatusBit2 & + otherInits.nullAssignmentValueBit2); // exclude assigned unknown + this.nullAssignmentValueBit2 = + otherInits.nullAssignmentValueBit2; + thisHasNulls = + this.nullAssignmentValueBit1 != 0 || + this.nullAssignmentValueBit2 != 0; + } + // extra storage management + if (otherInits.extra != null) { + int mergeLimit = 0, copyLimit = 0; + int otherLength = otherInits.extra[0].length; + if (this.extra == null) { + copyLimit = otherLength; + // cannot happen when called from addPotentialInitializationsFrom + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; + } + } + else { + mergeLimit = otherLength; + if (mergeLimit > this.extra[0].length) { + copyLimit = mergeLimit; + mergeLimit = this.extra[0].length; + System.arraycopy(this.extra[0], 0, + this.extra[0] = new long[otherLength], 0, + mergeLimit); + System.arraycopy(this.extra[1], 0, + this.extra[1] = new long[otherLength], 0, + mergeLimit); + for (int j = 2; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; } } } - } else - if (otherInits.extraDefiniteInits != null) { - // no storage here, but other has extra storage. - int otherLength; - this.extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]; - System.arraycopy(otherInits.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, otherLength); - this.extraDefiniteNulls = new long[otherLength]; - this.extraDefiniteNonNulls = new long[otherLength]; + int i; + for (i = 0; i < mergeLimit; i++) { + if (otherInits.extra[4][i] != 0 || + otherInits.extra[5][i] != 0) { + this.extra[4][i] |= + otherInits.extra[4][i] & + ~(otherInits.extra[2][i] & + ~otherInits.extra[3][i] & + otherInits.extra[5][i]); + this.extra[5][i] |= + otherInits.extra[5][i]; + thisHasNulls = thisHasNulls || + this.extra[4][i] != 0 || + this.extra[5][i] != 0; + } + } + for (; i < copyLimit; i++) { + if (otherInits.extra[4][i] != 0 || + otherInits.extra[5][i] != 0) { + this.extra[4][i] = + otherInits.extra[4][i] & + ~(otherInits.extra[2][i] & + ~otherInits.extra[3][i] & + otherInits.extra[5][i]); + this.extra[5][i] = + otherInits.extra[5][i]; + thisHasNulls = thisHasNulls || + this.extra[4][i] != 0 || + this.extra[5][i] != 0; + } } - return this; - } - - /** - * Answers a copy of the current instance - */ - public FlowInfo copy() { - - // do not clone the DeadEnd - if (this == DEAD_END) - return this; - - // look for an unused preallocated object - UnconditionalFlowInfo copy = new UnconditionalFlowInfo(); - - // copy slots - copy.definiteInits = this.definiteInits; - copy.potentialInits = this.potentialInits; - copy.definiteNulls = this.definiteNulls; - copy.definiteNonNulls = this.definiteNonNulls; - copy.reachMode = this.reachMode; - copy.maxFieldCount = this.maxFieldCount; - - if (this.extraDefiniteInits != null) { - int length; - System.arraycopy(this.extraDefiniteInits, 0, (copy.extraDefiniteInits = new long[length = extraDefiniteInits.length]), 0, length); - System.arraycopy(this.extraPotentialInits, 0, (copy.extraPotentialInits = new long[length]), 0, length); - System.arraycopy(this.extraDefiniteNulls, 0, (copy.extraDefiniteNulls = new long[length]), 0, length); - System.arraycopy(this.extraDefiniteNonNulls, 0, (copy.extraDefiniteNonNulls = new long[length]), 0, length); } - return copy; } - - public UnconditionalFlowInfo discardFieldInitializations(){ - - int limit = this.maxFieldCount; - - if (limit < BitCacheSize) { - long mask = (1L << limit)-1; - this.definiteInits &= ~mask; - this.potentialInits &= ~mask; - this.definiteNulls &= ~mask; - this.definiteNonNulls &= ~mask; - return this; - } - - this.definiteInits = 0; - this.potentialInits = 0; - this.definiteNulls = 0; - this.definiteNonNulls = 0; - - // use extra vector - if (extraDefiniteInits == null) { - return this; // if vector not yet allocated, then not initialized - } - int vectorIndex, length = this.extraDefiniteInits.length; - if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) { - return this; // not enough room yet - } - for (int i = 0; i < vectorIndex; i++) { - this.extraDefiniteInits[i] = 0L; - this.extraPotentialInits[i] = 0L; - this.extraDefiniteNulls[i] = 0L; - this.extraDefiniteNonNulls[i] = 0L; - } - long mask = (1L << (limit % BitCacheSize))-1; - this.extraDefiniteInits[vectorIndex] &= ~mask; - this.extraPotentialInits[vectorIndex] &= ~mask; - this.extraDefiniteNulls[vectorIndex] &= ~mask; - this.extraDefiniteNonNulls[vectorIndex] &= ~mask; - return this; + if (thisHasNulls) { + this.tagBits |= NULL_FLAG_MASK; + } + else { + this.tagBits &= NULL_FLAG_MASK; } + return this; +} - public UnconditionalFlowInfo discardNonFieldInitializations(){ - - int limit = this.maxFieldCount; - - if (limit < BitCacheSize) { - long mask = (1L << limit)-1; - this.definiteInits &= mask; - this.potentialInits &= mask; - this.definiteNulls &= mask; - this.definiteNonNulls &= mask; - return this; - } - // use extra vector - if (extraDefiniteInits == null) { - return this; // if vector not yet allocated, then not initialized - } - int vectorIndex, length = this.extraDefiniteInits.length; - if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) { - return this; // not enough room yet - } - long mask = (1L << (limit % BitCacheSize))-1; - this.extraDefiniteInits[vectorIndex] &= mask; - this.extraPotentialInits[vectorIndex] &= mask; - this.extraDefiniteNulls[vectorIndex] &= mask; - this.extraDefiniteNonNulls[vectorIndex] &= mask; - for (int i = vectorIndex+1; i < length; i++) { - this.extraDefiniteInits[i] = 0L; - this.extraPotentialInits[i] = 0L; - this.extraDefiniteNulls[i] = 0L; - this.extraDefiniteNonNulls[i] = 0L; - } +public FlowInfo copy() { + // do not clone the DeadEnd + if (this == DEAD_END) { return this; } - - public UnconditionalFlowInfo discardNullRelatedInitializations(){ - - this.definiteNulls = 0; - this.definiteNonNulls = 0; - - int length = this.extraDefiniteInits == null ? 0 : this.extraDefiniteInits.length; - for (int i = 0; i < length; i++) { - this.extraDefiniteNulls[i] = 0L; - this.extraDefiniteNonNulls[i] = 0L; + UnconditionalFlowInfo copy = new UnconditionalFlowInfo(); + // copy slots + copy.definiteInits = this.definiteInits; + copy.potentialInits = this.potentialInits; + boolean hasNullInfo = (this.tagBits & NULL_FLAG_MASK) != 0; + if (hasNullInfo) { + copy.nullAssignmentStatusBit1 = this.nullAssignmentStatusBit1; + copy.nullAssignmentStatusBit2 = this.nullAssignmentStatusBit2; + copy.nullAssignmentValueBit1 = this.nullAssignmentValueBit1; + copy.nullAssignmentValueBit2 = this.nullAssignmentValueBit2; + } + copy.tagBits = this.tagBits; + copy.maxFieldCount = this.maxFieldCount; + if (this.extra != null) { + int length; + copy.extra = new long[extraLength][]; + System.arraycopy(this.extra[0], 0, + (copy.extra[0] = new long[length = this.extra[0].length]), 0, + length); + System.arraycopy(this.extra[1], 0, + (copy.extra[1] = new long[length]), 0, length); + if (hasNullInfo) { + for (int j = 2; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (copy.extra[j] = new long[length]), 0, length); + } + } + else { + for (int j = 2; j < extraLength; j++) { + copy.extra[j] = new long[length]; + } } - return this; } + return copy; +} - public FlowInfo initsWhenFalse() { - - return this; - } - - public FlowInfo initsWhenTrue() { - - return this; +/** + * Remove local variables information from this flow info and return this. + * @return this, deprived from any local variable information + */ +public UnconditionalFlowInfo discardNonFieldInitializations() { + int limit = this.maxFieldCount; + if (limit < BitCacheSize) { + long mask = (1L << limit)-1; + this.definiteInits &= mask; + this.potentialInits &= mask; + this.nullAssignmentStatusBit1 &= mask; + this.nullAssignmentStatusBit2 &= mask; + this.nullAssignmentValueBit1 &= mask; + this.nullAssignmentValueBit2 &= mask; + } + // use extra vector + if (this.extra == null) { + return this; // if vector not yet allocated, then not initialized + } + int vectorIndex, length = this.extra[0].length; + if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) { + return this; // not enough room yet } - - /** - * Check status of definite assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private boolean isDefinitelyAssigned(int position) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // id is zero-based - if (position < BitCacheSize) { - return (definiteInits & (1L << position)) != 0; // use bits + if (vectorIndex >= 0) { + // else we only have complete non field array items left + long mask = (1L << (limit % BitCacheSize))-1; + for (int j = 0; j < extraLength; j++) { + this.extra[j][vectorIndex] &= mask; } - // use extra vector - if (extraDefiniteInits == null) - return false; // if vector not yet allocated, then not initialized - int vectorIndex; - if ((vectorIndex = (position / BitCacheSize) - 1) >= extraDefiniteInits.length) - return false; // if not enough room in vector, then not initialized - return ((extraDefiniteInits[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; } - - /** - * Check status of definite non-null assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private boolean isDefinitelyNonNull(int position) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // id is zero-based - if (position < BitCacheSize) { - return (definiteNonNulls & (1L << position)) != 0; // use bits + for (int i = vectorIndex + 1; i < length; i++) { + for (int j = 0; j < extraLength; j++) { + this.extra[j][i] = 0; } - // use extra vector - if (extraDefiniteNonNulls == null) - return false; // if vector not yet allocated, then not initialized - int vectorIndex; - if ((vectorIndex = (position / BitCacheSize) - 1) >= extraDefiniteNonNulls.length) - return false; // if not enough room in vector, then not initialized - return ((extraDefiniteNonNulls[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; - } - - /** - * Check status of definite null assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private boolean isDefinitelyNull(int position) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // id is zero-based - if (position < BitCacheSize) { - return (definiteNulls & (1L << position)) != 0; // use bits - } - // use extra vector - if (extraDefiniteNulls == null) - return false; // if vector not yet allocated, then not initialized - int vectorIndex; - if ((vectorIndex = (position / BitCacheSize) - 1) >= extraDefiniteNulls.length) - return false; // if not enough room in vector, then not initialized - return ((extraDefiniteNulls[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; - } - - /** - * Check status of definite assignment for a field. - */ - final public boolean isDefinitelyAssigned(FieldBinding field) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return true; - return isDefinitelyAssigned(field.id); } - - /** - * Check status of definite assignment for a local. - */ - final public boolean isDefinitelyAssigned(LocalVariableBinding local) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return true; - - // final constants are inlined, and thus considered as always initialized - if (local.constant() != Constant.NotAConstant) { - return true; - } - return isDefinitelyAssigned(local.id + maxFieldCount); + return this; +} + +public FlowInfo initsWhenFalse() { + return this; +} + +public FlowInfo initsWhenTrue() { + return this; +} + +/** + * Check status of definite assignment at a given position. + * It deals with the dual representation of the InitializationInfo2: + * bits for the first 64 entries, then an array of booleans. + */ +final private boolean isDefinitelyAssigned(int position) { + if (position < BitCacheSize) { + // use bits + return (this.definiteInits & (1L << position)) != 0; + } + // use extra vector + if (this.extra == null) + return false; // if vector not yet allocated, then not initialized + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) + >= this.extra[0].length) { + return false; // if not enough room in vector, then not initialized } - - /** - * Check status of definite non-null assignment for a field. - */ - final public boolean isDefinitelyNonNull(FieldBinding field) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return false; - return isDefinitelyNonNull(field.id); + return ((this.extra[0][vectorIndex]) & + (1L << (position % BitCacheSize))) != 0; +} + +final public boolean isDefinitelyAssigned(FieldBinding field) { + // Dependant of CodeStream.isDefinitelyAssigned(..) + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0) { + return true; } - - /** - * Check status of definite non-null assignment for a local. - */ - final public boolean isDefinitelyNonNull(LocalVariableBinding local) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return false; - // final constants are inlined, and thus considered as always initialized - if (local.constant() != Constant.NotAConstant) { - return true; - } - return isDefinitelyNonNull(local.id + maxFieldCount); - } - - /** - * Check status of definite null assignment for a field. - */ - final public boolean isDefinitelyNull(FieldBinding field) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return false; - return isDefinitelyNull(field.id); + return isDefinitelyAssigned(field.id); +} + +final public boolean isDefinitelyAssigned(LocalVariableBinding local) { + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0) { + return true; + } + // final constants are inlined, and thus considered as always initialized + if (local.constant() != Constant.NotAConstant) { + return true; } - - /** - * Check status of definite null assignment for a local. - */ - final public boolean isDefinitelyNull(LocalVariableBinding local) { - - // Dependant of CodeStream.isDefinitelyAssigned(..) - // We do not want to complain in unreachable code - if ((this.reachMode & UNREACHABLE) != 0) - return false; - return isDefinitelyNull(local.id + maxFieldCount); + return isDefinitelyAssigned(local.id + this.maxFieldCount); +} + +final public boolean isDefinitelyNonNull(LocalVariableBinding local) { + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0 || + (this.tagBits & NULL_FLAG_MASK) == 0) { + return false; + } + if ((local.type.tagBits & TagBits.IsBaseType) != 0 || + local.constant() != Constant.NotAConstant) { + return true; + } + int position = local.id + this.maxFieldCount; + long mask; + if (position < BitCacheSize) { // use bits + return + (this.nullAssignmentStatusBit2 & + (mask = 1L << position)) != 0 ? + (this.nullAssignmentStatusBit1 & mask) != 0 : + (this.nullAssignmentStatusBit1 & + this.nullAssignmentValueBit2 & mask) != 0 && + (this.nullAssignmentValueBit1 & mask) == 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) + >= this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return + (this.extra[3][vectorIndex] & + (mask = 1L << (position % BitCacheSize))) != 0 ? + (this.extra[2][vectorIndex] & mask) != 0 : + (this.extra[2][vectorIndex] & + this.extra[5][vectorIndex] & mask) != 0 && + (this.extra[4][vectorIndex] & mask) == 0; +} + +final public boolean isDefinitelyNull(LocalVariableBinding local) { + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0 || + (this.tagBits & NULL_FLAG_MASK) == 0 || + (local.type.tagBits & TagBits.IsBaseType) != 0) { + return false; + } + int position = local.id + this.maxFieldCount; + long mask; + if (position < BitCacheSize) { // use bits + return + (this.nullAssignmentStatusBit2 & (mask = 1L << position)) != 0 ? + (this.nullAssignmentStatusBit1 & mask) == 0 : + (this.nullAssignmentStatusBit1 & + this.nullAssignmentValueBit1 & mask) != 0 && + (this.nullAssignmentValueBit2 & mask) == 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return + (this.extra[3][vectorIndex] & + (mask = 1L << (position % BitCacheSize))) != 0 ? + (this.extra[2][vectorIndex] & mask) == 0 : + (this.extra[2][vectorIndex] & + this.extra[4][vectorIndex] & mask) != 0 && + (this.extra[5][vectorIndex] & mask) == 0; +} + +final public boolean isDefinitelyUnknown(LocalVariableBinding local) { + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0 || + (this.tagBits & NULL_FLAG_MASK) == 0) { + return false; + } + int position = local.id + this.maxFieldCount; + long mask; + if (position < BitCacheSize) { // use bits + return + (this.nullAssignmentStatusBit2 & (mask = 1L << position)) != 0 ? + false : + (this.nullAssignmentStatusBit1 & + this.nullAssignmentValueBit1 & + this.nullAssignmentValueBit2 & mask) != 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return + (this.extra[3][vectorIndex] & + (mask = 1L << (position % BitCacheSize))) != 0 ? + false : + (this.extra[2][vectorIndex] & + this.extra[4][vectorIndex] & + this.extra[5][vectorIndex] & + mask) != 0; +} + +/** + * Check status of potential assignment at a given position. + * It deals with the dual representation of the InitializationInfo3: + * bits for the first 64 entries, then an array of booleans. + */ +final private boolean isPotentiallyAssigned(int position) { + // id is zero-based + if (position < BitCacheSize) { + // use bits + return (this.potentialInits & (1L << position)) != 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) + >= this.extra[0].length) { + return false; // if not enough room in vector, then not initialized } + return ((this.extra[1][vectorIndex]) & + (1L << (position % BitCacheSize))) != 0; +} + +/** + * Check status of definite assignment for a field. + */ +final public boolean isPotentiallyAssigned(FieldBinding field) { + return isPotentiallyAssigned(field.id); +} - public boolean isReachable() { - - return this.reachMode == REACHABLE; +/** + * Check status of potential assignment for a local. + */ +final public boolean isPotentiallyAssigned(LocalVariableBinding local) { + // final constants are inlined, and thus considered as always initialized + if (local.constant() != Constant.NotAConstant) { + return true; } - - /** - * Check status of potential assignment at a given position. - * It deals with the dual representation of the InitializationInfo3: - * bits for the first 64 entries, then an array of booleans. - */ - final private boolean isPotentiallyAssigned(int position) { - - // id is zero-based - if (position < BitCacheSize) { + return isPotentiallyAssigned(local.id + this.maxFieldCount); +} + +final public boolean isPotentiallyNull(LocalVariableBinding local) { + if ((this.tagBits & NULL_FLAG_MASK) == 0 || + (local.type.tagBits & TagBits.IsBaseType) != 0) { + return false; + } + int position; + long mask; + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { + // use bits + return + (this.nullAssignmentStatusBit2 & (mask = 1L << position)) != 0 ? + (this.nullAssignmentStatusBit1 & mask) == 0 : // protected null + (this.nullAssignmentValueBit1 & mask) != 0 && // null bit set and + ((this.nullAssignmentStatusBit1 & mask) == 0 || // (potential or + (this.nullAssignmentValueBit2 & mask) == 0); + // assigned, but not unknown) + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return + (this.extra[3][vectorIndex] & + (mask = 1L << (position % BitCacheSize))) != 0 ? + (this.extra[2][vectorIndex] & mask) == 0 : + (this.extra[4][vectorIndex] & mask) != 0 && + ((this.extra[2][vectorIndex] & mask) == 0 || + (this.extra[5][vectorIndex] & mask) == 0); +} + +final public boolean isPotentiallyUnknown(LocalVariableBinding local) { + // do not want to complain in unreachable code + if ((this.tagBits & UNREACHABLE) != 0 || + (this.tagBits & NULL_FLAG_MASK) == 0) { + return false; + } + int position = local.id + this.maxFieldCount; + long mask; + if (position < BitCacheSize) { // use bits + return + (this.nullAssignmentStatusBit2 & (mask = 1L << position)) != 0 ? + false : + ((this.nullAssignmentStatusBit1 & + this.nullAssignmentValueBit1 | + ~this.nullAssignmentStatusBit1 & + ~this.nullAssignmentValueBit1) & + this.nullAssignmentValueBit2 & mask) != 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return + (this.extra[3][vectorIndex] & + (mask = 1L << (position % BitCacheSize))) != 0 ? + false : + ((this.extra[2][vectorIndex] & + this.extra[4][vectorIndex] | + ~this.extra[2][vectorIndex] & + ~this.extra[4][vectorIndex]) & + this.extra[5][vectorIndex] & + mask) != 0; +} + +final public boolean isProtectedNonNull(LocalVariableBinding local) { + if ((this.tagBits & NULL_FLAG_MASK) == 0 || + (local.type.tagBits & TagBits.IsBaseType) != 0) { + return false; + } + int position; + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { + // use bits + return (this.nullAssignmentStatusBit1 & + this.nullAssignmentStatusBit2 & (1L << position)) != 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return (this.extra[4][vectorIndex] & + this.extra[5][vectorIndex] & + (1L << (position % BitCacheSize))) != 0; +} + +final public boolean isProtectedNull(LocalVariableBinding local) { + if ((this.tagBits & NULL_FLAG_MASK) == 0 || + (local.type.tagBits & TagBits.IsBaseType) != 0) { + return false; + } + int position; + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { + // use bits + return (~this.nullAssignmentStatusBit1 & + this.nullAssignmentStatusBit2 & (1L << position)) != 0; + } + // use extra vector + if (this.extra == null) { + return false; // if vector not yet allocated, then not initialized + } + int vectorIndex; + if ((vectorIndex = (position / BitCacheSize) - 1) >= + this.extra[0].length) { + return false; // if not enough room in vector, then not initialized + } + return (~this.extra[4][vectorIndex] & + this.extra[5][vectorIndex] & + (1L << (position % BitCacheSize))) != 0; +} + +public void markAsComparedEqualToNonNull(LocalVariableBinding local) { + // protected from non-object locals in calling methods + if (this != DEAD_END) { + this.tagBits |= NULL_FLAG_MASK; + int position; + long mask; + // position is zero-based + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits - return (potentialInits & (1L << position)) != 0; + if (((mask = 1L << position) & // leave assigned non null unchanged + this.nullAssignmentStatusBit1 & + ~this.nullAssignmentStatusBit2 & + ~this.nullAssignmentValueBit1 & + this.nullAssignmentValueBit2) == 0) { + // set protected non null + this.nullAssignmentStatusBit1 |= mask; + this.nullAssignmentStatusBit2 |= mask; + // clear potential null + this.nullAssignmentValueBit1 &= ~mask; + } + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + if (this.extra == null) { + int length = vectorIndex + 1; + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[length]; + } + } + else { + int oldLength; + if (vectorIndex >= (oldLength = this.extra[0].length)) { + int newLength = vectorIndex + 1; + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[newLength]), 0, + oldLength); + } + } + } + if (((mask = 1L << (position % BitCacheSize)) & + this.extra[2][vectorIndex] & + ~this.extra[3][vectorIndex] & + ~this.extra[4][vectorIndex] & + this.extra[5][vectorIndex]) == 0) { + this.extra[2][vectorIndex] |= mask; + this.extra[3][vectorIndex] |= mask; + this.extra[4][vectorIndex] &= ~mask; + } } - // use extra vector - if (extraPotentialInits == null) - return false; // if vector not yet allocated, then not initialized - int vectorIndex; - if ((vectorIndex = (position / BitCacheSize) - 1) >= extraPotentialInits.length) - return false; // if not enough room in vector, then not initialized - return ((extraPotentialInits[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; - } - - /** - * Check status of definite assignment for a field. - */ - final public boolean isPotentiallyAssigned(FieldBinding field) { - - return isPotentiallyAssigned(field.id); } - - /** - * Check status of potential assignment for a local. - */ - final public boolean isPotentiallyAssigned(LocalVariableBinding local) { - - // final constants are inlined, and thus considered as always initialized - if (local.constant() != Constant.NotAConstant) { - return true; +} + +public void markAsComparedEqualToNull(LocalVariableBinding local) { + // protected from non-object locals in calling methods + if (this != DEAD_END) { + this.tagBits |= NULL_FLAG_MASK; + int position; + long mask, unknownAssigned; + // position is zero-based + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { + // use bits + mask = 1L << position; + if ((mask & // leave assigned null unchanged + this.nullAssignmentStatusBit1 & + ~this.nullAssignmentStatusBit2 & + this.nullAssignmentValueBit1 & + ~this.nullAssignmentValueBit2) == 0) { + unknownAssigned = this.nullAssignmentStatusBit1 & + ~this.nullAssignmentStatusBit2 & + this.nullAssignmentValueBit1 & + this.nullAssignmentValueBit2; + // set protected + this.nullAssignmentStatusBit2 |= mask; + this.nullAssignmentStatusBit1 &= (mask = ~mask); + // protected is null + this.nullAssignmentValueBit1 &= mask | ~unknownAssigned; + this.nullAssignmentValueBit2 &= mask; + // clear potential anything but null + } + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + mask = 1L << (position % BitCacheSize); + if (this.extra == null) { + int length = vectorIndex + 1; + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[length ]; + } + } + else { + int oldLength; + if (vectorIndex >= (oldLength = this.extra[0].length)) { + int newLength = vectorIndex + 1; + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[newLength]), 0, + oldLength); + } + } + } + if ((mask & + this.extra[2][vectorIndex] & + ~this.extra[3][vectorIndex] & + this.extra[4][vectorIndex] & + ~this.extra[5][vectorIndex]) == 0) { + unknownAssigned = this.extra[2][vectorIndex] & + ~this.extra[3][vectorIndex] & + this.extra[4][vectorIndex] & + this.extra[5][vectorIndex]; + this.extra[3][vectorIndex] |= mask; + this.extra[2][vectorIndex] &= (mask = ~mask); + this.extra[4][vectorIndex] &= mask | ~unknownAssigned; + this.extra[5][vectorIndex] &= mask; + } } - return isPotentiallyAssigned(local.id + maxFieldCount); } +} + +/** + * Record a definite assignment at a given position. + * It deals with the dual representation of the InitializationInfo2: + * bits for the first 64 entries, then an array of booleans. + */ +final private void markAsDefinitelyAssigned(int position) { - /** - * Record a definite assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private void markAsDefinitelyAssigned(int position) { - - if (this != DEAD_END) { - - // position is zero-based - if (position < BitCacheSize) { - // use bits - long mask; - definiteInits |= (mask = 1L << position); - potentialInits |= mask; - definiteNulls &= ~mask; - definiteNonNulls &= ~mask; - } else { - // use extra vector - int vectorIndex = (position / BitCacheSize) - 1; - if (extraDefiniteInits == null) { - int length; - extraDefiniteInits = new long[length = vectorIndex + 1]; - extraPotentialInits = new long[length]; - extraDefiniteNulls = new long[length]; - extraDefiniteNonNulls = new long[length]; - } else { - int oldLength; // might need to grow the arrays - if (vectorIndex >= (oldLength = extraDefiniteInits.length)) { - System.arraycopy(extraDefiniteInits, 0, (extraDefiniteInits = new long[vectorIndex + 1]), 0, oldLength); - System.arraycopy(extraPotentialInits, 0, (extraPotentialInits = new long[vectorIndex + 1]), 0, oldLength); - System.arraycopy(extraDefiniteNulls, 0, (extraDefiniteNulls = new long[vectorIndex + 1]), 0, oldLength); - System.arraycopy(extraDefiniteNonNulls, 0, (extraDefiniteNonNulls = new long[vectorIndex + 1]), 0, oldLength); + if (this != DEAD_END) { + // position is zero-based + if (position < BitCacheSize) { + // use bits + long mask; + this.definiteInits |= (mask = 1L << position); + this.potentialInits |= mask; + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + if (this.extra == null) { + int length = vectorIndex + 1; + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[length]; + } + } + else { + int oldLength; // might need to grow the arrays + if (vectorIndex >= (oldLength = this.extra[0].length)) { + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[vectorIndex + 1]), 0, + oldLength); } } - long mask; - extraDefiniteInits[vectorIndex] |= (mask = 1L << (position % BitCacheSize)); - extraPotentialInits[vectorIndex] |= mask; - extraDefiniteNulls[vectorIndex] &= ~mask; - extraDefiniteNonNulls[vectorIndex] &= ~mask; } + long mask; + this.extra[0][vectorIndex] |= + (mask = 1L << (position % BitCacheSize)); + this.extra[1][vectorIndex] |= mask; } } - - /** - * Record a field got definitely assigned. - */ - public void markAsDefinitelyAssigned(FieldBinding field) { - if (this != DEAD_END) - markAsDefinitelyAssigned(field.id); +} + +public void markAsDefinitelyAssigned(FieldBinding field) { + if (this != DEAD_END) + markAsDefinitelyAssigned(field.id); +} + +public void markAsDefinitelyAssigned(LocalVariableBinding local) { + if (this != DEAD_END) + markAsDefinitelyAssigned(local.id + this.maxFieldCount); +} + +/** + * Record a definite non-null assignment at a given position. + */ +final private void markAsDefinitelyNonNull(int position) { + // DEAD_END guarded above + this.tagBits |= NULL_FLAG_MASK; + long mask; + // position is zero-based + if (position < BitCacheSize) { + // use bits + this.nullAssignmentStatusBit1 |= (mask = 1L << position); + this.nullAssignmentValueBit2 |= mask; // set non null + this.nullAssignmentStatusBit2 &= ~mask; // clear protection + this.nullAssignmentValueBit1 &= ~mask; // clear null + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + this.extra[2][vectorIndex] |= + (mask = 1L << (position % BitCacheSize)); + this.extra[5][vectorIndex] |= mask; + this.extra[3][vectorIndex] &= ~mask; + this.extra[4][vectorIndex] &= ~mask; } - - /** - * Record a local got definitely assigned. - */ - public void markAsDefinitelyAssigned(LocalVariableBinding local) { - if (this != DEAD_END) - markAsDefinitelyAssigned(local.id + maxFieldCount); - } - - /** - * Record a definite non-null assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private void markAsDefinitelyNonNull(int position) { - - if (this != DEAD_END) { - - // position is zero-based - if (position < BitCacheSize) { - // use bits - long mask; - definiteNonNulls |= (mask = 1L << position); - definiteNulls &= ~mask; - } else { - // use extra vector - int vectorIndex = (position / BitCacheSize) - 1; - long mask; - extraDefiniteNonNulls[vectorIndex] |= (mask = 1L << (position % BitCacheSize)); - extraDefiniteNulls[vectorIndex] &= ~mask; - } - } +} + +public void markAsDefinitelyNonNull(FieldBinding field) { + if (this != DEAD_END) { + markAsDefinitelyNonNull(field.id); } +} - /** - * Record a field got definitely assigned to non-null value. - */ - public void markAsDefinitelyNonNull(FieldBinding field) { - if (this != DEAD_END) - markAsDefinitelyNonNull(field.id); +public void markAsDefinitelyNonNull(LocalVariableBinding local) { + // protected from non-object locals in calling methods + if (this != DEAD_END) { + markAsDefinitelyNonNull(local.id + this.maxFieldCount); } - - /** - * Record a local got definitely assigned to non-null value. - */ - public void markAsDefinitelyNonNull(LocalVariableBinding local) { - if (this != DEAD_END) - markAsDefinitelyNonNull(local.id + maxFieldCount); - } - - /** - * Record a definite null assignment at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private void markAsDefinitelyNull(int position) { - - if (this != DEAD_END) { - - // position is zero-based - if (position < BitCacheSize) { - // use bits - long mask; - definiteNulls |= (mask = 1L << position); - definiteNonNulls &= ~mask; - } else { - // use extra vector - int vectorIndex = (position / BitCacheSize) - 1; - long mask; - extraDefiniteNulls[vectorIndex] |= (mask = 1L << (position % BitCacheSize)); - extraDefiniteNonNulls[vectorIndex] &= ~mask; - } - } +} + +/** + * Record a definite null assignment at a given position. + */ +final private void markAsDefinitelyNull(int position) { + // DEAD_END guarded above + this.tagBits |= NULL_FLAG_MASK; + long mask; + if (position < BitCacheSize) { + // use bits + this.nullAssignmentStatusBit1 |= (mask = 1L << position); // set assignment + this.nullAssignmentStatusBit2 &= ~mask; // clear protection + this.nullAssignmentValueBit1 |= mask; // set null + this.nullAssignmentValueBit2 &= ~mask; // clear non null + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + this.extra[2][vectorIndex] |= + (mask = 1L << (position % BitCacheSize)); + this.extra[3][vectorIndex] &= ~mask; + this.extra[4][vectorIndex] |= mask; + this.extra[5][vectorIndex] &= ~mask; } +} - /** - * Record a field got definitely assigned to null. - */ - public void markAsDefinitelyNull(FieldBinding field) { - if (this != DEAD_END) - markAsDefinitelyAssigned(field.id); +public void markAsDefinitelyNull(FieldBinding field) { + if (this != DEAD_END) { + markAsDefinitelyNull(field.id); } - - /** - * Record a local got definitely assigned to null. - */ - public void markAsDefinitelyNull(LocalVariableBinding local) { - if (this != DEAD_END) - markAsDefinitelyNull(local.id + maxFieldCount); +} + +public void markAsDefinitelyNull(LocalVariableBinding local) { + // protected from non-object locals in calling methods + if (this != DEAD_END) { + markAsDefinitelyNull(local.id + this.maxFieldCount); } - - /** - * Clear initialization information at a given position. - * It deals with the dual representation of the InitializationInfo2: - * bits for the first 64 entries, then an array of booleans. - */ - final private void markAsDefinitelyNotAssigned(int position) { - if (this != DEAD_END) { - - // position is zero-based - if (position < BitCacheSize) { - // use bits - long mask; - definiteInits &= ~(mask = 1L << position); - potentialInits &= ~mask; - definiteNulls &= ~mask; - definiteNonNulls &= ~mask; - } else { - // use extra vector - int vectorIndex = (position / BitCacheSize) - 1; - if (extraDefiniteInits == null) { - return; // nothing to do, it was not yet set - } - // might need to grow the arrays - if (vectorIndex >= extraDefiniteInits.length) { - return; // nothing to do, it was not yet set - } - long mask; - extraDefiniteInits[vectorIndex] &= ~(mask = 1L << (position % BitCacheSize)); - extraPotentialInits[vectorIndex] &= ~mask; - extraDefiniteNulls[vectorIndex] &= ~mask; - extraDefiniteNonNulls[vectorIndex] &= ~mask; - } +} + +/** + * Mark a local as having been assigned to an unknown value. + * @param local the local to mark + */ +// PREMATURE may try to get closer to markAsDefinitelyAssigned, but not +// obvious +public void markAsDefinitelyUnknown(LocalVariableBinding local) { + // protected from non-object locals in calling methods + if (this != DEAD_END) { + this.tagBits |= NULL_FLAG_MASK; + long mask; + int position; + // position is zero-based + if ((position = local.id + this.maxFieldCount) < BitCacheSize) { + // use bits + this.nullAssignmentValueBit1 |= (mask = 1L << position); + this.nullAssignmentValueBit2 |= mask; + // set unknown + this.nullAssignmentStatusBit1 |= mask; + // set assignment + this.nullAssignmentStatusBit2 &= ~mask; + // clear protection + } + else { + // use extra vector + int vectorIndex = (position / BitCacheSize) - 1; + this.extra[4][vectorIndex] |= + (mask = 1L << (position % BitCacheSize)); + this.extra[5][vectorIndex] |= mask; + this.extra[2][vectorIndex] |= mask; + this.extra[3][vectorIndex] &= ~mask; } } - - /** - * Clear the initialization info for a field - */ - public void markAsDefinitelyNotAssigned(FieldBinding field) { - - if (this != DEAD_END) - markAsDefinitelyNotAssigned(field.id); +} + +public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) { + if ((otherInits.tagBits & UNREACHABLE) != 0 && this != DEAD_END) { + // DEAD_END + unreachable other -> other + return this; } - - /** - * Clear the initialization info for a local variable - */ - - public void markAsDefinitelyNotAssigned(LocalVariableBinding local) { - - if (this != DEAD_END) - markAsDefinitelyNotAssigned(local.id + maxFieldCount); - } - - /** - * Returns the receiver updated in the following way:
    - *
  • intersection of definitely assigned variables, - *
  • union of potentially assigned variables. - *
- */ - public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) { - - if (this == DEAD_END) return otherInits; - if (otherInits == DEAD_END) return this; - - if ((this.reachMode & UNREACHABLE) != (otherInits.reachMode & UNREACHABLE)){ - if ((this.reachMode & UNREACHABLE) != 0){ - return otherInits; - } - return this; - } - - // if one branch is not fake reachable, then the merged one is reachable - this.reachMode &= otherInits.reachMode; - - // intersection of definitely assigned variables, - this.definiteInits &= otherInits.definiteInits; - // union of potentially set ones - this.potentialInits |= otherInits.potentialInits; - // intersection of definitely null variables, - this.definiteNulls &= otherInits.definiteNulls; - // intersection of definitely non-null variables, - this.definiteNonNulls &= otherInits.definiteNonNulls; - - // treating extra storage - if (this.extraDefiniteInits != null) { - if (otherInits.extraDefiniteInits != null) { + if ((this.tagBits & UNREACHABLE) != 0) { + return (UnconditionalFlowInfo) otherInits.copy(); // make sure otherInits won't be affected + } + + // intersection of definitely assigned variables, + this.definiteInits &= otherInits.definiteInits; + // union of potentially set ones + this.potentialInits |= otherInits.potentialInits; + + // null combinations + boolean otherHasNulls = (otherInits.tagBits & NULL_FLAG_MASK) != 0, + thisHasNulls = false; + long a1, a2, na2, a3, na3, a4, na4, b1, nb1, b2, nb2, b3, nb3, b4, nb4; + if (otherHasNulls) { + this.nullAssignmentStatusBit1 = + (a1 = this.nullAssignmentStatusBit1) & + (b1 = otherInits.nullAssignmentStatusBit1) & ( + (nb4 = ~(b4 = otherInits.nullAssignmentValueBit2)) & + ((b2 = otherInits.nullAssignmentStatusBit2) & + (nb3 = ~(b3 = otherInits.nullAssignmentValueBit1)) & + (na3 = ~(a3 = this.nullAssignmentValueBit1)) & + ((a2 = this.nullAssignmentStatusBit2) & + (na4 = ~(a4 = this.nullAssignmentValueBit2)) | a4) | + (na2 = ~a2) & a3 & na4 & (nb2 = ~b2) & b3 ) | + b4 & (na3 & nb3 & (na4 & a2 | a4) | + na2 & a4 & nb2)); + this.nullAssignmentStatusBit2 = + a2 & b2 & ~(a1 ^ b1) & (na3 & nb3 | na4 & nb4) | + a1 & b1 & (a2 ^ b2) & na3 & nb3 | + (a1 & na2 & (nb1 = ~b1) & b2 | ~a1 & a2 & b1 & nb2) & na4 & nb4; + this.nullAssignmentValueBit1 = + b1 & nb2 & nb4 | + ~a1 & (a3 | + a2 & na3 & (b1 | nb2)) | + (a1 | na2) & nb1 & b2 & nb3 | + nb1 & b3 | + a1 & na2 & (na4 | + b1 & nb2 & (a3 | b3)); + this.nullAssignmentValueBit2 = + a4 | b4; + } + else { + // tune potentials + this.nullAssignmentValueBit1 = + ~(~this.nullAssignmentStatusBit1 & + ~this.nullAssignmentStatusBit2 & + ~this.nullAssignmentValueBit1) & + ~(this.nullAssignmentStatusBit1 & + (this.nullAssignmentStatusBit2 | this.nullAssignmentValueBit2)); + // reset assignment and protected + this.nullAssignmentStatusBit1 = + this.nullAssignmentStatusBit2 = 0; + } + thisHasNulls = this.nullAssignmentStatusBit1 != 0 || + this.nullAssignmentStatusBit2 != 0 || + this.nullAssignmentValueBit1 != 0 || + this.nullAssignmentValueBit2 != 0; + + // treating extra storage + if (this.extra != null || otherInits.extra != null) { + int mergeLimit = 0, copyLimit = 0, resetLimit = 0; + if (this.extra != null) { + if (otherInits.extra != null) { // both sides have extra storage - int i = 0, length, otherLength; - if ((length = this.extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { - // current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) - System.arraycopy(this.extraDefiniteInits, 0, (this.extraDefiniteInits = new long[otherLength]), 0, length); - System.arraycopy(this.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, length); - System.arraycopy(this.extraDefiniteNulls, 0, (this.extraDefiniteNulls = new long[otherLength]), 0, length); - System.arraycopy(this.extraDefiniteNonNulls, 0, (this.extraDefiniteNonNulls = new long[otherLength]), 0, length); - while (i < length) { - this.extraDefiniteInits[i] &= otherInits.extraDefiniteInits[i]; - this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - this.extraDefiniteNulls[i] &= otherInits.extraDefiniteNulls[i]; - this.extraDefiniteNonNulls[i] &= otherInits.extraDefiniteNonNulls[i++]; + int length, otherLength; + if ((length = this.extra[0].length) < + (otherLength = otherInits.extra[0].length)) { + // current storage is shorter -> grow current + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], 0, + (this.extra[j] = new long[otherLength]), 0, length); } - while (i < otherLength) { - this.extraPotentialInits[i] = otherInits.extraPotentialInits[i++]; - } - } else { + mergeLimit = length; + copyLimit = otherLength; + } + else { // current storage is longer - while (i < otherLength) { - this.extraDefiniteInits[i] &= otherInits.extraDefiniteInits[i]; - this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i]; - this.extraDefiniteNulls[i] &= otherInits.extraDefiniteNulls[i]; - this.extraDefiniteNonNulls[i] &= otherInits.extraDefiniteNonNulls[i++]; - } - while (i < length) { - this.extraDefiniteInits[i] = 0; - this.extraDefiniteNulls[i] = 0; - this.extraDefiniteNonNulls[i++] = 0; - } + mergeLimit = otherLength; + resetLimit = length; } - } else { - // no extra storage on otherInits - int i = 0, length = this.extraDefiniteInits.length; - while (i < length) { - this.extraDefiniteInits[i] = 0; - this.extraDefiniteNulls[i] = 0; - this.extraDefiniteNonNulls[i++] = 0; - } - } - } else - if (otherInits.extraDefiniteInits != null) { - // no storage here, but other has extra storage. - int otherLength; - this.extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]; - System.arraycopy(otherInits.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, otherLength); - this.extraDefiniteNulls = new long[otherLength]; - this.extraDefiniteNonNulls = new long[otherLength]; + } + else { + resetLimit = this.extra[0].length; } - return this; + } + else if (otherInits.extra != null) { + // no storage here, but other has extra storage. + int otherLength = otherInits.extra[0].length; + this.extra = new long[extraLength][]; + for (int j = 0; j < extraLength; j++) { + this.extra[j] = new long[otherLength]; + } + System.arraycopy(otherInits.extra[1], 0, + this.extra[1], 0, otherLength); + copyLimit = otherLength; + } + int i; + if (otherHasNulls) { + for (i = 0; i < mergeLimit; i++) { + this.extra[2][i] = + (a1 = this.extra[2][i]) & + (b1 = otherInits.extra[2][i]) & ( + (nb4 = ~(b4 = otherInits.extra[5][i])) & + ((b2 = otherInits.extra[3][i]) & + (nb3 = ~(b3 = otherInits.extra[4][i])) & + (na3 = ~(a3 = this.extra[4][i])) & + ((a2 = this.extra[3][i]) & + (na4 = ~(a4 = this.extra[5][i])) | a4) | + (na2 = ~a2) & a3 & na4 & (nb2 = ~b2) & b3 ) | + b4 & (na3 & nb3 & (na4 & a2 | a4) | + na2 & a4 & nb2)); + this.extra[3][i] = + a2 & b2 & ~(a1 ^ b1) & (na3 & nb3 | na4 & nb4) | + a1 & b1 & (a2 ^ b2) & na3 & nb3 | + (a1 & na2 & (nb1 = ~b1) & b2 | ~a1 & a2 & b1 & nb2) & na4 & nb4; + this.extra[4][i] = + b1 & nb2 & nb4 | + ~a1 & (a3 | + a2 & na3 & (b1 | nb2)) | + (a1 | na2) & nb1 & b2 & nb3 | + nb1 & b3 | + a1 & na2 & (na4 | + b1 & nb2 & (a3 | b3)); + this.extra[5][i] = + a4 | b4; + thisHasNulls = thisHasNulls || + this.extra[5][i] != 0 || + this.extra[2][i] != 0 || + this.extra[3][i] != 0 || + this.extra[4][i] != 0; + } + } + else { + for (i = 0; i < mergeLimit; i++) { + this.extra[0][i] &= + otherInits.extra[0][i]; + this.extra[1][i] |= + otherInits.extra[1][i]; + this.extra[4][i] = + ~(~this.extra[2][i] & + ~this.extra[3][i] & + ~this.extra[4][i]) & + ~(this.extra[2][i] & + (this.extra[3][i] | + this.extra[5][i])); + this.extra[2][i] = + this.extra[3][i] = 0; + thisHasNulls = thisHasNulls || + this.extra[4][i] != 0 || + this.extra[5][i] != 0; + } + } + for (; i < copyLimit; i++) { + this.extra[1][i] = otherInits.extra[1][i]; + this.extra[4][i] = + ~(~otherInits.extra[2][i] & + ~otherInits.extra[3][i] & + ~otherInits.extra[4][i]) & + ~(otherInits.extra[2][i] & + (otherInits.extra[3][i] | + otherInits.extra[5][i])); + this.extra[5][i] = otherInits.extra[5][i]; + thisHasNulls = thisHasNulls || + this.extra[4][i] != 0 || + this.extra[5][i] != 0; + } + for (; i < resetLimit; i++) { + this.extra[4][i] = + ~(~this.extra[2][i] & + ~this.extra[3][i] & + ~this.extra[4][i]) & + ~(this.extra[2][i] & + (this.extra[3][i] | + this.extra[5][i])); + this.extra[0][i] = + this.extra[2][i] = + this.extra[3][i] = 0; + thisHasNulls = thisHasNulls || + this.extra[4][i] != 0 || + this.extra[5][i] != 0; + } } - - /* - * Answer the total number of fields in enclosing types of a given type - */ - static int numberOfEnclosingFields(ReferenceBinding type){ - - int count = 0; + if (thisHasNulls) { + this.tagBits |= NULL_FLAG_MASK; + } + else { + this.tagBits &= ~NULL_FLAG_MASK; + } + return this; +} + +/* + * Answer the total number of fields in enclosing types of a given type + */ +static int numberOfEnclosingFields(ReferenceBinding type){ + int count = 0; + type = type.enclosingType(); + while(type != null) { + count += type.fieldCount(); type = type.enclosingType(); - while(type != null) { - count += type.fieldCount(); - type = type.enclosingType(); - } - return count; } - - public int reachMode(){ - return this.reachMode; + return count; +} + +public UnconditionalFlowInfo nullInfoLessUnconditionalCopy() { + if (this == DEAD_END) { + return this; } - - public FlowInfo setReachMode(int reachMode) { - - if (this == DEAD_END) return this; // cannot modify DEAD_END - - // reset optional inits when becoming unreachable - if ((this.reachMode & UNREACHABLE) == 0 && (reachMode & UNREACHABLE) != 0) { + UnconditionalFlowInfo copy = new UnconditionalFlowInfo(); + copy.definiteInits = this.definiteInits; + copy.potentialInits = this.potentialInits; + copy.tagBits = this.tagBits & ~NULL_FLAG_MASK; + copy.maxFieldCount = this.maxFieldCount; + if (this.extra != null) { + int length; + copy.extra = new long[extraLength][]; + System.arraycopy(this.extra[0], 0, + (copy.extra[0] = + new long[length = this.extra[0].length]), 0, length); + System.arraycopy(this.extra[1], 0, + (copy.extra[1] = new long[length]), 0, length); + for (int j = 2; j < extraLength; j++) { + copy.extra[j] = new long[length]; + } + } + return copy; +} + +public FlowInfo safeInitsWhenTrue() { + return copy(); +} + +public FlowInfo setReachMode(int reachMode) { + if (reachMode == REACHABLE && this != DEAD_END) { // cannot modify DEAD_END + this.tagBits &= ~UNREACHABLE; + } + else { + if ((this.tagBits & UNREACHABLE) == 0) { + // reset optional inits when becoming unreachable + // see InitializationTest#test090 (and others) this.potentialInits = 0; - if (this.extraPotentialInits != null){ - for (int i = 0, length = this.extraPotentialInits.length; i < length; i++){ - this.extraPotentialInits[i] = 0; + if (this.extra != null) { + for (int i = 0, length = this.extra[0].length; + i < length; i++) { + this.extra[1][i] = 0; } } } - this.reachMode = reachMode; - - return this; + this.tagBits |= UNREACHABLE; } + return this; +} - public String toString(){ - - if (this == DEAD_END){ - return "FlowInfo.DEAD_END"; //$NON-NLS-1$ - } - return "FlowInfo"; //$NON-NLS-1$ +public String toString(){ + // PREMATURE consider printing bit fields as 0001 0001 1000 0001... + if (this == DEAD_END){ + return "FlowInfo.DEAD_END"; //$NON-NLS-1$ + } + if ((this.tagBits & NULL_FLAG_MASK) != 0) { + if (this.extra == null) { + return "FlowInfo"; //$NON-NLS-1$ + } + else { + String def = "FlowInfo 3 ? + 3 : + this.extra[0].length; + i < ceil; i++) { + def += "," + this.extra[0][i]; //$NON-NLS-1$ + pot += "," + this.extra[1][i]; //$NON-NLS-1$ + nullS1 += "," + this.extra[2][i]; //$NON-NLS-1$ + nullS2 += "," + this.extra[3][i]; //$NON-NLS-1$ + nullV1 += "," + this.extra[4][i]; //$NON-NLS-1$ + nullV2 += "," + this.extra[5][i]; //$NON-NLS-1$ + } + if (ceil < this.extra[0].length) { + def += ",..."; //$NON-NLS-1$ + pot += ",..."; //$NON-NLS-1$ + nullS1 += ",..."; //$NON-NLS-1$ + nullS2 += ",..."; //$NON-NLS-1$ + nullV1 += ",..."; //$NON-NLS-1$ + nullV2 += ",..."; //$NON-NLS-1$ + } + return def + pot + + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + + nullS1 + nullS2 + nullV1 + nullV2 + + "]>"; //$NON-NLS-1$ + } + } + else { + if (this.extra == null) { + return "FlowInfo"; //$NON-NLS-1$ + } + else { + String def = "FlowInfo 3 ? + 3 : + this.extra[0].length; + i < ceil; i++) { + def += "," + this.extra[0][i]; //$NON-NLS-1$ + pot += "," + this.extra[1][i]; //$NON-NLS-1$ + } + if (ceil < this.extra[0].length) { + def += ",..."; //$NON-NLS-1$ + pot += ",..."; //$NON-NLS-1$ + } + return def + pot + + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + + ", no null info>"; //$NON-NLS-1$ + } } +} + +public UnconditionalFlowInfo unconditionalCopy() { + return (UnconditionalFlowInfo) copy(); +} - public UnconditionalFlowInfo unconditionalInits() { - - // also see conditional inits, where it requests them to merge - return this; +public UnconditionalFlowInfo unconditionalFieldLessCopy() { + // TODO (maxime) may consider leveraging null contribution verification as it is done in copy + UnconditionalFlowInfo copy = new UnconditionalFlowInfo(); + copy.tagBits = this.tagBits; + copy.maxFieldCount = this.maxFieldCount; + int limit = this.maxFieldCount; + if (limit < BitCacheSize) { + long mask; + copy.definiteInits = this.definiteInits & (mask = ~((1L << limit)-1)); + copy.potentialInits = this.potentialInits & mask; + copy.nullAssignmentStatusBit1 = this.nullAssignmentStatusBit1 & mask; + copy.nullAssignmentStatusBit2 = this.nullAssignmentStatusBit2 & mask; + copy.nullAssignmentValueBit1 = this.nullAssignmentValueBit1 & mask; + copy.nullAssignmentValueBit2 = this.nullAssignmentValueBit2 & mask; + } + // use extra vector + if (this.extra == null) { + return copy; // if vector not yet allocated, then not initialized + } + int vectorIndex, length, copyStart; + if ((vectorIndex = (limit / BitCacheSize) - 1) >= + (length = this.extra[0].length)) { + return copy; // not enough room yet + } + long mask; + copy.extra = new long[extraLength][]; + if ((copyStart = vectorIndex + 1) < length) { + int copyLength = length - copyStart; + for (int j = 0; j < extraLength; j++) { + System.arraycopy(this.extra[j], copyStart, + (copy.extra[j] = new long[length]), copyStart, + copyLength); + } + } + else if (vectorIndex >= 0) { + for (int j = 0; j < extraLength; j++) { + copy.extra[j] = new long[length]; + } + } + if (vectorIndex >= 0) { + mask = ~((1L << (limit % BitCacheSize))-1); + for (int j = 0; j < extraLength; j++) { + copy.extra[j][vectorIndex] = + this.extra[j][vectorIndex] & mask; + } } + return copy; +} + +public UnconditionalFlowInfo unconditionalInits() { + // also see conditional inits, where it requests them to merge + return this; +} + +public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect() { + return this; +} } Index: compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java,v retrieving revision 1.150 diff -u -r1.150 CompilerOptions.java --- compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 14 Nov 2005 18:47:41 -0000 1.150 +++ compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 28 Nov 2005 12:08:08 -0000 @@ -984,6 +984,8 @@ case 'n' : if ("nls".equals(warningToken)) //$NON-NLS-1$ return NonExternalizedString; + if ("null".equals(warningToken)) //$NON-NLS-1$ + return NullReference; break; case 's' : if ("serial".equals(warningToken)) //$NON-NLS-1$ Index: compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java,v retrieving revision 1.56 diff -u -r1.56 MethodScope.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java 20 Oct 2005 13:26:45 -0000 1.56 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java 28 Nov 2005 12:08:08 -0000 @@ -402,8 +402,9 @@ if (!flowInfo.isReachable()) return -1; - UnconditionalFlowInfo unconditionalFlowInfo = flowInfo.unconditionalInits(); - long[] extraInits = unconditionalFlowInfo.extraDefiniteInits; + UnconditionalFlowInfo unconditionalFlowInfo = flowInfo.unconditionalInitsWithoutSideEffect(); + long[] extraInits = unconditionalFlowInfo.extra == null ? + null : unconditionalFlowInfo.extra[0]; long inits = unconditionalFlowInfo.definiteInits; checkNextEntry : for (int i = lastIndex; --i >= 0;) { if (definiteInits[i] == inits) { Index: compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java,v retrieving revision 1.274 diff -u -r1.274 ProblemReporter.java --- compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java 16 Nov 2005 12:19:37 -0000 1.274 +++ compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java 28 Nov 2005 12:08:10 -0000 @@ -1374,6 +1374,7 @@ case IProblem.LocalVariableCannotBeNull : case IProblem.LocalVariableCanOnlyBeNull : + case IProblem.LocalVariableMayBeNull : return CompilerOptions.NullReference; case IProblem.BoxingConversion : @@ -3980,6 +3981,15 @@ local.sourceEnd); } } +public void localVariableMayBeNull(LocalVariableBinding local, ASTNode location) { + String[] arguments = new String[] {new String(local.name)}; + this.handle( + IProblem.LocalVariableMayBeNull, + arguments, + arguments, + location.sourceStart, + location.sourceEnd); +} public void methodMustOverride(AbstractMethodDeclaration method) { MethodBinding binding = method.binding; this.handle( Index: compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties,v retrieving revision 1.190 diff -u -r1.190 messages.properties --- compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties 21 Oct 2005 07:19:17 -0000 1.190 +++ compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties 28 Nov 2005 12:08:10 -0000 @@ -324,6 +324,7 @@ 396 = Illegal modifier for the variable {0}; only final is permitted 397 = The variable {0} cannot be null; it was either set to a non-null value or assumed to be non-null when last used 398 = The variable {0} can only be null; it was either set to null or checked for null when last used +399 = The variable {0} may be null 400 = The type {3} must implement the inherited abstract method {2}.{0}({1}) 401 = Cannot override the final method from {0}