diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java index 49002c44880..243f84f5368 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java @@ -65,7 +65,9 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; -import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.*; +import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.ASSIGNMENT_CONTEXT; +import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.INVOCATION_CONTEXT; +import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.VANILLA_CONTEXT; import java.util.HashMap; import java.util.function.BiConsumer; @@ -75,6 +77,7 @@ import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; import org.eclipse.jdt.internal.compiler.codegen.Opcodes; +import org.eclipse.jdt.internal.compiler.flow.ConditionalFlowInfo; import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; @@ -221,16 +224,22 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl flowInfo = analyseNullAssertion(currentScope, argument, flowContext, flowInfo, true); break; case ARG_NONNULL_IF_TRUE: - recordFlowUpdateOnResult(((SingleNameReference) argument).localVariableBinding(), true, false); + LocalVariableBinding localBinding = ((SingleNameReference) argument).localVariableBinding(); + recordFlowUpdateOnResult(localBinding, true, false); flowInfo = argument.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + flowInfo = setUnreachable(flowInfo, flowContext, localBinding, AssertUtil.ARG_NONNULL_IF_TRUE); break; case ARG_NONNULL_IF_TRUE_NEGATABLE: - recordFlowUpdateOnResult(((SingleNameReference) argument).localVariableBinding(), true, true); + localBinding = ((SingleNameReference) argument).localVariableBinding(); + recordFlowUpdateOnResult(localBinding, true, true); flowInfo = argument.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + flowInfo = setUnreachable(flowInfo, flowContext, localBinding, AssertUtil.ARG_NONNULL_IF_TRUE_NEGATABLE); break; case ARG_NULL_IF_TRUE: - recordFlowUpdateOnResult(((SingleNameReference) argument).localVariableBinding(), false, true); + localBinding = ((SingleNameReference) argument).localVariableBinding(); + recordFlowUpdateOnResult(localBinding, false, true); flowInfo = argument.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); + flowInfo = setUnreachable(flowInfo, flowContext, localBinding, AssertUtil.ARG_NULL_IF_TRUE); break; default: flowInfo = argument.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); @@ -264,6 +273,49 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl flowContext.expireNullCheckedFieldInfo(); // no longer trust this info after any message send return flowInfo; } + +private FlowInfo setUnreachable(FlowInfo flowInfo, FlowContext flowContext, LocalVariableBinding localBinding, + AssertUtil assertUtil) { + if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) { + boolean isDefinitellyNull; + boolean isDefinitellyNonNull; + switch (assertUtil) { + case ARG_NONNULL_IF_TRUE: { + isDefinitellyNull = flowInfo.isDefinitelyNull(localBinding); + if (isDefinitellyNull) { + flowInfo = FlowInfo.conditional(flowInfo.copy(), flowInfo.copy()); + ((ConditionalFlowInfo) flowInfo).initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } + break; + } + case ARG_NULL_IF_TRUE: { + isDefinitellyNull = flowInfo.isDefinitelyNull(localBinding); + isDefinitellyNonNull = flowInfo.isDefinitelyNonNull(localBinding); + flowInfo = FlowInfo.conditional(flowInfo.copy(), flowInfo.copy()); + if (isDefinitellyNull) { + ((ConditionalFlowInfo) flowInfo).initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } else if (isDefinitellyNonNull) { + ((ConditionalFlowInfo) flowInfo).initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } + break; + } + case ARG_NONNULL_IF_TRUE_NEGATABLE: { + isDefinitellyNull = flowInfo.isDefinitelyNull(localBinding); + isDefinitellyNonNull = flowInfo.isDefinitelyNonNull(localBinding); + flowInfo = FlowInfo.conditional(flowInfo.copy(), flowInfo.copy()); + if (isDefinitellyNull) { + ((ConditionalFlowInfo) flowInfo).initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } else if (isDefinitellyNonNull) { + ((ConditionalFlowInfo) flowInfo).initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS); + } + break; + } + default: + break; + } + } + return flowInfo; +} public void recordFlowUpdateOnResult(LocalVariableBinding local, boolean nonNullIfTrue, boolean negatable) { this.flowUpdateOnBooleanResult = (f, result) -> { if (result || negatable) { diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java index c8f2847fe67..44c22ebc77e 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java @@ -18568,4 +18568,166 @@ void m(String packageName, IPS[] packages) { """ }); } +// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runNegativeTest( + new String[] { + "X.java", + """ + import java.util.Objects; + public class X { + public void test() { + String name = null; + if (Objects.isNull(name)) { + System.out.println("Name is null"); + return; + } + System.out.println(name.substring(0, 4)); + } + } + """ + }, + """ + ---------- + 1. WARNING in X.java (at line 9) + System.out.println(name.substring(0, 4)); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Dead code + ---------- + """); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461SuppressWarnings() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runConformTest( + new String[] { + "X.java", + """ + import java.util.Objects; + @SuppressWarnings("unused") + public class X { + public void test() { + String name = null; + if (Objects.isNull(name)) { + System.out.println("Name is null"); + return; + } + System.out.println(name.substring(0, 4)); + } + } + """ + }, + ""); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461_a() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runNegativeTest( + new String[] { + "X.java", + """ + import java.util.Objects; + public class X { + public void test() { + String name = "name"; + if (Objects.nonNull(name)) { + System.out.println("Name is null"); + return; + } + System.out.println(name.substring(0, 4)); + } + } + """ + }, + """ + ---------- + 1. WARNING in X.java (at line 9) + System.out.println(name.substring(0, 4)); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Dead code + ---------- + """); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461_b() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runNegativeTest( + new String[] { + "X.java", + """ + import java.util.Objects; + public class X { + public void test() { + String name = null; + if (!Objects.nonNull(name)) { + System.out.println("Name is null"); + return; + } + System.out.println(name.substring(0, 4)); + } + } + """ + }, + """ + ---------- + 1. WARNING in X.java (at line 9) + System.out.println(name.substring(0, 4)); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Dead code + ---------- + """); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461_c() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String... args) { + Foo foo = new Foo(); + if (Bar.class.isInstance(foo)) { + return; + } + System.out.println("Hello, world!"); + } + } + class Foo{} + class Bar{} + """ + }, + """ + """); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1461 +public void testGH1461_d() { + if (this.complianceLevel < ClassFileConstants.JDK15) return; + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String... args) { + Foo foo = null; + if (!Bar.class.isInstance(foo)) { + return; + } + System.out.println("Hello, world!"); + } + } + class Foo{} + class Bar{} + """ + }, + """ + ---------- + 1. WARNING in X.java (at line 7) + System.out.println("Hello, world!"); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Dead code + ---------- + """); +} } \ No newline at end of file