blob: aa69d05ea9a63abf23537e8ebb9a76fc02f0bcca [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.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author peter
*/
class StateMerger {
private final Map<DfaMemoryStateImpl, Map<DfaVariableValue, DfaConstValue>> myVarValues = ContainerUtil.newIdentityHashMap();
private final Map<DfaMemoryStateImpl, List<UnorderedPair<DfaValue>>> myEqPairs = ContainerUtil.newIdentityHashMap();
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
@Nullable
public List<DfaMemoryStateImpl> mergeByEquality(List<DfaMemoryStateImpl> states) {
final MultiMap<UnorderedPair<DfaValue>,DfaMemoryStateImpl> statesByEq = new MultiMap<UnorderedPair<DfaValue>, DfaMemoryStateImpl>();
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
for (UnorderedPair<DfaValue> pair : getEqPairs(state)) {
statesByEq.putValue(pair, state);
}
}
for (final DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
MultiMap<DfaVariableValue, DfaValue> distincts = getDistinctsMap(state);
for (DfaVariableValue var : distincts.keySet()) {
Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue = getCompatibleStatesByValue(state, var, distincts, statesByEq);
if (statesByValue == null) {
continue;
}
final THashSet<DfaMemoryStateImpl> complementaryStates = findComplementaryStates(var, statesByValue, state);
if (complementaryStates == null) {
continue;
}
DfaMemoryStateImpl copy = copyWithoutVar(state, var).createCopy();
complementaryStates.add(state);
mergeNullableState(var, copy, complementaryStates);
mergeUnknowns(copy, complementaryStates);
return getMergeResult(states, complementaryStates, copy);
}
}
return null;
}
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));
}
}
}
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.equalsSuperficially(state2) && 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.equalsSuperficially(state2) &&
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;
}
@Nullable
public List<DfaMemoryStateImpl> mergeByType(List<DfaMemoryStateImpl> states) {
MultiMap<Pair<DfaVariableValue, DfaPsiType>,DfaMemoryStateImpl> byInstanceof = new MultiMap<Pair<DfaVariableValue, DfaPsiType>, DfaMemoryStateImpl>();
for (final DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
for (DfaVariableValue value : state.getChangedVariables()) {
for (DfaPsiType instanceofValue : state.getVariableState(value).myInstanceofValues) {
byInstanceof.putValue(Pair.create(value, instanceofValue), state);
}
}
}
for (final DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
for (final DfaVariableValue var : state.getChangedVariables()) {
for (final DfaPsiType notInstanceof : state.getVariableState(var).myNotInstanceofValues) {
final DfaVariableState varStateWithoutType = getVarStateWithoutType(state, var, notInstanceof);
List<DfaMemoryStateImpl> complementaryStates = ContainerUtil.filter(
byInstanceof.get(Pair.create(var, notInstanceof)),
new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl another) {
return seemCompatible(state, another, var) &&
another.getVariableState(var).myInstanceofValues.contains(notInstanceof) &&
varStateWithoutType.equals(getVarStateWithoutType(another, var, notInstanceof)) &&
areEquivalentModuloVar(another, state, var) &&
!(state.isNull(var) && another.isNotNull(var));
}
});
if (complementaryStates.isEmpty()) {
continue;
}
DfaMemoryStateImpl copy = state.createCopy();
copy.setVariableState(var, varStateWithoutType);
complementaryStates.add(state);
mergeNullableState(var, copy, complementaryStates);
mergeUnknowns(copy, complementaryStates);
return getMergeResult(states, ContainerUtil.newIdentityTroveSet(complementaryStates), 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 static DfaVariableState getVarStateWithoutType(DfaMemoryStateImpl s, DfaVariableValue var, DfaPsiType type) {
return s.getVariableState(var).withoutType(type).withNullability(Nullness.UNKNOWN);
}
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;
}
@Nullable
private THashSet<DfaMemoryStateImpl> findComplementaryStates(DfaVariableValue var,
Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue,
DfaMemoryStateImpl state) {
THashSet<DfaMemoryStateImpl> removedStates = ContainerUtil.newIdentityTroveSet();
eachValue:
for (DfaValue value : statesByValue.keySet()) {
for (DfaMemoryStateImpl originalState : statesByValue.get(value)) {
if (areEquivalentModuloVar(originalState, state, var)) {
removedStates.add(originalState);
continue eachValue;
}
}
return null;
}
return removedStates;
}
@Nullable
private Map<DfaValue, Collection<DfaMemoryStateImpl>> getCompatibleStatesByValue(final DfaMemoryStateImpl state,
final DfaVariableValue var,
MultiMap<DfaVariableValue, DfaValue> distincts,
MultiMap<UnorderedPair<DfaValue>,DfaMemoryStateImpl> statesByEq) {
Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue = ContainerUtil.newHashMap();
for (DfaValue value : distincts.get(var)) {
List<DfaMemoryStateImpl> compatible = ContainerUtil.filter(statesByEq.get(createPair(var, value)), new Condition<DfaMemoryStateImpl>() {
@Override
public boolean value(DfaMemoryStateImpl state2) {
return seemCompatible(state, state2, var) &&
areVarStatesEqualModuloNullability(state, state2, var);
}
});
if (compatible.isEmpty()) {
return null;
}
statesByValue.put(value, compatible);
}
return statesByValue;
}
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 boolean seemCompatible(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue differentVar) {
if (!state1.equalsSuperficially(state2)) {
return false;
}
Map<DfaVariableValue, DfaConstValue> varValues1 = getVarValues(state1);
Map<DfaVariableValue, DfaConstValue> varValues2 = getVarValues(state2);
for (DfaVariableValue var : varValues1.keySet()) {
if (var != differentVar && varValues1.get(var) != varValues2.get(var)) {
return false;
}
}
for (DfaVariableValue var : varValues2.keySet()) {
if (var != differentVar && !varValues1.containsKey(var)) {
return false;
}
}
return true;
}
private Map<DfaVariableValue, DfaConstValue> getVarValues(DfaMemoryStateImpl state) {
Map<DfaVariableValue, DfaConstValue> map = myVarValues.get(state);
if (map == null) {
map = ContainerUtil.newHashMap();
for (UnorderedPair<DfaValue> pair : getEqPairs(state)) {
if (pair.first instanceof DfaVariableValue && pair.second instanceof DfaConstValue) {
map.put((DfaVariableValue)pair.first, (DfaConstValue)pair.second);
}
}
myVarValues.put(state, map);
}
return map;
}
private static MultiMap<DfaVariableValue, DfaValue> getDistinctsMap(DfaMemoryStateImpl state) {
MultiMap<DfaVariableValue, DfaValue> distincts = new MultiMap<DfaVariableValue, DfaValue>();
for (UnorderedPair<EqClass> classPair : state.getDistinctClassPairs()) {
for (DfaValue value1 : classPair.first.getMemberValues()) {
value1 = DfaMemoryStateImpl.unwrap(value1);
for (DfaValue value2 : classPair.second.getMemberValues()) {
value2 = DfaMemoryStateImpl.unwrap(value2);
if (value1 instanceof DfaVariableValue) {
if (value2 instanceof DfaVariableValue || value2 instanceof DfaConstValue) {
distincts.putValue((DfaVariableValue)value1, value2);
}
}
if (value2 instanceof DfaVariableValue) {
if (value1 instanceof DfaVariableValue || value1 instanceof DfaConstValue) {
distincts.putValue((DfaVariableValue)value2, value1);
}
}
}
}
}
return distincts;
}
private List<UnorderedPair<DfaValue>> getEqPairs(DfaMemoryStateImpl state) {
List<UnorderedPair<DfaValue>> result = myEqPairs.get(state);
if (result == null) {
Set<UnorderedPair<DfaValue>> eqPairs = ContainerUtil.newHashSet();
for (EqClass eqClass : state.getNonTrivialEqClasses()) {
DfaConstValue constant = eqClass.findConstant(true);
List<DfaVariableValue> vars = eqClass.getVariables();
for (int i = 0; i < vars.size(); i++) {
DfaVariableValue var = vars.get(i);
if (constant != null) {
eqPairs.add(createPair(var, constant));
}
for (int j = i + 1; j < vars.size(); j++) {
eqPairs.add(createPair(var, vars.get(j)));
}
}
}
myEqPairs.put(state, result = ContainerUtil.newArrayList(eqPairs));
}
return result;
}
private static UnorderedPair<DfaValue> createPair(DfaVariableValue var, DfaValue val) {
return new UnorderedPair<DfaValue>(var, val);
}
}