From 193088959cd33235d39d90a7e024f3fd7f2c737f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 16 May 2017 15:54:19 +0700 Subject: [PATCH] Nullability as DfaFact. DfaVariableValue#calcInherentNullability now part of DfaFactType --- .../codeInspection/dataFlow/DfaFactMap.java | 26 +++ .../codeInspection/dataFlow/DfaFactType.java | 204 ++++++++++++++++-- .../dataFlow/DfaMemoryStateImpl.java | 18 +- .../dataFlow/DfaVariableState.java | 68 ++---- .../NullParameterConstraintChecker.java | 3 +- .../codeInspection/dataFlow/Nullness.java | 26 ++- .../dataFlow/StandardInstructionVisitor.java | 2 +- .../codeInspection/dataFlow/StateMerger.java | 21 +- .../dataFlow/ValuableDataFlowRunner.java | 11 +- .../dataFlow/controlTransfer.kt | 2 +- .../dataFlow/value/DfaValue.java | 4 + .../dataFlow/value/DfaVariableValue.java | 133 ++---------- 12 files changed, 312 insertions(+), 206 deletions(-) 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 index 38964040f11c..57ef83135882 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaFactMap.java @@ -15,8 +15,10 @@ */ package com.intellij.codeInspection.dataFlow; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.openapi.util.Key; import com.intellij.util.keyFMap.KeyFMap; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -119,4 +121,28 @@ public final class DfaFactMap { public int hashCode() { return myMap.hashCode(); } + + @SuppressWarnings("unchecked") + @Override + public String toString() { + return StreamEx.of(myMap.getKeys()).map(key -> ((DfaFactType)key).toString(myMap.get(key))).joining(", "); + } + + /** + * Calculate facts from variable itself (not knowing its state). This method should not be used directly. + * Instead use {@link DfaVariableValue#getInherentFacts()} which caches the calculated value. + * + * @param value variable value to calculate facts from + * @return a calculated fact map. + */ + public static DfaFactMap calcFromVariable(@NotNull DfaVariableValue value) { + DfaFactMap map = EMPTY; + for (DfaFactType type : DfaFactType.getTypes()) { + @SuppressWarnings("unchecked") + DfaFactType objectType = (DfaFactType)type; + Object fact = objectType.calcFromVariable(value); + map = map.with(objectType, fact); + } + return map; + } } 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 5615bad12b64..0e66c5cdc8b3 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 @@ -15,21 +15,47 @@ */ package com.intellij.codeInspection.dataFlow; +import com.intellij.codeInsight.daemon.ImplicitUsageProvider; +import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; 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.codeInspection.dataFlow.value.*; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Key; -import one.util.streamex.StreamEx; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.intellij.patterns.PsiJavaPatterns.psiMember; +import static com.intellij.patterns.PsiJavaPatterns.psiParameter; +import static com.intellij.patterns.StandardPatterns.or; + /** * A type of the fact which restricts some value. * * @author Tagir Valeev */ public abstract class DfaFactType extends Key { + private static final List> ourFactTypes = new ArrayList<>(); + + /** + * This fact specifies whether the value can be null. The absence of the fact means that the nullability is unknown. + */ + public static final DfaFactType CAN_BE_NULL = new CanBeNullFactType(); + /** * 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. @@ -52,7 +78,7 @@ public abstract class DfaFactType extends Key { * 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") { + public static final DfaFactType RANGE = new DfaFactType("Range") { @Override boolean isSuper(@NotNull LongRangeSet superFact, @NotNull LongRangeSet subFact) { return superFact.contains(subFact); @@ -62,17 +88,22 @@ public abstract class DfaFactType extends Key { @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 calcFromVariable((DfaVariableValue)value); + } + return LongRangeSet.fromDfaValue(value); + } + + @Nullable + @Override + LongRangeSet calcFromVariable(@NotNull DfaVariableValue var) { + if (var.getQualifier() != null) { + for (SpecialField sf : SpecialField.values()) { + if (sf.isMyAccessor(var.getPsiVariable())) { + return sf.getRange(); } } } - return LongRangeSet.fromDfaValue(value); + return LongRangeSet.fromType(var.getVariableType()); } @Nullable @@ -89,7 +120,9 @@ public abstract class DfaFactType extends Key { }; private DfaFactType(String name) { - super(name); + super("DfaFactType: " + name); + // Thread-safe as all DfaFactType instances are created only from DfaFactType class static initializer + ourFactTypes.add(this); } @Nullable @@ -97,6 +130,12 @@ public abstract class DfaFactType extends Key { return null; } + // Could be expensive + @Nullable + T calcFromVariable(@NotNull DfaVariableValue value) { + return null; + } + boolean isSuper(@NotNull T superFact, @NotNull T subFact) { return false; } @@ -116,4 +155,141 @@ public abstract class DfaFactType extends Key { String toString(T fact) { return fact.toString(); } + + static List> getTypes() { + return Collections.unmodifiableList(ourFactTypes); + } + + private static class CanBeNullFactType extends DfaFactType { + private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = + or(psiMember(), psiParameter().withSuperParent(2, psiMember())); + + private CanBeNullFactType() {super("Can be null");} + + @Override + String toString(Boolean fact) { + return fact ? "Nullable" : "NotNull"; + } + + @Nullable + @Override + Boolean fromDfaValue(DfaValue value) { + if (value instanceof DfaConstValue) { + return ((DfaConstValue)value).getValue() == null; + } + if (value instanceof DfaBoxedValue || value instanceof DfaUnboxedValue || value instanceof DfaRangeValue) { + return false; + } + if (value instanceof DfaTypeValue) { + return ((DfaTypeValue)value).getNullness().toBoolean(); + } + return null; + } + + @Nullable + @Override + Boolean calcFromVariable(@NotNull DfaVariableValue value) { + PsiModifierListOwner var = value.getPsiVariable(); + Nullness nullability = DfaPsiUtil.getElementNullability(value.getVariableType(), var); + if (nullability != Nullness.UNKNOWN) { + return nullability.toBoolean(); + } + + Nullness defaultNullability = + value.getFactory().isUnknownMembersAreNullable() && MEMBER_OR_METHOD_PARAMETER.accepts(var) ? Nullness.NULLABLE : Nullness.UNKNOWN; + + if (var instanceof PsiParameter && var.getParent() instanceof PsiForeachStatement) { + PsiExpression iteratedValue = ((PsiForeachStatement)var.getParent()).getIteratedValue(); + if (iteratedValue != null) { + PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); + if (itemType != null) { + return DfaPsiUtil.getElementNullability(itemType, var).toBoolean(); + } + } + } + + if (var instanceof PsiField && value.getFactory().isHonorFieldInitializers()) { + return getNullabilityFromFieldInitializers((PsiField)var, defaultNullability).toBoolean(); + } + + return defaultNullability.toBoolean(); + } + + private static Nullness getNullabilityFromFieldInitializers(PsiField field, Nullness defaultNullability) { + if (DfaPsiUtil.isFinalField(field)) { + PsiExpression initializer = field.getInitializer(); + if (initializer != null) { + return getFieldInitializerNullness(initializer); + } + + List initializers = DfaPsiUtil.findAllConstructorInitializers(field); + if (initializers.isEmpty()) { + return defaultNullability; + } + + for (PsiExpression expression : initializers) { + if (getFieldInitializerNullness(expression) == Nullness.NULLABLE) { + return Nullness.NULLABLE; + } + } + + if (DfaPsiUtil.isInitializedNotNull(field)) { + return Nullness.NOT_NULL; + } + } + else if (isOnlyImplicitlyInitialized(field)) { + return Nullness.NOT_NULL; + } + return defaultNullability; + } + + private static boolean isOnlyImplicitlyInitialized(PsiField field) { + return CachedValuesManager.getCachedValue(field, () -> CachedValueProvider.Result.create( + isImplicitlyInitializedNotNull(field) && weAreSureThereAreNoExplicitWrites(field), + PsiModificationTracker.MODIFICATION_COUNT)); + } + + private static boolean isImplicitlyInitializedNotNull(PsiField field) { + return ContainerUtil.exists(Extensions.getExtensions(ImplicitUsageProvider.EP_NAME), p -> p.isImplicitlyNotNullInitialized(field)); + } + + private static boolean weAreSureThereAreNoExplicitWrites(PsiField field) { + String name = field.getName(); + if (name == null || field.getInitializer() != null) return false; + + if (!isCheapEnoughToSearch(field, name)) return false; + + return ReferencesSearch + .search(field).forEach( + reference -> reference instanceof PsiReferenceExpression && !PsiUtil.isAccessedForWriting((PsiReferenceExpression)reference)); + } + + private static boolean isCheapEnoughToSearch(PsiField field, String name) { + SearchScope scope = field.getUseScope(); + if (!(scope instanceof GlobalSearchScope)) return true; + + PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(field.getProject()); + PsiSearchHelper.SearchCostResult result = + helper.isCheapEnoughToSearch(name, (GlobalSearchScope)scope, field.getContainingFile(), null); + return result != PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES; + } + + private static Nullness getFieldInitializerNullness(@NotNull PsiExpression expression) { + if (expression.textMatches(PsiKeyword.NULL)) return Nullness.NULLABLE; + if (expression instanceof PsiNewExpression || + expression instanceof PsiLiteralExpression || + expression instanceof PsiPolyadicExpression) { + return Nullness.NOT_NULL; + } + if (expression instanceof PsiReferenceExpression) { + PsiElement target = ((PsiReferenceExpression)expression).resolve(); + return DfaPsiUtil.getElementNullability(expression.getType(), (PsiModifierListOwner)target); + } + if (expression instanceof PsiMethodCallExpression) { + PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); + return method != null ? DfaPsiUtil.getElementNullability(expression.getType(), method) : Nullness.UNKNOWN; + } + return Nullness.UNKNOWN; + } + } } 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 d7afd6ec7941..7c71453f99ee 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 @@ -252,7 +252,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { flushVariable(var); if (value instanceof DfaUnknownValue) { - setVariableState(var, getVariableState(var).withNullable(false)); + setVariableState(var, getVariableState(var).withNotNull()); return; } @@ -282,8 +282,12 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } private DfaVariableState withValueNullability(DfaValue value, DfaVariableState state) { - if (value instanceof DfaTypeValue) return state.withNullability(((DfaTypeValue)value).getNullness()); - if (isNull(value)) return state.withNullability(Nullness.NULLABLE); + if (value instanceof DfaTypeValue) { + return state.withFact(DfaFactType.CAN_BE_NULL, ((DfaTypeValue)value).getNullness().toBoolean()); + } + if (isNull(value)) { + return state.withFact(DfaFactType.CAN_BE_NULL, true); + } return state; } @@ -795,9 +799,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private void updateVarStateOnComparison(@NotNull DfaVariableValue dfaVar, DfaValue value) { if (!isUnknownState(dfaVar)) { if (value instanceof DfaConstValue && ((DfaConstValue)value).getValue() == null) { - setVariableState(dfaVar, getVariableState(dfaVar).withNullability(Nullness.NULLABLE)); + setVariableState(dfaVar, getVariableState(dfaVar).withFact(DfaFactType.CAN_BE_NULL, true)); } else if (isNotNull(value) && !isNotNull(dfaVar)) { - setVariableState(dfaVar, getVariableState(dfaVar).withNullability(Nullness.UNKNOWN)); + setVariableState(dfaVar, getVariableState(dfaVar).withoutFact(DfaFactType.CAN_BE_NULL)); applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true); } } @@ -981,7 +985,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { DfaVariableValue varValue = (DfaVariableValue)value; if (varValue.getVariableType() instanceof PsiPrimitiveType) return true; if (isNotNull(varValue)) return true; - if (getVariableState(varValue).isNullable()) return false; + if (getVariableState(varValue).getNullability() == Nullness.NULLABLE) return false; } return true; } @@ -1054,7 +1058,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } if (isUnknownState(dfaVar)) { - return state.withNullable(false); + return state.withNotNull(); } } 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 a3169edd7baa..9340c9461125 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 @@ -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.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.PsiModifierListOwner; import com.intellij.psi.PsiPrimitiveType; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; @@ -34,48 +32,26 @@ import java.util.*; class DfaVariableState { @NotNull final Set myInstanceofValues; @NotNull final Set myNotInstanceofValues; - @NotNull final Nullness myNullability; @NotNull final DfaFactMap myFactMap; private final int myHash; DfaVariableState(@NotNull DfaVariableValue dfaVar) { - this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentNullability(), - DfaFactMap.EMPTY.with(DfaFactType.RANGE, getInitialRange(dfaVar))); + this(Collections.emptySet(), Collections.emptySet(), dfaVar.getInherentFacts()); } 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; return myFactMap.isSuperStateOf(that.myFactMap); } - private static LongRangeSet getInitialRange(DfaVariableValue var) { - DfaVariableValue qualifier = var.getQualifier(); - if(qualifier != null) { - PsiModifierListOwner owner = var.getPsiVariable(); - for (SpecialField sf : SpecialField.values()) { - if(sf.isMyAccessor(owner)) { - return sf.getRange(); - } - } - } - return LongRangeSet.fromType(var.getVariableType()); - } - DfaVariableState(@NotNull Set instanceofValues, @NotNull Set notInstanceofValues, - @NotNull Nullness nullability, @NotNull DfaFactMap factMap) { myInstanceofValues = instanceofValues; myNotInstanceofValues = notInstanceofValues; - myNullability = nullability; myFactMap = factMap; - myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myNullability, myFactMap); - } - - public boolean isNullable() { - return myNullability == Nullness.NULLABLE; + myHash = Objects.hash(myInstanceofValues, myNotInstanceofValues, myFactMap); } private boolean checkInstanceofValue(@NotNull DfaPsiType dfaType) { @@ -97,7 +73,7 @@ class DfaVariableState { if (dfaType.getDfaType().getPsiType() instanceof PsiPrimitiveType) return this; if (checkInstanceofValue(dfaType.getDfaType())) { - DfaVariableState result = dfaType.isNullable() ? withNullability(Nullness.NULLABLE) : this; + DfaVariableState result = dfaType.isNullable() ? withFact(DfaFactType.CAN_BE_NULL, true) : this; List moreGeneric = ContainerUtil.newArrayList(); for (DfaPsiType alreadyInstanceof : myInstanceofValues) { if (dfaType.getDfaType().isAssignableFrom(alreadyInstanceof)) { @@ -111,7 +87,7 @@ class DfaVariableState { HashSet newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); newInstanceof.removeAll(moreGeneric); newInstanceof.add(dfaType.getDfaType()); - result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability, myFactMap); + result = createCopy(newInstanceof, myNotInstanceofValues, result.myFactMap); return result; } @@ -139,7 +115,7 @@ class DfaVariableState { HashSet newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); newNotInstanceof.removeAll(moreSpecific); newNotInstanceof.add(dfaType.getDfaType()); - return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myFactMap); + return createCopy(myInstanceofValues, newNotInstanceof, myFactMap); } @NotNull @@ -147,12 +123,12 @@ class DfaVariableState { if (myInstanceofValues.contains(type)) { HashSet newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); newInstanceof.remove(type); - return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myFactMap); + return createCopy(newInstanceof, myNotInstanceofValues, myFactMap); } if (myNotInstanceofValues.contains(type)) { HashSet newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); newNotInstanceof.remove(type); - return createCopy(myInstanceofValues, newNotInstanceof, myNullability, myFactMap); + return createCopy(myInstanceofValues, newNotInstanceof, myFactMap); } return this; } @@ -166,7 +142,6 @@ class DfaVariableState { if (!(obj instanceof DfaVariableState)) return false; DfaVariableState aState = (DfaVariableState) obj; return myHash == aState.myHash && - myNullability == aState.myNullability && myInstanceofValues.equals(aState.myInstanceofValues) && myNotInstanceofValues.equals(aState.myNotInstanceofValues) && Objects.equals(myFactMap, aState.myFactMap); @@ -175,15 +150,13 @@ class DfaVariableState { @NotNull protected DfaVariableState createCopy(@NotNull Set instanceofValues, @NotNull Set notInstanceofValues, - @NotNull Nullness nullability, - @NotNull DfaFactMap fact) { - return new DfaVariableState(instanceofValues, notInstanceofValues, nullability, fact); + @NotNull DfaFactMap factMap) { + return new DfaVariableState(instanceofValues, notInstanceofValues, factMap); } public String toString() { - @NonNls StringBuilder buf = new StringBuilder(); + @NonNls StringBuilder buf = new StringBuilder("State:"); - buf.append(myNullability); if (!myInstanceofValues.isEmpty()) { buf.append(" instanceof ").append(StringUtil.join(myInstanceofValues, ",")); } @@ -201,27 +174,26 @@ class DfaVariableState { @NotNull Nullness getNullability() { - return myNullability; + return Nullness.fromBoolean(myFactMap.get(DfaFactType.CAN_BE_NULL)); } public boolean isNotNull() { - return myNullability == Nullness.NOT_NULL; + return getNullability() == Nullness.NOT_NULL; } @NotNull - DfaVariableState withNullability(@NotNull Nullness nullness) { - return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness, myFactMap); - } - - @NotNull - DfaVariableState withNullable(final boolean nullable) { - return myNullability != Nullness.NOT_NULL ? withNullability(nullable ? Nullness.NULLABLE : Nullness.UNKNOWN) : this; + DfaVariableState withNotNull() { + return getNullability() == Nullness.NOT_NULL ? this : withoutFact(DfaFactType.CAN_BE_NULL); } @NotNull DfaVariableState withFact(DfaFactType type, T value) { DfaFactMap factMap = myFactMap.with(type, value); - return myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, factMap); + return myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, factMap); + } + + DfaVariableState withoutFact(DfaFactType type) { + return withFact(type, null); } @Nullable @@ -229,7 +201,7 @@ class DfaVariableState { DfaFactMap factMap = myFactMap.intersect(type, value); return factMap == null ? null - : myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, myNullability, factMap); + : myFactMap.equals(factMap) ? this : createCopy(myInstanceofValues, myNotInstanceofValues, factMap); } @NotNull 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 d3968ec13376..5b3058c9c6fa 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 @@ -129,7 +129,8 @@ class NullParameterConstraintChecker extends DataFlowRunner { super(factory); for (PsiParameter parameter : myPossiblyViolatedParameters) { setVariableState(getFactory().getVarFactory().createVariableValue(parameter, false), - new DfaVariableState(Collections.emptySet(), Collections.emptySet(), Nullness.NULLABLE, DfaFactMap.EMPTY)); + new DfaVariableState(Collections.emptySet(), Collections.emptySet(), + DfaFactMap.EMPTY.with(DfaFactType.CAN_BE_NULL, true))); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Nullness.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Nullness.java index 3dd099b0c1d3..9eb2f5a63dcb 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Nullness.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/Nullness.java @@ -15,9 +15,33 @@ */ package com.intellij.codeInspection.dataFlow; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * @author cdr */ public enum Nullness { - NOT_NULL, NULLABLE,UNKNOWN + NOT_NULL, NULLABLE,UNKNOWN; + + /** + * Convert to boolean which is used to encode nullability in DfaFactType.CAN_BE_NULL + * + * @return TRUE if NULLABLE, FALSE if NOT_NULL, null if UNKNOWN + */ + @Nullable + public Boolean toBoolean() { + return this == UNKNOWN ? null : this == NULLABLE; + } + + /** + * Convert from boolean fact which is used to encode nullability in DfaFactType.CAN_BE_NULL + * + * @param fact TRUE if NULLABLE, FALSE if NOT_NULL, null if UNKNOWN + * @return the corresponding nullness value + */ + @NotNull + public static Nullness fromBoolean(@Nullable Boolean fact) { + return fact == null ? UNKNOWN : fact ? NULLABLE : NOT_NULL; + } } 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 56e10c4e98b3..f9e5ec7972fc 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 @@ -102,7 +102,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { } if (var.getInherentNullability() == Nullness.NULLABLE && !memState.isNotNull(dfaSource) && instruction.isVariableInitializer()) { DfaMemoryStateImpl stateImpl = (DfaMemoryStateImpl)memState; - stateImpl.setVariableState(var, stateImpl.getVariableState(var).withNullability(Nullness.NULLABLE)); + stateImpl.setVariableState(var, stateImpl.getVariableState(var).withFact(DfaFactType.CAN_BE_NULL, true)); } } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull()) { 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 fc265c15d168..f1d866c27844 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 @@ -30,6 +30,9 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import static com.intellij.codeInspection.dataFlow.DfaFactType.CAN_BE_NULL; +import static com.intellij.codeInspection.dataFlow.DfaFactType.RANGE; + /** * @author peter */ @@ -233,10 +236,10 @@ class StateMerger { for (DfaMemoryStateImpl state : states) { for (Map.Entry> entry : ranges.entrySet()) { DfaVariableState variableState = state.getVariableState(entry.getKey()); - LongRangeSet range = variableState.getFact(DfaFactType.RANGE); + LongRangeSet range = variableState.getFact(RANGE); LongRangeSet boundingRange = entry.getValue().get(range); if (boundingRange != null && !boundingRange.equals(range)) { - state.setFact(entry.getKey(), DfaFactType.RANGE, boundingRange); + state.setFact(entry.getKey(), RANGE, boundingRange); changed = true; } } @@ -257,7 +260,7 @@ class StateMerger { ProgressManager.checkCanceled(); Map variableStates = state.getVariableStates(); variableStates.forEach((varValue, varState) -> { - LongRangeSet range = varState.getFact(DfaFactType.RANGE); + LongRangeSet range = varState.getFact(RANGE); if (range != null) { ranges.computeIfAbsent(varValue, k -> new HashMap<>()).put(range, range); } @@ -302,7 +305,7 @@ class StateMerger { DfaMemoryStateImpl getState() { if(myMerged) { myState.flushVariable(var); - myState.setFact(var, DfaFactType.RANGE, myRange); + myState.setFact(var, RANGE, myRange); } return myState; } @@ -312,7 +315,7 @@ class StateMerger { Map merged = new LinkedHashMap<>(); for (DfaMemoryStateImpl state : states) { DfaVariableState variableState = state.getVariableState(var); - LongRangeSet range = variableState.getFact(DfaFactType.RANGE); + LongRangeSet range = variableState.getFact(RANGE); if (range == null) { range = LongRangeSet.fromType(var.getVariableType()); if (range == null) return null; @@ -328,7 +331,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.setFact(lastVar, DfaFactType.RANGE, null); + state.setFact(lastVar, RANGE, null); } return new ArrayList<>(new HashSet<>(states)); } @@ -360,8 +363,10 @@ class StateMerger { return copy; } - private static boolean areVarStatesEqualModuloNullability(@NotNull DfaMemoryStateImpl state1, @NotNull DfaMemoryStateImpl state2, @NotNull DfaVariableValue var) { - return state1.getVariableState(var).withNullability(Nullness.UNKNOWN).equals(state2.getVariableState(var).withNullability(Nullness.UNKNOWN)); + private static boolean areVarStatesEqualModuloNullability(@NotNull DfaMemoryStateImpl state1, + @NotNull DfaMemoryStateImpl state2, + @NotNull DfaVariableValue var) { + return state1.getVariableState(var).withoutFact(CAN_BE_NULL).equals(state2.getVariableState(var).withoutFact(CAN_BE_NULL)); } @NotNull 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 f7f5e7d2daca..d0fe9b2518fc 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 @@ -76,10 +76,10 @@ class ValuableDataFlowRunner extends DataFlowRunner { private ValuableDfaVariableState(Set instanceofValues, Set notInstanceofValues, - Nullness nullability, DfaValue value, + DfaValue value, @NotNull FList concatenation, @NotNull DfaFactMap factMap) { - super(instanceofValues, notInstanceofValues, nullability, factMap); + super(instanceofValues, notInstanceofValues, factMap); myValue = value; myConcatenation = concatenation; } @@ -88,21 +88,20 @@ class ValuableDataFlowRunner extends DataFlowRunner { @Override protected DfaVariableState createCopy(@NotNull Set instanceofValues, @NotNull Set notInstanceofValues, - @NotNull Nullness nullability, @NotNull DfaFactMap factMap) { - return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myConcatenation, factMap); + return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, 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, myFactMap); + return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, value, myConcatenation, myFactMap); } ValuableDfaVariableState withExpression(@NotNull final FList concatenation) { if (concatenation == myConcatenation) return this; - return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation, myFactMap); + return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myValue, concatenation, myFactMap); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt index 3a90442a9db2..243f6832dd17 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt @@ -138,7 +138,7 @@ private class ControlTransferHandler(val state: DfaMemoryState, val runner: Data private fun initVariableState(param: PsiParameter, throwable: DfaValue): DfaVariableState { val sampleVar = (state as DfaMemoryStateImpl).factory.varFactory.createVariableValue(param, false) - val varState = state.createVariableState(sampleVar).withNullability(Nullness.NOT_NULL) + val varState = state.createVariableState(sampleVar).withFact(DfaFactType.CAN_BE_NULL, false) return if (throwable is DfaTypeValue) varState.withInstanceofValue(throwable)!! else varState } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java index 61e95a85876d..c1b8d3ed5ccc 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java @@ -24,6 +24,10 @@ public abstract class DfaValue { myID = factory == null ? 0 : factory.registerValue(this); } + public DfaValueFactory getFactory() { + return myFactory; + } + public int getID() { return myID; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index f08624f6e88c..db6e327cf217 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -16,23 +16,16 @@ package com.intellij.codeInspection.dataFlow.value; -import com.intellij.codeInsight.daemon.ImplicitUsageProvider; -import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; -import com.intellij.codeInspection.dataFlow.DfaPsiUtil; +import com.intellij.codeInspection.dataFlow.DfaFactMap; +import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.codeInspection.dataFlow.SpecialField; -import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Trinity; -import com.intellij.patterns.ElementPattern; import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.PsiSearchHelper; -import com.intellij.psi.search.SearchScope; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,13 +33,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; -import static com.intellij.patterns.PsiJavaPatterns.*; - public class DfaVariableValue extends DfaValue { - private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = - or(psiMember(), psiParameter().withSuperParent(2, psiMember())); - public static class Factory { private final MultiMap,DfaVariableValue> myExistingVars = new MultiMap<>(); private final DfaValueFactory myFactory; @@ -92,7 +80,7 @@ public class DfaVariableValue extends DfaValue { @Nullable private final DfaVariableValue myQualifier; private DfaVariableValue myNegatedValue; private final boolean myIsNegated; - private Nullness myInherentNullability; + private DfaFactMap myInherentFacts; private final DfaTypeValue myTypeValue; private final List myDependents = new SmartList<>(); @@ -158,110 +146,17 @@ public class DfaVariableValue extends DfaValue { return myQualifier; } + public DfaFactMap getInherentFacts() { + if(myInherentFacts == null) { + myInherentFacts = DfaFactMap.calcFromVariable(this); + } + + return myInherentFacts; + } + @NotNull public Nullness getInherentNullability() { - if (myInherentNullability != null) { - return myInherentNullability; - } - - return myInherentNullability = calcInherentNullability(); - } - - @NotNull - private Nullness calcInherentNullability() { - PsiModifierListOwner var = getPsiVariable(); - Nullness nullability = DfaPsiUtil.getElementNullability(getVariableType(), var); - if (nullability != Nullness.UNKNOWN) { - return nullability; - } - - Nullness defaultNullability = myFactory.isUnknownMembersAreNullable() && MEMBER_OR_METHOD_PARAMETER.accepts(var) ? Nullness.NULLABLE : Nullness.UNKNOWN; - - if (var instanceof PsiParameter && var.getParent() instanceof PsiForeachStatement) { - PsiExpression iteratedValue = ((PsiForeachStatement)var.getParent()).getIteratedValue(); - if (iteratedValue != null) { - PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); - if (itemType != null) { - return DfaPsiUtil.getElementNullability(itemType, var); - } - } - } - - if (var instanceof PsiField && myFactory.isHonorFieldInitializers()) { - return getNullabilityFromFieldInitializers((PsiField)var, defaultNullability); - } - - return defaultNullability; - } - - private static Nullness getNullabilityFromFieldInitializers(PsiField field, Nullness defaultNullability) { - if (DfaPsiUtil.isFinalField(field)) { - PsiExpression initializer = field.getInitializer(); - if (initializer != null) { - return getFieldInitializerNullness(initializer); - } - - List initializers = DfaPsiUtil.findAllConstructorInitializers(field); - if (initializers.isEmpty()) { - return defaultNullability; - } - - for (PsiExpression expression : initializers) { - if (getFieldInitializerNullness(expression) == Nullness.NULLABLE) { - return Nullness.NULLABLE; - } - } - - if (DfaPsiUtil.isInitializedNotNull(field)) { - return Nullness.NOT_NULL; - } - } - else if (isOnlyImplicitlyInitialized(field)) { - return Nullness.NOT_NULL; - } - return defaultNullability; - } - - private static boolean isOnlyImplicitlyInitialized(PsiField field) { - return CachedValuesManager.getCachedValue(field, () -> CachedValueProvider.Result.create( - isImplicitlyInitializedNotNull(field) && weAreSureThereAreNoExplicitWrites(field), - PsiModificationTracker.MODIFICATION_COUNT)); - } - - private static boolean isImplicitlyInitializedNotNull(PsiField field) { - return ContainerUtil.exists(Extensions.getExtensions(ImplicitUsageProvider.EP_NAME), p -> p.isImplicitlyNotNullInitialized(field)); - } - - private static boolean weAreSureThereAreNoExplicitWrites(PsiField field) { - String name = field.getName(); - if (name == null || field.getInitializer() != null) return false; - - if (!isCheapEnoughToSearch(field, name)) return false; - - return ReferencesSearch.search(field).forEach(reference -> reference instanceof PsiReferenceExpression && !PsiUtil.isAccessedForWriting((PsiReferenceExpression)reference)); - } - - private static boolean isCheapEnoughToSearch(PsiField field, String name) { - SearchScope scope = field.getUseScope(); - if (!(scope instanceof GlobalSearchScope)) return true; - - PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(field.getProject()); - PsiSearchHelper.SearchCostResult result = helper.isCheapEnoughToSearch(name, (GlobalSearchScope)scope, field.getContainingFile(), null); - return result != PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES; - } - - private static Nullness getFieldInitializerNullness(@NotNull PsiExpression expression) { - if (expression.textMatches(PsiKeyword.NULL)) return Nullness.NULLABLE; - if (expression instanceof PsiNewExpression || expression instanceof PsiLiteralExpression || expression instanceof PsiPolyadicExpression) return Nullness.NOT_NULL; - if (expression instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression)expression).resolve(); - return DfaPsiUtil.getElementNullability(expression.getType(), (PsiModifierListOwner)target); - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); - return method != null ? DfaPsiUtil.getElementNullability(expression.getType(), method) : Nullness.UNKNOWN; - } - return Nullness.UNKNOWN; + return Nullness.fromBoolean(getInherentFacts().get(DfaFactType.CAN_BE_NULL)); } public boolean isFlushableByCalls() {