mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
dfa: fighting too complex methods; join complementary memory states after fork to avoid having too many states
This commit is contained in:
+62
-29
@@ -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();
|
||||
}
|
||||
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
|
||||
+60
@@ -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(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user