Nullability as DfaFact. DfaVariableValue#calcInherentNullability now part of DfaFactType

This commit is contained in:
Tagir Valeev
2017-05-18 17:19:04 +07:00
parent fd45e2db55
commit 193088959c
12 changed files with 312 additions and 206 deletions
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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
@@ -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;
}
}
@@ -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
@@ -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;
}
@@ -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() {