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 3a6282f04595..cfa3e6624735 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 @@ -1064,7 +1064,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (componentType != null) { generateBoxingUnboxingInstructionFor(initializer, componentType); if (componentNullability == Nullness.NOT_NULL) { - addInstruction(new CheckNotNullInstruction(initializer, NullabilityProblem.assigningToNotNull)); + addInstruction(new CheckNotNullInstruction(initializer, NullabilityProblem.storingToNotNullArray)); } } addInstruction(new PopInstruction()); 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 fb80ea62934b..42583ee11214 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 @@ -325,7 +325,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { reportNullableFunctions(visitor, holder, reportedAnchors); reportNullableArguments(visitor, holder, reportedAnchors); - reportNullableAssignments(visitor, holder, reportedAnchors); + reportNullableAssignments(visitor, holder, reportedAnchors, onTheFly); reportUnboxedNullables(visitor, holder, reportedAnchors); reportNullableReturns(visitor, holder, reportedAnchors, scope); if (SUGGEST_NULLABLE_ANNOTATIONS) { @@ -682,14 +682,29 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - private static void reportNullableAssignments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) { + private void reportNullableAssignments(DataFlowInstructionVisitor visitor, + ProblemsHolder holder, + Set reportedAnchors, + boolean onTheFly) { 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); + holder.registerProblem(expr, text, + createNPEFixes((PsiExpression)expr, (PsiExpression)expr, onTheFly).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, onTheFly).toArray(LocalQuickFix.EMPTY_ARRAY)); } } 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 index b5c62a649954..36e738d67e8d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java @@ -8,6 +8,7 @@ public enum NullabilityProblem { fieldAccessNPE, unboxingNullable, assigningToNotNull, + storingToNotNullArray, nullableReturn, nullableFunctionReturn, passingNullableToNotNullParameter, 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 9ee4c874c25b..bf5a01b4ffce 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 @@ -72,13 +72,16 @@ public class StandardInstructionVisitor extends InstructionVisitor { dfaDest = instruction.getAssignedValue(); } + NullabilityProblem problem = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()) instanceof PsiArrayAccessExpression ? + NullabilityProblem.storingToNotNullArray : NullabilityProblem.assigningToNotNull; + if (dfaDest instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) dfaDest; final PsiModifierListOwner psi = var.getPsiVariable(); boolean forceDeclaredNullity = !(psi instanceof PsiParameter && psi.getParent() instanceof PsiParameterList); if (forceDeclaredNullity && var.getInherentNullability() == Nullness.NOT_NULL) { - checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); + checkNotNullable(memState, dfaSource, problem, instruction.getRExpression()); } if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) { memState.setVarValue(var, dfaSource); @@ -89,7 +92,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { } } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull()) { - checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); + checkNotNullable(memState, dfaSource, problem, instruction.getRExpression()); } memState.push(dfaDest); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/AssignInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/AssignInstruction.java index 38b7b98edf10..d6260c15d9a2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/AssignInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/AssignInstruction.java @@ -21,6 +21,7 @@ import com.intellij.codeInspection.dataFlow.DfaInstructionState; import com.intellij.codeInspection.dataFlow.DfaMemoryState; import com.intellij.codeInspection.dataFlow.InstructionVisitor; import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiVariable; import org.jetbrains.annotations.Nullable; @@ -44,6 +45,15 @@ public class AssignInstruction extends Instruction { return myRExpression; } + @Nullable + public PsiExpression getLExpression() { + if(myRExpression == null) return null; + if(myRExpression.getParent() instanceof PsiAssignmentExpression) { + return ((PsiAssignmentExpression)myRExpression.getParent()).getLExpression(); + } + return null; + } + public boolean isVariableInitializer() { return myRExpression != null && myRExpression.getParent() instanceof PsiVariable; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayComponentAndMethodAnnotationConflict.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayComponentAndMethodAnnotationConflict.java index b1874526dad1..2b58f950ec77 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayComponentAndMethodAnnotationConflict.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayComponentAndMethodAnnotationConflict.java @@ -19,10 +19,17 @@ class FooImpl implements Foo { return null; } else if (Math.random() > 0.5) { - @NotNull Object @Nullable [] arr1 = new Object[]{null, new Object(), Math.random() > 0.5 ? new Object() : null}; - @NotNull Object @Nullable [] arr2 = {null, new Object(), Math.random() > 0.5 ? new Object() : null}; - return new Object[]{null, new Object(), Math.random() > 0.5 ? new Object() : null}; + @NotNull Object @Nullable [] arr1 = new Object[]{null, new Object(), Math.random() > 0.5 ? new Object() : null}; + @NotNull Object @Nullable [] arr2 = {null, new Object(), Math.random() > 0.5 ? new Object() : null}; + return new Object[]{null, new Object(), Math.random() > 0.5 ? new Object() : null}; } - return new @NotNull Object @Nullable []{null, new Object(), Math.random() > 0.5 ? new Object() : null}; + return new @NotNull Object @Nullable []{null, new Object(), Math.random() > 0.5 ? new Object() : null}; + } + + void test() { + @NotNull Object @Nullable [] array = getNullableArrayOfNotNullObjects(); + assert array != null; + array[0] = null; + array[1] = Math.random() > 0.5 ? null : "foo"; } } \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 544e47947ede..f9e9b5a4f378 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -85,6 +85,8 @@ dataflow.message.passing.nullable.argument=Argument #ref #loc might dataflow.message.passing.nullable.argument.methodref=Method reference argument might be null dataflow.message.assigning.null=null is assigned to a variable that is annotated with @NotNull dataflow.message.assigning.nullable=Expression #ref might evaluate to null but is assigned to a variable that is annotated with @NotNull +dataflow.message.storing.array.null=null is stored to an array which elements are @NotNull +dataflow.message.storing.array.nullable=Expression #ref might evaluate to null but is stored to an array of @NotNull elements dataflow.message.return.null.from.notnull=null is returned by the method declared as @{0} dataflow.message.return.nullable.from.notnull=Expression #ref might evaluate to null but is returned by the method declared as @{0} dataflow.message.return.null.from.notnullable=null is returned by the method which is not declared as @{0}