IDEA-188303 Track value escaping (arrays only currently)

This commit is contained in:
Tagir Valeev
2018-03-24 18:55:02 +07:00
parent f83334f2e9
commit e1e9158472
7 changed files with 145 additions and 6 deletions
@@ -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<PsiVariable> variables = VariableAccessUtils.collectUsedVariables(element);
Set<DfaVariableValue> 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)));
@@ -220,6 +220,19 @@ public abstract class DfaFactType<T> extends Key<T> {
}
};
public static final DfaFactType<Boolean> LOCALITY = new DfaFactType<Boolean>("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) {
@@ -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));
}
}
@@ -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) {
@@ -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<DfaVariableValue> myEscapedVars;
public EscapeInstruction(Set<DfaVariableValue> 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;
}
}
@@ -0,0 +1,63 @@
class EscapeAnalysis {
void testSimple() {
int[] x = new int[] {0};
sideEffect();
if(<warning descr="Condition 'x[0] == 1' is always 'false'">x[0] == 1</warning>) {
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(<warning descr="Condition 'x[0] == 1' is always 'false'">x[0] == 1</warning>) {
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");
}
}
}
@@ -223,4 +223,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testOptionalTooComplex() { doTest(); }
public void testMethodReferenceBoundToNullable() { doTestWithCustomAnnotations(); }
public void testEscapeAnalysis() { doTest(); }
}