blob: 1fa65263de27d5bbe5b61213ba93e088b7c3bf2c [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.psi.JavaTokenType;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author peter
*/
class StateMerger {
private final Map<DfaMemoryStateImpl, LinkedHashSet<Fact>> myFacts = ContainerUtil.newIdentityHashMap();
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
@Nullable
List<DfaMemoryStateImpl> mergeByFacts(List<DfaMemoryStateImpl> states) {
MultiMap<Fact, DfaMemoryStateImpl> statesByFact = MultiMap.createLinked();
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
for (Fact fact : getFacts(state)) {
statesByFact.putValue(fact, state);
}
}
for (final Fact fact : statesByFact.keySet()) {
if (statesByFact.get(fact).size() == states.size() || fact.myPositive) continue;
Collection<DfaMemoryStateImpl> statesWithNegations = statesByFact.get(fact.getPositiveCounterpart());
if (statesWithNegations.isEmpty()) continue;
ProgressManager.checkCanceled();
MultiMap<Set<Fact>, DfaMemoryStateImpl> statesByUnrelatedFacts = MultiMap.createLinked();
for (DfaMemoryStateImpl state : ContainerUtil.concat(statesByFact.get(fact), statesWithNegations)) {
statesByUnrelatedFacts.putValue(getUnrelatedFacts(fact, state), state);
}
Set<DfaMemoryStateImpl> removedStates = ContainerUtil.newIdentityTroveSet();
List<DfaMemoryStateImpl> result = ContainerUtil.newArrayList();
for (Set<Fact> key : statesByUnrelatedFacts.keySet()) {
Collection<DfaMemoryStateImpl> group = statesByUnrelatedFacts.get(key);
if (group.size() > 1) {
DfaMemoryStateImpl copy = group.iterator().next().createCopy();
fact.removeFromState(copy);
if (fact.myType == FactType.equality) {
restoreOtherInequalities(fact, group, copy);
}
mergeUnknowns(copy, group);
removedStates.addAll(group);
result.add(copy);
}
}
if (!result.isEmpty()) {
for (DfaMemoryStateImpl state : states) {
if (!removedStates.contains(state)) {
result.add(state);
}
}
return result;
}
}
return null;
}
private LinkedHashSet<Fact> getUnrelatedFacts(final Fact fact, DfaMemoryStateImpl state) {
return new LinkedHashSet<Fact>(ContainerUtil.filter(getFacts(state), new Condition<Fact>() {
@Override
public boolean value(Fact another) {
return !fact.invalidatesFact(another);
}
}));
}
private void restoreOtherInequalities(Fact removedFact, Collection<DfaMemoryStateImpl> mergedGroup, DfaMemoryStateImpl state) {
Set<DfaConstValue> inequalitiesToRestore = null;
for (DfaMemoryStateImpl member : mergedGroup) {
LinkedHashSet<Fact> memberFacts = getFacts(member);
if (memberFacts.contains(removedFact)) {
Set<DfaConstValue> otherInequalities = getOtherInequalities(removedFact, memberFacts, member);
if (inequalitiesToRestore == null) {
inequalitiesToRestore = otherInequalities;
} else {
inequalitiesToRestore.retainAll(otherInequalities);
}
}
}
if (inequalitiesToRestore != null) {
DfaRelationValue.Factory relationFactory = state.getFactory().getRelationFactory();
for (DfaConstValue toRestore : inequalitiesToRestore) {
state.applyCondition(relationFactory.createRelation(removedFact.myVar, toRestore, JavaTokenType.EQEQ, true));
}
}
}
private static Set<DfaConstValue> getOtherInequalities(Fact removedFact, LinkedHashSet<Fact> memberFacts, DfaMemoryStateImpl state) {
Set<DfaConstValue> otherInequalities = ContainerUtil.newLinkedHashSet();
Set<DfaValue> eqValues = ContainerUtil.newHashSet(state.getEquivalentValues((DfaValue)removedFact.myArg));
for (Fact candidate : memberFacts) {
if (candidate.myType == FactType.equality && !candidate.myPositive && candidate.myVar == removedFact.myVar &&
!eqValues.contains((DfaValue)candidate.myArg) &&
candidate.myArg instanceof DfaConstValue) {
otherInequalities.add((DfaConstValue)candidate.myArg);
}
}
return otherInequalities;
}
private static void mergeUnknowns(DfaMemoryStateImpl mergedState, Collection<DfaMemoryStateImpl> complementaryStates) {
for (DfaMemoryStateImpl removedState : complementaryStates) {
for (DfaVariableValue unknownVar : removedState.getUnknownVariables()) {
mergedState.doFlush(unknownVar, true);
}
}
}
private static List<DfaMemoryStateImpl> getMergeResult(List<DfaMemoryStateImpl> statesBeforeMerge,
final THashSet<DfaMemoryStateImpl> mergedStates,
DfaMemoryStateImpl mergeResult) {
List<DfaMemoryStateImpl> result = ContainerUtil.newArrayList();
result.add(mergeResult);
result.addAll(ContainerUtil.filter(statesBeforeMerge, new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl state) {
return !mergedStates.contains(state);
}
}));
return result;
}
@Nullable
public List<DfaMemoryStateImpl> mergeByUnknowns(List<DfaMemoryStateImpl> states) {
MultiMap<Integer, DfaMemoryStateImpl> byHash = new MultiMap<Integer, DfaMemoryStateImpl>();
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
byHash.putValue(state.getPartialHashCode(false, true), 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.equalsByRelations(state2) && state1.equalsByVariableStates(state2);
}
});
if (complementary.size() > 1) {
DfaMemoryStateImpl copy = state1.createCopy();
mergeUnknowns(copy, complementary);
return getMergeResult(states, ContainerUtil.newIdentityTroveSet(complementary), copy);
}
}
}
return null;
}
@Nullable
public List<DfaMemoryStateImpl> mergeByNullability(List<DfaMemoryStateImpl> states) {
MultiMap<Integer, DfaMemoryStateImpl> byHash = new MultiMap<Integer, DfaMemoryStateImpl>();
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
byHash.putValue(state.getPartialHashCode(false, 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();
for (final DfaVariableValue var : state1.getChangedVariables()) {
if (state1.getVariableState(var).getNullability() != Nullness.NULLABLE) {
continue;
}
List<DfaMemoryStateImpl> complementary = ContainerUtil.filter(similarStates, new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl state2) {
return state1.equalsByRelations(state2) &&
areEquivalentModuloVar(state1, state2, var) &&
areVarStatesEqualModuloNullability(state1, state2, var);
}
});
if (complementary.size() > 1) {
DfaMemoryStateImpl copy = state1.createCopy();
mergeUnknowns(copy, complementary);
return getMergeResult(states, ContainerUtil.newIdentityTroveSet(complementary), copy);
}
}
}
}
return null;
}
private boolean areEquivalentModuloVar(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue var) {
DfaMemoryStateImpl copy1 = copyWithoutVar(state1, var);
DfaMemoryStateImpl copy2 = copyWithoutVar(state2, var);
return copy2.equalsByRelations(copy1) && copy2.equalsByVariableStates(copy1);
}
private DfaMemoryStateImpl copyWithoutVar(DfaMemoryStateImpl state, DfaVariableValue var) {
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.get(state);
if (map == null) {
myCopyCache.put(state, map = ContainerUtil.newIdentityHashMap());
}
DfaMemoryStateImpl copy = map.get(var);
if (copy == null) {
copy = state.createCopy();
copy.flushVariable(var);
map.put(var, copy);
}
return copy;
}
private static boolean areVarStatesEqualModuloNullability(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue var) {
return state1.getVariableState(var).withNullability(Nullness.UNKNOWN).equals(state2.getVariableState(var).withNullability(Nullness.UNKNOWN));
}
private LinkedHashSet<Fact> getFacts(DfaMemoryStateImpl state) {
LinkedHashSet<Fact> result = myFacts.get(state);
if (result != null) {
return result;
}
result = ContainerUtil.newLinkedHashSet();
for (EqClass eqClass : state.getNonTrivialEqClasses()) {
DfaValue constant = eqClass.findConstant(true);
List<DfaVariableValue> vars = eqClass.getVariables(false);
for (DfaVariableValue var : vars) {
if (constant != null) {
result.add(Fact.createEqualityFact(var, constant, true));
}
for (DfaVariableValue eqVar : vars) {
if (var != eqVar) {
result.add(Fact.createEqualityFact(var, eqVar, true));
}
}
}
}
for (UnorderedPair<EqClass> classPair : state.getDistinctClassPairs()) {
List<DfaVariableValue> vars1 = classPair.first.getVariables(false);
List<DfaVariableValue> vars2 = classPair.second.getVariables(false);
LinkedHashSet<DfaValue> firstSet = new LinkedHashSet<DfaValue>(vars1);
ContainerUtil.addIfNotNull(firstSet, classPair.first.findConstant(true));
LinkedHashSet<DfaValue> secondSet = new LinkedHashSet<DfaValue>(vars2);
ContainerUtil.addIfNotNull(secondSet, classPair.second.findConstant(true));
for (DfaVariableValue var : vars1) {
for (DfaValue value : secondSet) {
result.add(new Fact(FactType.equality, var, false, value));
}
}
for (DfaVariableValue var : vars2) {
for (DfaValue value : firstSet) {
result.add(new Fact(FactType.equality, var, false, value));
}
}
}
Map<DfaVariableValue, DfaVariableState> states = state.getVariableStates();
for (DfaVariableValue var : states.keySet()) {
DfaVariableState variableState = states.get(var);
for (DfaPsiType type : variableState.getInstanceofValues()) {
result.add(new Fact(FactType.instanceOf, var, true, type));
}
for (DfaPsiType type : variableState.getNotInstanceofValues()) {
result.add(new Fact(FactType.instanceOf, var, false, type));
}
}
myFacts.put(state, result);
return result;
}
private enum FactType { equality, instanceOf }
private static class Fact {
final FactType myType;
final DfaVariableValue myVar;
final boolean myPositive;
final Object myArg; // DfaValue for equality fact, DfaPsiType for instanceOf fact
private Fact(FactType type, DfaVariableValue var, boolean positive, Object arg) {
myType = type;
myVar = var;
myPositive = positive;
myArg = arg;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Fact)) return false;
Fact fact = (Fact)o;
if (myPositive != fact.myPositive) return false;
if (!myArg.equals(fact.myArg)) return false;
if (myType != fact.myType) return false;
if (!myVar.equals(fact.myVar)) return false;
return true;
}
@Override
public int hashCode() {
int result = myType.hashCode();
result = 31 * result + myVar.hashCode();
result = 31 * result + (myPositive ? 1 : 0);
result = 31 * result + myArg.hashCode();
return result;
}
@Override
public String toString() {
return myVar + " " + (myPositive ? "" : "!") + myType + " " + myArg;
}
static Fact createEqualityFact(DfaVariableValue var, DfaValue val, boolean equal) {
if (val instanceof DfaVariableValue && val.getID() < var.getID()) {
return new Fact(FactType.equality, (DfaVariableValue)val, equal, var);
}
return new Fact(FactType.equality, var, equal, val);
}
Fact getPositiveCounterpart() {
return new Fact(myType, myVar, true, myArg);
}
boolean invalidatesFact(Fact another) {
if (another.myType != myType) return false;
if (myType == FactType.equality) {
return myVar == another.myVar || myVar == another.myArg;
}
return myVar == another.myVar && myArg == another.myArg;
}
void removeFromState(DfaMemoryStateImpl state) {
DfaVariableState varState = state.getVariableState(myVar);
if (myType == FactType.equality) {
state.flushVariable(myVar);
state.setVariableState(myVar, varState);
} else {
state.setVariableState(myVar, varState.withoutType((DfaPsiType)myArg));
}
}
}
}