mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 17:03:12 +07:00
Nullability as DfaFact. DfaVariableValue#calcInherentNullability now part of DfaFactType
This commit is contained in:
@@ -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<Object>)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<Object> objectType = (DfaFactType<Object>)type;
|
||||
Object fact = objectType.calcFromVariable(value);
|
||||
map = map.with(objectType, fact);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
+190
-14
@@ -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<T> extends Key<T> {
|
||||
private static final List<DfaFactType<?>> 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<Boolean> 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<T> extends Key<T> {
|
||||
* This fact is applied to the integral values (of types byte, char, short, int, long).
|
||||
* Its value represents a range of possible values.
|
||||
*/
|
||||
public static final DfaFactType<LongRangeSet> RANGE = new DfaFactType<LongRangeSet>("Mutability") {
|
||||
public static final DfaFactType<LongRangeSet> RANGE = new DfaFactType<LongRangeSet>("Range") {
|
||||
@Override
|
||||
boolean isSuper(@NotNull LongRangeSet superFact, @NotNull LongRangeSet subFact) {
|
||||
return superFact.contains(subFact);
|
||||
@@ -62,17 +88,22 @@ public abstract class DfaFactType<T> extends Key<T> {
|
||||
@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<T> extends Key<T> {
|
||||
};
|
||||
|
||||
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<T> extends Key<T> {
|
||||
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<T> extends Key<T> {
|
||||
String toString(T fact) {
|
||||
return fact.toString();
|
||||
}
|
||||
|
||||
static List<DfaFactType<?>> getTypes() {
|
||||
return Collections.unmodifiableList(ourFactTypes);
|
||||
}
|
||||
|
||||
private static class CanBeNullFactType extends DfaFactType<Boolean> {
|
||||
private static final ElementPattern<? extends PsiModifierListOwner> 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<PsiExpression> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-48
@@ -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<DfaPsiType> myInstanceofValues;
|
||||
@NotNull final Set<DfaPsiType> 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<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> 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<DfaPsiType> moreGeneric = ContainerUtil.newArrayList();
|
||||
for (DfaPsiType alreadyInstanceof : myInstanceofValues) {
|
||||
if (dfaType.getDfaType().isAssignableFrom(alreadyInstanceof)) {
|
||||
@@ -111,7 +87,7 @@ class DfaVariableState {
|
||||
HashSet<DfaPsiType> 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<DfaPsiType> 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<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues);
|
||||
newInstanceof.remove(type);
|
||||
return createCopy(newInstanceof, myNotInstanceofValues, myNullability, myFactMap);
|
||||
return createCopy(newInstanceof, myNotInstanceofValues, myFactMap);
|
||||
}
|
||||
if (myNotInstanceofValues.contains(type)) {
|
||||
HashSet<DfaPsiType> 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<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> 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
|
||||
<T> DfaVariableState withFact(DfaFactType<T> 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);
|
||||
}
|
||||
|
||||
<T> DfaVariableState withoutFact(DfaFactType<T> 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
|
||||
|
||||
+2
-1
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()) {
|
||||
|
||||
@@ -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<DfaVariableValue, Map<LongRangeSet, LongRangeSet>> 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<DfaVariableValue, DfaVariableState> 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<DfaMemoryStateImpl, Record> 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
|
||||
|
||||
+5
-6
@@ -76,10 +76,10 @@ class ValuableDataFlowRunner extends DataFlowRunner {
|
||||
|
||||
private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues,
|
||||
Set<DfaPsiType> notInstanceofValues,
|
||||
Nullness nullability, DfaValue value,
|
||||
DfaValue value,
|
||||
@NotNull FList<PsiExpression> 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<DfaPsiType> instanceofValues,
|
||||
@NotNull Set<DfaPsiType> 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<PsiExpression> concatenation) {
|
||||
if (concatenation == myConcatenation) return this;
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, concatenation, myFactMap);
|
||||
return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myValue, concatenation, myFactMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+14
-119
@@ -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<? extends PsiModifierListOwner> MEMBER_OR_METHOD_PARAMETER =
|
||||
or(psiMember(), psiParameter().withSuperParent(2, psiMember()));
|
||||
|
||||
public static class Factory {
|
||||
private final MultiMap<Trinity<Boolean,String,DfaVariableValue>,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<DfaVariableValue> 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<PsiExpression> 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() {
|
||||
|
||||
Reference in New Issue
Block a user