[java-dfa] Rework unknowns are nullables mode: now tracked by visitor instead of factory

GitOrigin-RevId: e9af3d831999536f83f58fc431a1a7a819d9e785
This commit is contained in:
Tagir Valeev
2021-02-11 05:23:58 +00:00
committed by intellij-monorepo-bot
parent d8e57214a9
commit 0b0c0b1bd5
15 changed files with 59 additions and 74 deletions
@@ -12,6 +12,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.ThreeState;
import com.siyeh.ig.psiutils.BoolUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -78,9 +79,9 @@ class DebuggerInstructionVisitor extends StandardInstructionVisitor {
}
@Override
protected boolean checkNotNullable(DfaMemoryState state,
@NotNull DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
protected ThreeState checkNotNullable(DfaMemoryState state,
@NotNull DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
if (problem != null) {
PsiExpression expression = problem.getDereferencedExpression();
if (expression != null && problem.thrownException() != null) {
@@ -205,7 +205,7 @@ public final class CommonDataflow {
@NotNull
private static DataflowResult runDFA(@Nullable PsiElement block) {
if (block == null) return new DataflowResult(RunnerResult.NOT_APPLICABLE);
DataFlowRunner runner = new DataFlowRunner(block.getProject(), block, false, ThreeState.UNSURE);
DataFlowRunner runner = new DataFlowRunner(block.getProject(), block, ThreeState.UNSURE);
CommonDataflowVisitor visitor = new CommonDataflowVisitor();
RunnerResult result = runner.analyzeMethodRecursively(block, visitor);
if (result != RunnerResult.OK) return new DataflowResult(result);
@@ -91,8 +91,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
if (aClass instanceof PsiTypeParameter) return;
if (PsiUtil.isLocalOrAnonymousClass(aClass) && !(aClass instanceof PsiEnumConstantInitializer)) return;
final DataFlowRunner runner = new DataFlowRunner(holder.getProject(), aClass, TREAT_UNKNOWN_MEMBERS_AS_NULLABLE,
ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS));
final DataFlowRunner runner = new DataFlowRunner(holder.getProject(), aClass, ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS));
DataFlowInstructionVisitor visitor =
analyzeDfaWithNestedClosures(aClass, holder, runner, Collections.singletonList(runner.createMemoryState()));
List<DfaMemoryState> states = visitor.getEndOfInitializerStates();
@@ -117,7 +116,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
public void visitMethod(PsiMethod method) {
if (method.isConstructor()) return;
final DataFlowRunner runner = new DataFlowRunner(
holder.getProject(), method.getBody(), TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS));
holder.getProject(), method.getBody(), ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS));
analyzeMethod(method, runner, Collections.singletonList(runner.createMemoryState()));
}
@@ -201,7 +200,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
ProblemsHolder holder,
DataFlowRunner dfaRunner,
Collection<? extends DfaMemoryState> initialStates) {
final DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor();
final DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor(TREAT_UNKNOWN_MEMBERS_AS_NULLABLE);
final RunnerResult rc = dfaRunner.analyzeMethod(scope, visitor, initialStates);
if (rc == RunnerResult.OK) {
if (dfaRunner.wasForciblyMerged() &&
@@ -50,6 +50,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final Map<PsiExpression, ThreeState> mySwitchLabelsReachability = new HashMap<>();
private boolean myAlwaysReturnsNotNull = true;
private final List<DfaMemoryState> myEndOfInitializerStates = new ArrayList<>();
private final boolean myStrictMode;
private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf(
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min", "max").parameterCount(2),
@@ -60,6 +61,10 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "replace").parameterCount(2)
);
DataFlowInstructionVisitor(boolean strictMode) {
myStrictMode = strictMode;
}
@Override
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression left = instruction.getLExpression();
@@ -144,7 +149,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
@Override
protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) {
myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible);
myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, ThreeState.fromBoolean(castPossible));
}
StreamEx<NullabilityProblemKind.NullabilityProblem<?>> problems() {
@@ -316,12 +321,15 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
@Override
protected boolean checkNotNullable(DfaMemoryState state, @NotNull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
protected ThreeState checkNotNullable(DfaMemoryState state, @NotNull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) {
myAlwaysReturnsNotNull = false;
}
boolean ok = super.checkNotNullable(state, value, problem);
ThreeState ok = super.checkNotNullable(state, value, problem);
if (!myStrictMode && ok == ThreeState.UNSURE) {
ok = ThreeState.YES;
}
if (problem == null) return ok;
StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo());
info.update(state, ok);
@@ -349,14 +357,19 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
boolean ephemeralException;
boolean normalException;
boolean normalOk;
boolean unknown = true;
void update(DfaMemoryState state, boolean ok) {
void update(DfaMemoryState state, ThreeState ok) {
if (state.isEphemeral()) {
if (!ok) ephemeralException = true;
if (ok != ThreeState.YES) ephemeralException = true;
if (ok != ThreeState.UNSURE) unknown = false;
}
else {
if (ok) normalOk = true;
else normalException = true;
if (ok == ThreeState.YES) normalOk = true;
else {
normalException = true;
if (ok != ThreeState.UNSURE) unknown = false;
}
}
}
@@ -61,21 +61,19 @@ public class DataFlowRunner {
}
public DataFlowRunner(@NotNull Project project, @Nullable PsiElement context) {
this(project, context, false, ThreeState.NO);
this(project, context, ThreeState.NO);
}
/**
* @param project current project
* @param context analysis context element (code block, class, expression, etc.); used to determine whether we can trust
* field initializers (e.g. we usually cannot if context is a constructor)
* @param unknownMembersAreNullable if true every parameter or method return value without nullity annotation is assumed to be nullable
* @param ignoreAssertions if true, assertion statements will be ignored, as if JVM is started with -da.
*/
public DataFlowRunner(@NotNull Project project,
@Nullable PsiElement context,
boolean unknownMembersAreNullable,
@NotNull ThreeState ignoreAssertions) {
myValueFactory = new DfaValueFactory(project, context, unknownMembersAreNullable);
myValueFactory = new DfaValueFactory(project, context);
myIgnoreAssertions = ignoreAssertions;
}
@@ -293,8 +293,9 @@ public final class DfaUtil {
return Integer.MAX_VALUE; // accessed after initialization or at unknown moment
}
public static boolean hasInitializationHacks(@NotNull PsiField field) {
PsiClass containingClass = field.getContainingClass();
public static boolean hasInitializationHacks(@NotNull PsiVariable var) {
if (!(var instanceof PsiField)) return false;
PsiClass containingClass = ((PsiField)var).getContainingClass();
return containingClass != null && System.class.getName().equals(containingClass.getQualifiedName());
}
@@ -39,14 +39,15 @@ public final class NullabilityUtil {
if (value.getType() instanceof PsiPrimitiveType) {
return DfaNullability.UNKNOWN;
}
if (var instanceof PsiField && DfaUtil.hasInitializationHacks((PsiField)var)) {
return DfaNullability.FLUSHED;
}
Nullability nullability = DfaPsiUtil.getElementNullabilityIgnoringParameterInference(value.getType(), var);
if (nullability != Nullability.UNKNOWN) {
return DfaNullability.fromNullability(nullability);
}
if (var == null) return DfaNullability.UNKNOWN;
Nullability defaultNullability = value.getFactory().suggestNullabilityForNonAnnotatedMember(var);
if (var instanceof PsiParameter && var.getParent() instanceof PsiForeachStatement) {
PsiExpression iteratedValue = ((PsiForeachStatement)var.getParent()).getIteratedValue();
if (iteratedValue != null) {
@@ -58,13 +59,13 @@ public final class NullabilityUtil {
}
if (var instanceof PsiField && value.getFactory().canTrustFieldInitializer((PsiField)var)) {
return DfaNullability.fromNullability(getNullabilityFromFieldInitializers((PsiField)var, defaultNullability).second);
return DfaNullability.fromNullability(getNullabilityFromFieldInitializers((PsiField)var).second);
}
return DfaNullability.fromNullability(defaultNullability);
return DfaNullability.fromNullability(Nullability.UNKNOWN);
}
static Pair<PsiExpression, Nullability> getNullabilityFromFieldInitializers(PsiField field, Nullability defaultNullability) {
static Pair<PsiExpression, Nullability> getNullabilityFromFieldInitializers(PsiField field) {
if (DfaPsiUtil.isFinalField(field)) {
PsiExpression initializer = field.getInitializer();
if (initializer != null) {
@@ -73,7 +74,7 @@ public final class NullabilityUtil {
List<PsiExpression> initializers = DfaPsiUtil.findAllConstructorInitializers(field);
if (initializers.isEmpty()) {
return Pair.create(null, defaultNullability);
return Pair.create(null, Nullability.UNKNOWN);
}
for (PsiExpression expression : initializers) {
@@ -90,7 +91,7 @@ public final class NullabilityUtil {
else if (isOnlyImplicitlyInitialized(field)) {
return Pair.create(null, Nullability.NOT_NULL);
}
return Pair.create(null, defaultNullability);
return Pair.create(null, Nullability.UNKNOWN);
}
private static boolean isOnlyImplicitlyInitialized(PsiField field) {
@@ -618,12 +618,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private <T extends PsiElement> DfaValue dereference(DfaMemoryState memState,
DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<T> problem) {
boolean ok = checkNotNullable(memState, value, problem);
ThreeState ok = checkNotNullable(memState, value, problem);
if (value instanceof DfaTypeValue) {
DfType dfType = value.getDfType().meet(NOT_NULL_OBJECT);
return value.getFactory().fromDfType(dfType == BOTTOM ? NOT_NULL_OBJECT : dfType);
}
if (ok) return value;
if (ok != ThreeState.NO) return value;
if (memState.isNull(value) && problem != null && problem.getKind() == NullabilityProblemKind.nullableFunctionReturn) {
return value.getFactory().fromDfType(NOT_NULL_OBJECT);
}
@@ -700,9 +700,6 @@ public class StandardInstructionVisitor extends InstructionVisitor {
mutable = Mutability.getMutability(realMethod);
}
type = narrowReturnType(type, qualifierType, realMethod);
if (nullability == Nullability.UNKNOWN) {
nullability = factory.suggestNullabilityForNonAnnotatedMember(targetMethod);
}
}
DfType dfType = instruction.getContext() instanceof PsiNewExpression ?
TypeConstraints.exact(type).asDfType().meet(NOT_NULL_OBJECT) :
@@ -768,13 +765,14 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return precalculated;
}
protected boolean checkNotNullable(DfaMemoryState state, @NotNull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
protected ThreeState checkNotNullable(DfaMemoryState state, @NotNull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
DfaNullability nullability = DfaNullability.fromDfType(state.getDfType(value));
boolean notNullable = nullability != DfaNullability.NULL && nullability != DfaNullability.NULLABLE;
if (notNullable && problem != null && problem.thrownException() != null) {
state.applyCondition(value.cond(RelationType.NE, value.getFactory().getNull()));
}
return notNullable;
boolean unknown = nullability == DfaNullability.UNKNOWN;
return notNullable ? unknown ? ThreeState.UNSURE : ThreeState.YES : ThreeState.NO;
}
@Override
@@ -60,9 +60,8 @@ public final class TrackingRunner extends DataFlowRunner {
private TrackingRunner(@NotNull PsiElement context,
PsiExpression expression,
boolean unknownMembersAreNullable,
boolean ignoreAssertions) {
super(context.getProject(), context, unknownMembersAreNullable, ThreeState.fromBoolean(ignoreAssertions));
super(context.getProject(), context, ThreeState.fromBoolean(ignoreAssertions));
myExpression = expression;
}
@@ -121,13 +120,12 @@ public final class TrackingRunner extends DataFlowRunner {
}
@Nullable
public static CauseItem findProblemCause(boolean unknownAreNullables,
boolean ignoreAssertions,
public static CauseItem findProblemCause(boolean ignoreAssertions,
PsiExpression expression,
DfaProblemType type) {
PsiElement body = DfaUtil.getDataflowContext(expression);
if (body == null) return null;
TrackingRunner runner = new TrackingRunner(body, expression, unknownAreNullables, ignoreAssertions);
TrackingRunner runner = new TrackingRunner(body, expression, ignoreAssertions);
if (!runner.analyze(expression, body)) return null;
return runner.findProblemCause(expression, type);
}
@@ -1230,7 +1228,7 @@ public final class TrackingRunner extends DataFlowRunner {
}
if (owner instanceof PsiField && getFactory().canTrustFieldInitializer((PsiField)owner)) {
Pair<PsiExpression, Nullability> fieldNullability =
NullabilityUtil.getNullabilityFromFieldInitializers((PsiField)owner, Nullability.UNKNOWN);
NullabilityUtil.getNullabilityFromFieldInitializers((PsiField)owner);
if (fieldNullability.second == DfaNullability.toNullability(nullability)) {
PsiExpression initializer = fieldNullability.first;
if (initializer != null) {
@@ -368,7 +368,8 @@ public class DfaExpressionFactory {
@Override
public boolean isStable() {
return PsiUtil.isJvmLocalVariable(myVariable) || myVariable.hasModifierProperty(PsiModifier.FINAL);
return PsiUtil.isJvmLocalVariable(myVariable) ||
(myVariable.hasModifierProperty(PsiModifier.FINAL) && !DfaUtil.hasInitializationHacks(myVariable));
}
@NotNull
@@ -379,8 +380,7 @@ public class DfaExpressionFactory {
return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable));
}
if (PsiUtil.isJvmLocalVariable(myVariable) ||
(myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC) &&
(!myVariable.hasModifierProperty(PsiModifier.FINAL) || !DfaUtil.hasInitializationHacks((PsiField)myVariable)))) {
(myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC))) {
return factory.getVarFactory().createVariableValue(this);
}
return VariableDescriptor.super.createValue(factory, qualifier, forAccessor);
@@ -2,7 +2,6 @@
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.types.DfType;
@@ -10,7 +9,6 @@ import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFieldImpl;
import com.intellij.psi.util.*;
@@ -25,13 +23,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.patterns.PsiJavaPatterns.psiMember;
import static com.intellij.patterns.PsiJavaPatterns.psiParameter;
import static com.intellij.patterns.StandardPatterns.or;
public class DfaValueFactory {
private final @NotNull List<DfaValue> myValues = new ArrayList<>();
private final boolean myUnknownMembersAreNullable;
private final @NotNull FieldChecker myFieldChecker;
private final @NotNull Project myProject;
private @Nullable DfaVariableValue myAssertionDisabled;
@@ -39,12 +32,10 @@ public class DfaValueFactory {
/**
* @param project a project in which context the analysis is performed
* @param context an item to analyze (code-block, expression, class)
* @param unknownMembersAreNullable if true, unknown (non-annotated members) are assumed to be nullable
*/
public DfaValueFactory(@NotNull Project project, @Nullable PsiElement context, boolean unknownMembersAreNullable) {
public DfaValueFactory(@NotNull Project project, @Nullable PsiElement context) {
myProject = project;
myFieldChecker = new FieldChecker(context);
myUnknownMembersAreNullable = unknownMembersAreNullable;
myValues.add(null);
myVarFactory = new DfaVariableValue.Factory(this);
myBoxedFactory = new DfaBoxedValue.Factory(this);
@@ -57,21 +48,6 @@ public class DfaValueFactory {
return myFieldChecker.canTrustFieldInitializer(field);
}
private static final ElementPattern<? extends PsiModifierListOwner> MEMBER_OR_METHOD_PARAMETER =
or(psiMember(), psiParameter().withSuperParent(2, psiMember()));
@NotNull
public Nullability suggestNullabilityForNonAnnotatedMember(@NotNull PsiModifierListOwner member) {
if (myUnknownMembersAreNullable &&
MEMBER_OR_METHOD_PARAMETER.accepts(member) &&
AnnotationUtil.getSuperAnnotationOwners(member).isEmpty()) {
return Nullability.NULLABLE;
}
return Nullability.UNKNOWN;
}
@NotNull
public DfaTypeValue getObjectType(@Nullable PsiType type, @NotNull Nullability nullability) {
return fromDfType(DfTypes.typedObject(type, nullability));
@@ -73,7 +73,7 @@ public final class FindDfaProblemCauseFix implements OnTheFlyLocalFix, LowPriori
ThrowableComputable<TrackingRunner.CauseItem, RuntimeException> causeFinder = () -> {
PsiExpression element = myAnchor.getElement();
if (element == null) return null;
return TrackingRunner.findProblemCause(myUnknownMembersAsNullable, myIgnoreAssertStatements, element, myProblemType);
return TrackingRunner.findProblemCause(myIgnoreAssertStatements, element, myProblemType);
};
TrackingRunner.CauseItem item = ProgressManager.getInstance().runProcessWithProgressSynchronously(
() -> ReadAction.compute(causeFinder), JavaBundle.message("progress.title.finding.cause"), true, project);
@@ -52,7 +52,7 @@ public class DataFlowInspectionTrackerTest extends LightJavaCodeInsightFixtureTe
assertTrue("Selected element is not an expression: " + selectedText, element instanceof PsiExpression);
PsiExpression expression = (PsiExpression)element;
TrackingRunner.DfaProblemType problemType = getProblemType(selectedText, expression);
TrackingRunner.CauseItem item = TrackingRunner.findProblemCause(true, false, expression, problemType);
TrackingRunner.CauseItem item = TrackingRunner.findProblemCause(false, expression, problemType);
assertNotNull(item);
String dump = item.dump(getEditor().getDocument());
PsiComment firstComment = PsiTreeUtil.findChildOfType(file, PsiComment.class);
@@ -337,7 +337,7 @@ public final class ReorderingUtils {
List<? extends MethodContract> contracts = DfaUtil.addRangeContracts(method, JavaMethodContractUtil.getMethodCallContracts(call));
contracts = ContainerUtil.filter(contracts, c -> c.getReturnValue().isFail() && c.getConditions().size() == 1);
if (contracts.isEmpty()) return null;
DfaValueFactory factory = new DfaValueFactory(expression.getProject(), null, false);
DfaValueFactory factory = new DfaValueFactory(expression.getProject(), null);
List<DfaRelation> conditions = new ArrayList<>();
for (MethodContract contract : contracts) {
ContractValue condition = contract.getConditions().get(0);
@@ -166,7 +166,7 @@ public class ConditionCoveredByFurtherConditionInspection extends AbstractBaseJa
Map<PsiExpression, ThreeState> values = new HashMap<>();
StandardInstructionVisitor visitor = new StandardInstructionVisitor() {
@Override
protected boolean checkNotNullable(DfaMemoryState state,
protected ThreeState checkNotNullable(DfaMemoryState state,
@NotNull DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
if (value instanceof DfaVariableValue) {
@@ -175,7 +175,7 @@ public class ConditionCoveredByFurtherConditionInspection extends AbstractBaseJa
state.setDfType(value, ((DfReferenceType)dfType).dropNullability().meet(DfaNullability.NULLABLE.asDfType()));
}
}
return true;
return ThreeState.YES;
}
@Override