From 81fe7187fe0243b58a0b87a56e24e674db1a1d80 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 15 Sep 2017 13:31:42 +0700 Subject: [PATCH] TypeConstraint as DfaFactType --- .../codeInspection/dataFlow/DfaFactType.java | 42 ++++ .../dataFlow/DfaMemoryState.java | 8 - .../dataFlow/DfaMemoryStateImpl.java | 17 -- .../dataFlow/DfaVariableState.java | 137 ++---------- .../NullParameterConstraintChecker.java | 4 +- .../dataFlow/StandardInstructionVisitor.java | 45 ++-- .../codeInspection/dataFlow/StateMerger.java | 5 +- .../dataFlow/TypeConstraint.java | 201 ++++++++++++++++++ .../dataFlow/ValuableDataFlowRunner.java | 19 +- 9 files changed, 306 insertions(+), 172 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java 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 index c0a686327d6e..5d3b62aae1a0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactType.java @@ -133,6 +133,48 @@ public abstract class DfaFactType extends Key { return fact.toString(); } }; + /** + * This fact represents a set of possible types of this value + * {@link TypeConstraint#EMPTY} value is equivalent to absent fact (not constrained) + */ + public static final DfaFactType TYPE_CONSTRAINT = new DfaFactType("Type") { + @Nullable + @Override + TypeConstraint fromDfaValue(DfaValue value) { + if(value instanceof DfaTypeValue) { + TypeConstraint constraint = TypeConstraint.EMPTY.withInstanceofValue((DfaTypeValue)value); + return constraint == null || constraint.isEmpty() ? null : constraint; + } + return null; + } + + @Override + boolean isSuper(@Nullable TypeConstraint superFact, @Nullable TypeConstraint subFact) { + return superFact == null || (subFact != null && superFact.isSuperStateOf(subFact)); + } + + @Nullable + @Override + TypeConstraint intersectFacts(@NotNull TypeConstraint left, @NotNull TypeConstraint right) { + for (DfaPsiType type : right.getInstanceofValues()) { + left = left.withInstanceofValue(type); + if (left == null) return null; + } + for (DfaPsiType type : right.getNotInstanceofValues()) { + left = left.withNotInstanceofValue(type); + if (left == null) return null; + } + return left; + } + + @Nullable + @Override + TypeConstraint unionFacts(@NotNull TypeConstraint left, @NotNull TypeConstraint right) { + if(left.isSuperStateOf(right)) return left; + if(right.isSuperStateOf(left)) return right; + return null; + } + }; private DfaFactType(String name) { super("DfaFactType: " + name); 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 e59cf69e61d6..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 @@ -19,7 +19,6 @@ 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.psi.PsiType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,13 +55,6 @@ public interface DfaMemoryState { @Nullable T getValueFact(@NotNull DfaFactType factType, @NotNull DfaValue value); - /** - * @param value to determine its type - * @return value type at this state if known (possibly erased) - */ - @Nullable - PsiType getValueType(DfaValue value); - void flushFields(); void flushVariable(DfaVariableValue variable); 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 3ea7c590c4df..ad60aab3cfff 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 @@ -1068,23 +1068,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return factType.fromDfaValue(value); } - @Nullable - @Override - public PsiType getValueType(DfaValue value) { - if (value instanceof DfaTypeValue) { - return ((DfaTypeValue)value).getDfaType().getPsiType(); - } - if (value instanceof DfaVariableValue) { - DfaVariableState state = getVariableState((DfaVariableValue)value); - Set values = state.getInstanceofValues(); - if (!values.isEmpty()) { - return values.iterator().next().getPsiType(); - } - return ((DfaVariableValue)value).getVariableType(); - } - return null; - } - @NotNull private DfaValue resolveVariableValue(DfaVariableValue var) { DfaConstValue constValue = getConstantValue(var); 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 a73f2330c532..aa565fb491f7 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 @@ -20,117 +20,49 @@ import com.intellij.codeInspection.dataFlow.value.DfaPsiType; import com.intellij.codeInspection.dataFlow.value.DfaTypeValue; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiPrimitiveType; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Objects; class DfaVariableState { - @NotNull final Set myInstanceofValues; - @NotNull final Set myNotInstanceofValues; @NotNull final DfaFactMap myFactMap; private final int myHash; DfaVariableState(@NotNull DfaVariableValue dfaVar) { - this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentFacts()); + this(dfaVar.getInherentFacts()); } public boolean isSuperStateOf(DfaVariableState that) { - if(!that.myNotInstanceofValues.containsAll(myNotInstanceofValues)) return false; - if(!that.myInstanceofValues.containsAll(myInstanceofValues)) return false; return myFactMap.isSuperStateOf(that.myFactMap); } - DfaVariableState(@NotNull Set instanceofValues, - @NotNull Set notInstanceofValues, - @NotNull DfaFactMap factMap) { - myInstanceofValues = instanceofValues; - myNotInstanceofValues = notInstanceofValues; + DfaVariableState(@NotNull DfaFactMap factMap) { myFactMap = factMap; - myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myFactMap); - } - - private boolean checkInstanceofValue(@NotNull DfaPsiType dfaType) { - if (myInstanceofValues.contains(dfaType)) return true; - - for (DfaPsiType dfaTypeValue : myNotInstanceofValues) { - if (dfaTypeValue.isAssignableFrom(dfaType)) return false; - } - - for (DfaPsiType dfaTypeValue : myInstanceofValues) { - if (!dfaType.isConvertibleFrom(dfaTypeValue)) return false; - } - - return true; + myHash = myFactMap.hashCode(); } @Nullable DfaVariableState withInstanceofValue(@NotNull DfaTypeValue dfaType) { if (dfaType.getDfaType().getPsiType() instanceof PsiPrimitiveType) return this; - - if (checkInstanceofValue(dfaType.getDfaType())) { - DfaVariableState result = dfaType.isNullable() ? withFact(DfaFactType.CAN_BE_NULL, true) : this; - List moreGeneric = ContainerUtil.newArrayList(); - for (DfaPsiType alreadyInstanceof : myInstanceofValues) { - if (dfaType.getDfaType().isAssignableFrom(alreadyInstanceof)) { - return result; - } - if (alreadyInstanceof.isAssignableFrom(dfaType.getDfaType())) { - moreGeneric.add(alreadyInstanceof); - } - } - - HashSet newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); - newInstanceof.removeAll(moreGeneric); - newInstanceof.add(dfaType.getDfaType()); - result = createCopy(newInstanceof, myNotInstanceofValues, result.myFactMap); - return result; - } - - return null; + TypeConstraint typeConstraint = getTypeConstraint(); + TypeConstraint newTypeConstraint = typeConstraint.withInstanceofValue(dfaType); + if (newTypeConstraint == null) return null; + DfaVariableState result = dfaType.isNullable() ? withFact(DfaFactType.CAN_BE_NULL, true) : this; + return result.withFact(DfaFactType.TYPE_CONSTRAINT, newTypeConstraint); } @Nullable DfaVariableState withNotInstanceofValue(@NotNull DfaTypeValue dfaType) { - if (myNotInstanceofValues.contains(dfaType.getDfaType())) return this; - - for (DfaPsiType dfaTypeValue : myInstanceofValues) { - if (dfaType.getDfaType().isAssignableFrom(dfaTypeValue)) return null; - } - - List moreSpecific = ContainerUtil.newArrayList(); - for (DfaPsiType alreadyNotInstanceof : myNotInstanceofValues) { - if (alreadyNotInstanceof.isAssignableFrom(dfaType.getDfaType())) { - return this; - } - if (dfaType.getDfaType().isAssignableFrom(alreadyNotInstanceof)) { - moreSpecific.add(alreadyNotInstanceof); - } - } - - HashSet newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); - newNotInstanceof.removeAll(moreSpecific); - newNotInstanceof.add(dfaType.getDfaType()); - return createCopy(myInstanceofValues, newNotInstanceof, myFactMap); + TypeConstraint typeConstraint = getTypeConstraint(); + TypeConstraint newTypeConstraint = typeConstraint.withNotInstanceofValue(dfaType); + return newTypeConstraint == null ? null : withFact(DfaFactType.TYPE_CONSTRAINT, newTypeConstraint); } @NotNull DfaVariableState withoutType(@NotNull DfaPsiType type) { - if (myInstanceofValues.contains(type)) { - HashSet newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); - newInstanceof.remove(type); - return createCopy(newInstanceof, myNotInstanceofValues, myFactMap); - } - if (myNotInstanceofValues.contains(type)) { - HashSet newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); - newNotInstanceof.remove(type); - return createCopy(myInstanceofValues, newNotInstanceof, myFactMap); - } - return this; + return withFact(DfaFactType.TYPE_CONSTRAINT, getTypeConstraint().withoutType(type)); } public int hashCode() { @@ -141,35 +73,16 @@ class DfaVariableState { if (obj == this) return true; if (!(obj instanceof DfaVariableState)) return false; DfaVariableState aState = (DfaVariableState) obj; - return myHash == aState.myHash && - myInstanceofValues.equals(aState.myInstanceofValues) && - myNotInstanceofValues.equals(aState.myNotInstanceofValues) && - Objects.equals(myFactMap, aState.myFactMap); + return myHash == aState.myHash && Objects.equals(myFactMap, aState.myFactMap); } @NotNull - protected DfaVariableState createCopy(@NotNull Set instanceofValues, - @NotNull Set notInstanceofValues, - @NotNull DfaFactMap factMap) { - return new DfaVariableState(instanceofValues, notInstanceofValues, factMap); + protected DfaVariableState createCopy(@NotNull DfaFactMap factMap) { + return new DfaVariableState(factMap); } public String toString() { - @NonNls StringBuilder buf = new StringBuilder("State:"); - - if (!myInstanceofValues.isEmpty()) { - buf.append(" instanceof ").append(StringUtil.join(myInstanceofValues, ",")); - } - - if (!myNotInstanceofValues.isEmpty()) { - buf.append(" not instanceof ").append(StringUtil.join(myNotInstanceofValues, ",")); - } - - String factString = myFactMap.toString(); - if(!factString.isEmpty()) { - buf.append(" ").append(factString); - } - return buf.toString(); + return "State: " + myFactMap; } @NotNull @@ -189,7 +102,7 @@ class DfaVariableState { @NotNull DfaVariableState withFact(DfaFactType type, T value) { DfaFactMap factMap = myFactMap.with(type, value); - return myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, factMap); + return myFactMap.equals(factMap) ? this : createCopy(factMap); } DfaVariableState withoutFact(DfaFactType type) { @@ -199,9 +112,7 @@ class DfaVariableState { @Nullable DfaVariableState intersectFact(DfaFactType type, T value) { DfaFactMap factMap = myFactMap.intersect(type, value); - return factMap == null - ? null - : myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, factMap); + return factMap == null ? null : myFactMap.equals(factMap) ? this : createCopy(factMap); } @NotNull @@ -215,13 +126,9 @@ class DfaVariableState { } @NotNull - public Set getInstanceofValues() { - return myInstanceofValues; - } - - @NotNull - public Set getNotInstanceofValues() { - return myNotInstanceofValues; + public TypeConstraint getTypeConstraint() { + TypeConstraint fact = getFact(DfaFactType.TYPE_CONSTRAINT); + return fact == null ? TypeConstraint.EMPTY : fact; } @Nullable 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 5c11c64277d5..7cb6b67b7753 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 @@ -33,7 +33,6 @@ import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import java.util.Collection; -import java.util.Collections; import java.util.Set; /** @@ -138,8 +137,7 @@ class NullParameterConstraintChecker extends DataFlowRunner { super(factory); for (PsiParameter parameter : myPossiblyViolatedParameters) { setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false), - new DfaVariableState(Collections.emptySet(), Collections.emptySet(), - DfaFactMap.EMPTY.with(DfaFactType.CAN_BE_NULL, true))); + new DfaVariableState(DfaFactMap.EMPTY.with(DfaFactType.CAN_BE_NULL, true))); } } 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 957a3dc01807..897515f55d12 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 @@ -20,6 +20,7 @@ import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType; +import com.intellij.lang.jvm.JvmModifier; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; @@ -477,6 +478,28 @@ public class StandardInstructionVisitor extends InstructionVisitor { } } + @NotNull + private static PsiMethod findSpecificMethod(@NotNull PsiMethod method, @NotNull DfaMemoryState state, @Nullable DfaValue qualifier) { + if (qualifier == null || + method.hasModifier(JvmModifier.STATIC) || + method.hasModifier(JvmModifier.FINAL) || + method.hasModifier(JvmModifier.PRIVATE)) { + return method; + } + TypeConstraint constraint = state.getValueFact(DfaFactType.TYPE_CONSTRAINT, qualifier); + PsiClass specificQualifierClass = PsiUtil.resolveClassInClassTypeOnly(constraint == null ? null : constraint.getPsiType()); + PsiClass qualifierClass = method.getContainingClass(); + if (specificQualifierClass != null && qualifierClass != null && + !specificQualifierClass.equals(qualifierClass) && + InheritanceUtil.isInheritorOrSelf(specificQualifierClass, qualifierClass, true)) { + PsiMethod realMethod = specificQualifierClass.findMethodBySignature(method, true); + if (realMethod != null) { + return realMethod; + } + } + return method; + } + @NotNull private static DfaValue getMethodResultValue(MethodCallInstruction instruction, @Nullable DfaValue qualifierValue, @@ -520,19 +543,13 @@ public class StandardInstructionVisitor extends InstructionVisitor { Nullness nullability = instruction.getReturnNullability(); PsiMethod targetMethod = instruction.getTargetMethod(); if (targetMethod != null) { - PsiClass specificQualifierClass = PsiUtil.resolveClassInClassTypeOnly(state.getValueType(qualifierValue)); - PsiClass qualifierClass = targetMethod.getContainingClass(); - if (specificQualifierClass != null && qualifierClass != null && - !specificQualifierClass.equals(qualifierClass) && - InheritanceUtil.isInheritorOrSelf(specificQualifierClass, qualifierClass, true)) { - PsiMethod realMethod = specificQualifierClass.findMethodBySignature(targetMethod, true); - if (realMethod != null && realMethod != targetMethod) { - nullability = DfaPsiUtil.getElementNullability(type, realMethod); - PsiType returnType = realMethod.getReturnType(); - if(returnType != null && TypeConversionUtil.erasure(type).isAssignableFrom(returnType)) { - // possibly covariant return type - type = returnType; - } + PsiMethod realMethod = findSpecificMethod(targetMethod, state, qualifierValue); + if (realMethod != targetMethod) { + nullability = DfaPsiUtil.getElementNullability(type, realMethod); + PsiType returnType = realMethod.getReturnType(); + if (returnType != null && TypeConversionUtil.erasure(type).isAssignableFrom(returnType)) { + // possibly covariant return type + type = returnType; } } if (nullability == Nullness.UNKNOWN) { @@ -700,7 +717,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { return checkComparisonWithKnownValue(instruction, runner, memState, relationType, leftValue, rightValue); } } - + if (dfaRight instanceof DfaConstValue && dfaLeft instanceof DfaVariableValue) { Object value = ((DfaConstValue)dfaRight).getValue(); if (value instanceof Number) { 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 b0f7d6172c4e..99ab239dccb3 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 @@ -430,10 +430,11 @@ class StateMerger { for (Map.Entry entry : states.entrySet()) { DfaVariableValue var = entry.getKey(); DfaVariableState variableState = entry.getValue(); - for (DfaPsiType type : variableState.getInstanceofValues()) { + TypeConstraint typeConstraint = variableState.getTypeConstraint(); + for (DfaPsiType type : typeConstraint.getInstanceofValues()) { result.add(new InstanceofFact(var, true, type)); } - for (DfaPsiType type : variableState.getNotInstanceofValues()) { + for (DfaPsiType type : typeConstraint.getNotInstanceofValues()) { result.add(new InstanceofFact(var, false, type)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java new file mode 100644 index 000000000000..363785b1c3ee --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraint.java @@ -0,0 +1,201 @@ +/* + * 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.value.DfaPsiType; +import com.intellij.codeInspection.dataFlow.value.DfaTypeValue; +import com.intellij.psi.PsiPrimitiveType; +import com.intellij.psi.PsiType; +import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.EntryStream; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Immutable class representing a number of non-primitive type constraints applied to some value. + * There are two types of constrains: value is instance of some type and value is not an instance of some type. + */ +public final class TypeConstraint { + /** + * An instance representing no constraints + */ + public static final TypeConstraint EMPTY = new TypeConstraint(Collections.emptySet(), Collections.emptySet()); + + @NotNull private final Set myInstanceofValues; + @NotNull private final Set myNotInstanceofValues; + + private TypeConstraint(@NotNull Set instanceofValues, @NotNull Set notInstanceofValues) { + myInstanceofValues = instanceofValues; + myNotInstanceofValues = notInstanceofValues; + } + + private static TypeConstraint create(@NotNull Set instanceofValues, @NotNull Set notInstanceofValues) { + if (instanceofValues.isEmpty() && notInstanceofValues.isEmpty()) { + return EMPTY; + } + if (instanceofValues.isEmpty()) { + instanceofValues = Collections.emptySet(); + } + else if (instanceofValues.size() == 1) { + instanceofValues = Collections.singleton(instanceofValues.iterator().next()); + } + if (notInstanceofValues.isEmpty()) { + notInstanceofValues = Collections.emptySet(); + } + else if (notInstanceofValues.size() == 1) { + notInstanceofValues = Collections.singleton(notInstanceofValues.iterator().next()); + } + return new TypeConstraint(instanceofValues, notInstanceofValues); + } + + boolean checkInstanceofValue(@NotNull DfaPsiType dfaType) { + if (myInstanceofValues.contains(dfaType)) return true; + + for (DfaPsiType dfaTypeValue : myNotInstanceofValues) { + if (dfaTypeValue.isAssignableFrom(dfaType)) return false; + } + + for (DfaPsiType dfaTypeValue : myInstanceofValues) { + if (!dfaType.isConvertibleFrom(dfaTypeValue)) return false; + } + + return true; + } + + @Nullable + TypeConstraint withInstanceofValue(@NotNull DfaTypeValue dfaType) { + return withInstanceofValue(dfaType.getDfaType()); + } + + @Nullable + TypeConstraint withInstanceofValue(@NotNull DfaPsiType type) { + if (type.getPsiType() instanceof PsiPrimitiveType) return this; + + if (!checkInstanceofValue(type)) { + return null; + } + List moreGeneric = new ArrayList<>(); + for (DfaPsiType alreadyInstanceof : myInstanceofValues) { + if (type.isAssignableFrom(alreadyInstanceof)) { + return this; + } + if (alreadyInstanceof.isAssignableFrom(type)) { + moreGeneric.add(alreadyInstanceof); + } + } + + Set newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); + newInstanceof.removeAll(moreGeneric); + newInstanceof.add(type); + return create(newInstanceof, myNotInstanceofValues); + } + + @Nullable + TypeConstraint withNotInstanceofValue(@NotNull DfaTypeValue dfaType) { + return withNotInstanceofValue(dfaType.getDfaType()); + } + + @Nullable + TypeConstraint withNotInstanceofValue(DfaPsiType type) { + if (myNotInstanceofValues.contains(type)) return this; + + for (DfaPsiType dfaTypeValue : myInstanceofValues) { + if (type.isAssignableFrom(dfaTypeValue)) return null; + } + + List moreSpecific = new ArrayList<>(); + for (DfaPsiType alreadyNotInstanceof : myNotInstanceofValues) { + if (alreadyNotInstanceof.isAssignableFrom(type)) { + return this; + } + if (type.isAssignableFrom(alreadyNotInstanceof)) { + moreSpecific.add(alreadyNotInstanceof); + } + } + + Set newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); + newNotInstanceof.removeAll(moreSpecific); + newNotInstanceof.add(type); + return create(myInstanceofValues, newNotInstanceof); + } + + @NotNull + TypeConstraint withoutType(@NotNull DfaPsiType type) { + if (myInstanceofValues.contains(type)) { + HashSet newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); + newInstanceof.remove(type); + return create(newInstanceof, myNotInstanceofValues); + } + if (myNotInstanceofValues.contains(type)) { + HashSet newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); + newNotInstanceof.remove(type); + return create(myInstanceofValues, newNotInstanceof); + } + return this; + } + + @Nullable + public PsiType getPsiType() { + if (myInstanceofValues.isEmpty()) { + return null; + } + return myInstanceofValues.iterator().next().getPsiType(); + } + + boolean isSuperStateOf(@NotNull TypeConstraint that) { + return that.myNotInstanceofValues.containsAll(myNotInstanceofValues) && that.myInstanceofValues.containsAll(myInstanceofValues); + } + + @NotNull + public Set getInstanceofValues() { + return Collections.unmodifiableSet(myInstanceofValues); + } + + @NotNull + public Set getNotInstanceofValues() { + return Collections.unmodifiableSet(myNotInstanceofValues); + } + + public boolean isEmpty() { + return myInstanceofValues.isEmpty() && myNotInstanceofValues.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TypeConstraint that = (TypeConstraint)o; + return Objects.equals(myInstanceofValues, that.myInstanceofValues) && + Objects.equals(myNotInstanceofValues, that.myNotInstanceofValues); + } + + @Override + public int hashCode() { + return Objects.hash(myInstanceofValues, myNotInstanceofValues); + } + + @Override + public String toString() { + return EntryStream.of("instanceof ", myInstanceofValues, + "not instanceof ", myNotInstanceofValues) + .removeValues(Set::isEmpty) + .mapKeyValue((prefix, set) -> StreamEx.of(set).joining(",", prefix, "")) + .joining(" "); + } +} 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 d0fe9b2518fc..a06ce2a04483 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,7 +16,6 @@ package com.intellij.codeInspection.dataFlow; -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; @@ -25,8 +24,6 @@ import com.intellij.util.containers.FList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Set; - /** * @author Gregory.Shrago */ @@ -74,34 +71,30 @@ class ValuableDataFlowRunner extends DataFlowRunner { myConcatenation = FList.emptyList(); } - private ValuableDfaVariableState(Set instanceofValues, - Set notInstanceofValues, - DfaValue value, + private ValuableDfaVariableState(DfaValue value, @NotNull FList concatenation, @NotNull DfaFactMap factMap) { - super(instanceofValues, notInstanceofValues, factMap); + super(factMap); myValue = value; myConcatenation = concatenation; } @NotNull @Override - protected DfaVariableState createCopy(@NotNull Set instanceofValues, - @NotNull Set notInstanceofValues, - @NotNull DfaFactMap factMap) { - return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, myValue, myConcatenation, factMap); + protected DfaVariableState createCopy(@NotNull DfaFactMap factMap) { + return new ValuableDfaVariableState(myValue, myConcatenation, factMap); } @NotNull @Override public DfaVariableState withValue(@Nullable final DfaValue value) { if (value == myValue) return this; - return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, value, myConcatenation, myFactMap); + return new ValuableDfaVariableState(value, myConcatenation, myFactMap); } ValuableDfaVariableState withExpression(@NotNull final FList concatenation) { if (concatenation == myConcatenation) return this; - return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myValue, concatenation, myFactMap); + return new ValuableDfaVariableState(myValue, concatenation, myFactMap); } @Override