2 * Copyright 2000-2013 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInspection.dataFlow;
18 import com.intellij.codeInspection.dataFlow.value.*;
19 import com.intellij.openapi.progress.ProgressManager;
20 import com.intellij.openapi.util.Condition;
21 import com.intellij.openapi.util.UnorderedPair;
22 import com.intellij.psi.JavaTokenType;
23 import com.intellij.util.Function;
24 import com.intellij.util.containers.ContainerUtil;
25 import com.intellij.util.containers.MultiMap;
26 import org.jetbrains.annotations.Nullable;
34 private final Map<DfaMemoryStateImpl, LinkedHashSet<Fact>> myFacts = ContainerUtil.newIdentityHashMap();
35 private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = ContainerUtil.newIdentityHashMap();
38 List<DfaMemoryStateImpl> mergeByFacts(List<DfaMemoryStateImpl> states) {
39 MultiMap<Fact, DfaMemoryStateImpl> statesByFact = MultiMap.createLinked();
40 for (DfaMemoryStateImpl state : states) {
41 ProgressManager.checkCanceled();
42 for (Fact fact : getFacts(state)) {
43 statesByFact.putValue(fact, state);
47 for (final Fact fact : statesByFact.keySet()) {
48 if (statesByFact.get(fact).size() == states.size() || fact.myPositive) continue;
50 Collection<DfaMemoryStateImpl> statesWithNegations = statesByFact.get(fact.getPositiveCounterpart());
51 if (statesWithNegations.isEmpty()) continue;
53 ProgressManager.checkCanceled();
55 MultiMap<Set<Fact>, DfaMemoryStateImpl> statesByUnrelatedFacts = MultiMap.createLinked();
56 for (DfaMemoryStateImpl state : ContainerUtil.concat(statesByFact.get(fact), statesWithNegations)) {
57 statesByUnrelatedFacts.putValue(getUnrelatedFacts(fact, state), state);
60 Replacements replacements = new Replacements(states);
61 for (Set<Fact> key : statesByUnrelatedFacts.keySet()) {
62 final Collection<DfaMemoryStateImpl> group = statesByUnrelatedFacts.get(key);
63 final Set<DfaVariableValue> unknowns = getAllUnknownVariables(group);
64 replacements.stripAndMerge(group, new Function<DfaMemoryStateImpl, DfaMemoryStateImpl>() {
66 public DfaMemoryStateImpl fun(DfaMemoryStateImpl original) {
67 DfaMemoryStateImpl copy = withUnknownVariables(original, unknowns);
68 fact.removeFromState(copy);
69 if (fact.myType == FactType.equality) {
70 restoreOtherInequalities(fact, group, copy);
77 if (replacements.hasMerges()) return replacements.getMergeResult();
82 private LinkedHashSet<Fact> getUnrelatedFacts(final Fact fact, DfaMemoryStateImpl state) {
83 return new LinkedHashSet<Fact>(ContainerUtil.filter(getFacts(state), new Condition<Fact>() {
85 public boolean value(Fact another) {
86 return !fact.invalidatesFact(another);
91 private void restoreOtherInequalities(Fact removedFact, Collection<DfaMemoryStateImpl> mergedGroup, DfaMemoryStateImpl state) {
92 Set<DfaConstValue> inequalitiesToRestore = null;
93 for (DfaMemoryStateImpl member : mergedGroup) {
94 LinkedHashSet<Fact> memberFacts = getFacts(member);
95 if (memberFacts.contains(removedFact)) {
96 Set<DfaConstValue> otherInequalities = getOtherInequalities(removedFact, memberFacts, member);
97 if (inequalitiesToRestore == null) {
98 inequalitiesToRestore = otherInequalities;
100 inequalitiesToRestore.retainAll(otherInequalities);
104 if (inequalitiesToRestore != null) {
105 DfaRelationValue.Factory relationFactory = state.getFactory().getRelationFactory();
106 for (DfaConstValue toRestore : inequalitiesToRestore) {
107 state.applyCondition(relationFactory.createRelation(removedFact.myVar, toRestore, JavaTokenType.EQEQ, true));
112 private static Set<DfaConstValue> getOtherInequalities(Fact removedFact, LinkedHashSet<Fact> memberFacts, DfaMemoryStateImpl state) {
113 Set<DfaConstValue> otherInequalities = ContainerUtil.newLinkedHashSet();
114 Set<DfaValue> eqValues = ContainerUtil.newHashSet(state.getEquivalentValues((DfaValue)removedFact.myArg));
115 for (Fact candidate : memberFacts) {
116 if (candidate.myType == FactType.equality && !candidate.myPositive && candidate.myVar == removedFact.myVar &&
117 !eqValues.contains((DfaValue)candidate.myArg) &&
118 candidate.myArg instanceof DfaConstValue) {
119 otherInequalities.add((DfaConstValue)candidate.myArg);
122 return otherInequalities;
125 private static Set<DfaVariableValue> getAllUnknownVariables(Collection<DfaMemoryStateImpl> complementary) {
126 final Set<DfaVariableValue> toFlush = ContainerUtil.newLinkedHashSet();
127 for (DfaMemoryStateImpl removedState : complementary) {
128 toFlush.addAll(removedState.getUnknownVariables());
133 private static DfaMemoryStateImpl withUnknownVariables(DfaMemoryStateImpl original, Set<DfaVariableValue> toFlush) {
134 DfaMemoryStateImpl copy = original.createCopy();
135 for (DfaVariableValue value : toFlush) {
136 copy.doFlush(value, true);
142 public List<DfaMemoryStateImpl> mergeByUnknowns(List<DfaMemoryStateImpl> states) {
143 MultiMap<Integer, DfaMemoryStateImpl> byHash = new MultiMap<Integer, DfaMemoryStateImpl>();
144 for (DfaMemoryStateImpl state : states) {
145 ProgressManager.checkCanceled();
146 byHash.putValue(state.getPartialHashCode(false, true), state);
149 Replacements replacements = new Replacements(states);
150 for (Integer key : byHash.keySet()) {
151 Collection<DfaMemoryStateImpl> similarStates = byHash.get(key);
152 if (similarStates.size() < 2) continue;
154 for (final DfaMemoryStateImpl state1 : similarStates) {
155 ProgressManager.checkCanceled();
156 List<DfaMemoryStateImpl> complementary = ContainerUtil.filter(similarStates, new Condition<DfaMemoryStateImpl>() {
158 public boolean value(DfaMemoryStateImpl state2) {
159 return state1.equalsByRelations(state2) && state1.equalsByVariableStates(state2);
162 if (mergeUnknowns(replacements, complementary)) break;
166 return replacements.getMergeResult();
170 public List<DfaMemoryStateImpl> mergeByNullability(List<DfaMemoryStateImpl> states) {
171 MultiMap<Integer, DfaMemoryStateImpl> byHash = new MultiMap<Integer, DfaMemoryStateImpl>();
172 for (DfaMemoryStateImpl state : states) {
173 ProgressManager.checkCanceled();
174 byHash.putValue(state.getPartialHashCode(false, false), state);
177 Replacements replacements = new Replacements(states);
178 for (Integer key : byHash.keySet()) {
179 Collection<DfaMemoryStateImpl> similarStates = byHash.get(key);
180 if (similarStates.size() < 2) continue;
183 for (final DfaMemoryStateImpl state1 : similarStates) {
184 ProgressManager.checkCanceled();
185 for (final DfaVariableValue var : state1.getChangedVariables()) {
186 if (state1.getVariableState(var).getNullability() != Nullness.NULLABLE) {
190 List<DfaMemoryStateImpl> complementary = ContainerUtil.filter(similarStates, new Condition<DfaMemoryStateImpl>() {
192 public boolean value(DfaMemoryStateImpl state2) {
193 return state1.equalsByRelations(state2) &&
194 areEquivalentModuloVar(state1, state2, var) &&
195 areVarStatesEqualModuloNullability(state1, state2, var);
198 if (mergeUnknowns(replacements, complementary)) break groupLoop;
203 return replacements.getMergeResult();
206 private static boolean mergeUnknowns(Replacements replacements, List<DfaMemoryStateImpl> complementary) {
207 if (complementary.size() < 2) return false;
209 final Set<DfaVariableValue> toFlush = getAllUnknownVariables(complementary);
210 if (toFlush.isEmpty()) return false;
212 return replacements.stripAndMerge(complementary, new Function<DfaMemoryStateImpl, DfaMemoryStateImpl>() {
214 public DfaMemoryStateImpl fun(DfaMemoryStateImpl original) {
215 return withUnknownVariables(original, toFlush);
220 private boolean areEquivalentModuloVar(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue var) {
221 DfaMemoryStateImpl copy1 = copyWithoutVar(state1, var);
222 DfaMemoryStateImpl copy2 = copyWithoutVar(state2, var);
223 return copy2.equalsByRelations(copy1) && copy2.equalsByVariableStates(copy1);
226 private DfaMemoryStateImpl copyWithoutVar(DfaMemoryStateImpl state, DfaVariableValue var) {
227 Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.get(state);
229 myCopyCache.put(state, map = ContainerUtil.newIdentityHashMap());
231 DfaMemoryStateImpl copy = map.get(var);
233 copy = state.createCopy();
234 copy.flushVariable(var);
240 private static boolean areVarStatesEqualModuloNullability(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue var) {
241 return state1.getVariableState(var).withNullability(Nullness.UNKNOWN).equals(state2.getVariableState(var).withNullability(Nullness.UNKNOWN));
244 private LinkedHashSet<Fact> getFacts(DfaMemoryStateImpl state) {
245 LinkedHashSet<Fact> result = myFacts.get(state);
246 if (result != null) {
250 result = ContainerUtil.newLinkedHashSet();
251 for (EqClass eqClass : state.getNonTrivialEqClasses()) {
252 DfaValue constant = eqClass.findConstant(true);
253 List<DfaVariableValue> vars = eqClass.getVariables(false);
254 for (DfaVariableValue var : vars) {
255 if (constant != null) {
256 result.add(Fact.createEqualityFact(var, constant, true));
258 for (DfaVariableValue eqVar : vars) {
260 result.add(Fact.createEqualityFact(var, eqVar, true));
266 for (UnorderedPair<EqClass> classPair : state.getDistinctClassPairs()) {
267 List<DfaVariableValue> vars1 = classPair.first.getVariables(false);
268 List<DfaVariableValue> vars2 = classPair.second.getVariables(false);
270 LinkedHashSet<DfaValue> firstSet = new LinkedHashSet<DfaValue>(vars1);
271 ContainerUtil.addIfNotNull(firstSet, classPair.first.findConstant(true));
273 LinkedHashSet<DfaValue> secondSet = new LinkedHashSet<DfaValue>(vars2);
274 ContainerUtil.addIfNotNull(secondSet, classPair.second.findConstant(true));
276 for (DfaVariableValue var : vars1) {
277 for (DfaValue value : secondSet) {
278 result.add(new Fact(FactType.equality, var, false, value));
281 for (DfaVariableValue var : vars2) {
282 for (DfaValue value : firstSet) {
283 result.add(new Fact(FactType.equality, var, false, value));
288 Map<DfaVariableValue, DfaVariableState> states = state.getVariableStates();
289 for (DfaVariableValue var : states.keySet()) {
290 DfaVariableState variableState = states.get(var);
291 for (DfaPsiType type : variableState.getInstanceofValues()) {
292 result.add(new Fact(FactType.instanceOf, var, true, type));
294 for (DfaPsiType type : variableState.getNotInstanceofValues()) {
295 result.add(new Fact(FactType.instanceOf, var, false, type));
299 myFacts.put(state, result);
303 private enum FactType { equality, instanceOf }
305 private static class Fact {
306 final FactType myType;
307 final DfaVariableValue myVar;
308 final boolean myPositive;
309 final Object myArg; // DfaValue for equality fact, DfaPsiType for instanceOf fact
311 private Fact(FactType type, DfaVariableValue var, boolean positive, Object arg) {
314 myPositive = positive;
319 public boolean equals(Object o) {
320 if (this == o) return true;
321 if (!(o instanceof Fact)) return false;
325 if (myPositive != fact.myPositive) return false;
326 if (!myArg.equals(fact.myArg)) return false;
327 if (myType != fact.myType) return false;
328 if (!myVar.equals(fact.myVar)) return false;
334 public int hashCode() {
335 int result = myType.hashCode();
336 result = 31 * result + myVar.hashCode();
337 result = 31 * result + (myPositive ? 1 : 0);
338 result = 31 * result + myArg.hashCode();
343 public String toString() {
344 return myVar + " " + (myPositive ? "" : "!") + myType + " " + myArg;
347 static Fact createEqualityFact(DfaVariableValue var, DfaValue val, boolean equal) {
348 if (val instanceof DfaVariableValue && val.getID() < var.getID()) {
349 return new Fact(FactType.equality, (DfaVariableValue)val, equal, var);
351 return new Fact(FactType.equality, var, equal, val);
354 Fact getPositiveCounterpart() {
355 return new Fact(myType, myVar, true, myArg);
358 boolean invalidatesFact(Fact another) {
359 if (another.myType != myType) return false;
360 if (myType == FactType.equality) {
361 return myVar == another.myVar || myVar == another.myArg;
363 return myVar == another.myVar && myArg == another.myArg;
366 void removeFromState(DfaMemoryStateImpl state) {
367 DfaVariableState varState = state.getVariableState(myVar);
368 if (myType == FactType.equality) {
369 state.flushVariable(myVar);
370 state.setVariableState(myVar, varState);
372 state.setVariableState(myVar, varState.withoutType((DfaPsiType)myArg));
377 private static class Replacements {
378 private final List<DfaMemoryStateImpl> myAllStates;
379 private final Set<DfaMemoryStateImpl> myRemovedStates = ContainerUtil.newIdentityTroveSet();
380 private final List<DfaMemoryStateImpl> myMerged = ContainerUtil.newArrayList();
382 Replacements(List<DfaMemoryStateImpl> allStates) {
383 myAllStates = allStates;
386 boolean hasMerges() { return !myMerged.isEmpty(); }
389 List<DfaMemoryStateImpl> getMergeResult() {
391 List<DfaMemoryStateImpl> result = ContainerUtil.newArrayList(myMerged);
392 for (DfaMemoryStateImpl state : myAllStates) {
393 if (!myRemovedStates.contains(state)) {
402 boolean stripAndMerge(Collection<DfaMemoryStateImpl> group,
403 Function<DfaMemoryStateImpl, DfaMemoryStateImpl> stripper) {
404 if (group.size() <= 1) return false;
406 boolean hasMerges = false;
407 MultiMap<DfaMemoryStateImpl, DfaMemoryStateImpl> strippedToOriginals = MultiMap.create();
408 for (DfaMemoryStateImpl original : group) {
409 strippedToOriginals.putValue(stripper.fun(original), original);
411 for (Map.Entry<DfaMemoryStateImpl, Collection<DfaMemoryStateImpl>> entry : strippedToOriginals.entrySet()) {
412 Collection<DfaMemoryStateImpl> merged = entry.getValue();
413 if (merged.size() > 1) {
414 myRemovedStates.addAll(merged);
415 myMerged.add(entry.getKey());