mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-188303 Track value escaping (arrays only currently)
This commit is contained in:
+27
-3
@@ -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) {
|
||||
|
||||
+8
-3
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -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) {
|
||||
|
||||
+27
@@ -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(); }
|
||||
}
|
||||
Reference in New Issue
Block a user