diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index c5a09170bc82..380e5c25d289 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -259,6 +259,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { for (PsiElement element : elements) { if (element instanceof PsiClass) { addInstruction(new EmptyInstruction(element)); + handleEscapedVariables(element); } else if (element instanceof PsiVariable) { PsiVariable variable = (PsiVariable)element; @@ -667,10 +668,27 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { startElement(expression); DfaValue dfaValue = myFactory.createValue(expression); addInstruction(new PushInstruction(dfaValue, expression)); + handleEscapedVariables(expression); addInstruction(new LambdaInstruction(expression)); finishElement(expression); } + private void handleEscapedVariables(PsiElement element) { + Set variables = VariableAccessUtils.collectUsedVariables(element); + Set escapedVars = new HashSet<>(); + for (DfaValue value : getFactory().getValues()) { + if(value instanceof DfaVariableValue && !((DfaVariableValue)value).isNegated()) { + PsiModifierListOwner var = ((DfaVariableValue)value).getPsiVariable(); + if (var instanceof PsiLocalVariable && variables.contains(var)) { + escapedVars.add((DfaVariableValue)value); + } + } + } + if (!escapedVars.isEmpty()) { + addInstruction(new EscapeInstruction(escapedVars)); + } + } + @Override public void visitReturnStatement(PsiReturnStatement statement) { startElement(statement); @@ -1086,9 +1104,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { arrayWriteTarget = null; } } + DfaValue arrayValue = myFactory.withFact(myFactory.createTypeValue(type, Nullness.NOT_NULL), DfaFactType.LOCALITY, true); if (arrayWriteTarget != null) { addInstruction(new PushInstruction(arrayWriteTarget, null, true)); - addInstruction(new PushInstruction(getFactory().createTypeValue(type, Nullness.NOT_NULL), expression)); + addInstruction(new PushInstruction(arrayValue, expression)); addInstruction(new AssignInstruction(originalExpression, arrayWriteTarget)); int index = 0; for (PsiExpression initializer : initializers) { @@ -1124,7 +1143,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new PopInstruction()); } addInstruction(new PushInstruction(var, null, true)); - addInstruction(new PushInstruction(getFactory().createTypeValue(type, Nullness.NOT_NULL), expression)); + addInstruction(new PushInstruction(arrayValue, expression)); addInstruction(new AssignInstruction(originalExpression, var)); } // Declaration: write array length @@ -1655,7 +1674,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } // stack: ... var.length actual_size addInstruction(new PushInstruction(var, null, true)); - addInstruction(new PushInstruction(getFactory().createTypeValue(type, Nullness.NOT_NULL), expression)); + DfaValue arrayValue = myFactory.withFact(myFactory.createTypeValue(type, Nullness.NOT_NULL), DfaFactType.LOCALITY, true); + addInstruction(new PushInstruction(arrayValue, expression)); addInstruction(new AssignInstruction(expression, var)); // stack: ... var.length actual_size var addInstruction(new SpliceInstruction(3, 0, 2, 1)); @@ -1667,6 +1687,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { else { pushUnknown(); // qualifier PsiMethod constructor = pushConstructorArguments(expression); + PsiAnonymousClass anonymousClass = expression.getAnonymousClass(); + if (anonymousClass != null) { + handleEscapedVariables(anonymousClass); + } addConditionalRuntimeThrow(); addInstruction(new MethodCallInstruction(expression, null, constructor == null ? Collections.emptyList() : getMethodContracts(constructor))); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java index 72dc1b85ceb5..f7169e004db5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java @@ -220,6 +220,19 @@ public abstract class DfaFactType extends Key { } }; + public static final DfaFactType LOCALITY = new DfaFactType("Locality") { + @Override + boolean isUnknown(@NotNull Boolean fact) { + return !fact; + } + + @NotNull + @Override + public String toString(@NotNull Boolean fact) { + return fact ? "Local object" : ""; + } + }; + private final String myName; private DfaFactType(String name) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index 8efc155719ea..5e1d76e2d4e7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -1242,10 +1242,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } for (DfaVariableValue value : vars) { - if (value.isFlushableByCalls() && (value.getQualifier() == null || - getValueFact(value.getQualifier(), DfaFactType.MUTABILITY) != Mutability.UNMODIFIABLE)) { - doFlush(value, shouldMarkUnknown(value)); + if (!value.isFlushableByCalls()) continue; + DfaVariableValue qualifier = value.getQualifier(); + if (qualifier != null) { + if (getValueFact(qualifier, DfaFactType.MUTABILITY) == Mutability.UNMODIFIABLE || + Boolean.TRUE.equals(getValueFact(qualifier, DfaFactType.LOCALITY))) { + continue; + } } + doFlush(value, shouldMarkUnknown(value)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 15ac209b7281..e4a81d167a48 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -56,6 +56,11 @@ public class StandardInstructionVisitor extends InstructionVisitor { // (e.g. during StateMerger#mergeByFacts), so we try to restore the original destination. dfaDest = instruction.getAssignedValue(); } + if (dfaSource == dfaDest) { + memState.push(dfaDest); + return nextInstruction(instruction, runner, memState); + } + memState.dropFact(dfaSource, DfaFactType.LOCALITY); PsiExpression lValue = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()); PsiExpression rValue = instruction.getRExpression(); @@ -373,6 +378,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { DfaValue arg = memState.pop(); int paramIndex = argCount - i - 1; + memState.dropFact(arg, DfaFactType.LOCALITY); PsiElement anchor = instruction.getArgumentAnchor(paramIndex); Nullness requiredNullability = instruction.getArgRequiredNullability(paramIndex); if (requiredNullability == Nullness.NOT_NULL) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/EscapeInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/EscapeInstruction.java new file mode 100644 index 000000000000..665962879922 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/EscapeInstruction.java @@ -0,0 +1,27 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection.dataFlow.instructions; + +import com.intellij.codeInspection.dataFlow.*; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; + +import java.util.Set; + +/** + * Marks given variables as escaped (usually necessary for captured variables in lambdas/local classes) + */ +public class EscapeInstruction extends Instruction { + private final Set myEscapedVars; + + public EscapeInstruction(Set escapedVars) {myEscapedVars = escapedVars;} + + @Override + public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { + myEscapedVars.forEach(var -> stateBefore.dropFact(var, DfaFactType.LOCALITY)); + return nextInstruction(runner, stateBefore); + } + + @Override + public String toString() { + return "ESCAPE " + myEscapedVars; + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/EscapeAnalysis.java b/java/java-tests/testData/inspection/dataFlow/fixture/EscapeAnalysis.java new file mode 100644 index 000000000000..f0c8ec1bc42d --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/EscapeAnalysis.java @@ -0,0 +1,63 @@ +class EscapeAnalysis { + void testSimple() { + int[] x = new int[] {0}; + sideEffect(); + if(x[0] == 1) { + System.out.println("Impossible"); + } + } + + void testEscaped() { + int[] x = new int[] {0}; + sideEffect(x); + if(x[0] == 1) { + System.out.println("Who knows?"); + } + } + + void testEscapedAfterLoop() { + int[] x; + for (int i = 0; i < 10; i++) { + x = new int[] {0}; + sideEffect(); + if(x[0] == 1) { + System.out.println("Impossible"); + } + sideEffect(x); + if(x[0] == 1) { + System.out.println("Who knows?"); + } + } + } + + native void sideEffect(); + native void sideEffect(int[] array); + + void testLambda() { + int[] x = new int[] {0}; + Runnable r = () -> x[0] = 1; + r.run(); + if(x[0] == 1) { + System.out.println("ok"); + } + } + + class X { + X() {run();} + + void run() {}; + } + + + void testClass() { + int[] x = new int[] {0}; + new X() { + void run() { + x[0] = Math.random() > 0.5 ? 1 : 0; + } + } + if(x[0] == 1) { + System.out.println("possible"); + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index 930afe6ea8e8..6c31f926c3d0 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -223,4 +223,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { public void testOptionalTooComplex() { doTest(); } public void testMethodReferenceBoundToNullable() { doTestWithCustomAnnotations(); } + public void testEscapeAnalysis() { doTest(); } } \ No newline at end of file