DfaFactMap/DfaFactType introduced (reviewed: IDEA-CR-20337)

This commit is contained in:
Tagir Valeev
2017-05-16 10:32:31 +07:00
parent 8364b60fed
commit cf28cc4419
13 changed files with 352 additions and 151 deletions
@@ -112,15 +112,15 @@ public class CustomMethodHandlers {
DfaValueFactory factory,
SpecialField specialField) {
DfaValue length = specialField.createValue(factory, qualifier);
LongRangeSet range = memState.getRange(length);
LongRangeSet range = memState.getValueFact(DfaFactType.RANGE, length);
long maxLen = range == null || range.isEmpty() ? Integer.MAX_VALUE : range.max();
return singleResult(memState, factory.getRangeFactory().create(LongRangeSet.range(-1, maxLen - 1)));
}
private static List<DfaMemoryState> mathMinMax(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean max) {
if(args == null || args.length != 2) return Collections.emptyList();
LongRangeSet first = memState.getRange(args[0]);
LongRangeSet second = memState.getRange(args[1]);
LongRangeSet first = memState.getValueFact(DfaFactType.RANGE, args[0]);
LongRangeSet second = memState.getValueFact(DfaFactType.RANGE, args[1]);
if (first == null || second == null || first.isEmpty() || second.isEmpty()) return Collections.emptyList();
LongRangeSet domain = max ? LongRangeSet.range(Math.max(first.min(), second.min()), Long.MAX_VALUE)
: LongRangeSet.range(Long.MIN_VALUE, Math.min(first.max(), second.max()));
@@ -131,7 +131,7 @@ public class CustomMethodHandlers {
private static List<DfaMemoryState> mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
DfaValue arg = ArrayUtil.getFirstElement(args);
if(arg == null) return Collections.emptyList();
LongRangeSet range = memState.getRange(arg);
LongRangeSet range = memState.getValueFact(DfaFactType.RANGE, arg);
if (range == null) return Collections.emptyList();
return singleResult(memState, factory.getRangeFactory().create(range.abs(isLong)));
}
@@ -1013,7 +1013,8 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
myOptionalQualifiers.add(qualifier);
}
else if (DfaOptionalSupport.isOptionalGetMethodName(methodName)) {
ThreeState state = memState.checkOptional(memState.peek());
Boolean fact = memState.getValueFact(DfaFactType.OPTIONAL_PRESENCE, memState.peek());
ThreeState state = fact == null ? ThreeState.UNSURE : ThreeState.fromBoolean(fact);
myOptionalCalls.merge(call, state, ThreeState::merge);
}
}
@@ -0,0 +1,122 @@
/*
* Copyright 2000-2017 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.openapi.util.Key;
import com.intellij.util.keyFMap.KeyFMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* An immutable collection of facts which are known for some value. Each fact is identified by {@link DfaFactType} and fact value.
* A null value for some fact type means that the value is not restricted by given fact type or given fact type is not
* applicable to given value.
* <p>
* To create a new {@code DfaFactMap}, use {@link #EMPTY} and call {@link #with(DfaFactType, Object)} method.
*
* @author Tagir Valeev
*/
public final class DfaFactMap {
public static final DfaFactMap EMPTY = new DfaFactMap(KeyFMap.EMPTY_MAP);
// Contains DfaFactType as keys only
private final @NotNull KeyFMap myMap;
private DfaFactMap(@NotNull KeyFMap map) {
myMap = map;
}
/**
* Returns a fact value for given fact type stored in current fact map or
* null if current fact map is not restricted by given fact type.
*
* @param type fact type to fetch
* @param <T> type of the fact value
* @return a fact value or null
*/
@Nullable
public <T> T get(@NotNull DfaFactType<T> type) {
return myMap.get(type);
}
/**
* Returns a new fact map which is the same as current map, but replaces fact value of given type with the new value.
*
* @param type fact type to replace
* @param value new value (supplying null here effectively removes the value)
* @param <T> the type of fact value
* @return a new fact map. May return itself if it's detected that this fact map already contains the supplied value.
*/
@NotNull
public <T> DfaFactMap with(@NotNull DfaFactType<T> type, @Nullable T value) {
KeyFMap newMap = value == null ? myMap.minus(type) : myMap.plus(type, value);
return newMap == myMap ? this : new DfaFactMap(newMap);
}
/**
* Checks whether the passed fact map is a sub-state of this map (i.e. any exact value
* which conforms the passed fact map also conforms this fact map).
*
* @param subMap a fact map to check
* @return true if this fact map is a super-state of supplied fact map.
*/
public boolean isSuperStateOf(DfaFactMap subMap) {
for (Key key : myMap.getKeys()) {
@SuppressWarnings("unchecked")
DfaFactType<Object> type = (DfaFactType<Object>)key;
Object other = subMap.get(type);
if(other == null) return false;
Object thisValue = myMap.get(type);
Objects.requireNonNull(thisValue); // cannot be null as type is known to be my key and we never store null values
if(!type.isSuper(thisValue, other)) return false;
}
return true;
}
/**
* Returns a fact map which is additionally restricted by supplied fact.
* The returned map is a sub-state of this map.
*
* @param type a type of a new fact
* @param value a fact value which should be true for the resulting map. Passing null
* is essentially a no-op as no additional restriction is applied.
* @param <T> a fact value type
* @return a new fact map or null if new fact is incompatible with current fact map (no value is possible
* which conforms to the new fact and to this fact map simultaneously). May return itself if
* it's known that new fact does not actually change this map.
*/
@Nullable
public <T> DfaFactMap intersect(@NotNull DfaFactType<T> type, @Nullable T value) {
if (value == null) return this;
T curFact = get(type);
if (curFact == null) return with(type, value);
T newFact = type.intersectFacts(curFact, value);
return newFact == null ? null : with(type, newFact);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof DfaFactMap && myMap.equals(((DfaFactMap)o).myMap);
}
@Override
public int hashCode() {
return myMap.hashCode();
}
}
@@ -0,0 +1,119 @@
/*
* Copyright 2000-2017 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.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaOptionalValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.util.Key;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A type of the fact which restricts some value.
*
* @author Tagir Valeev
*/
public abstract class DfaFactType<T> extends Key<T> {
/**
* This fact is applied to the Optional values (like {@link java.util.Optional} or Guava Optional).
* When its value is true, then optional is known to be present.
* When its value is false, then optional is known to be empty (absent).
*/
public static final DfaFactType<Boolean> OPTIONAL_PRESENCE = new DfaFactType<Boolean>("Optional presense") {
@Override
String toString(Boolean fact) {
return fact ? "present Optional" : "absent Optional";
}
@Nullable
@Override
Boolean fromDfaValue(DfaValue value) {
return value instanceof DfaOptionalValue ? ((DfaOptionalValue)value).isPresent() : null;
}
};
/**
* This fact is applied to the integral values (of types byte, char, short, int, long).
* Its value represents a range of possible values.
*/
public static final DfaFactType<LongRangeSet> RANGE = new DfaFactType<LongRangeSet>("Mutability") {
@Override
boolean isSuper(@NotNull LongRangeSet superFact, @NotNull LongRangeSet subFact) {
return superFact.contains(subFact);
}
@Nullable
@Override
LongRangeSet fromDfaValue(DfaValue value) {
if(value instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)value;
if(var.getQualifier() != null) {
LongRangeSet specialRange =
StreamEx.of(SpecialField.values()).findFirst(sf -> sf.isMyAccessor(var.getPsiVariable())).map(SpecialField::getRange)
.orElse(null);
if(specialRange != null) {
return specialRange;
}
}
}
return LongRangeSet.fromDfaValue(value);
}
@Nullable
@Override
LongRangeSet intersectFacts(@NotNull LongRangeSet left, @NotNull LongRangeSet right) {
LongRangeSet intersection = left.intersect(right);
return intersection.isEmpty() ? null : intersection;
}
@Override
String toString(LongRangeSet fact) {
return fact.toString();
}
};
private DfaFactType(String name) {
super(name);
}
@Nullable
T fromDfaValue(DfaValue value) {
return null;
}
boolean isSuper(@NotNull T superFact, @NotNull T subFact) {
return false;
}
/**
* Intersects two facts of this type.
*
* @param left left fact
* @param right right fact
* @return intersection fact or null if facts are incompatible
*/
@Nullable
T intersectFacts(@NotNull T left, @NotNull T right) {
return left.equals(right) ? left : null;
}
String toString(T fact) {
return fact.toString();
}
}
@@ -15,12 +15,10 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -45,10 +43,17 @@ public interface DfaMemoryState {
boolean applyContractCondition(DfaValue dfaCond);
ThreeState checkOptional(DfaValue value);
/**
* Returns a value fact about supplied value within the context of current memory state.
* Returns null if the fact of given type is not known or not applicable to a given value.
*
* @param factType a type of the fact to get
* @param value a value to get the fact about
* @param <T> a type of the fact value
* @return a fact about value, if known
*/
@Nullable
LongRangeSet getRange(DfaValue value);
<T> T getValueFact(@NotNull DfaFactType<T> factType, @NotNull DfaValue value);
void flushFields();
@@ -30,7 +30,6 @@ import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.siyeh.ig.psiutils.MethodUtils;
@@ -211,9 +210,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
if (!myVariableStates.isEmpty()) {
result.append("\n vars: ");
for (Map.Entry<DfaVariableValue, DfaVariableState> entry : myVariableStates.entrySet()) {
result.append("[").append(entry.getKey()).append("->").append(entry.getValue()).append("] ");
}
myVariableStates.forEach((key, value) -> result.append("[").append(key).append("->").append(value).append("] "));
}
if (!myUnknownVariables.isEmpty()) {
result.append("\n unknowns: ").append(new HashSet<>(myUnknownVariables));
@@ -647,29 +644,18 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return false;
}
private void applyIsPresentCheck(boolean present, DfaValue qualifier) {
if (qualifier instanceof DfaVariableValue && !isUnknownState(qualifier)) {
setVariableState((DfaVariableValue)qualifier, getVariableState((DfaVariableValue)qualifier).withOptionalPresense(present));
<T> void setFact(DfaValue target, DfaFactType<T> factType, T fact) {
if (target instanceof DfaVariableValue && !isUnknownState(target)) {
setVariableState((DfaVariableValue)target, getVariableState((DfaVariableValue)target).withFact(factType, fact));
}
}
void setRange(DfaVariableValue target, LongRangeSet range) {
if (!isUnknownState(target)) {
setVariableState(target, getVariableState(target).withRange(range));
}
}
boolean applyRange(LongRangeSet range, DfaVariableValue target) {
<T> boolean applyFact(DfaVariableValue target, DfaFactType<T> factType, T range) {
if (!isUnknownState(target) && range != null) {
DfaVariableState state = getVariableState(target);
LongRangeSet oldRange = state.getRange();
if (oldRange == null) {
oldRange = LongRangeSet.fromType(target.getVariableType());
if (oldRange == null) return true;
}
LongRangeSet newRange = oldRange.intersect(range);
if (newRange.isEmpty()) return false;
setVariableState(target, state.withRange(newRange));
DfaVariableState newState = state.intersectFact(factType, range);
if (newState == null) return false;
setVariableState(target, newState);
}
return true;
}
@@ -735,33 +721,27 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
RelationType relationType = dfaRelation.getRelation();
if (dfaLeft instanceof DfaVariableValue) {
LongRangeSet right = getRange(dfaRight);
LongRangeSet right = getValueFact(DfaFactType.RANGE, dfaRight);
if (right != null) {
if (!applyRange(right.fromRelation(relationType), (DfaVariableValue)dfaLeft)) {
if (!applyFact((DfaVariableValue)dfaLeft, DfaFactType.RANGE, right.fromRelation(relationType))) {
return false;
}
}
}
if (dfaRight instanceof DfaVariableValue) {
LongRangeSet left = getRange(dfaLeft);
LongRangeSet left = getValueFact(DfaFactType.RANGE, dfaLeft);
if (left != null) {
if (!applyRange(left.fromRelation(relationType.getFlipped()), (DfaVariableValue)dfaRight)) {
if (!applyFact((DfaVariableValue)dfaRight, DfaFactType.RANGE, left.fromRelation(relationType.getFlipped()))) {
return false;
}
}
}
if (dfaRight instanceof DfaOptionalValue && (relationType == RelationType.IS || relationType == RelationType.IS_NOT)) {
ThreeState state = checkOptional(dfaLeft);
boolean present = ((DfaOptionalValue)dfaRight).isPresent();
if (relationType == RelationType.IS_NOT) {
present = !present;
}
if (state == ThreeState.UNSURE) {
applyIsPresentCheck(present, dfaLeft);
return true;
}
return state == ThreeState.fromBoolean(present);
if (dfaLeft instanceof DfaVariableValue &&
dfaRight instanceof DfaOptionalValue &&
(relationType == RelationType.IS || relationType == RelationType.IS_NOT)) {
boolean present = ((DfaOptionalValue)dfaRight).isPresent() == (relationType == RelationType.IS);
return applyFact((DfaVariableValue)dfaLeft, DfaFactType.OPTIONAL_PRESENCE, present);
}
if (dfaRight instanceof DfaTypeValue) {
@@ -871,7 +851,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
if (!isNegated && dfaRight instanceof DfaOptionalValue) {
applyIsPresentCheck(((DfaOptionalValue)dfaRight).isPresent(), dfaLeft);
setFact(dfaLeft, DfaFactType.OPTIONAL_PRESENCE, ((DfaOptionalValue)dfaRight).isPresent());
}
return true;
@@ -1009,40 +989,35 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return true;
}
@Override
public ThreeState checkOptional(DfaValue value) {
if (value instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)value;
DfaVariableState state = getVariableState(var);
return state.getOptionalPresense();
@Nullable
@SuppressWarnings("unchecked")
public <T> T getValueFact(@NotNull DfaFactType<T> factType, @NotNull DfaValue value) {
if (factType == DfaFactType.RANGE) {
LongRangeSet range = getRange(value);
if (range != null) {
return (T)range;
}
}
return value instanceof DfaOptionalValue ? ThreeState.fromBoolean(((DfaOptionalValue)value).isPresent()) : ThreeState.UNSURE;
if (value instanceof DfaVariableValue) {
DfaVariableState state = myVariableStates.get((DfaVariableValue)value);
if (state != null) {
T fact = state.getFact(factType);
if (fact != null) {
return fact;
}
}
DfaConstValue constValue = getConstantValue((DfaVariableValue)value);
if (constValue != null) {
value = constValue;
}
}
return factType.fromDfaValue(value);
}
/**
* Returns range of possible values for given DfaValue if possible
*
* @param value value to get the range from
* @return possible range or null if range is not known/non-applicable. Empty range indicates that no exact value is possible
* for given DfaValue (likely impossible code path).
*/
@Nullable
@Override
public LongRangeSet getRange(DfaValue value) {
private LongRangeSet getRange(DfaValue value) {
if (value instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)value;
if (!TypeConversionUtil.isPrimitiveAndNotNull(var.getVariableType())) {
return null;
}
DfaVariableState state = getVariableState(var);
LongRangeSet range = state.getRange();
if (range == null) {
DfaConstValue constValue = getConstantValue(var);
if (constValue != null) {
return LongRangeSet.fromConstant(constValue.getValue());
}
return LongRangeSet.fromType(var.getVariableType());
}
if (var.getPsiVariable() instanceof PsiMethod && MethodUtils.isStringLength((PsiMethod)var.getPsiVariable())) {
DfaVariableValue qualifier = var.getQualifier();
if(qualifier != null) {
@@ -1052,9 +1027,8 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
}
return range;
}
return LongRangeSet.fromDfaValue(value);
return null;
}
void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) {
@@ -24,7 +24,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -36,21 +35,19 @@ class DfaVariableState {
@NotNull final Set<DfaPsiType> myInstanceofValues;
@NotNull final Set<DfaPsiType> myNotInstanceofValues;
@NotNull final Nullness myNullability;
@NotNull final ThreeState myOptionalPresence;
@Nullable final LongRangeSet myRange;
@NotNull final DfaFactMap myFactMap;
private final int myHash;
DfaVariableState(@NotNull DfaVariableValue dfaVar) {
this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(), ThreeState.UNSURE, getInitialRange(dfaVar));
this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(),
DfaFactMap.EMPTY.with(DfaFactType.RANGE, getInitialRange(dfaVar)));
}
public boolean isSuperStateOf(DfaVariableState that) {
if(!myNotInstanceofValues.equals(that.myNotInstanceofValues)) return false;
if(!myInstanceofValues.equals(that.myNotInstanceofValues)) return false;
if(!myNullability.equals(that.myNullability)) return false;
if(!myOptionalPresence.equals(that.myOptionalPresence)) return false;
if(Objects.equals(myRange, that.myRange)) return true;
return myRange != null && that.myRange != null && myRange.contains(that.myRange);
return myFactMap.isSuperStateOf(that.myFactMap);
}
private static LongRangeSet getInitialRange(DfaVariableValue var) {
@@ -69,14 +66,12 @@ class DfaVariableState {
DfaVariableState(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
@NotNull ThreeState optionalPresence,
@Nullable LongRangeSet range) {
@NotNull DfaFactMap factMap) {
myInstanceofValues = instanceofValues;
myNotInstanceofValues = notInstanceofValues;
myNullability = nullability;
myOptionalPresence = optionalPresence;
myRange = range;
myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
myFactMap = factMap;
myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myNullability, myFactMap);
}
public boolean isNullable() {
@@ -116,7 +111,7 @@ class DfaVariableState {
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
newInstanceof.removeAll(moreGeneric);
newInstanceof.add(dfaType.getDfaType());
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myOptionalPresence, myRange);
result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myFactMap);
return result;
}
@@ -144,7 +139,7 @@ class DfaVariableState {
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
newNotInstanceof.removeAll(moreSpecific);
newNotInstanceof.add(dfaType.getDfaType());
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myFactMap);
}
@NotNull
@@ -152,12 +147,12 @@ class DfaVariableState {
if (myInstanceofValues.contains(type)) {
HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
newInstanceof.remove(type);
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myOptionalPresence, myRange);
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myFactMap);
}
if (myNotInstanceofValues.contains(type)) {
HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues);
newNotInstanceof.remove(type);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myOptionalPresence, myRange);
return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myFactMap);
}
return this;
}
@@ -172,19 +167,17 @@ class DfaVariableState {
DfaVariableState aState = (DfaVariableState) obj;
return myHash == aState.myHash &&
myNullability == aState.myNullability &&
myOptionalPresence == aState.myOptionalPresence &&
myInstanceofValues.equals(aState.myInstanceofValues) &&
myNotInstanceofValues.equals(aState.myNotInstanceofValues) &&
Objects.equals(myRange, aState.myRange);
Objects.equals(myFactMap, aState.myFactMap);
}
@NotNull
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
ThreeState optionalPresent,
LongRangeSet range) {
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, optionalPresent, range);
@NotNull DfaFactMap fact) {
return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, fact);
}
public String toString() {
@@ -199,11 +192,9 @@ class DfaVariableState {
buf.append(" not instanceof ").append(StringUtil.join(myNotInstanceofValues, ","));
}
if (myOptionalPresence != ThreeState.UNSURE) {
buf.append(myOptionalPresence == ThreeState.YES ? " Optional with value" : " empty Optional");
}
if (myRange != null) {
buf.append(" ").append(myRange);
String factString = myFactMap.toString();
if(!factString.isEmpty()) {
buf.append(" ").append(factString);
}
return buf.toString();
}
@@ -219,7 +210,7 @@ class DfaVariableState {
@NotNull
DfaVariableState withNullability(@NotNull Nullness nullness) {
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myOptionalPresence, myRange);
return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myFactMap);
}
@NotNull
@@ -227,17 +218,18 @@ class DfaVariableState {
return myNullability != Nullness.NOT_NULL ? withNullability(nullable ? Nullness.NULLABLE : Nullness.UNKNOWN) : this;
}
DfaVariableState withOptionalPresense(final boolean presense) {
ThreeState optionalPresent = ThreeState.fromBoolean(presense);
return myOptionalPresence != optionalPresent
? createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, optionalPresent, myRange)
: this;
@NotNull
<T> DfaVariableState withFact(DfaFactType<T> type, T value) {
DfaFactMap factMap = myFactMap.with(type, value);
return myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, factMap);
}
DfaVariableState withRange(@Nullable LongRangeSet range) {
return Objects.equals(range, myRange)
? this
: createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, myOptionalPresence, range);
@Nullable
<T> DfaVariableState intersectFact(DfaFactType<T> type, T value) {
DfaFactMap factMap = myFactMap.intersect(type, value);
return factMap == null
? null
: myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, factMap);
}
@NotNull
@@ -260,12 +252,8 @@ class DfaVariableState {
return myNotInstanceofValues;
}
public ThreeState getOptionalPresense() {
return myOptionalPresence;
}
@Nullable
public LongRangeSet getRange() {
return myRange;
public <T> T getFact(@NotNull DfaFactType<T> factType) {
return myFactMap.get(factType);
}
}
@@ -29,7 +29,6 @@ import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil;
import com.intellij.util.SmartList;
import com.intellij.util.ThreeState;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
@@ -130,7 +129,7 @@ class NullParameterConstraintChecker extends DataFlowRunner {
super(factory);
for (PsiParameter parameter : myPossiblyViolatedParameters) {
setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false),
new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, ThreeState.UNSURE, null));
new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, DfaFactMap.EMPTY));
}
}
@@ -636,8 +636,8 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
DfaValue result = null;
if (JavaTokenType.AND == opSign) {
LongRangeSet left = memState.getRange(dfaLeft);
LongRangeSet right = memState.getRange(dfaRight);
LongRangeSet left = memState.getValueFact(DfaFactType.RANGE, dfaLeft);
LongRangeSet right = memState.getValueFact(DfaFactType.RANGE, dfaRight);
if(left != null && right != null) {
result = runner.getFactory().getRangeFactory().create(left.bitwiseAnd(right));
}
@@ -233,10 +233,10 @@ class StateMerger {
for (DfaMemoryStateImpl state : states) {
for (Map.Entry<DfaVariableValue, Map<LongRangeSet, LongRangeSet>> entry : ranges.entrySet()) {
DfaVariableState variableState = state.getVariableState(entry.getKey());
LongRangeSet range = variableState.getRange();
LongRangeSet range = variableState.getFact(DfaFactType.RANGE);
LongRangeSet boundingRange = entry.getValue().get(range);
if (boundingRange != null && !boundingRange.equals(range)) {
state.setRange(entry.getKey(), boundingRange);
state.setFact(entry.getKey(), DfaFactType.RANGE, boundingRange);
changed = true;
}
}
@@ -256,12 +256,12 @@ class StateMerger {
for (DfaMemoryStateImpl state : states) {
ProgressManager.checkCanceled();
Map<DfaVariableValue, DfaVariableState> variableStates = state.getVariableStates();
for (Map.Entry<DfaVariableValue, DfaVariableState> entry : variableStates.entrySet()) {
LongRangeSet range = entry.getValue().getRange();
variableStates.forEach((varValue, varState) -> {
LongRangeSet range = varState.getFact(DfaFactType.RANGE);
if (range != null) {
ranges.computeIfAbsent(entry.getKey(), k -> new HashMap<>()).put(range, range);
ranges.computeIfAbsent(varValue, k -> new HashMap<>()).put(range, range);
}
}
});
}
return ranges;
}
@@ -302,7 +302,7 @@ class StateMerger {
DfaMemoryStateImpl getState() {
if(myMerged) {
myState.flushVariable(var);
myState.setRange(var, myRange);
myState.setFact(var, DfaFactType.RANGE, myRange);
}
return myState;
}
@@ -312,7 +312,7 @@ class StateMerger {
Map<DfaMemoryStateImpl, Record> merged = new LinkedHashMap<>();
for (DfaMemoryStateImpl state : states) {
DfaVariableState variableState = state.getVariableState(var);
LongRangeSet range = variableState.getRange();
LongRangeSet range = variableState.getFact(DfaFactType.RANGE);
if (range == null) {
range = LongRangeSet.fromType(var.getVariableType());
if (range == null) return null;
@@ -328,7 +328,7 @@ class StateMerger {
// If there are too many states, try to drop range information from some variable
DfaVariableValue lastVar = Collections.max(rangeVariables, Comparator.comparingInt(DfaVariableValue::getID));
for (DfaMemoryStateImpl state : states) {
state.setRange(lastVar, null);
state.setFact(lastVar, DfaFactType.RANGE, null);
}
return new ArrayList<>(new HashSet<>(states));
}
@@ -16,13 +16,11 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaPsiType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiExpression;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.FList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -80,9 +78,8 @@ class ValuableDataFlowRunner extends DataFlowRunner {
Set<DfaPsiType> notInstanceofValues,
Nullness nullability, DfaValue value,
@NotNull FList<PsiExpression> concatenation,
ThreeState optionalPresence,
LongRangeSet range) {
super(instanceofValues, notInstanceofValues, nullability, optionalPresence, range);
@NotNull DfaFactMap factMap) {
super(instanceofValues, notInstanceofValues, nullability, factMap);
myValue = value;
myConcatenation = concatenation;
}
@@ -92,24 +89,20 @@ class ValuableDataFlowRunner extends DataFlowRunner {
protected DfaVariableState createCopy(@NotNull Set<DfaPsiType> instanceofValues,
@NotNull Set<DfaPsiType> notInstanceofValues,
@NotNull Nullness nullability,
ThreeState optionalPresence,
LongRangeSet range) {
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, optionalPresence,
range);
@NotNull DfaFactMap factMap) {
return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, factMap);
}
@NotNull
@Override
public DfaVariableState withValue(@Nullable final DfaValue value) {
if (value == myValue) return this;
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation,
myOptionalPresence, myRange);
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myConcatenation, myFactMap);
}
ValuableDfaVariableState withExpression(@NotNull final FList<PsiExpression> concatenation) {
if (concatenation == myConcatenation) return this;
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation,
myOptionalPresence, myRange);
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation, myFactMap);
}
@Override
@@ -15,10 +15,7 @@
*/
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRangeValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.util.ThreeState;
@@ -336,6 +333,9 @@ public abstract class LongRangeSet {
if (value instanceof DfaConstValue) {
return fromConstant(((DfaConstValue)value).getValue());
}
if (value instanceof DfaVariableValue) {
return fromType(((DfaVariableValue)value).getVariableType());
}
return null;
}