### Eclipse Workspace Patch 1.0 #P org.eclipse.jdt.core 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.98 diff -u -r1.98 ConditionalExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java 14 Jan 2011 17:02:24 -0000 1.98 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java 3 Mar 2011 23:02:51 -0000 @@ -7,7 +7,10 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Stephen Herrmann - Contributions for bugs 133125, 292478 + * Stephen Herrmann - Contributions for + * bug 133125 - [compiler][null] need to report the null status of expressions and analyze them simultaneously + * bug 292478 - Report potentially null across variable assignment + * bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -29,6 +32,10 @@ int trueInitStateIndex = -1; int falseInitStateIndex = -1; int mergedInitStateIndex = -1; + + // remember flow infos of true/false branches for nullStatus (see comment in analyseCode): + private FlowInfo trueBranchInfo; + private FlowInfo falseBranchInfo; public ConditionalExpression( Expression condition, @@ -84,7 +91,26 @@ } else if (isConditionOptimizedFalse) { mergedInfo = falseFlowInfo.addPotentialInitializationsFrom(trueFlowInfo); } else { - // if ((t && (v = t)) ? t : t && (v = f)) r = v; -- ok + // this block must meet two conflicting requirements (see https://bugs.eclipse.org/324178): + // (1) For null analysis of "Object o2 = (o1 != null) ? o1 : new Object();" we need to distinguish + // the paths *originating* from the evaluation of the condition to true/false respectively. + // This is used to determine the possible null status of the entire conditional expression. + // (2) For definite assignment analysis (JLS 16.1.5) of boolean conditional expressions of the form + // "if (c1 ? expr1 : expr2) use(v);" we need to check whether any variable v will be definitely + // assigned whenever the entire conditional expression evaluates to true (to reach the then branch). + // I.e., we need to collect flowInfo *towards* the overall outcome true/false + // (regardless of the evaluation of the condition). + + // to support (1) save the branches originating from the condition (for use by nullStatus): + this.trueBranchInfo = trueFlowInfo; + this.falseBranchInfo = falseFlowInfo; + + // to support (2) we split the true/false branches according to their inner structure. Consider this: + // if (b ? false : (true && (v = false))) return v; -- ok + // - expr1 ("false") has no path towards true (mark as unreachable) + // - expr2 ("(true && (v = false))") has a branch towards true on which v is assigned. + // -> merging these two branches yields: v is assigned + // - the paths towards false are irrelevant since the enclosing if has no else. cst = this.optimizedIfTrueConstant; boolean isValueIfTrueOptimizedTrue = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isValueIfTrueOptimizedFalse = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; @@ -93,26 +119,26 @@ boolean isValueIfFalseOptimizedTrue = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == true; boolean isValueIfFalseOptimizedFalse = cst != null && cst != Constant.NotAConstant && cst.booleanValue() == false; - UnconditionalFlowInfo trueInfoWhenTrue = trueFlowInfo.initsWhenTrue().unconditionalCopy(); - UnconditionalFlowInfo falseInfoWhenTrue = falseFlowInfo.initsWhenTrue().unconditionalCopy(); - UnconditionalFlowInfo trueInfoWhenFalse = trueFlowInfo.initsWhenFalse().unconditionalInits(); - UnconditionalFlowInfo falseInfoWhenFalse = falseFlowInfo.initsWhenFalse().unconditionalInits(); + UnconditionalFlowInfo trueFlowTowardsTrue = trueFlowInfo.initsWhenTrue().unconditionalCopy(); + UnconditionalFlowInfo falseFlowTowardsTrue = falseFlowInfo.initsWhenTrue().unconditionalCopy(); + UnconditionalFlowInfo trueFlowTowardsFalse = trueFlowInfo.initsWhenFalse().unconditionalInits(); + UnconditionalFlowInfo falseFlowTowardsFalse = falseFlowInfo.initsWhenFalse().unconditionalInits(); if (isValueIfTrueOptimizedFalse) { - trueInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); + trueFlowTowardsTrue.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfFalseOptimizedFalse) { - falseInfoWhenTrue.setReachMode(FlowInfo.UNREACHABLE); + falseFlowTowardsTrue.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfTrueOptimizedTrue) { - trueInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); + trueFlowTowardsFalse.setReachMode(FlowInfo.UNREACHABLE); } if (isValueIfFalseOptimizedTrue) { - falseInfoWhenFalse.setReachMode(FlowInfo.UNREACHABLE); + falseFlowTowardsFalse.setReachMode(FlowInfo.UNREACHABLE); } mergedInfo = FlowInfo.conditional( - trueInfoWhenTrue.mergedWith(falseInfoWhenTrue), - trueInfoWhenFalse.mergedWith(falseInfoWhenFalse)); + trueFlowTowardsTrue.mergedWith(falseFlowTowardsTrue), + trueFlowTowardsFalse.mergedWith(falseFlowTowardsFalse)); } this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); @@ -314,8 +340,11 @@ } return this.valueIfFalse.nullStatus(flowInfo); } - int ifTrueNullStatus = this.valueIfTrue.nullStatus(flowInfo), - ifFalseNullStatus = this.valueIfFalse.nullStatus(flowInfo); + int ifTrueNullStatus = + this.valueIfTrue.nullStatus(this.trueBranchInfo != null ? this.trueBranchInfo : flowInfo.initsWhenTrue()); + int ifFalseNullStatus = + this.valueIfFalse.nullStatus(this.falseBranchInfo != null ? this.falseBranchInfo : flowInfo.initsWhenFalse()); + if (ifTrueNullStatus == ifFalseNullStatus) { return ifTrueNullStatus; } #P org.eclipse.jdt.core.tests.compiler Index: src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java,v retrieving revision 1.114 diff -u -r1.114 NullReferenceTest.java --- src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java 3 Mar 2011 13:00:33 -0000 1.114 +++ src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java 3 Mar 2011 23:03:10 -0000 @@ -16,6 +16,7 @@ * bug 332637 - Dead Code detection removing code that isn't dead * bug 338303 - Warning about Redundant assignment conflicts with definite assignment * bug 336428 - [compiler][null] bogus warning "redundant null check" in condition of do {} while() loop + * bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself *******************************************************************************/ package org.eclipse.jdt.core.tests.compiler.regression; @@ -43,7 +44,7 @@ // Only the highest compliance level is run; add the VM argument // -Dcompliance=1.4 (for example) to lower it if needed static { -// TESTS_NAMES = new String[] { "testBug336428e" }; +// TESTS_NAMES = new String[] { "testBug324178" }; // TESTS_NUMBERS = new int[] { 561 }; // TESTS_RANGE = new int[] { 1, 2049 }; } @@ -14204,4 +14205,106 @@ }, ""); } +// Bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself +public void testBug324178() { + this.runConformTest( + new String[] { + "Bug324178.java", + "public class Bug324178 {\n" + + " boolean b;\n" + + " void foo(Object u) {\n" + + " if (u == null) {}\n" + + " Object o = (u == null) ? new Object() : u;\n" + + " o.toString(); // Incorrect potential NPE\n" + + " }\n" + + "}\n" + }, + ""); +} + +// Bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself +// definite assignment along all true-yielding paths is sufficient +public void testBug324178b() { + this.runConformTest( + new String[] { + "Bug324178.java", + "public class Bug324178 {\n" + + " boolean foo(boolean b) {\n" + + " boolean v;\n" + + " if (b ? false : (true && (v = true)))\n" + + " return v;\n" + // OK to read v! + " return false;\n" + + " }\n" + + " public static void main(String[] args) {\n" + + " System.out.print(new Bug324178().foo(false));\n" + + " }\n" + + "}\n" + }, + "true"); +} +// Bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself +// definite assignment along all true-yielding paths is sufficient +public void testBug324178c() { + this.runConformTest( + new String[] { + "Bug324178.java", + "public class Bug324178 {\n" + + " boolean foo() {\n" + + " boolean r=false;" + + " boolean v;\n" + + " if ((true && (v = true)) ? true : true && (v = false)) r = v;\n" + + " return r;\n" + + " }\n" + + " public static void main(String[] args) {\n" + + " System.out.print(new Bug324178().foo());\n" + + " }\n" + + "}\n" + }, + "true"); +} +// Bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself +// must detect that b2 may be uninitialized +public void testBug324178d() { + if (this.complianceLevel < ClassFileConstants.JDK1_5) + return; + this.runNegativeTest( + new String[] { + "Bug324178.java", + "public class Bug324178 {\n" + + " boolean foo(boolean b1) {\n" + + " Boolean b2;\n" + + " if (b1 ? (b2 = Boolean.TRUE) : null)\n" + + " return b2;\n" + + " return false;\n" + + " }\n" + + " public static void main(String[] args) {\n" + + " System.out.print(new Bug324178().foo(true));\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in Bug324178.java (at line 5)\n" + + " return b2;\n" + + " ^^\n" + + "The local variable b2 may not have been initialized\n" + + "----------\n"); +} +// Bug 324178 - [null] ConditionalExpression.nullStatus(..) doesn't take into account the analysis of condition itself +public void testBug324178e() { + this.runConformTest( + new String[] { + "Bug324178.java", + "public class Bug324178 {\n" + + " boolean b;\n" + + " void foo(Boolean u) {\n" + + " if (u == null) {}\n" + + " Boolean o;\n" + + " o = (u == null) ? Boolean.TRUE : u;\n" + + " o.toString(); // Incorrect potential NPE\n" + + " }\n" + + "}\n" + }, + ""); +} + } \ No newline at end of file