dfa: fighting too complex methods; join complementary memory states after fork to avoid having too many states

This commit is contained in:
peter
2013-09-21 00:00:03 +02:00
parent 0ac20848d3
commit fea88718d0
6 changed files with 170 additions and 35 deletions
@@ -38,6 +38,7 @@ import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,7 +124,7 @@ public class DataFlowRunner {
return RunnerResult.TOO_COMPLEX;
}
final ArrayList<DfaInstructionState> queue = new ArrayList<DfaInstructionState>();
final PriorityQueue<DfaInstructionState> queue = new PriorityQueue<DfaInstructionState>();
for (final DfaMemoryState initialState : initialStates) {
queue.add(new DfaInstructionState(myInstructions[0], initialState));
}
@@ -131,39 +132,38 @@ public class DataFlowRunner {
WorkingTimeMeasurer measurer = new WorkingTimeMeasurer(shouldCheckTimeLimit() ? ourTimeLimit : ourTimeLimit * 42);
int count = 0;
while (!queue.isEmpty()) {
if (count % 1024 == 0 && measurer.isTimeOver()) {
LOG.debug("Too complex because the analysis took too long");
psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode());
return RunnerResult.TOO_COMPLEX;
}
ProgressManager.checkCanceled();
for (DfaInstructionState instructionState : getNextInstructionStates(queue)) {
if (count++ % 1024 == 0 && measurer.isTimeOver()) {
LOG.debug("Too complex because the analysis took too long");
psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode());
return RunnerResult.TOO_COMPLEX;
}
ProgressManager.checkCanceled();
DfaInstructionState instructionState = queue.remove(0);
if (LOG.isDebugEnabled()) {
LOG.debug(instructionState.toString());
}
//System.out.println(instructionState.toString());
if (LOG.isDebugEnabled()) {
LOG.debug(instructionState.toString());
}
//System.out.println(instructionState.toString());
Instruction instruction = instructionState.getInstruction();
long distance = instructionState.getDistanceFromStart();
Instruction instruction = instructionState.getInstruction();
if (instruction instanceof BranchingInstruction) {
if (!instruction.setMemoryStateProcessed(instructionState.getMemoryState().createCopy())) {
LOG.debug("Too complex because too many different possible states");
return RunnerResult.TOO_COMPLEX; // Too complex :(
if (instruction instanceof BranchingInstruction) {
if (instruction.isMemoryStateProcessed(instructionState.getMemoryState())) {
continue;
}
if (!instruction.setMemoryStateProcessed(instructionState.getMemoryState().createCopy())) {
LOG.debug("Too complex because too many different possible states");
return RunnerResult.TOO_COMPLEX; // Too complex :(
}
}
DfaInstructionState[] after = acceptInstruction(visitor, instructionState);
for (DfaInstructionState state : after) {
if (instruction.getIndex() < endOffset) {
queue.add(state);
}
}
}
DfaInstructionState[] after = acceptInstruction(visitor, instructionState);
for (DfaInstructionState state : after) {
Instruction nextInstruction = state.getInstruction();
if ((!(nextInstruction instanceof BranchingInstruction) || !nextInstruction.isMemoryStateProcessed(state.getMemoryState())) && instruction.getIndex() < endOffset) {
state.setDistanceFromStart(distance + 1);
queue.add(state);
}
}
count++;
}
psiBlock.putUserData(TOO_EXPENSIVE_HASH, null);
@@ -182,6 +182,39 @@ public class DataFlowRunner {
}
}
private static List<DfaInstructionState> getNextInstructionStates(PriorityQueue<DfaInstructionState> queue) {
DfaInstructionState state = queue.poll();
Instruction instruction = state.getInstruction();
DfaInstructionState next = queue.peek();
if (next == null || next.compareTo(state) != 0) return Collections.singletonList(state);
List<DfaInstructionState> sameInstructionStates = ContainerUtil.newArrayList();
sameInstructionStates.add(state);
while (!queue.isEmpty() && queue.peek().compareTo(state) == 0) {
sameInstructionStates.add(queue.poll());
}
findMergeable:
while (true) {
for (int i = 0; i < sameInstructionStates.size(); i++) {
DfaInstructionState state1 = sameInstructionStates.get(i);
for (int j = i + 1; j < sameInstructionStates.size(); j++) {
DfaInstructionState state2 = sameInstructionStates.get(j);
DfaMemoryState merged = state1.getMemoryState().mergeWith(state2.getMemoryState());
if (merged != null) {
sameInstructionStates.set(i, new DfaInstructionState(instruction, merged));
sameInstructionStates.remove(j);
continue findMergeable;
}
}
}
break;
}
return sameInstructionStates;
}
protected boolean shouldCheckTimeLimit() {
return !ApplicationManager.getApplication().isUnitTestMode();
}
@@ -27,21 +27,16 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
import org.jetbrains.annotations.NotNull;
public class DfaInstructionState {
public class DfaInstructionState implements Comparable<DfaInstructionState> {
public static final DfaInstructionState[] EMPTY_ARRAY = new DfaInstructionState[0];
private final DfaMemoryState myBeforeMemoryState;
private final Instruction myInstruction;
private long myDistanceFromStart = 0;
public DfaInstructionState(@NotNull Instruction myInstruction, @NotNull DfaMemoryState myBeforeMemoryState) {
this.myBeforeMemoryState = myBeforeMemoryState;
this.myInstruction = myInstruction;
}
public long getDistanceFromStart() { return myDistanceFromStart; }
public void setDistanceFromStart(long distanceFromStart) { myDistanceFromStart = distanceFromStart; }
@NotNull
public Instruction getInstruction() {
return myInstruction;
@@ -55,4 +50,9 @@ public class DfaInstructionState {
public String toString() {
return getInstruction().getIndex() + " " + getInstruction() + ": " + getMemoryState().toString();
}
@Override
public int compareTo(@NotNull DfaInstructionState o) {
return myInstruction.getIndex() - o.myInstruction.getIndex();
}
}
@@ -68,4 +68,7 @@ public interface DfaMemoryState {
void markEphemeral();
boolean isEphemeral();
@Nullable
DfaMemoryState mergeWith(DfaMemoryState other);
}
@@ -291,6 +291,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (set == null) {
return Collections.emptyList();
}
return getEqClassValues(set);
}
private List<DfaValue> getEqClassValues(SortedIntSet set) {
final List<DfaValue> result = new ArrayList<DfaValue>(set.size());
set.forEach(new TIntProcedure() {
@Override
@@ -531,6 +535,62 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return myEphemeral;
}
@Nullable
@Override
public DfaMemoryState mergeWith(DfaMemoryState _other) {
DfaMemoryStateImpl other = (DfaMemoryStateImpl)_other;
if (myEphemeral != other.myEphemeral ||
!myStack.equals(other.myStack) ||
!myOffsetStack.equals(other.myOffsetStack)) {
return null;
}
Set<DfaVariableValue> differentVars = ContainerUtil.newHashSet();
addDifferentlyValuedVariables(other, differentVars);
other.addDifferentlyValuedVariables(this, differentVars);
for (DfaVariableValue differentVar : differentVars) {
DfaMemoryStateImpl copy1 = createCopy();
DfaMemoryStateImpl copy2 = other.createCopy();
copy1.flushVariable(differentVar);
copy2.flushVariable(differentVar);
copy1.myUnknownVariables.addAll(copy2.myUnknownVariables);
copy2.myUnknownVariables.addAll(copy1.myUnknownVariables);
if (!copy1.equals(copy2)) {
continue;
}
if (isNull(differentVar) || other.isNull(differentVar)) {
copy1.getVariableState(differentVar).setNullable(true);
}
return copy1;
}
return null;
}
private void addDifferentlyValuedVariables(DfaMemoryStateImpl other, Set<DfaVariableValue> differentVars) {
for (SortedIntSet aClass : myEqClasses) {
if (aClass == null || aClass.size() < 2) continue;
List<DfaValue> values = getEqClassValues(aClass);
outer: for (DfaValue var : values) {
if (var instanceof DfaVariableValue && !differentVars.contains(var)) {
for (DfaValue val : values) {
if (val != var && other.areDistinct(var, val)) {
differentVars.add((DfaVariableValue)var);
continue outer;
}
}
}
}
}
}
private boolean areDistinct(DfaValue val1, DfaValue val2) {
return myDistinctClasses.contains(createPair(getEqClassIndex(val1), getEqClassIndex(val2)));
}
@Override
public boolean applyInstanceofOrNull(DfaRelationValue dfaCond) {
DfaValue left = unwrap(dfaCond.getLeftOperand());
@@ -0,0 +1,37 @@
class Some {
void foo(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, Foo foo) {
if (i1 == 0) {
i3 = 2;
System.out.println(foo.getBar1().length());
}
if (i2 == 0) System.out.println(foo.getBar2().length());
if (i3 == 0) System.out.println(foo.getBar3().length());
if (i4 == 0) System.out.println(foo.getBar4().length());
if (i5 == 0) System.out.println(foo.getBar5().length());
if (i6 == 0) System.out.println(foo.getBar6().length());
if (i7 == 0) System.out.println(foo.getBar7().length());
if (i8 == 0) System.out.println(foo.getBar8().length());
if (i9 == 0) System.out.println(foo.getBar9().length());
if (i10 == 0) System.out.println(foo.getBar10().length());
if (i11 == 0) System.out.println(foo.getBar11().length());
if (i12 == 0) System.out.println(foo.getBar12().length());
if (i13 == 0) System.out.println(foo.getBar13().length());
}
}
interface Foo {
String getBar1();
String getBar2();
String getBar3();
String getBar4();
String getBar5();
String getBar6();
String getBar7();
String getBar8();
String getBar9();
String getBar10();
String getBar11();
String getBar12();
String getBar13();
}
@@ -298,5 +298,7 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
public void testNoConfusionWithAnonymousConstantInitializer() { doTest(); }
public void testForeachOverWildcards() { doTest(); }
public void testManySequentialIfsNotComplex() { doTest(); }
public void _testNullCheckBeforeInstanceof() { doTest(); }
}