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 57ef83135882..b5e582dcc957 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 @@ -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 objectType = (DfaFactType)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 DfaFactMap updateMap(DfaFactMap map, DfaFactType factType, DfaVariableValue value) { + return map.with(factType, factType.calcFromVariable(value)); } } 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 0e66c5cdc8b3..9110206d3814 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,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 extends Key { /** * 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(); + public static final DfaFactType CAN_BE_NULL = new DfaFactType("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 extends Key { 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 7c71453f99ee..b8109767dbf3 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 @@ -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); 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 9340c9461125..c4d413979d12 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 @@ -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() { 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 9eb2f5a63dcb..3dd099b0c1d3 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,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 } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java new file mode 100644 index 000000000000..a1a599673dfa --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java @@ -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 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 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; + } +} 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 db6e327cf217..790801df6ab7 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,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() {