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 a02ce43314f1..bf670551e43c 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 @@ -305,6 +305,8 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool reportArrayAccessProblems(holder, visitor); + reportArrayStoreProblems(holder, visitor); + if (REPORT_CONSTANT_REFERENCE_VALUES) { reportConstantReferenceValues(holder, visitor, reportedAnchors); } @@ -323,6 +325,12 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool }); } + private static void reportArrayStoreProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { + visitor.getArrayStoreProblems().forEach( + (assignment, types) -> holder.registerProblem(assignment.getOperationSign(), InspectionsBundle + .message("dataflow.message.arraystore", types.getFirst().getCanonicalText(), types.getSecond().getCanonicalText()))); + } + private void reportMethodReferenceProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { visitor.getMethodReferenceResults().forEach((methodRef, dfaValue) -> { if (dfaValue instanceof DfaConstValue) { @@ -915,6 +923,7 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool private final Map myOptionalCalls = new HashMap<>(); private final Map myBooleanCalls = new HashMap<>(); private final Map myOfNullableCalls = new HashMap<>(); + private final Map> myArrayStoreProblems = new HashMap<>(); private final Map myMethodReferenceResults = new HashMap<>(); private final Map myOutOfBoundsArrayAccesses = new HashMap<>(); private final List myOptionalQualifiers = new ArrayList<>(); @@ -935,6 +944,10 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool }); } + public Map> getArrayStoreProblems() { + return myArrayStoreProblems; + } + Map getOptionalCalls() { return myOptionalCalls; } @@ -1032,6 +1045,13 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge); } + @Override + protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { + if (assignmentExpression != null) { + myArrayStoreProblems.put(assignmentExpression, Pair.create(fromType, toType)); + } + } + @Override protected void processMethodReferenceResult(PsiMethodReferenceExpression methodRef, List contracts, 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 ab8778b0a684..4b231ddefb92 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 @@ -23,10 +23,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.InheritanceUtil; -import com.intellij.psi.util.MethodSignatureUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.psi.util.*; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; @@ -72,8 +69,16 @@ public class StandardInstructionVisitor extends InstructionVisitor { dfaDest = instruction.getAssignedValue(); } - NullabilityProblem problem = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()) instanceof PsiArrayAccessExpression ? - NullabilityProblem.storingToNotNullArray : NullabilityProblem.assigningToNotNull; + PsiExpression lValue = PsiUtil.skipParenthesizedExprDown(instruction.getLExpression()); + PsiExpression rValue = instruction.getRExpression(); + NullabilityProblem problem; + if (lValue instanceof PsiArrayAccessExpression) { + problem = NullabilityProblem.storingToNotNullArray; + checkArrayElementAssignability(runner, memState, dfaSource, lValue, rValue); + } + else { + problem = NullabilityProblem.assigningToNotNull; + } if (dfaDest instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) dfaDest; @@ -81,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, instruction.getRExpression()); + checkNotNullable(memState, dfaSource, problem, rValue); } if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) { memState.setVarValue(var, dfaSource); @@ -90,9 +95,8 @@ public class StandardInstructionVisitor extends InstructionVisitor { DfaMemoryStateImpl stateImpl = (DfaMemoryStateImpl)memState; stateImpl.setVariableState(var, stateImpl.getVariableState(var).withFact(DfaFactType.CAN_BE_NULL, true)); } - } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull()) { - checkNotNullable(memState, dfaSource, problem, instruction.getRExpression()); + checkNotNullable(memState, dfaSource, problem, rValue); } memState.push(dfaDest); @@ -100,6 +104,39 @@ public class StandardInstructionVisitor extends InstructionVisitor { return nextInstruction(instruction, runner, memState); } + private void checkArrayElementAssignability(DataFlowRunner runner, + DfaMemoryState memState, + DfaValue dfaSource, + PsiExpression lValue, + PsiExpression rValue) { + if (rValue == null) return; + PsiType rCodeType = rValue.getType(); + PsiType lCodeType = lValue.getType(); + // If types known from source are not convertible, a compilation error is displayed, additional warning is unnecessary + if (rCodeType == null || lCodeType == null || !TypeConversionUtil.areTypesConvertible(rCodeType, lCodeType)) return; + PsiExpression array = ((PsiArrayAccessExpression)lValue).getArrayExpression(); + DfaValue arrayValue = runner.getFactory().createValue(array); + PsiType arrayType = getType(array, arrayValue, memState); + if (!(arrayType instanceof PsiArrayType)) return; + PsiType componentType = ((PsiArrayType)arrayType).getComponentType(); + PsiType sourceType = getType(rValue, dfaSource, memState); + if (sourceType == null || TypeConversionUtil.areTypesConvertible(sourceType, componentType)) return; + PsiAssignmentExpression assignmentExpression = + PsiTreeUtil.getParentOfType(rValue, PsiAssignmentExpression.class); + processArrayStoreTypeMismatch(assignmentExpression, sourceType, componentType); + } + + @Nullable + private static PsiType getType(@Nullable PsiExpression expression, @Nullable DfaValue value, @NotNull DfaMemoryState memState) { + TypeConstraint fact = value == null ? null : memState.getValueFact(DfaFactType.TYPE_CONSTRAINT, value); + PsiType type = fact == null ? null : fact.getPsiType(); + if (type != null) return type; + return expression == null ? null : expression.getType(); + } + + protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { + } + @Override public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction, DataFlowRunner runner, diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ArrayStoreProblems.java b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayStoreProblems.java new file mode 100644 index 000000000000..c2b1c4ee6d0a --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ArrayStoreProblems.java @@ -0,0 +1,15 @@ +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +class ArrayStoreProblems { + void test(String[] args, Integer[] args2) { + Object[] arr = args; + arr[0] = 123; + arr = args2; + arr[1] = 124; + arr[2] = "foo"; + arr = args; + arr[3] = "bar"; + arr[4] = 125; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 0ed263c12eea..61270d7f0835 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -548,4 +548,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testGetterOfNullableFieldIsNotNull() { doTest(); } public void testTryWithResourceExpressions() { doTest(); } + public void testArrayStoreProblems() { doTest(); } } diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index f70a8d4815b1..86a5abc24632 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -78,6 +78,7 @@ dataflow.message.npe.array.access=Array access #ref #loc may produc dataflow.message.npe.field.access.sure=Dereference of #ref #loc will produce java.lang.NullPointerException dataflow.message.npe.field.access=Dereference of #ref #loc may produce java.lang.NullPointerException dataflow.message.cce=Casting {0} to #ref #loc may produce java.lang.ClassCastException +dataflow.message.arraystore=Storing element of type {0} to array of {1} elements may produce java.lang.ArrayStoreException dataflow.message.redundant.instanceof=Condition #ref #loc is redundant and can be replaced with != null dataflow.message.constant.condition=Condition #ref #loc is always {0} dataflow.message.constant.condition.when.reached=Condition #ref #loc is always {0} when reached