mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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));
|
||||
|
||||
+10
-11
@@ -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
|
||||
|
||||
+70
-130
@@ -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()) {
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public enum NullabilityProblem {
|
||||
callNPE,
|
||||
fieldAccessNPE,
|
||||
unboxingNullable,
|
||||
assigningToNotNull,
|
||||
storingToNotNullArray,
|
||||
nullableReturn,
|
||||
nullableFunctionReturn,
|
||||
passingNullableToNotNullParameter,
|
||||
passingNullableArgumentToNonAnnotatedParameter
|
||||
}
|
||||
+140
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-25
@@ -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);
|
||||
}
|
||||
|
||||
+9
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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()
|
||||
|
||||
+4
-11
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-16
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -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;
|
||||
|
||||
+1
-2
@@ -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);
|
||||
|
||||
+1
-1
@@ -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() { }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user