DFA: refactor nullability problems for stricter type checking

NullabilityProblemKind is introduced which is parameterized by anchor type. A NullabilityProblem now represents a pair of problem kind and anchor bound by a type variable.
This commit is contained in:
Tagir Valeev
2017-10-27 16:19:01 +07:00
parent 54aa0ffc65
commit 9b386a211d
15 changed files with 299 additions and 235 deletions
@@ -384,11 +384,11 @@ public class CFGBuilder {
* this is not satisfied. Stack is unchanged.
*
* @param expression an anchor expression to bind a warning to
* @param problem a type of nullability problem to report if value is nullable
* @param kind a type of nullability problem to report if value is nullable
* @return this builder
*/
public CFGBuilder checkNotNull(PsiExpression expression, NullabilityProblem problem) {
myAnalyzer.addInstruction(new CheckNotNullInstruction(expression, problem));
public <T extends PsiElement> CFGBuilder checkNotNull(T expression, NullabilityProblemKind<T> kind) {
myAnalyzer.addInstruction(new CheckNotNullInstruction(kind.problem(expression)));
return this;
}
@@ -448,7 +448,7 @@ public class CFGBuilder {
PsiVariable qualifierBinding = createTempVariable(qualifier.getType());
pushVariable(qualifierBinding)
.pushExpression(qualifier)
.checkNotNull(qualifier, NullabilityProblem.fieldAccessNPE)
.checkNotNull(qualifier, NullabilityProblemKind.fieldAccessNPE)
.assign()
.pop();
myMethodRefQualifiers.put(methodRef, qualifierBinding);
@@ -456,7 +456,7 @@ public class CFGBuilder {
return this;
}
return pushExpression(functionalExpression)
.checkNotNull(functionalExpression, NullabilityProblem.passingNullableToNotNullParameter)
.checkNotNull(functionalExpression, NullabilityProblemKind.passingNullableToNotNullParameter)
.pop();
}
@@ -518,7 +518,7 @@ public class CFGBuilder {
myAnalyzer.generateBoxingUnboxingInstructionFor(methodRef, resolveResult.getSubstitutor().substitute(method.getReturnType()),
LambdaUtil.getFunctionalInterfaceReturnType(methodRef));
if (resultNullness == Nullness.NOT_NULL) {
checkNotNull(methodRef, NullabilityProblem.nullableFunctionReturn);
checkNotNull(methodRef, NullabilityProblemKind.nullableFunctionReturn);
}
return this;
}
@@ -585,7 +585,7 @@ public class CFGBuilder {
pushExpression(expression);
boxUnbox(expression, LambdaUtil.getFunctionalInterfaceReturnType(lambda));
if(resultNullness == Nullness.NOT_NULL) {
checkNotNull(expression, NullabilityProblem.nullableFunctionReturn);
checkNotNull(expression, NullabilityProblemKind.nullableFunctionReturn);
}
} else if(body instanceof PsiCodeBlock) {
PsiVariable variable = createTempVariable(LambdaUtil.getFunctionalInterfaceReturnType(lambda));
@@ -49,7 +49,6 @@ import static com.intellij.psi.CommonClassNames.*;
public class ControlFlowAnalyzer extends JavaElementVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer");
public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
static final String METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD = "Method reference qualifier";
private final PsiElement myCodeFragment;
private final boolean myIgnoreAssertions;
private final boolean myInlining;
@@ -111,7 +110,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (parent instanceof PsiLambdaExpression && myCodeFragment instanceof PsiExpression) {
generateBoxingUnboxingInstructionFor((PsiExpression)myCodeFragment,
LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)parent));
addInstruction(new CheckReturnValueInstruction(myCodeFragment));
addInstruction(new CheckReturnValueInstruction((PsiExpression)myCodeFragment));
}
addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, FList.emptyList()), null));
@@ -477,7 +476,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (iteratedValue != null) {
iteratedValue.accept(this);
addInstruction(new FieldReferenceInstruction(iteratedValue, "Collection iterator or array.length"));
addInstruction(new FieldReferenceInstruction(iteratedValue));
DfaValue qualifier = myFactory.createValue(iteratedValue);
if (qualifier instanceof DfaVariableValue) {
@@ -692,7 +691,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
returnValue.accept(this);
generateBoxingUnboxingInstructionFor(returnValue, var.getVariableType());
if (myInlinedBlockContext.myForceNonNullBlockResult) {
addInstruction(new CheckNotNullInstruction(returnValue, NullabilityProblem.nullableFunctionReturn));
addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.nullableFunctionReturn.problem(returnValue)));
}
addInstruction(new AssignInstruction(returnValue, null));
addInstruction(new PopInstruction());
@@ -738,7 +737,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
generateBoxingUnboxingInstructionFor(caseExpression, PsiType.INT);
final PsiClass psiClass = PsiUtil.resolveClassInType(caseExpression.getType());
if (psiClass != null) {
addInstruction(new FieldReferenceInstruction(caseExpression, "switch statement expression"));
addInstruction(new FieldReferenceInstruction(caseExpression));
if (psiClass.isEnum()) {
enumValues = new HashSet<>();
for (PsiField f : psiClass.getFields()) {
@@ -818,7 +817,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression qualifier = expression.getQualifierExpression();
if (qualifier != null) {
qualifier.accept(this);
addInstruction(new FieldReferenceInstruction(qualifier, METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD));
addInstruction(new FieldReferenceInstruction(qualifier));
}
addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getFunctionalInterfaceType(), Nullness.NOT_NULL), expression));
@@ -832,7 +831,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression lock = statement.getLockExpression();
if (lock != null) {
lock.accept(this);
addInstruction(new FieldReferenceInstruction(lock, "Synchronized value"));
addInstruction(new FieldReferenceInstruction(lock));
}
addInstruction(new FlushVariableInstruction(null));
@@ -854,7 +853,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
exception.accept(this);
addConditionalRuntimeThrow();
addInstruction(new FieldReferenceInstruction(exception, "thrown exception"));
addInstruction(new FieldReferenceInstruction(exception));
throwException(exception.getType(), statement);
}
@@ -1096,7 +1095,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (componentType != null) {
generateBoxingUnboxingInstructionFor(initializer, componentType);
if (componentNullability == Nullness.NOT_NULL) {
addInstruction(new CheckNotNullInstruction(initializer, NullabilityProblem.storingToNotNullArray));
addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.storingToNotNullArray.problem(initializer)));
}
}
addInstruction(new PopInstruction());
@@ -1598,7 +1597,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression qualifier = expression.getQualifier();
if (qualifier != null) {
qualifier.accept(this);
addInstruction(new CheckNotNullInstruction(expression, NullabilityProblem.callNPE));
addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.innerClassNPE.problem(expression)));
addInstruction(new PopInstruction());
}
@@ -1763,7 +1762,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
final PsiExpression qualifierExpression = expression.getQualifierExpression();
if (qualifierExpression != null) {
qualifierExpression.accept(this);
addInstruction(expression.resolve() instanceof PsiField ? new FieldReferenceInstruction(expression, null) : new PopInstruction());
addInstruction(expression.resolve() instanceof PsiField ? new FieldReferenceInstruction(qualifierExpression) : new PopInstruction());
}
// complex assignments (e.g. "|=") are both reading and writing
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix;
import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind.NullabilityProblem;
import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix;
import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix;
@@ -237,31 +238,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
allProblems.addAll(ContainerUtil.filter(runner.getInstructions(), instruction1 -> instruction1 instanceof InstanceofInstruction && visitor.isInstanceofRedundant((InstanceofInstruction)instruction1)));
HashSet<PsiElement> reportedAnchors = new HashSet<>();
for (PsiElement element : visitor.getProblems(NullabilityProblem.callNPE)) {
if (reportedAnchors.add(element)) {
if (element instanceof PsiMethodReferenceExpression) {
holder.registerProblem(element, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"),
createMethodReferenceNPEFixes((PsiMethodReferenceExpression)element).toArray(LocalQuickFix.EMPTY_ARRAY));
}
else if (element instanceof PsiNewExpression) {
List<LocalQuickFix> fixes =
createNPEFixes(((PsiNewExpression)element).getQualifier(), (PsiExpression)element, holder.isOnTheFly());
holder.registerProblem(getElementToHighlight((PsiCall)element),
InspectionsBundle.message("dataflow.message.npe.inner.class.construction"),
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
else {
reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element);
}
}
}
for (PsiElement element : visitor.getProblems(NullabilityProblem.fieldAccessNPE)) {
if (reportedAnchors.add(element)) {
PsiElement parent = element.getParent();
PsiElement fieldAccess = parent instanceof PsiArrayAccessExpression || parent instanceof PsiReferenceExpression ? parent : element;
reportFieldAccessMayProduceNpe(holder, element, (PsiExpression)fieldAccess);
}
}
for (Instruction instruction : allProblems) {
if (instruction instanceof TypeCastInstruction &&
@@ -277,11 +253,8 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportConstantPushes(runner, holder, reportedAnchors);
reportNullableFunctions(visitor, holder, reportedAnchors);
reportNullableArguments(visitor, holder, reportedAnchors);
reportNullableAssignments(visitor, holder, reportedAnchors);
reportUnboxedNullables(visitor, holder, reportedAnchors);
reportNullableReturns(visitor, holder, reportedAnchors, scope);
reportNullabilityProblems(holder, visitor, reportedAnchors);
if (SUGGEST_NULLABLE_ANNOTATIONS) {
reportNullableArgumentsPassedToNonAnnotated(visitor, holder, reportedAnchors);
}
@@ -311,6 +284,47 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
}
private void reportNullabilityProblems(ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
HashSet<PsiElement> reportedAnchors) {
visitor.problems().forEach(problem -> {
if (NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter.isMyProblem(problem) ||
NullabilityProblemKind.nullableReturn.isMyProblem(problem)) {
// these two kinds are still reported separately
return;
}
if (!reportedAnchors.add(problem.getAnchor())) return;
NullabilityProblemKind.innerClassNPE.ifMyProblem(problem, newExpression -> {
List<LocalQuickFix> fixes = createNPEFixes(newExpression.getQualifier(), newExpression, holder.isOnTheFly());
holder.registerProblem(getElementToHighlight(newExpression), problem.getMessage(), fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
});
NullabilityProblemKind.callMethodRefNPE.ifMyProblem(problem, methodRef ->
holder.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.npe.methodref.invocation"),
createMethodReferenceNPEFixes(methodRef).toArray(LocalQuickFix.EMPTY_ARRAY)));
NullabilityProblemKind.callNPE.ifMyProblem(problem, call -> reportCallMayProduceNpe(holder, call));
NullabilityProblemKind.passingNullableToNotNullParameter.ifMyProblem(problem, expr -> reportNullableArgument(holder, expr));
NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, expression -> {
LocalQuickFix[] fix =
createNPEFixes(expression.getArrayExpression(), expression, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
holder.registerProblem(expression, problem.getMessage(), fix);
});
NullabilityProblemKind.fieldAccessNPE.ifMyProblem(problem, element -> {
PsiElement parent = element.getParent();
PsiExpression fieldAccess = parent instanceof PsiReferenceExpression ? (PsiExpression)parent : element;
LocalQuickFix[] fix = createNPEFixes(element, fieldAccess, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
holder.registerProblem(element, problem.getMessage(), fix);
});
NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> holder.registerProblem(element, problem.getMessage()));
NullabilityProblemKind.nullableFunctionReturn.ifMyProblem(problem, expr -> holder.registerProblem(expr, problem.getMessage()));
NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr));
NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, expr -> reportNullabilityProblem(holder, problem, expr));
});
}
private void reportNullabilityProblem(ProblemsHolder holder, NullabilityProblem<?> problem, PsiExpression expr) {
holder.registerProblem(expr, problem.getMessage(), createNPEFixes(expr, expr, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY));
}
private static void reportArrayAccessProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) {
visitor.outOfBoundsArrayAccesses().forEach(access -> {
PsiExpression indexExpression = access.getIndexExpression();
@@ -491,7 +505,9 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private void reportNullableArgumentsPassedToNonAnnotated(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter)) {
for (PsiElement expr : visitor.problems()
.map(NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter::asMyProblem).nonNull()
.map(NullabilityProblem::getAnchor)) {
if (reportedAnchors.contains(expr)) continue;
if (expr.getParent() instanceof PsiMethodReferenceExpression) {
@@ -536,24 +552,6 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
private void reportFieldAccessMayProduceNpe(ProblemsHolder holder, PsiElement elementToAssert, @NotNull PsiExpression expression) {
LocalQuickFix[] fix = createNPEFixes((PsiExpression)elementToAssert, expression, holder.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY);
if (expression instanceof PsiArrayAccessExpression) {
holder.registerProblem(expression,
InspectionsBundle.message("dataflow.message.npe.array.access"),
fix);
}
else {
assert elementToAssert != null;
//noinspection ConditionalExpressionWithIdenticalBranches
holder.registerProblem(elementToAssert,
expression.textMatches("null")
? InspectionsBundle.message("dataflow.message.npe.field.access.sure")
: InspectionsBundle.message("dataflow.message.npe.field.access"),
fix);
}
}
private static void reportCastMayFail(ProblemsHolder holder, TypeCastInstruction instruction) {
PsiTypeCastExpression typeCast = instruction.getCastExpression();
PsiExpression operand = typeCast.getOperand();
@@ -640,59 +638,18 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
return LocalQuickFix.EMPTY_ARRAY;
}
private static void reportNullableFunctions(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.nullableFunctionReturn)) {
if (!reportedAnchors.add(expr)) continue;
holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.return.nullable.from.notnull.function"));
private void reportNullableArgument(ProblemsHolder holder, PsiElement expr) {
if (expr.getParent() instanceof PsiMethodReferenceExpression) {
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expr.getParent();
holder.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"),
createMethodReferenceNPEFixes(methodRef).toArray(LocalQuickFix.EMPTY_ARRAY));
}
}
private void reportNullableArguments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableToNotNullParameter)) {
if (!reportedAnchors.add(expr)) continue;
if (expr.getParent() instanceof PsiMethodReferenceExpression) {
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expr.getParent();
holder.registerProblem(methodRef, InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref"),
createMethodReferenceNPEFixes(methodRef).toArray(LocalQuickFix.EMPTY_ARRAY));
}
else {
final String text = isNullLiteralExpression(expr)
else {
final String text = isNullLiteralExpression(expr)
? InspectionsBundle.message("dataflow.message.passing.null.argument")
: InspectionsBundle.message("dataflow.message.passing.nullable.argument");
List<LocalQuickFix> fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly());
holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
}
}
private void reportNullableAssignments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.assigningToNotNull)) {
if (!reportedAnchors.add(expr)) continue;
assert expr instanceof PsiExpression;
final String text = isNullLiteralExpression(expr)
? InspectionsBundle.message("dataflow.message.assigning.null")
: InspectionsBundle.message("dataflow.message.assigning.nullable");
holder.registerProblem(expr, text, createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly())
.toArray(LocalQuickFix.EMPTY_ARRAY));
}
for (PsiElement expr : visitor.getProblems(NullabilityProblem.storingToNotNullArray)) {
if (!reportedAnchors.add(expr)) continue;
assert expr instanceof PsiExpression;
final String text = isNullLiteralExpression(expr)
? InspectionsBundle.message("dataflow.message.storing.array.null")
: InspectionsBundle.message("dataflow.message.storing.array.nullable");
holder.registerProblem(expr, text, createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly())
.toArray(LocalQuickFix.EMPTY_ARRAY));
}
}
private static void reportUnboxedNullables(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) {
for (PsiElement expr : visitor.getProblems(NullabilityProblem.unboxingNullable)) {
if (!reportedAnchors.add(expr)) continue;
holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.unboxing"));
List<LocalQuickFix> fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly());
holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
}
}
@@ -721,9 +678,8 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
// no warnings for Void methods, where only null can be possibly returned
if (returnType == null || returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID)) return;
for (PsiElement statement : visitor.getProblems(NullabilityProblem.nullableReturn)) {
assert statement instanceof PsiExpression;
final PsiExpression expr = (PsiExpression)statement;
for (NullabilityProblem<PsiExpression> problem : visitor.problems().map(NullabilityProblemKind.nullableReturn::asMyProblem).nonNull()) {
final PsiExpression expr = problem.getAnchor();
if (!reportedAnchors.add(expr)) continue;
if (notNullAnno != null) {
@@ -733,7 +689,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
: InspectionsBundle.message("dataflow.message.return.nullable.from.notnull", presentable);
holder.registerProblem(expr, text);
}
else if (AnnotationUtil.isAnnotatingApplicable(statement)) {
else if (AnnotationUtil.isAnnotatingApplicable(expr)) {
final NullableNotNullManager manager = NullableNotNullManager.getInstance(expr.getProject());
final String defaultNullable = manager.getDefaultNullable();
final String presentableNullable = StringUtil.getShortName(defaultNullable);
@@ -832,11 +788,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private static boolean isNullLiteralExpression(PsiElement expr) {
if (expr instanceof PsiLiteralExpression) {
final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expr;
return PsiType.NULL.equals(literalExpression.getType());
}
return false;
return expr instanceof PsiExpression && ExpressionUtils.isNullLiteral((PsiExpression)expr);
}
@Nullable
@@ -902,8 +854,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private static class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final MultiMap<NullabilityProblem, PsiElement> myProblems = new MultiMap<>();
private final Map<Pair<NullabilityProblem, PsiElement>, StateInfo> myStateInfos = ContainerUtil.newHashMap();
private final Map<NullabilityProblem<?>, StateInfo> myStateInfos = ContainerUtil.newHashMap();
private final Set<Instruction> myCCEInstructions = ContainerUtil.newHashSet();
private final Map<MethodCallInstruction, Boolean> myFailingCalls = new HashMap<>();
private final Map<PsiMethodCallExpression, ThreeState> myOptionalCalls = new HashMap<>();
@@ -920,14 +871,11 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
myCCEInstructions.add(instruction);
}
Collection<PsiElement> getProblems(final NullabilityProblem kind) {
return ContainerUtil.filter(myProblems.get(kind), psiElement -> {
StateInfo info = myStateInfos.get(Pair.create(kind, psiElement));
// non-ephemeral NPE should be reported
// ephemeral NPE should also be reported if only ephemeral states have reached a particular problematic instruction
// (e.g. if it's inside "if (var == null)" check after contract method invocation
return info.normalNpe || info.ephemeralNpe && !info.normalOk;
});
StreamEx<NullabilityProblem<?>> problems() {
// non-ephemeral NPE should be reported
// ephemeral NPE should also be reported if only ephemeral states have reached a particular problematic instruction
// (e.g. if it's inside "if (var == null)" check after contract method invocation
return StreamEx.ofKeys(myStateInfos, info -> info.normalNpe || info.ephemeralNpe && !info.normalOk);
}
public Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> getArrayStoreProblems() {
@@ -1065,22 +1013,14 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
@Override
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) {
if (problem == NullabilityProblem.nullableReturn && !state.isNotNull(value)) {
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, @Nullable NullabilityProblem<?> problem) {
if (NullabilityProblemKind.nullableReturn.isMyProblem(problem) && !state.isNotNull(value)) {
myAlwaysReturnsNotNull = false;
}
boolean ok = super.checkNotNullable(state, value, problem, anchor);
if (!ok && anchor != null) {
if (problem == NullabilityProblem.callNPE &&
!(anchor instanceof PsiNewExpression || anchor instanceof PsiMethodReferenceExpression ||
anchor instanceof PsiMethodCallExpression)) {
throw new IllegalArgumentException("Invalid anchor for callNPE problem: "+anchor);
}
myProblems.putValue(problem, anchor);
}
Pair<NullabilityProblem, PsiElement> key = Pair.create(problem, anchor);
StateInfo info = myStateInfos.computeIfAbsent(key, k -> new StateInfo());
boolean ok = super.checkNotNullable(state, value, problem);
if (problem == null) return ok;
StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo());
if (state.isEphemeral() && !ok) {
info.ephemeralNpe = true;
} else if (!state.isEphemeral()) {
@@ -1,16 +0,0 @@
package com.intellij.codeInspection.dataFlow;
/**
* @author peter
*/
public enum NullabilityProblem {
callNPE,
fieldAccessNPE,
unboxingNullable,
assigningToNotNull,
storingToNotNullArray,
nullableReturn,
nullableFunctionReturn,
passingNullableToNotNullParameter,
passingNullableArgumentToNonAnnotatedParameter
}
@@ -0,0 +1,140 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.psi.*;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.Objects;
import java.util.function.Consumer;
import static com.intellij.codeInspection.InspectionsBundle.BUNDLE;
/**
* Represents a kind of nullability problem
* @param <T> a type of anchor element which could be associated with given nullability problem kind
*/
public class NullabilityProblemKind<T extends PsiElement> {
private final String myName;
private final String myNullLiteralMessage;
private final String myNormalMessage;
private NullabilityProblemKind(@NotNull String name) {
myName = name;
myNullLiteralMessage = null;
myNormalMessage = null;
}
private NullabilityProblemKind(@NotNull String name, @NotNull @PropertyKey(resourceBundle = BUNDLE) String message) {
this(name, message, message);
}
private NullabilityProblemKind(@NotNull String name,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String nullLiteralMessage,
@NotNull @PropertyKey(resourceBundle = BUNDLE) String normalMessage) {
myName = name;
myNullLiteralMessage = InspectionsBundle.message(nullLiteralMessage);
myNormalMessage = InspectionsBundle.message(normalMessage);
}
public static final NullabilityProblemKind<PsiMethodCallExpression> callNPE = new NullabilityProblemKind<>("callNPE");
public static final NullabilityProblemKind<PsiMethodReferenceExpression> callMethodRefNPE =
new NullabilityProblemKind<>("callMethodRefNPE", "dataflow.message.npe.methodref.invocation");
public static final NullabilityProblemKind<PsiNewExpression> innerClassNPE =
new NullabilityProblemKind<>("innerClassNPE", "dataflow.message.npe.inner.class.construction");
public static final NullabilityProblemKind<PsiExpression> fieldAccessNPE =
new NullabilityProblemKind<>("fieldAccessNPE", "dataflow.message.npe.field.access.sure", "dataflow.message.npe.field.access");
public static final NullabilityProblemKind<PsiArrayAccessExpression> arrayAccessNPE =
new NullabilityProblemKind<>("arrayAccessNPE", "dataflow.message.npe.array.access");
public static final NullabilityProblemKind<PsiElement> unboxingNullable =
new NullabilityProblemKind<>("unboxingNullable", "dataflow.message.unboxing");
public static final NullabilityProblemKind<PsiExpression> assigningToNotNull =
new NullabilityProblemKind<>("assigningToNotNull", "dataflow.message.assigning.null", "dataflow.message.assigning.nullable");
public static final NullabilityProblemKind<PsiExpression> storingToNotNullArray =
new NullabilityProblemKind<>("storingToNotNullArray", "dataflow.message.storing.array.null", "dataflow.message.storing.array.nullable");
public static final NullabilityProblemKind<PsiExpression> nullableReturn = new NullabilityProblemKind<>("nullableReturn");
public static final NullabilityProblemKind<PsiExpression> nullableFunctionReturn =
new NullabilityProblemKind<>("nullableFunctionReturn", "dataflow.message.return.nullable.from.notnull.function",
"dataflow.message.return.nullable.from.notnull.function");
public static final NullabilityProblemKind<PsiElement> passingNullableToNotNullParameter =
new NullabilityProblemKind<>("passingNullableToNotNullParameter");
public static final NullabilityProblemKind<PsiElement> passingNullableArgumentToNonAnnotatedParameter =
new NullabilityProblemKind<>("passingNullableArgumentToNonAnnotatedParameter");
@Contract("null -> null; !null -> !null")
public final NullabilityProblem<T> problem(@Nullable T anchor) {
return anchor == null ? null : new NullabilityProblem<>(this, anchor);
}
@SuppressWarnings("unchecked")
@Nullable
public final NullabilityProblem<T> asMyProblem(NullabilityProblem<?> problem) {
return problem != null && problem.myKind == this ? (NullabilityProblem<T>)problem : null;
}
public final boolean isMyProblem(@Nullable NullabilityProblem<?> problem) {
return problem != null && problem.myKind == this;
}
public void ifMyProblem(NullabilityProblem<?> problem, Consumer<T> consumer) {
NullabilityProblem<T> myProblem = asMyProblem(problem);
if (myProblem != null) {
consumer.accept(myProblem.getAnchor());
}
}
@Override
public String toString() {
return myName;
}
/**
* Represents a concrete nullability problem on PSI which consists of PSI element (anchor) and {@link NullabilityProblemKind}.
* @param <T> a type of anchor element
*/
public static final class NullabilityProblem<T extends PsiElement> {
private final @NotNull NullabilityProblemKind<T> myKind;
private final @NotNull T myAnchor;
NullabilityProblem(@NotNull NullabilityProblemKind<T> kind, @NotNull T anchor) {
myKind = kind;
myAnchor = anchor;
}
@NotNull
public T getAnchor() {
return myAnchor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NullabilityProblem)) return false;
NullabilityProblem<?> problem = (NullabilityProblem<?>)o;
return myKind.equals(problem.myKind) && myAnchor.equals(problem.myAnchor);
}
@NotNull
public String getMessage() {
if (myKind.myNullLiteralMessage == null || myKind.myNormalMessage == null) {
throw new IllegalStateException("This problem kind has no message associated: " + myKind);
}
return myAnchor instanceof PsiExpression && ExpressionUtils.isNullLiteral((PsiExpression)myAnchor)
? myKind.myNullLiteralMessage
: myKind.myNormalMessage;
}
@Override
public int hashCode() {
return Objects.hash(myKind, myAnchor);
}
@Override
public String toString() {
return "[" + myKind + "] " + myAnchor.getText();
}
}
}
@@ -71,13 +71,13 @@ public class StandardInstructionVisitor extends InstructionVisitor {
PsiExpression lValue = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression());
PsiExpression rValue = instruction.getRExpression();
NullabilityProblem problem;
NullabilityProblemKind<PsiExpression> kind;
if (lValue instanceof PsiArrayAccessExpression) {
problem = NullabilityProblem.storingToNotNullArray;
kind = NullabilityProblemKind.storingToNotNullArray;
checkArrayElementAssignability(runner, memState, dfaSource, lValue, rValue);
}
else {
problem = NullabilityProblem.assigningToNotNull;
kind = NullabilityProblemKind.assigningToNotNull;
}
if (dfaDest instanceof DfaVariableValue) {
@@ -86,7 +86,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
final PsiModifierListOwner psi = var.getPsiVariable();
boolean forceDeclaredNullity = !(psi instanceof PsiParameter && psi.getParent() instanceof PsiParameterList);
if (forceDeclaredNullity && var.getInherentNullability() == Nullness.NOT_NULL) {
checkNotNullable(memState, dfaSource, problem, rValue);
checkNotNullable(memState, dfaSource, kind.problem(rValue));
}
if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) {
memState.setVarValue(var, dfaSource);
@@ -96,7 +96,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
stateImpl.setVariableState(var, stateImpl.getVariableState(var).withFact(DfaFactType.CAN_BE_NULL, true));
}
} else if (dfaDest instanceof DfaFactMapValue && Boolean.FALSE.equals(((DfaFactMapValue)dfaDest).get(DfaFactType.CAN_BE_NULL))) {
checkNotNullable(memState, dfaSource, problem, rValue);
checkNotNullable(memState, dfaSource, kind.problem(rValue));
}
memState.push(dfaDest);
@@ -142,7 +142,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState memState) {
final DfaValue retValue = memState.pop();
checkNotNullable(memState, retValue, NullabilityProblem.nullableReturn, instruction.getReturn());
checkNotNullable(memState, retValue, NullabilityProblemKind.nullableReturn.problem(instruction.getReturn()));
return nextInstruction(instruction, runner, memState);
}
@@ -150,7 +150,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
public DfaInstructionState[] visitArrayAccess(ArrayAccessInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiArrayAccessExpression arrayExpression = instruction.getExpression();
DfaValue index = memState.pop();
DfaValue array = dereference(memState, memState.pop(), NullabilityProblem.fieldAccessNPE, arrayExpression.getArrayExpression());
DfaValue array = dereference(memState, memState.pop(), NullabilityProblemKind.arrayAccessNPE.problem(arrayExpression));
boolean alwaysOutOfBounds = false;
if (index != DfaUnknownValue.getInstance()) {
DfaValueFactory factory = runner.getFactory();
@@ -177,8 +177,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@Override
public DfaInstructionState[] visitFieldReference(FieldReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
final DfaValue qualifier = dereference(memState, memState.pop(), NullabilityProblem.fieldAccessNPE, instruction.getElementToAssert());
PsiElement parent = instruction.getExpression().getParent();
PsiExpression expression = instruction.getExpression();
final DfaValue qualifier = dereference(memState, memState.pop(), NullabilityProblemKind.fieldAccessNPE.problem(expression));
PsiElement parent = expression.getParent();
if (parent instanceof PsiMethodReferenceExpression) {
handleMethodReference(qualifier, (PsiMethodReferenceExpression)parent, runner, memState);
}
@@ -423,10 +424,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
PsiElement anchor = instruction.getArgumentAnchor(paramIndex);
Nullness requiredNullability = instruction.getArgRequiredNullability(paramIndex);
if (requiredNullability == Nullness.NOT_NULL) {
arg = dereference(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, anchor);
arg = dereference(memState, arg, NullabilityProblemKind.passingNullableToNotNullParameter.problem(anchor));
}
else if (requiredNullability == Nullness.UNKNOWN) {
checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, anchor);
checkNotNullable(memState, arg, NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter.problem(anchor));
}
if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) {
argValues[paramIndex] = arg;
@@ -436,11 +437,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
private DfaValue popQualifier(MethodCallInstruction instruction, DfaMemoryState memState) {
@NotNull final DfaValue qualifier = memState.pop();
boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING;
NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE;
PsiElement anchor = instruction.getContext();
return dereference(memState, qualifier, problem, anchor);
return dereference(memState, memState.pop(), instruction.getQualifierNullabilityProblem());
}
private static LinkedHashSet<DfaMemoryState> addContractResults(DfaCallArguments callArguments,
@@ -491,12 +488,14 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return falseStates;
}
private DfaValue dereference(DfaMemoryState memState, DfaValue value, NullabilityProblem problem, PsiElement anchor) {
if (checkNotNullable(memState, value, problem, anchor)) return value;
private <T extends PsiElement> DfaValue dereference(DfaMemoryState memState,
DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<T> problem) {
if (checkNotNullable(memState, value, problem)) return value;
if (value instanceof DfaFactMapValue) {
return ((DfaFactMapValue)value).withFact(DfaFactType.CAN_BE_NULL, false);
}
if (memState.isNull(value) && problem == NullabilityProblem.nullableFunctionReturn) {
if (memState.isNull(value) && NullabilityProblemKind.nullableFunctionReturn.isMyProblem(problem)) {
return value.getFactory().getFactValue(DfaFactType.CAN_BE_NULL, false);
}
if (value instanceof DfaVariableValue) {
@@ -597,12 +596,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return DfaUnknownValue.getInstance();
}
protected boolean checkNotNullable(DfaMemoryState state,
DfaValue value, NullabilityProblem problem,
PsiElement anchor) {
protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
boolean notNullable = state.checkNotNullable(value);
if (notNullable &&
problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) {
if (notNullable && !NullabilityProblemKind.passingNullableArgumentToNonAnnotatedParameter.isMyProblem(problem)) {
DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory();
state.applyCondition(factory.createCondition(value, RelationType.NE, factory.getConstFactory().getNull()));
}
@@ -611,7 +607,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@Override
public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
DfaValue result = dereference(memState, memState.pop(), instruction.getProblem(), instruction.getExpression());
DfaValue result = dereference(memState, memState.pop(), instruction.getProblem());
memState.push(result);
return super.visitCheckNotNull(instruction, runner, memState);
}
@@ -17,7 +17,7 @@ package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInspection.dataFlow.CFGBuilder;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.NullabilityProblem;
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
@@ -138,7 +138,7 @@ public class OptionalChainInliner implements CallInliner {
BiConsumer<CFGBuilder, PsiMethodCallExpression> terminalInliner = TERMINAL_MAPPER.mapFirst(call);
if (terminalInliner != null) {
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) {
if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblemKind.callNPE)) {
return false;
}
terminalInliner.accept(builder, call);
@@ -172,8 +172,8 @@ public class OptionalChainInliner implements CallInliner {
return parameters[0];
}
private static boolean pushOptionalValue(CFGBuilder builder, PsiExpression expression,
PsiExpression dereferenceContext, NullabilityProblem problem) {
private static <T extends PsiElement> boolean pushOptionalValue(CFGBuilder builder, PsiExpression expression,
T dereferenceContext, NullabilityProblemKind<T> problem) {
PsiType optionalElementType = getOptionalElementType(expression);
if (optionalElementType == null) return false;
if (expression instanceof PsiMethodCallExpression) {
@@ -211,7 +211,9 @@ public class OptionalChainInliner implements CallInliner {
if (intermediateInliner == null) return false;
PsiExpression argument = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) return false;
if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblemKind.callNPE)) {
return false;
}
intermediateInliner.accept(builder, argument);
return true;
}
@@ -225,7 +227,7 @@ public class OptionalChainInliner implements CallInliner {
PsiExpression lambdaBody = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
if (parameters.length == argCount && lambdaBody != null) {
StreamEx.ofReversed(parameters).forEach(p -> builder.assignTo(p).pop());
if (pushOptionalValue(builder, lambdaBody, lambdaBody, NullabilityProblem.nullableFunctionReturn)) {
if (pushOptionalValue(builder, lambdaBody, lambdaBody, NullabilityProblemKind.nullableFunctionReturn)) {
return;
}
// Restore stack for common invokeFunction
@@ -257,7 +259,7 @@ public class OptionalChainInliner implements CallInliner {
.invoke(qualifierCall) // ... arg, opt -- keep original call in CFG so some warnings like "ofNullable for null" can work
.pop(); // ... arg
if ("of".equals(qualifierCall.getMethodExpression().getReferenceName())) {
builder.checkNotNull(argument, NullabilityProblem.passingNullableToNotNullParameter);
builder.checkNotNull(argument, NullabilityProblemKind.passingNullableToNotNullParameter);
}
}
}
@@ -369,7 +369,7 @@ public class StreamChainInliner implements CallInliner {
void before(CFGBuilder builder) {
if (myStreamSource == null) {
PsiExpression arg = myCall.getArgumentList().getExpressions()[0];
builder.pushExpression(arg).checkNotNull(arg, NullabilityProblem.passingNullableToNotNullParameter).pop();
builder.pushExpression(arg).checkNotNull(arg, NullabilityProblemKind.passingNullableToNotNullParameter).pop();
}
super.before(builder);
}
@@ -549,7 +549,7 @@ public class StreamChainInliner implements CallInliner {
if (qualifierValue != null) {
builder.pushExpression(qualifierExpression)
.chain(firstStep::before)
.checkNotNull(qualifierExpression, NullabilityProblem.passingNullableToNotNullParameter)
.checkNotNull(qualifierExpression, NullabilityProblemKind.passingNullableToNotNullParameter)
.pop()
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getInt(0))
@@ -565,7 +565,7 @@ public class StreamChainInliner implements CallInliner {
if (qualifierValue != null) {
builder.pushExpression(qualifierExpression)
.chain(firstStep::before)
.checkNotNull(sourceCall, NullabilityProblem.callNPE)
.checkNotNull(sourceCall, NullabilityProblemKind.callNPE)
.pop()
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getInt(0))
@@ -577,7 +577,7 @@ public class StreamChainInliner implements CallInliner {
}
builder
.pushExpression(originalQualifier)
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
.checkNotNull(firstStep.myCall, NullabilityProblemKind.callNPE)
.pop()
.chain(firstStep::before)
.pushUnknown()
@@ -16,22 +16,15 @@
package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.psi.PsiExpression;
public class CheckNotNullInstruction extends Instruction {
private final PsiExpression myExpression;
private final NullabilityProblem myProblem;
private final NullabilityProblemKind.NullabilityProblem<?> myProblem;
public CheckNotNullInstruction(PsiExpression expression, NullabilityProblem problem) {
myExpression = expression;
public CheckNotNullInstruction(NullabilityProblemKind.NullabilityProblem<?> problem) {
myProblem = problem;
}
public PsiExpression getExpression() {
return myExpression;
}
public NullabilityProblem getProblem() {
public NullabilityProblemKind.NullabilityProblem<?> getProblem() {
return myProblem;
}
@@ -42,6 +35,6 @@ public class CheckNotNullInstruction extends Instruction {
@Override
public String toString() {
return "CHECK_NOT_NULL ["+myProblem+"] "+myExpression.getText();
return "CHECK_NOT_NULL "+myProblem;
}
}
@@ -19,15 +19,16 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner;
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
/**
* @author max
*/
public class CheckReturnValueInstruction extends Instruction {
private final PsiElement myReturnValue;
private final @NotNull PsiExpression myReturnValue;
public CheckReturnValueInstruction(final PsiElement returnValue) {
public CheckReturnValueInstruction(@NotNull PsiExpression returnValue) {
myReturnValue = returnValue;
}
@@ -36,7 +37,8 @@ public class CheckReturnValueInstruction extends Instruction {
return visitor.visitCheckReturnValue(this, runner, stateBefore);
}
public PsiElement getReturn() {
@NotNull
public PsiExpression getReturn() {
return myReturnValue;
}
@@ -19,23 +19,17 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner;
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
import com.intellij.psi.PsiArrayAccessExpression;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiReferenceExpression;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author max
*/
public class FieldReferenceInstruction extends Instruction {
private final PsiExpression myExpression;
@Nullable private final String mySyntheticFieldName;
private final @NotNull PsiExpression myExpression;
public FieldReferenceInstruction(@NotNull PsiExpression expression, @Nullable @NonNls String syntheticFieldName) {
public FieldReferenceInstruction(@NotNull PsiExpression expression) {
myExpression = expression;
mySyntheticFieldName = syntheticFieldName;
}
@Override
@@ -51,12 +45,4 @@ public class FieldReferenceInstruction extends Instruction {
public PsiExpression getExpression() {
return myExpression;
}
@Nullable
public PsiExpression getElementToAssert() {
if (mySyntheticFieldName != null) return myExpression;
return myExpression instanceof PsiArrayAccessExpression
? ((PsiArrayAccessExpression)myExpression).getArrayExpression()
: ((PsiReferenceExpression)myExpression).getQualifierExpression();
}
}
@@ -253,6 +253,29 @@ public class MethodCallInstruction extends Instruction {
return myContext;
}
/**
* @return a nullability problem which will occur on call if qualifier is nullable.
* May return null if nullability problem is impossible for this instruction.
*/
@Nullable
public NullabilityProblemKind.NullabilityProblem<?> getQualifierNullabilityProblem() {
switch (getMethodType()) {
case UNBOXING:
return NullabilityProblemKind.unboxingNullable.problem(getContext());
case METHOD_REFERENCE_CALL:
return NullabilityProblemKind.callMethodRefNPE.problem((PsiMethodReferenceExpression)getContext());
case REGULAR_METHOD_CALL:
if (getContext() instanceof PsiMethodCallExpression) {
return NullabilityProblemKind.callNPE.problem((PsiMethodCallExpression)getContext());
}
// If context is something else (e.g. PsiNewExpression), qualifier is not dereferenced
return null;
default:
// Qualifier is not dereferenced for BOXING or CAST
return null;
}
}
@Nullable
public DfaValue getPrecalculatedReturnValue() {
return myPrecalculatedReturnValue;
@@ -437,8 +437,7 @@ public class ExtractMethodProcessor implements MatchProvider {
public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState) {
PsiElement aReturn = instruction.getReturn();
if (aReturn instanceof PsiExpression && returnedExpressions.contains(aReturn)) {
if (returnedExpressions.contains(instruction.getReturn())) {
myResult &= nullsExpected ? memState.isNull(memState.peek()) : memState.isNotNull(memState.peek());
}
return super.visitCheckReturnValue(instruction, runner, memState);
@@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull;
class Foo {
void main() {
foo(<warning descr="Argument '(String) null' might be null">(String) nu<caret>ll</warning>);
foo(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">(String) null</warning>);
}
static void foo(@NotNull String s) {}
@@ -29,7 +29,7 @@ import java.util.ResourceBundle;
public class InspectionsBundle {
private static Reference<ResourceBundle> ourBundle;
private static final String BUNDLE = "messages.InspectionsBundle";
public static final String BUNDLE = "messages.InspectionsBundle";
private InspectionsBundle() { }