diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index 48768ebf42f9..64747a49dc3d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -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 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 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))); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index bcf417ec9bf0..a4e0b06f3835 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -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); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java new file mode 100644 index 000000000000..38964040f11c --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java @@ -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. + *

+ * 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 type of the fact value + * @return a fact value or null + */ + @Nullable + public T get(@NotNull DfaFactType 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 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 DfaFactMap with(@NotNull DfaFactType 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 type = (DfaFactType)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 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 DfaFactMap intersect(@NotNull DfaFactType 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(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java new file mode 100644 index 000000000000..5615bad12b64 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java @@ -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 extends Key { + /** + * 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 OPTIONAL_PRESENCE = new DfaFactType("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 RANGE = new DfaFactType("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(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index 96b19db4e7dc..f8d8ef30ed02 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -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 a type of the fact value + * @return a fact about value, if known + */ @Nullable - LongRangeSet getRange(DfaValue value); + T getValueFact(@NotNull DfaFactType factType, @NotNull DfaValue value); void flushFields(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index cc52f7a53252..b76718c8bf96 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -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 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)); + void setFact(DfaValue target, DfaFactType 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) { + boolean applyFact(DfaVariableValue target, DfaFactType 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 getValueFact(@NotNull DfaFactType 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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java index 648434d49b5a..a3169edd7baa 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java @@ -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 myInstanceofValues; @NotNull final Set 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 instanceofValues, @NotNull Set 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 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 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 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 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 instanceofValues, @NotNull Set 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 + DfaVariableState withFact(DfaFactType 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 + DfaVariableState intersectFact(DfaFactType 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 getFact(@NotNull DfaFactType factType) { + return myFactMap.get(factType); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java index 7c25483a22cf..d3968ec13376 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullParameterConstraintChecker.java @@ -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)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 212f2235c851..56e10c4e98b3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java index e36fc897cdc7..fc265c15d168 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java @@ -233,10 +233,10 @@ class StateMerger { for (DfaMemoryStateImpl state : states) { for (Map.Entry> 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 variableStates = state.getVariableStates(); - for (Map.Entry 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 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)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java index 73b7f0cb897f..f7f5e7d2daca 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java @@ -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 notInstanceofValues, Nullness nullability, DfaValue value, @NotNull FList 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 instanceofValues, @NotNull Set 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 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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java index 56aea001e759..8b5e3e9d6bb0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -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; } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java index 8087a528ff18..537011d42909 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/numeric/CastThatLosesPrecisionInspection.java @@ -143,7 +143,7 @@ public class CastThatLosesPrecisionInspection extends BaseInspection { @Override public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { if (instruction.getMethodType() == MethodCallInstruction.MethodType.CAST && instruction.getContext() == operand) { - LongRangeSet curRange = memState.getRange(memState.peek()); + LongRangeSet curRange = memState.getValueFact(DfaFactType.RANGE, memState.peek()); if (curRange == null) { range.set(LongRangeSet.all()); }