mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 11:50:54 +07:00
[java-dfa] StateMerger is rewritten
Now we just try to join exactly memory state pairs GitOrigin-RevId: 3e19d20e9b4d8f1640cf1a552f42e6d38154c307
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2be2da76de
commit
52de7022a0
@@ -14,7 +14,6 @@ import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -26,7 +25,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Invariant: qualifiers of the variables used in myEqClasses or myVariableTypes must be canonical variables
|
||||
@@ -106,10 +107,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
myVariableTypes.equals(that.myVariableTypes);
|
||||
}
|
||||
|
||||
Object getSuperficialKey() {
|
||||
return Pair.create(myEphemeral, myStack);
|
||||
}
|
||||
|
||||
DistinctPairSet getDistinctClassPairs() {
|
||||
return myDistinctClasses;
|
||||
}
|
||||
@@ -275,6 +272,151 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
return resultIndex;
|
||||
}
|
||||
|
||||
public @Nullable DfaMemoryStateImpl tryJoinExactly(DfaMemoryStateImpl that) {
|
||||
StateMerger merger = new StateMerger();
|
||||
if (!merger.update(that.myEphemeral || !myEphemeral, myEphemeral || !that.myEphemeral)) return null;
|
||||
if (myStack.size() != that.myStack.size()) return null;
|
||||
for (int i = 0; i < myStack.size(); i++) {
|
||||
DfaValue thisValue = myStack.get(i);
|
||||
DfaValue thatValue = that.myStack.get(i);
|
||||
int finalI = i;
|
||||
if (!merger.update(isSuperValue(thisValue, thatValue),
|
||||
isSuperValue(thatValue, thisValue),
|
||||
() -> {
|
||||
if (thisValue instanceof DfaTypeValue && thatValue instanceof DfaTypeValue) {
|
||||
DfType type = thisValue.getDfType().tryJoinExactly(thatValue.getDfType());
|
||||
if (type != null) {
|
||||
return new MergePatch(false, ms -> ms.myStack.set(finalI, myFactory.fromDfType(type)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
int[] thisToThat = getClassesMap(that);
|
||||
if (!merger.update(thisToThat != null, true)) return null;
|
||||
int[] thatToThis = that.getClassesMap(this);
|
||||
if (!merger.update(true, thatToThis != null)) return null;
|
||||
if (merger.myMaybeThisSuper && thisToThat != null) {
|
||||
for (DistinctPairSet.DistinctPair pair : myDistinctClasses) {
|
||||
int firstIndex = thisToThat[pair.getFirstIndex()];
|
||||
int secondIndex = thisToThat[pair.getSecondIndex()];
|
||||
if (!merger.updateEquivalence(pair, firstIndex, secondIndex, false)) return null;
|
||||
RelationType relation = that.myDistinctClasses.getRelation(firstIndex, secondIndex);
|
||||
if (!merger.updateOrdering(pair, relation, false)) return null;
|
||||
}
|
||||
}
|
||||
if (merger.myMaybeThatSuper && thatToThis != null) {
|
||||
for (DistinctPairSet.DistinctPair pair : that.myDistinctClasses) {
|
||||
int firstIndex = thatToThis[pair.getFirstIndex()];
|
||||
int secondIndex = thatToThis[pair.getSecondIndex()];
|
||||
if (!merger.updateEquivalence(pair, firstIndex, secondIndex, true)) return null;
|
||||
RelationType relation = myDistinctClasses.getRelation(firstIndex, secondIndex);
|
||||
if (!merger.updateOrdering(pair, relation, true)) return null;
|
||||
}
|
||||
}
|
||||
for (Map.Entry<DfaVariableValue, DfType> entry : this.myVariableTypes.entrySet()) {
|
||||
DfaVariableValue value = entry.getKey();
|
||||
DfType thisType = entry.getValue();
|
||||
// the inherent variable type is not always a superstate for any non-inherent type
|
||||
// (e.g. inherent can be nullable, but current type can be notnull)
|
||||
// so we cannot limit checking to myVariableTypes map only
|
||||
DfType thatType = that.getDfType(value);
|
||||
if (!merger.updateVariable(value, thisType, thatType)) return null;
|
||||
}
|
||||
for (Map.Entry<DfaVariableValue, DfType> entry : that.myVariableTypes.entrySet()) {
|
||||
DfaVariableValue value = entry.getKey();
|
||||
if (this.myVariableTypes.containsKey(value)) continue; // already processed in the previous loop
|
||||
DfType thisType = this.getDfType(value);
|
||||
DfType thatType = entry.getValue();
|
||||
if (!merger.updateVariable(value, thisType, thatType)) return null;
|
||||
}
|
||||
return merger.merge(this, that);
|
||||
}
|
||||
|
||||
private static class MergePatch {
|
||||
final boolean myApplyToRight;
|
||||
final Consumer<DfaMemoryStateImpl> myPatcher;
|
||||
|
||||
private MergePatch(boolean right, Consumer<DfaMemoryStateImpl> patcher) {
|
||||
myApplyToRight = right;
|
||||
myPatcher = patcher;
|
||||
}
|
||||
|
||||
DfaMemoryStateImpl apply(DfaMemoryStateImpl left, DfaMemoryStateImpl right) {
|
||||
DfaMemoryStateImpl result = (myApplyToRight ? right : left).createCopy();
|
||||
myPatcher.accept(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Two memory states can be merged exactly if a.isSuperState(b) (then return a), b.isSuperState(a) (then return b),
|
||||
// or they have mergeable difference in exactly one variable. This class tracks which of these cases are possible.
|
||||
private static class StateMerger {
|
||||
private boolean myMaybeThisSuper = true, myMaybeThatSuper = true;
|
||||
private @Nullable MergePatch mySingleDiff = null;
|
||||
|
||||
boolean update(boolean thisSuper, boolean thatSuper, Supplier<MergePatch> singleDiff) {
|
||||
if (thisSuper && thatSuper) return true;
|
||||
MergePatch diff = null;
|
||||
if (myMaybeThatSuper && myMaybeThisSuper) {
|
||||
assert mySingleDiff == null;
|
||||
diff = singleDiff.get();
|
||||
}
|
||||
myMaybeThisSuper &= thisSuper;
|
||||
myMaybeThatSuper &= thatSuper;
|
||||
if (!myMaybeThisSuper && !myMaybeThatSuper && diff == null) return false;
|
||||
mySingleDiff = diff;
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean update(boolean thisSuper, boolean thatSuper) {
|
||||
return update(thisSuper, thatSuper, () -> null);
|
||||
}
|
||||
|
||||
boolean updateVariable(DfaVariableValue value, DfType thisType, DfType thatType) {
|
||||
return update(thisType.isMergeable(thatType), thatType.isMergeable(thisType),
|
||||
() -> {
|
||||
DfType type = thisType.tryJoinExactly(thatType);
|
||||
if (type != null) {
|
||||
return new MergePatch(false, s -> s.recordVariableType(value, type));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
boolean updateEquivalence(DistinctPairSet.DistinctPair pair, int firstIndex, int secondIndex, boolean rightDistinct) {
|
||||
if (firstIndex != -1 && secondIndex != -1 && firstIndex != secondIndex) return true;
|
||||
return update(rightDistinct, !rightDistinct, () -> {
|
||||
if (firstIndex == secondIndex && !pair.isOrdered()) {
|
||||
DfaVariableValue canonicalVariable = pair.getFirst().getCanonicalVariable();
|
||||
if (canonicalVariable != null) {
|
||||
return new MergePatch(!rightDistinct, ms -> ms.removeEquivalence(canonicalVariable));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
boolean updateOrdering(DistinctPairSet.DistinctPair pair, RelationType relation, boolean rightDistinct) {
|
||||
if (relation != null && (!pair.isOrdered() || relation == RelationType.LT)) return true;
|
||||
return update(rightDistinct, !rightDistinct, () -> {
|
||||
if (pair.isOrdered() && relation == RelationType.GT) {
|
||||
return new MergePatch(rightDistinct, ms -> ms.myDistinctClasses.dropOrder(pair));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
DfaMemoryStateImpl merge(DfaMemoryStateImpl left, DfaMemoryStateImpl right) {
|
||||
if (myMaybeThisSuper) return left;
|
||||
if (myMaybeThatSuper) return right;
|
||||
assert mySingleDiff != null;
|
||||
return mySingleDiff.apply(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if current state describes all possible concrete program states described by {@code that} state.
|
||||
*
|
||||
|
||||
@@ -208,6 +208,9 @@ public final class DfaUtil {
|
||||
else if (nullability == DfaNullability.NULL) {
|
||||
hasNulls = true;
|
||||
}
|
||||
else if (nullability == DfaNullability.NULLABLE) {
|
||||
hasNulls = hasNotNulls = true;
|
||||
}
|
||||
else {
|
||||
hasUnknowns = true;
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ public class StandardDataFlowRunner {
|
||||
List<DfaMemoryStateImpl> stateList = StreamEx.of(states)
|
||||
.peek(state -> unusedVars.forEach(state::flushVariable))
|
||||
.map(state -> (DfaMemoryStateImpl)state).distinct().toList();
|
||||
states = StateQueue.mergeGroup(stateList);
|
||||
states = StateQueue.squash(stateList);
|
||||
}
|
||||
consumer.accept(closure, states);
|
||||
}
|
||||
|
||||
@@ -1,509 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.memory.EqClass;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.codeInspection.dataFlow.types.*;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import one.util.streamex.LongStreamEx;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
final class StateMerger {
|
||||
private static final int COMPLEXITY_LIMIT = 250000;
|
||||
private final Map<DfaMemoryStateImpl, Set<Fact>> myFacts = new IdentityHashMap<>();
|
||||
private final Map<DfaMemoryState, Map<DfaVariableValue, DfaMemoryStateImpl>> myCopyCache = new IdentityHashMap<>();
|
||||
|
||||
@Nullable
|
||||
List<DfaMemoryStateImpl> mergeByFacts(@NotNull List<DfaMemoryStateImpl> states) {
|
||||
MultiMap<Fact, DfaMemoryStateImpl> statesByFact = createFactToStateMap(states);
|
||||
Set<Fact> facts = statesByFact.keySet();
|
||||
|
||||
int complexity = 0;
|
||||
|
||||
for (final Fact fact : facts) {
|
||||
if (fact.myPositive) continue;
|
||||
Collection<DfaMemoryStateImpl> negativeStates = statesByFact.get(fact);
|
||||
if (negativeStates.size() == states.size()) continue;
|
||||
Collection<DfaMemoryStateImpl> positiveStates = statesByFact.get(fact.getPositiveCounterpart());
|
||||
if (positiveStates.isEmpty()) continue;
|
||||
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
MultiMap<CompactFactSet, DfaMemoryStateImpl> statesByUnrelatedFacts1 = mapByUnrelatedFacts(fact, negativeStates, facts);
|
||||
MultiMap<CompactFactSet, DfaMemoryStateImpl> statesByUnrelatedFacts2 = mapByUnrelatedFacts(fact, positiveStates, facts);
|
||||
|
||||
complexity += StreamEx.of(statesByUnrelatedFacts1, statesByUnrelatedFacts2).flatCollection(MultiMap::keySet)
|
||||
.mapToInt(CompactFactSet::size).sum();
|
||||
if (complexity > COMPLEXITY_LIMIT) return null;
|
||||
|
||||
Replacements replacements = new Replacements(states);
|
||||
for (Map.Entry<CompactFactSet, Collection<DfaMemoryStateImpl>> entry : statesByUnrelatedFacts1.entrySet()) {
|
||||
final Collection<DfaMemoryStateImpl> group1 = entry.getValue();
|
||||
final Collection<DfaMemoryStateImpl> group2 = statesByUnrelatedFacts2.get(entry.getKey());
|
||||
if (group1.isEmpty() || group2.isEmpty()) continue;
|
||||
|
||||
final Collection<DfaMemoryStateImpl> group = ContainerUtil.newArrayList(ContainerUtil.concat(group1, group2));
|
||||
|
||||
replacements.stripAndMerge(group, fact);
|
||||
}
|
||||
|
||||
if (replacements.hasMerges()) return replacements.getMergeResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NotNull MultiMap<Fact, DfaMemoryStateImpl> createFactToStateMap(@NotNull List<DfaMemoryStateImpl> states) {
|
||||
MultiMap<Fact, DfaMemoryStateImpl> statesByFact = MultiMap.createLinked();
|
||||
Map<DfaTypeValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars = new HashMap<>();
|
||||
for (DfaMemoryStateImpl state : states) {
|
||||
ProgressManager.checkCanceled();
|
||||
for (Fact fact : getFacts(state)) {
|
||||
statesByFact.putValue(fact, state);
|
||||
DfaTypeValue value = fact.comparedToConstant();
|
||||
if (value != null) {
|
||||
constantVars.computeIfAbsent(value, k -> new HashMap<>())
|
||||
.computeIfAbsent(fact.myVar, k -> new ReferenceOpenHashSet<>()).add(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Fact fact : new ArrayList<>(statesByFact.keySet())) {
|
||||
if (fact.myPositive) continue;
|
||||
Collection<DfaMemoryStateImpl> negativeStates = statesByFact.get(fact);
|
||||
Collection<DfaMemoryStateImpl> positiveStates = statesByFact.get(fact.getPositiveCounterpart());
|
||||
if (isComparisonOfVariablesComparedWithConstant(fact, constantVars, positiveStates, negativeStates)) {
|
||||
statesByFact.remove(fact);
|
||||
statesByFact.remove(fact.getPositiveCounterpart());
|
||||
}
|
||||
}
|
||||
return statesByFact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if fact is {@link EqualityFact} which compares two variables, which both are known to be compared with
|
||||
* the same constant for all states. In this case the fact looks implied (like "a == null && b == null" implies "a == b")
|
||||
* and it's unnecessary to process it separately (processing "a == null" and "b == null" would be enough).
|
||||
*
|
||||
* @param fact fact to check
|
||||
* @param constantVars constant vars map
|
||||
* @param positiveStates states for which fact is positive
|
||||
* @param negativeStates states for which fact is negative
|
||||
* @return true if fact is {@link EqualityFact} which compares two variables which were compared with some constant
|
||||
*/
|
||||
private static boolean isComparisonOfVariablesComparedWithConstant(Fact fact,
|
||||
Map<DfaTypeValue, Map<DfaVariableValue, Set<DfaMemoryStateImpl>>> constantVars,
|
||||
Collection<DfaMemoryStateImpl> positiveStates,
|
||||
Collection<DfaMemoryStateImpl> negativeStates) {
|
||||
if (!(fact instanceof EqualityFact) || !(((EqualityFact)fact).myArg instanceof DfaVariableValue)) return false;
|
||||
DfaVariableValue var1 = fact.myVar;
|
||||
DfaVariableValue var2 = (DfaVariableValue)((EqualityFact)fact).myArg;
|
||||
for (Map<DfaVariableValue, Set<DfaMemoryStateImpl>> map : constantVars.values()) {
|
||||
Set<DfaMemoryStateImpl> states1 = map.get(var1);
|
||||
Set<DfaMemoryStateImpl> states2 = map.get(var2);
|
||||
if (states1 != null && states2 != null &&
|
||||
states1.containsAll(negativeStates) && states1.containsAll(positiveStates) &&
|
||||
states2.containsAll(negativeStates) && states2.containsAll(positiveStates)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private @NotNull MultiMap<CompactFactSet, DfaMemoryStateImpl> mapByUnrelatedFacts(@NotNull Fact fact,
|
||||
@NotNull Collection<DfaMemoryStateImpl> states,
|
||||
@NotNull Set<Fact> interestingFacts) {
|
||||
MultiMap<CompactFactSet, DfaMemoryStateImpl> statesByUnrelatedFacts = MultiMap.createLinked();
|
||||
for (DfaMemoryStateImpl state : states) {
|
||||
statesByUnrelatedFacts.putValue(getUnrelatedFacts(fact, state, interestingFacts), state);
|
||||
}
|
||||
return statesByUnrelatedFacts;
|
||||
}
|
||||
|
||||
private @NotNull CompactFactSet getUnrelatedFacts(final @NotNull Fact fact,
|
||||
@NotNull DfaMemoryStateImpl state,
|
||||
@NotNull Set<Fact> interestingFacts) {
|
||||
final ArrayList<Fact> result = new ArrayList<>();
|
||||
for (Fact other : getFacts(state)) {
|
||||
if (!fact.invalidatesFact(other) && interestingFacts.contains(other)) {
|
||||
result.add(other);
|
||||
}
|
||||
}
|
||||
return new CompactFactSet(state.getFactory(), result);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<DfaMemoryStateImpl> mergeByRanges(List<DfaMemoryStateImpl> states) {
|
||||
Map<DfaVariableValue, Set<LongRangeSet>> ranges = createRangeMap(states);
|
||||
boolean changed = false;
|
||||
// For every variable with more than one range, try to unite range info and see if some states could be merged after that
|
||||
for (Map.Entry<DfaVariableValue, Set<LongRangeSet>> entry : ranges.entrySet()) {
|
||||
if (entry.getValue().size() > 1) {
|
||||
List<DfaMemoryStateImpl> updated = mergeIndependentRanges(states, entry.getKey());
|
||||
if (updated != null) {
|
||||
states = updated;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed ? states : null;
|
||||
}
|
||||
|
||||
private static @NotNull Map<DfaVariableValue, Set<LongRangeSet>> createRangeMap(List<DfaMemoryStateImpl> states) {
|
||||
Map<DfaVariableValue, Set<LongRangeSet>> ranges = new LinkedHashMap<>();
|
||||
for (DfaMemoryStateImpl state : states) {
|
||||
ProgressManager.checkCanceled();
|
||||
state.forRecordedVariableTypes((varValue, dfType) -> {
|
||||
if (dfType instanceof DfIntegralType) {
|
||||
ranges.computeIfAbsent(varValue, k -> new HashSet<>()).add(((DfIntegralType)dfType).getRange());
|
||||
}
|
||||
});
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private @Nullable List<DfaMemoryStateImpl> mergeIndependentRanges(List<DfaMemoryStateImpl> states, DfaVariableValue var) {
|
||||
ProgressManager.checkCanceled();
|
||||
Map<DfaMemoryStateImpl, List<DfaMemoryStateImpl>> merged = new LinkedHashMap<>();
|
||||
for (DfaMemoryStateImpl state : states) {
|
||||
DfType type = state.getDfType(var);
|
||||
if (!(type instanceof DfIntegralType)) return null;
|
||||
merged.computeIfAbsent(copyWithoutVar(state, var), k -> new ArrayList<>()).add(state);
|
||||
}
|
||||
if (merged.size() == states.size()) return null;
|
||||
return StreamEx.ofValues(merged).mapPartial(list -> list.stream().reduce((a, b) -> {
|
||||
assert a.getMergeabilityKey().equals(b.getMergeabilityKey());
|
||||
a.merge(b);
|
||||
return a;
|
||||
})).toList();
|
||||
}
|
||||
|
||||
private @NotNull DfaMemoryStateImpl copyWithoutVar(@NotNull DfaMemoryStateImpl state, @NotNull DfaVariableValue var) {
|
||||
Map<DfaVariableValue, DfaMemoryStateImpl> map = myCopyCache.computeIfAbsent(state, k -> new IdentityHashMap<>());
|
||||
DfaMemoryStateImpl copy = map.get(var);
|
||||
if (copy == null) {
|
||||
copy = state.createCopy();
|
||||
copy.recordVariableType(var, var.getInherentType());
|
||||
copy.flushVariable(var);
|
||||
map.put(var, copy);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
private @NotNull Set<Fact> getFacts(@NotNull DfaMemoryStateImpl state) {
|
||||
return myFacts.computeIfAbsent(state, StateMerger::doGetFacts);
|
||||
}
|
||||
|
||||
private static @NotNull Set<Fact> doGetFacts(DfaMemoryStateImpl state) {
|
||||
Set<Fact> result = new LinkedHashSet<>();
|
||||
|
||||
for (EqClass eqClass : state.getNonTrivialEqClasses()) {
|
||||
int size = eqClass.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
DfaVariableValue var = eqClass.getVariable(i);
|
||||
for (int j = i + 1; j < size; j++) {
|
||||
DfaVariableValue eqVar = eqClass.getVariable(j);
|
||||
result.add(Fact.createEqualityFact(var, eqVar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (DistinctPairSet.DistinctPair classPair : state.getDistinctClassPairs()) {
|
||||
EqClass class1 = classPair.getFirst();
|
||||
EqClass class2 = classPair.getSecond();
|
||||
for (DfaVariableValue var1 : class1) {
|
||||
for (DfaVariableValue var2 : class2) {
|
||||
result.add(new EqualityFact(var1, false, var2));
|
||||
result.add(new EqualityFact(var2, false, var1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DfaValueFactory factory = state.getFactory();
|
||||
state.forRecordedVariableTypes((var, dfType) -> {
|
||||
TypeConstraint typeConstraint = TypeConstraint.fromDfType(dfType);
|
||||
typeConstraint.instanceOfTypes().map(type -> new InstanceofFact(var, true, factory.fromDfType(type.asDfType()))).into(result);
|
||||
typeConstraint.notInstanceOfTypes().map(type -> new InstanceofFact(var, false, factory.fromDfType(type.asDfType()))).into(result);
|
||||
if (dfType instanceof DfConstantType) {
|
||||
result.add(new EqualityFact(var, true, var.getFactory().fromDfType(dfType)));
|
||||
}
|
||||
if (dfType instanceof DfAntiConstantType) {
|
||||
Set<?> notValues = ((DfAntiConstantType<?>)dfType).getNotValues();
|
||||
if (!notValues.isEmpty()) {
|
||||
for (Object notValue : notValues) {
|
||||
result.add(new EqualityFact(var, false, var.getFactory().fromDfType(DfTypes.constant(notValue, var.getDfType()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
static final class CompactFactSet {
|
||||
private final long[] myData;
|
||||
private final int myHashCode;
|
||||
private final DfaValueFactory myFactory;
|
||||
|
||||
CompactFactSet(DfaValueFactory factory, Collection<Fact> facts) {
|
||||
myData = facts.stream().mapToLong(Fact::pack).toArray();
|
||||
Arrays.sort(myData);
|
||||
myHashCode = Arrays.hashCode(myData);
|
||||
myFactory = factory;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return myData.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof CompactFactSet)) return false;
|
||||
CompactFactSet other = (CompactFactSet)obj;
|
||||
return this.myHashCode == other.myHashCode && Arrays.equals(this.myData, other.myData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return LongStreamEx.of(myData).mapToObj(f -> Fact.unpack(myFactory, f)).joining(", ", "{", "}");
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class Fact {
|
||||
final boolean myPositive;
|
||||
final @NotNull DfaVariableValue myVar;
|
||||
private final int myHash;
|
||||
|
||||
protected Fact(boolean positive, @NotNull DfaVariableValue var, int hash) {
|
||||
myPositive = positive;
|
||||
myVar = var;
|
||||
myHash = hash;
|
||||
}
|
||||
|
||||
private int packLow() {
|
||||
return myPositive ? myVar.getID() : -myVar.getID();
|
||||
}
|
||||
|
||||
abstract int packHigh();
|
||||
|
||||
long pack() {
|
||||
int lo = packLow();
|
||||
int hi = packHigh();
|
||||
return ((long)hi << 32) | (lo & 0xFFFF_FFFFL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
abstract @NotNull Fact getPositiveCounterpart();
|
||||
|
||||
DfaTypeValue comparedToConstant() {
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract boolean invalidatesFact(@NotNull Fact another);
|
||||
|
||||
abstract void removeFromState(@NotNull DfaMemoryStateImpl state);
|
||||
|
||||
void restoreCommonState(DfaMemoryStateImpl stripped, Collection<DfaMemoryStateImpl> merged) {
|
||||
DfType commonType = StreamEx.of(merged).map(s -> s.getDfType(myVar)).foldLeft(DfType.BOTTOM, DfType::join);
|
||||
stripped.meetDfType(myVar, commonType);
|
||||
}
|
||||
|
||||
static @NotNull EqualityFact createEqualityFact(@NotNull DfaVariableValue var, @NotNull DfaValue val) {
|
||||
if (val instanceof DfaVariableValue && val.getID() < var.getID()) {
|
||||
return new EqualityFact((DfaVariableValue)val, true, var);
|
||||
}
|
||||
return new EqualityFact(var, true, val);
|
||||
}
|
||||
|
||||
static Fact unpack(DfaValueFactory factory, long packed) {
|
||||
int lo = (int)(packed & 0xFFFF_FFFFL);
|
||||
int hi = (int)(packed >> 32);
|
||||
boolean positive = lo >= 0;
|
||||
DfaVariableValue var = (DfaVariableValue)factory.getValue(Math.abs(lo));
|
||||
if (hi >= 0) {
|
||||
return new EqualityFact(var, positive, factory.getValue(hi));
|
||||
} else {
|
||||
return new InstanceofFact(var, positive, (DfaTypeValue)factory.getValue(-hi));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class EqualityFact extends Fact {
|
||||
private final @NotNull DfaValue myArg;
|
||||
|
||||
private EqualityFact(@NotNull DfaVariableValue var, boolean positive, @NotNull DfaValue arg) {
|
||||
super(positive, var, (var.hashCode() * 31 + arg.hashCode()) * 31 + (positive ? 1 : 0));
|
||||
myArg = arg;
|
||||
}
|
||||
|
||||
@Override
|
||||
int packHigh() {
|
||||
return myArg.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof EqualityFact)) return false;
|
||||
|
||||
EqualityFact fact = (EqualityFact)o;
|
||||
return myArg == fact.myArg && myVar == fact.myVar && myPositive == fact.myPositive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myVar + (myPositive ? " EQ " : " NE ") + myArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
DfaTypeValue comparedToConstant() {
|
||||
return myArg instanceof DfaTypeValue ? (DfaTypeValue)myArg : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
EqualityFact getPositiveCounterpart() {
|
||||
return new EqualityFact(myVar, true, myArg);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean invalidatesFact(@NotNull Fact another) {
|
||||
if (!(another instanceof EqualityFact)) return false;
|
||||
return myVar == another.myVar || myVar == ((EqualityFact)another).myArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
void removeFromState(@NotNull DfaMemoryStateImpl state) {
|
||||
DfType dfType = state.getDfType(myVar);
|
||||
if (dfType instanceof DfConstantType ||
|
||||
dfType instanceof DfAntiConstantType && ((DfAntiConstantType<?>)dfType).getNotValues().size() == 1) {
|
||||
state.flushVariable(myVar);
|
||||
if (myArg.getDfType() == DfTypes.NULL) {
|
||||
state.meetDfType(myVar, DfaNullability.NULLABLE.asDfType());
|
||||
}
|
||||
} else {
|
||||
state.removeEquivalence(myVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class InstanceofFact extends Fact {
|
||||
private final @NotNull DfaTypeValue myType;
|
||||
|
||||
private InstanceofFact(@NotNull DfaVariableValue var, boolean positive, @NotNull DfaTypeValue type) {
|
||||
super(positive, var, (var.hashCode() * 31 + type.hashCode()) * 31 + (positive ? 1 : 0));
|
||||
myType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
int packHigh() {
|
||||
return -myType.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof InstanceofFact)) return false;
|
||||
|
||||
InstanceofFact fact = (InstanceofFact)o;
|
||||
return myPositive == fact.myPositive && myType == fact.myType && myVar == fact.myVar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myVar + (myPositive ? " IS " : " IS NOT ") + myType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
Fact getPositiveCounterpart() {
|
||||
return new InstanceofFact(myVar, true, myType);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean invalidatesFact(@NotNull Fact another) {
|
||||
return another instanceof InstanceofFact &&
|
||||
myType == ((InstanceofFact)another).myType &&
|
||||
myVar == another.myVar;
|
||||
}
|
||||
|
||||
@Override
|
||||
void removeFromState(@NotNull DfaMemoryStateImpl state) {
|
||||
DfType type = state.getDfType(myVar);
|
||||
if (type instanceof DfReferenceType) {
|
||||
state.recordVariableType(myVar, ((DfReferenceType)type).withoutType(TypeConstraint.fromDfType(myType.getDfType())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Replacements {
|
||||
private final @NotNull List<DfaMemoryStateImpl> myAllStates;
|
||||
private final Set<DfaMemoryStateImpl> myRemovedStates = new ReferenceOpenHashSet<>();
|
||||
private final List<DfaMemoryStateImpl> myMerged = new ArrayList<>();
|
||||
|
||||
private Replacements(@NotNull List<DfaMemoryStateImpl> allStates) {
|
||||
myAllStates = allStates;
|
||||
}
|
||||
|
||||
private boolean hasMerges() { return !myMerged.isEmpty(); }
|
||||
|
||||
private @Nullable List<DfaMemoryStateImpl> getMergeResult() {
|
||||
if (hasMerges()) {
|
||||
List<DfaMemoryStateImpl> result = new ArrayList<>(myMerged);
|
||||
for (DfaMemoryStateImpl state : myAllStates) {
|
||||
if (!myRemovedStates.contains(state)) {
|
||||
result.add(state);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void stripAndMerge(@NotNull Collection<DfaMemoryStateImpl> group, @NotNull Fact fact) {
|
||||
if (group.size() <= 1) return;
|
||||
|
||||
MultiMap<DfaMemoryStateImpl, DfaMemoryStateImpl> strippedToOriginals = MultiMap.create();
|
||||
for (DfaMemoryStateImpl original : group) {
|
||||
DfaMemoryStateImpl copy = original.createCopy();
|
||||
fact.removeFromState(copy);
|
||||
strippedToOriginals.putValue(copy, original);
|
||||
}
|
||||
for (Map.Entry<DfaMemoryStateImpl, Collection<DfaMemoryStateImpl>> entry : strippedToOriginals.entrySet()) {
|
||||
Collection<DfaMemoryStateImpl> merged = entry.getValue();
|
||||
if (merged.size() > 1) {
|
||||
DfaMemoryStateImpl stripped = entry.getKey();
|
||||
fact.restoreCommonState(stripped, merged);
|
||||
for (DfaMemoryStateImpl state : merged) {
|
||||
stripped.afterMerge(state);
|
||||
}
|
||||
myRemovedStates.addAll(merged);
|
||||
myMerged.add(stripped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ package com.intellij.codeInspection.dataFlow;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.lang.ir.Instruction;
|
||||
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -57,54 +57,39 @@ class StateQueue {
|
||||
memoryStates.add((DfaMemoryStateImpl)anotherState);
|
||||
}
|
||||
|
||||
memoryStates = forceMerge(memoryStates);
|
||||
if (memoryStates.size() > 1 && joinInstructions.contains(instruction)) {
|
||||
squash(memoryStates);
|
||||
}
|
||||
|
||||
if (memoryStates.size() > 1 && joinInstructions.contains(instruction)) {
|
||||
while (true) {
|
||||
int beforeSize = memoryStates.size();
|
||||
MultiMap<Object, DfaMemoryStateImpl> groups = MultiMap.create();
|
||||
for (DfaMemoryStateImpl memoryState : memoryStates) {
|
||||
groups.putValue(memoryState.getSuperficialKey(), memoryState);
|
||||
}
|
||||
|
||||
memoryStates = new ArrayList<>();
|
||||
for (Map.Entry<Object, Collection<DfaMemoryStateImpl>> entry : groups.entrySet()) {
|
||||
memoryStates.addAll(mergeGroup((List<DfaMemoryStateImpl>)entry.getValue()));
|
||||
}
|
||||
if (memoryStates.size() == beforeSize) break;
|
||||
beforeSize = memoryStates.size();
|
||||
if (beforeSize == 1) break;
|
||||
// If some states were merged it's possible that they could be further squashed
|
||||
squash(memoryStates);
|
||||
if (memoryStates.size() == beforeSize || memoryStates.size() == 1) break;
|
||||
}
|
||||
}
|
||||
|
||||
memoryStates = forceMerge(memoryStates);
|
||||
|
||||
return ContainerUtil.map(memoryStates, state1 -> new DfaInstructionState(instruction, state1));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<DfaMemoryStateImpl> squash(List<DfaMemoryStateImpl> states) {
|
||||
return DfaUtil.upwardsAntichain(states, (l, r) -> r.isSuperStateOf(l));
|
||||
}
|
||||
|
||||
static List<DfaMemoryStateImpl> mergeGroup(List<DfaMemoryStateImpl> group) {
|
||||
if (group.size() < 2) {
|
||||
return group;
|
||||
static List<DfaMemoryStateImpl> squash(List<DfaMemoryStateImpl> states) {
|
||||
for (int i = 1; i < states.size(); i++) {
|
||||
DfaMemoryStateImpl left = states.get(i);
|
||||
if (left == null) continue;
|
||||
for (int j = 0; j < i; j++) {
|
||||
ProgressManager.checkCanceled();
|
||||
DfaMemoryStateImpl right = states.get(j);
|
||||
if (right == null) continue;
|
||||
DfaMemoryStateImpl result = left.tryJoinExactly(right);
|
||||
if (result == left) {
|
||||
states.set(j, null);
|
||||
} else if (result == right) {
|
||||
states.set(i, null);
|
||||
break;
|
||||
} else if (result != null) {
|
||||
states.set(i, null);
|
||||
states.set(j, null);
|
||||
states.add(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateMerger merger = new StateMerger();
|
||||
while (group.size() > 1) {
|
||||
List<DfaMemoryStateImpl> nextStates = merger.mergeByRanges(group);
|
||||
if (nextStates == null) nextStates = merger.mergeByFacts(group);
|
||||
if (nextStates == null) break;
|
||||
group = nextStates;
|
||||
}
|
||||
return group;
|
||||
states.removeIf(Objects::isNull);
|
||||
return states;
|
||||
}
|
||||
|
||||
private List<DfaMemoryStateImpl> forceMerge(List<DfaMemoryStateImpl> states) {
|
||||
|
||||
@@ -392,24 +392,27 @@ public interface TypeConstraint {
|
||||
if (myInstanceOf.equals(constrained.myInstanceOf)) {
|
||||
return joinWithConstrained(constrained);
|
||||
}
|
||||
if (myNotInstanceOf.isEmpty() && myInstanceOf.containsAll(constrained.myInstanceOf) &&
|
||||
myInstanceOf.containsAll(constrained.myNotInstanceOf) &&
|
||||
myInstanceOf.size() == constrained.myInstanceOf.size() + constrained.myNotInstanceOf.size()) {
|
||||
Set<Exact> newInstanceOf = new HashSet<>(myInstanceOf);
|
||||
newInstanceOf.removeAll(constrained.myNotInstanceOf);
|
||||
return newInstanceOf.isEmpty() ? TypeConstraints.TOP : new Constrained(newInstanceOf, Set.of());
|
||||
int size1 = myInstanceOf.size() + myNotInstanceOf.size();
|
||||
int size2 = constrained.myInstanceOf.size() + constrained.myNotInstanceOf.size();
|
||||
// size1 >= 3 check allows to avoid merging too eagerly and preserves 'possible CCE' warnings
|
||||
if (size1 == size2 && size1 >= 3) {
|
||||
if (myInstanceOf.containsAll(constrained.myInstanceOf) &&
|
||||
constrained.myNotInstanceOf.containsAll(myNotInstanceOf)) {
|
||||
Set<Exact> diff = new HashSet<>(myInstanceOf);
|
||||
diff.removeAll(constrained.myInstanceOf);
|
||||
if (diff.size() == 1 && constrained.myNotInstanceOf.containsAll(diff)) {
|
||||
return new Constrained(constrained.myInstanceOf, myNotInstanceOf);
|
||||
}
|
||||
}
|
||||
if (constrained.myInstanceOf.containsAll(myInstanceOf) &&
|
||||
myNotInstanceOf.containsAll(constrained.myNotInstanceOf)) {
|
||||
Set<Exact> diff = new HashSet<>(constrained.myInstanceOf);
|
||||
diff.removeAll(myInstanceOf);
|
||||
if (diff.size() == 1 && myNotInstanceOf.containsAll(diff)) {
|
||||
return new Constrained(myInstanceOf, constrained.myNotInstanceOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (constrained.myNotInstanceOf.isEmpty() && constrained.myInstanceOf.containsAll(myInstanceOf) &&
|
||||
constrained.myInstanceOf.containsAll(myNotInstanceOf) &&
|
||||
constrained.myInstanceOf.size() == myInstanceOf.size() + myNotInstanceOf.size()) {
|
||||
Set<Exact> newInstanceOf = new HashSet<>(constrained.myInstanceOf);
|
||||
newInstanceOf.removeAll(myNotInstanceOf);
|
||||
return newInstanceOf.isEmpty() ? TypeConstraints.TOP : new Constrained(newInstanceOf, Set.of());
|
||||
}
|
||||
}
|
||||
if (other instanceof Exact && ((Exact)other).isFinal() && ((Exact)other).canBeInstantiated() &&
|
||||
myInstanceOf.isEmpty() && myNotInstanceOf.equals(Set.of(other))) {
|
||||
return TypeConstraints.TOP;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -212,8 +212,8 @@ final class DfGenericObjectType extends DfAntiConstantType<Object> implements Df
|
||||
|
||||
@Override
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) {
|
||||
if (isSuperType(other)) return this;
|
||||
if (other.isSuperType(this)) return other;
|
||||
if (isMergeable(other)) return this;
|
||||
if (other.isMergeable(this)) return other;
|
||||
if (!(other instanceof DfReferenceType)) return null;
|
||||
if (other instanceof DfNullConstantType) {
|
||||
if (mySpecialField != null) return null;
|
||||
@@ -226,8 +226,9 @@ final class DfGenericObjectType extends DfAntiConstantType<Object> implements Df
|
||||
if (mySpecialField != null) return null;
|
||||
Set<Object> notValues = new HashSet<>(myNotValues);
|
||||
notValues.remove(otherValue);
|
||||
return new DfGenericObjectType(notValues, getConstraint(), getNullability(),
|
||||
getMutability(), null, BOTTOM, isLocal());
|
||||
TypeConstraint constraint = getConstraint().tryJoinExactly(((DfReferenceConstantType)other).getConstraint());
|
||||
return constraint == null ? null : new DfGenericObjectType(notValues, constraint, getNullability(),
|
||||
getMutability(), null, BOTTOM, isLocal());
|
||||
}
|
||||
if (other instanceof DfGenericObjectType) {
|
||||
DfGenericObjectType objectType = (DfGenericObjectType)other;
|
||||
@@ -238,40 +239,47 @@ final class DfGenericObjectType extends DfAntiConstantType<Object> implements Df
|
||||
SpecialField otherSpecialField = objectType.getSpecialField();
|
||||
DfType otherSfType = objectType.getSpecialFieldType();
|
||||
boolean otherLocal = objectType.isLocal();
|
||||
int numberOfDifferences = 0;
|
||||
if (otherMutability != myMutability) numberOfDifferences++;
|
||||
if (otherNullability != myNullability) numberOfDifferences++;
|
||||
if (!otherNotValues.equals(myNotValues)) numberOfDifferences++;
|
||||
if (!otherConstraint.equals(myConstraint)) numberOfDifferences++;
|
||||
if (otherSpecialField != mySpecialField) numberOfDifferences++;
|
||||
if (!otherSfType.equals(mySpecialFieldType)) numberOfDifferences++;
|
||||
if (otherLocal != myLocal) numberOfDifferences++;
|
||||
if (numberOfDifferences != 1) return null;
|
||||
if (otherMutability != myMutability) {
|
||||
return new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability.unite(otherMutability),
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
if (otherNullability != myNullability) {
|
||||
return new DfGenericObjectType(myNotValues, myConstraint, myNullability.unite(otherNullability), myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
if (!otherNotValues.equals(myNotValues)) {
|
||||
Set<Object> notValues = new HashSet<>(myNotValues);
|
||||
notValues.retainAll(otherNotValues);
|
||||
return new DfGenericObjectType(notValues, myConstraint, myNullability, myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
if (!otherConstraint.equals(myConstraint)) {
|
||||
TypeConstraint constraint = otherConstraint.tryJoinExactly(myConstraint);
|
||||
return constraint == null ? null :
|
||||
new DfGenericObjectType(myNotValues, constraint, myNullability, myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
if (!otherSfType.equals(mySpecialFieldType)) {
|
||||
DfType sfType = otherSfType.tryJoinExactly(mySpecialFieldType);
|
||||
return sfType == null ? null :
|
||||
new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability,
|
||||
mySpecialField, sfType, myLocal);
|
||||
final int MUTABILITY = 0x01;
|
||||
final int NULLABILITY = 0x02;
|
||||
final int NOT_VALUES = 0x04;
|
||||
final int CONSTRAINT = 0x08;
|
||||
final int SPECIAL_FIELD = 0x10;
|
||||
final int SF_TYPE = 0x20;
|
||||
final int LOCAL = 0x40;
|
||||
int bits = 0;
|
||||
if (otherMutability != myMutability) bits |= MUTABILITY;
|
||||
if (otherNullability != myNullability) bits |= NULLABILITY;
|
||||
if (!otherNotValues.equals(myNotValues)) bits |= NOT_VALUES;
|
||||
if (!otherConstraint.equals(myConstraint)) bits |= CONSTRAINT;
|
||||
if (otherSpecialField != mySpecialField) bits |= SPECIAL_FIELD;
|
||||
if (!otherSfType.equals(mySpecialFieldType)) bits |= SF_TYPE;
|
||||
if (otherLocal != myLocal) bits |= LOCAL;
|
||||
switch (bits) {
|
||||
case CONSTRAINT:
|
||||
case NULLABILITY | CONSTRAINT: {
|
||||
TypeConstraint constraint = otherConstraint.tryJoinExactly(myConstraint);
|
||||
return constraint == null ? null :
|
||||
new DfGenericObjectType(myNotValues, constraint, myNullability.unite(otherNullability), myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
case MUTABILITY:
|
||||
return new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability.unite(otherMutability),
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
case NULLABILITY:
|
||||
return new DfGenericObjectType(myNotValues, myConstraint, myNullability.unite(otherNullability), myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
case NOT_VALUES: {
|
||||
Set<Object> notValues = new HashSet<>(myNotValues);
|
||||
notValues.retainAll(otherNotValues);
|
||||
return new DfGenericObjectType(notValues, myConstraint, myNullability, myMutability,
|
||||
mySpecialField, mySpecialFieldType, myLocal);
|
||||
}
|
||||
case SF_TYPE: {
|
||||
DfType sfType = otherSfType.tryJoinExactly(mySpecialFieldType);
|
||||
return sfType == null ? null :
|
||||
new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability,
|
||||
mySpecialField, sfType, myLocal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -51,8 +51,8 @@ public class DfNullConstantType extends DfConstantType<Object> implements DfRefe
|
||||
|
||||
@Override
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) {
|
||||
if (isSuperType(other)) return this;
|
||||
if (other.isSuperType(this)) return other;
|
||||
if (isMergeable(other)) return this;
|
||||
if (other.isMergeable(this)) return other;
|
||||
if (other instanceof DfGenericObjectType) return other.tryJoinExactly(this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -115,8 +115,8 @@ public class DfReferenceConstantType extends DfConstantType<Object> implements D
|
||||
|
||||
@Override
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) {
|
||||
if (isSuperType(other)) return this;
|
||||
if (other.isSuperType(this)) return other;
|
||||
if (isMergeable(other)) return this;
|
||||
if (other.isMergeable(this)) return other;
|
||||
if (other instanceof DfGenericObjectType) {
|
||||
other.tryJoinExactly(this);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ public final class DfTypes {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DfType tryJoinExactly(@NotNull DfType other) {
|
||||
return join(other);
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) {
|
||||
return other instanceof DfBooleanType ? this : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -112,7 +112,7 @@ public class BackPropagation {
|
||||
if (x - y > -1 && x <= y) {}
|
||||
if (x - y < -1 && x <= y) {}
|
||||
if (x - y == y && x == y) {}
|
||||
if (x - y < y && (x == y || x < y)) {}
|
||||
if (x - y < y && (<warning descr="Condition 'x == y' is always 'false' when reached">x == y</warning> || x < y)) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
import java.util.List;
|
||||
|
||||
abstract class Super {
|
||||
public static final Sub SUB = new Sub();
|
||||
}
|
||||
|
||||
class Sub extends Super {
|
||||
}
|
||||
public class JoinConstantAndSubtype {
|
||||
|
||||
void check(Super s) {
|
||||
if (s != Super.SUB && s instanceof Sub) return;
|
||||
if (s != Super.SUB) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,14 @@ public class JavaTypeProviderTest extends LightJavaCodeInsightTestCase {
|
||||
}
|
||||
|
||||
public void testFloatRangeHint2() {
|
||||
// TODO: merging order affects the result; we merge x == 0.5 and x == 1.8 first
|
||||
// resulting in [0.5..1.8], then we add <0.5 and >1.8 resulting in full range
|
||||
doTest("void test(double x) {" +
|
||||
"if (!(x > 0.5 && x < 1.8)) {" +
|
||||
"<selection>x</selection>" +
|
||||
"}}", "double",
|
||||
"double");
|
||||
"<table>" +
|
||||
"<tr><td align=\"left\" style=\"color:#909090\" valign=\"top\">Type:</td><td>double</td></tr>" +
|
||||
"<tr><td align=\"left\" style=\"color:#909090\" valign=\"top\">Range:</td><td><= 0.5 || >= 1.8 (or NaN)</td></tr>" +
|
||||
"</table>");
|
||||
}
|
||||
|
||||
public void testFloatConstantHint() {
|
||||
|
||||
@@ -703,4 +703,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
|
||||
public void testInferenceNullityMismatch() { doTestWith(insp -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false); }
|
||||
public void testFieldInInstanceInitializer() { doTest(); }
|
||||
public void testNullableCallWithPrecalculatedValueAndSpecialField() { doTest(); }
|
||||
public void testJoinConstantAndSubtype() { doTest(); }
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public interface DfType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DfType tryJoinExactly(@NotNull DfType other) { return join(other); }
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) { return other == this ? this : other == NOT_FAIL ? TOP : null; }
|
||||
|
||||
@Override
|
||||
public @NotNull DfType meet(@NotNull DfType other) {
|
||||
@@ -151,7 +151,7 @@ public interface DfType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DfType tryJoinExactly(@NotNull DfType other) { return join(other); }
|
||||
public @NotNull DfType tryJoinExactly(@NotNull DfType other) { return join(other); }
|
||||
|
||||
@Override
|
||||
public @NotNull DfType meet(@NotNull DfType other) {
|
||||
|
||||
Reference in New Issue
Block a user