DfaFactType improvements after review IDEA-CR-21121

1. DfaFactMap#calcFromVariable: unchecked warning fixed
2. NullnessUtil extracted out of CanBeNullFactType
3. CanBeNullFactType is anonymous now
4. New Nullness methods moved to NullnessUtil
This commit is contained in:
Tagir Valeev
2017-05-18 17:19:05 +07:00
parent 193088959c
commit 102eb1c43b
7 changed files with 207 additions and 192 deletions
@@ -136,13 +136,10 @@ public final class DfaFactMap {
* @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;
return StreamEx.of(DfaFactType.getTypes()).foldLeft(EMPTY, (factMap, type) -> updateMap(factMap, type, value));
}
private static <T> DfaFactMap updateMap(DfaFactMap map, DfaFactType<T> factType, DfaVariableValue value) {
return map.with(factType, factType.calcFromVariable(value));
}
}
@@ -15,23 +15,9 @@
*/
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.*;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Key;
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;
@@ -39,10 +25,6 @@ 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.
*
@@ -54,7 +36,33 @@ public abstract class DfaFactType<T> extends Key<T> {
/**
* 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();
public static final DfaFactType<Boolean> CAN_BE_NULL = new DfaFactType<Boolean>("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 NullnessUtil.toBoolean(((DfaTypeValue)value).getNullness());
}
return null;
}
@Nullable
@Override
Boolean calcFromVariable(@NotNull DfaVariableValue value) {
return NullnessUtil.calcCanBeNull(value);
}
};
/**
* This fact is applied to the Optional values (like {@link java.util.Optional} or Guava Optional).
@@ -159,137 +167,4 @@ public abstract class DfaFactType<T> extends Key<T> {
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;
}
}
}
@@ -283,7 +283,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
private DfaVariableState withValueNullability(DfaValue value, DfaVariableState state) {
if (value instanceof DfaTypeValue) {
return state.withFact(DfaFactType.CAN_BE_NULL, ((DfaTypeValue)value).getNullness().toBoolean());
return state.withFact(DfaFactType.CAN_BE_NULL, NullnessUtil.toBoolean(((DfaTypeValue)value).getNullness()));
}
if (isNull(value)) {
return state.withFact(DfaFactType.CAN_BE_NULL, true);
@@ -174,7 +174,7 @@ class DfaVariableState {
@NotNull
Nullness getNullability() {
return Nullness.fromBoolean(myFactMap.get(DfaFactType.CAN_BE_NULL));
return NullnessUtil.fromBoolean(myFactMap.get(DfaFactType.CAN_BE_NULL));
}
public boolean isNotNull() {
@@ -15,33 +15,9 @@
*/
package com.intellij.codeInspection.dataFlow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author cdr
*/
public enum Nullness {
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;
}
NOT_NULL, NULLABLE,UNKNOWN
}
@@ -0,0 +1,170 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.openapi.extensions.Extensions;
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.List;
import static com.intellij.patterns.PsiJavaPatterns.psiMember;
import static com.intellij.patterns.PsiJavaPatterns.psiParameter;
import static com.intellij.patterns.StandardPatterns.or;
public class NullnessUtil {
private static final ElementPattern<? extends PsiModifierListOwner> MEMBER_OR_METHOD_PARAMETER =
or(psiMember(), psiParameter().withSuperParent(2, psiMember()));
static Boolean calcCanBeNull(DfaVariableValue value) {
PsiModifierListOwner var = value.getPsiVariable();
Nullness nullability = DfaPsiUtil.getElementNullability(value.getVariableType(), var);
if (nullability != Nullness.UNKNOWN) {
return toBoolean(nullability);
}
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 toBoolean(DfaPsiUtil.getElementNullability(itemType, var));
}
}
}
if (var instanceof PsiField && value.getFactory().isHonorFieldInitializers()) {
return toBoolean(getNullabilityFromFieldInitializers((PsiField)var, defaultNullability));
}
return toBoolean(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;
}
/**
* 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 ? Nullness.UNKNOWN : fact ? Nullness.NULLABLE : Nullness.NOT_NULL;
}
/**
* Convert nullness 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 static Boolean toBoolean(@NotNull Nullness nullness) {
return nullness == Nullness.UNKNOWN ? null : nullness == Nullness.NULLABLE;
}
}
@@ -16,10 +16,7 @@
package com.intellij.codeInspection.dataFlow.value;
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.codeInspection.dataFlow.*;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
@@ -156,7 +153,7 @@ public class DfaVariableValue extends DfaValue {
@NotNull
public Nullness getInherentNullability() {
return Nullness.fromBoolean(getInherentFacts().get(DfaFactType.CAN_BE_NULL));
return NullnessUtil.fromBoolean(getInherentFacts().get(DfaFactType.CAN_BE_NULL));
}
public boolean isFlushableByCalls() {