fighting too complex methods: merge states which differ only in unknown variables

This commit is contained in:
peter
2013-09-26 23:33:22 +02:00
parent c0b7d5a74d
commit 12ef8082a1
6 changed files with 106 additions and 20 deletions
@@ -161,10 +161,11 @@ public class DataFlowRunner {
if (instruction instanceof BranchingInstruction) {
BranchingInstruction branching = (BranchingInstruction)instruction;
if (processedStates.get(branching).contains(instructionState.getMemoryState())) {
Collection<DfaMemoryState> processed = processedStates.get(branching);
if (processed.contains(instructionState.getMemoryState())) {
continue;
}
if (processedStates.get(branching).size() > MAX_STATES_PER_BRANCH) {
if (processed.size() > MAX_STATES_PER_BRANCH) {
LOG.debug("Too complex because too many different possible states");
return RunnerResult.TOO_COMPLEX; // Too complex :(
}
@@ -102,6 +102,9 @@ class StateQueue {
if (nextStates == null) {
nextStates = merger.mergeByType();
}
if (nextStates == null) {
nextStates = merger.mergeByUnknowns();
}
if (nextStates == null) break;
memoryStates = nextStates;
}
@@ -136,11 +136,18 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
public int hashCode() {
return (((getNonTrivialEqClasses().hashCode() * 31 +
getDistinctClassPairs().hashCode()) * 31 +
myStack.hashCode()) * 31 +
myUnknownVariables.hashCode()) * 31 +
myVariableStates.hashCode();
return getPartialHashCode(true);
}
int getPartialHashCode(boolean unknowns) {
int hash = ((getNonTrivialEqClasses().hashCode() * 31 +
getDistinctClassPairs().hashCode()) * 31 +
myStack.hashCode()) * 31 +
myVariableStates.hashCode();
if (unknowns) {
hash = hash * 31 + myUnknownVariables.hashCode();
}
return hash;
}
@SuppressWarnings({"HardCodedStringLiteral"})
@@ -38,7 +38,6 @@ import java.util.Set;
class StateMerger {
private final List<DfaMemoryStateImpl> myStates;
private final MultiMap<UnorderedPair<DfaValue>,DfaMemoryStateImpl> myStatesByEq = new MultiMap<UnorderedPair<DfaValue>, DfaMemoryStateImpl>();
private final MultiMap<Pair<DfaVariableValue, DfaPsiType>,DfaMemoryStateImpl> myStatesByInstanceof = new MultiMap<Pair<DfaVariableValue, DfaPsiType>, DfaMemoryStateImpl>();
private final Map<DfaMemoryStateImpl, Map<DfaVariableValue, DfaConstValue>> myVarValues = ContainerUtil.newIdentityHashMap();
public StateMerger(List<DfaMemoryStateImpl> states) {
@@ -54,12 +53,6 @@ class StateMerger {
}
}
myVarValues.put(state, varValues);
for (DfaVariableValue value : state.getChangedVariable()) {
for (DfaPsiType instanceofValue : state.getVariableState(value).myInstanceofValues) {
myStatesByInstanceof.putValue(Pair.create(value, instanceofValue), state);
}
}
}
}
@@ -82,7 +75,8 @@ class StateMerger {
DfaMemoryStateImpl copy = copyWithoutVar(state, var).createCopy();
complementaryStates.add(state);
postProcessMergedState(var, copy, complementaryStates);
mergeNullableState(var, copy, complementaryStates);
mergeUnknowns(copy, complementaryStates);
return getMergeResult(copy, complementaryStates);
}
@@ -90,13 +84,17 @@ class StateMerger {
return null;
}
private static void postProcessMergedState(DfaVariableValue var,
DfaMemoryStateImpl mergedState,
Collection<DfaMemoryStateImpl> complementaryStates) {
private static void mergeUnknowns(DfaMemoryStateImpl mergedState, Collection<DfaMemoryStateImpl> complementaryStates) {
for (DfaMemoryStateImpl removedState : complementaryStates) {
for (DfaVariableValue unknownVar : removedState.getUnknownVariables()) {
mergedState.doFlush(unknownVar, true);
}
}
}
private static void mergeNullableState(DfaVariableValue var,
DfaMemoryStateImpl mergedState,
Collection<DfaMemoryStateImpl> complementaryStates) {
for (DfaMemoryStateImpl removedState : complementaryStates) {
if (removedState.getVariableState(var).isNullable()) {
mergedState.setVariableState(var, mergedState.getVariableState(var).withNullability(Nullness.NULLABLE));
}
@@ -115,8 +113,50 @@ class StateMerger {
return result;
}
@Nullable
public List<DfaMemoryStateImpl> mergeByUnknowns() {
MultiMap<Integer, DfaMemoryStateImpl> byHash = new MultiMap<Integer, DfaMemoryStateImpl>();
for (DfaMemoryStateImpl state : myStates) {
ProgressManager.checkCanceled();
byHash.putValue(state.getPartialHashCode(false), state);
}
for (Integer key : byHash.keySet()) {
Collection<DfaMemoryStateImpl> similarStates = byHash.get(key);
if (similarStates.size() < 2) continue;
for (final DfaMemoryStateImpl state1 : similarStates) {
ProgressManager.checkCanceled();
List<DfaMemoryStateImpl> complementary = ContainerUtil.filter(similarStates, new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl state2) {
return state1.equalsSuperficially(state2) && state1.equalsByRelations(state2) && state1.equalsByVariableStates(state2);
}
});
if (complementary.size() > 1) {
DfaMemoryStateImpl copy = state1.createCopy();
mergeUnknowns(copy, complementary);
return getMergeResult(copy, ContainerUtil.newHashSet(complementary));
}
}
}
return null;
}
@Nullable
public List<DfaMemoryStateImpl> mergeByType() {
MultiMap<Pair<DfaVariableValue, DfaPsiType>,DfaMemoryStateImpl> byInstanceof = new MultiMap<Pair<DfaVariableValue, DfaPsiType>, DfaMemoryStateImpl>();
for (final DfaMemoryStateImpl state : myStates) {
ProgressManager.checkCanceled();
for (DfaVariableValue value : state.getChangedVariable()) {
for (DfaPsiType instanceofValue : state.getVariableState(value).myInstanceofValues) {
byInstanceof.putValue(Pair.create(value, instanceofValue), state);
}
}
}
for (final DfaMemoryStateImpl state : myStates) {
ProgressManager.checkCanceled();
@@ -124,7 +164,7 @@ class StateMerger {
for (final DfaPsiType notInstanceof : state.getVariableState(var).myNotInstanceofValues) {
final DfaVariableState varStateWithoutType = getVarStateWithoutType(state, var, notInstanceof);
List<DfaMemoryStateImpl> complementaryStates = ContainerUtil.filter(
myStatesByInstanceof.get(Pair.create(var, notInstanceof)),
byInstanceof.get(Pair.create(var, notInstanceof)),
new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl another) {
@@ -142,7 +182,8 @@ class StateMerger {
copy.setVariableState(var, varStateWithoutType);
complementaryStates.add(state);
postProcessMergedState(var, copy, complementaryStates);
mergeNullableState(var, copy, complementaryStates);
mergeUnknowns(copy, complementaryStates);
return getMergeResult(copy, ContainerUtil.newHashSet(complementaryStates));
}
}
@@ -0,0 +1,32 @@
import org.jetbrains.annotations.NotNull;
class Some {
String field1;
String field2;
String field3;
String field4;
String field5;
String field6;
String field7;
String field8;
String field9;
void a(String[] lines) {
for (String line : lines) {
if (line.startsWith("1")) field1 = someString();
else if (line.startsWith("2")) field2 = someString();
else if (line.startsWith("3")) field3 = someString();
else if (line.startsWith("4")) field4 = someString();
else if (line.startsWith("5")) field5 = someString();
else if (line.startsWith("6")) field6 = someString();
else if (line.startsWith("7")) field7 = someString();
else if (line.startsWith("8")) field8 = someString();
else if (line.startsWith("9")) field9 = someString();
}
}
@NotNull String someString() { return ""; }
}
@@ -303,6 +303,8 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
public void testManySequentialInstanceofsNotComplex() { doTest(); }
public void testLongDisjunctionsNotComplex() { doTest(); }
public void testWhileNotComplex() { doTest(); }
public void testManyDisjunctiveFieldAssignmentsInLoopNotComplex() { doTest(); }
public void testVariablesDiverge() { doTest(); }
public void _testNullCheckBeforeInstanceof() { doTest(); }