diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java index c389e0178d46..69187c433219 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java @@ -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 CFGBuilder checkNotNull(T expression, NullabilityProblemKind 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)); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index e6b2d7c3d6da..77d8be870a81 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index d411f71bd941..a653a1873759 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -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 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 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 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 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 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 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 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 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 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 reportedAnchors) { - for (PsiElement expr : visitor.getProblems(NullabilityProblem.unboxingNullable)) { - if (!reportedAnchors.add(expr)) continue; - holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.unboxing")); + List 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 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 myProblems = new MultiMap<>(); - private final Map, StateInfo> myStateInfos = ContainerUtil.newHashMap(); + private final Map, StateInfo> myStateInfos = ContainerUtil.newHashMap(); private final Set myCCEInstructions = ContainerUtil.newHashSet(); private final Map myFailingCalls = new HashMap<>(); private final Map myOptionalCalls = new HashMap<>(); @@ -920,14 +871,11 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool myCCEInstructions.add(instruction); } - Collection 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> 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> 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 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()) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java deleted file mode 100644 index 36e738d67e8d..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.intellij.codeInspection.dataFlow; - -/** - * @author peter - */ -public enum NullabilityProblem { - callNPE, - fieldAccessNPE, - unboxingNullable, - assigningToNotNull, - storingToNotNullArray, - nullableReturn, - nullableFunctionReturn, - passingNullableToNotNullParameter, - passingNullableArgumentToNonAnnotatedParameter -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java new file mode 100644 index 000000000000..6d5cbeb4a06c --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java @@ -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 a type of anchor element which could be associated with given nullability problem kind + */ +public class NullabilityProblemKind { + 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 callNPE = new NullabilityProblemKind<>("callNPE"); + public static final NullabilityProblemKind callMethodRefNPE = + new NullabilityProblemKind<>("callMethodRefNPE", "dataflow.message.npe.methodref.invocation"); + public static final NullabilityProblemKind innerClassNPE = + new NullabilityProblemKind<>("innerClassNPE", "dataflow.message.npe.inner.class.construction"); + public static final NullabilityProblemKind fieldAccessNPE = + new NullabilityProblemKind<>("fieldAccessNPE", "dataflow.message.npe.field.access.sure", "dataflow.message.npe.field.access"); + public static final NullabilityProblemKind arrayAccessNPE = + new NullabilityProblemKind<>("arrayAccessNPE", "dataflow.message.npe.array.access"); + public static final NullabilityProblemKind unboxingNullable = + new NullabilityProblemKind<>("unboxingNullable", "dataflow.message.unboxing"); + public static final NullabilityProblemKind assigningToNotNull = + new NullabilityProblemKind<>("assigningToNotNull", "dataflow.message.assigning.null", "dataflow.message.assigning.nullable"); + public static final NullabilityProblemKind storingToNotNullArray = + new NullabilityProblemKind<>("storingToNotNullArray", "dataflow.message.storing.array.null", "dataflow.message.storing.array.nullable"); + public static final NullabilityProblemKind nullableReturn = new NullabilityProblemKind<>("nullableReturn"); + public static final NullabilityProblemKind nullableFunctionReturn = + new NullabilityProblemKind<>("nullableFunctionReturn", "dataflow.message.return.nullable.from.notnull.function", + "dataflow.message.return.nullable.from.notnull.function"); + public static final NullabilityProblemKind passingNullableToNotNullParameter = + new NullabilityProblemKind<>("passingNullableToNotNullParameter"); + public static final NullabilityProblemKind passingNullableArgumentToNonAnnotatedParameter = + new NullabilityProblemKind<>("passingNullableArgumentToNonAnnotatedParameter"); + + @Contract("null -> null; !null -> !null") + public final NullabilityProblem problem(@Nullable T anchor) { + return anchor == null ? null : new NullabilityProblem<>(this, anchor); + } + + @SuppressWarnings("unchecked") + @Nullable + public final NullabilityProblem asMyProblem(NullabilityProblem problem) { + return problem != null && problem.myKind == this ? (NullabilityProblem)problem : null; + } + + public final boolean isMyProblem(@Nullable NullabilityProblem problem) { + return problem != null && problem.myKind == this; + } + + public void ifMyProblem(NullabilityProblem problem, Consumer consumer) { + NullabilityProblem 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 a type of anchor element + */ + public static final class NullabilityProblem { + private final @NotNull NullabilityProblemKind myKind; + private final @NotNull T myAnchor; + + NullabilityProblem(@NotNull NullabilityProblemKind 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(); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 8a6fbac53fc5..f0808f97da89 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -71,13 +71,13 @@ public class StandardInstructionVisitor extends InstructionVisitor { PsiExpression lValue = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()); PsiExpression rValue = instruction.getRExpression(); - NullabilityProblem problem; + NullabilityProblemKind 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 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 DfaValue dereference(DfaMemoryState memState, + DfaValue value, + @Nullable NullabilityProblemKind.NullabilityProblem 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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java index 7e039c7b4020..3f4c82477170 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -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 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 boolean pushOptionalValue(CFGBuilder builder, PsiExpression expression, + T dereferenceContext, NullabilityProblemKind 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); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java index 26f322ae0e19..f625d3f1939b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -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() diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java index 50c9ca23bffc..25d5d893725f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java @@ -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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckReturnValueInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckReturnValueInstruction.java index 6d17463b1318..ce406aedf616 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckReturnValueInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckReturnValueInstruction.java @@ -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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FieldReferenceInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FieldReferenceInstruction.java index a28e613ddad9..df68eabda9e4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FieldReferenceInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FieldReferenceInstruction.java @@ -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(); - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java index fb83bb03a60c..af6716f73ff4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -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; diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 3e2f8fe0a4e5..0f07a0995503 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -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); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NoNonSensicalFixesOnCastedNull.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoNonSensicalFixesOnCastedNull.java index a2ce8e2e63f2..d79a3750b8c7 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/NoNonSensicalFixesOnCastedNull.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoNonSensicalFixesOnCastedNull.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull; class Foo { void main() { - foo((String) null); + foo((String) null); } static void foo(@NotNull String s) {} diff --git a/platform/analysis-api/src/com/intellij/codeInspection/InspectionsBundle.java b/platform/analysis-api/src/com/intellij/codeInspection/InspectionsBundle.java index 1a5eaa0ac9f0..f71165c17dd9 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/InspectionsBundle.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/InspectionsBundle.java @@ -29,7 +29,7 @@ import java.util.ResourceBundle; public class InspectionsBundle { private static Reference ourBundle; - private static final String BUNDLE = "messages.InspectionsBundle"; + public static final String BUNDLE = "messages.InspectionsBundle"; private InspectionsBundle() { }