[java-dfa] Check new array length to be non-negative in DFA

Fixes IDEA-254545 Inspection for allocation of array with negative size
Tests are contributed by Ivo Smid (PR#1468)

Co-authored-by: Ivo Smid <ivo.smid@gmail.com>

GitOrigin-RevId: b58f281faa6caeeb5a7fbb06b110182994369426
This commit is contained in:
Tagir Valeev
2020-11-19 02:39:59 +00:00
committed by intellij-monorepo-bot
co-authored by Ivo Smid
parent 4d31494509
commit 5e6932f3b5
9 changed files with 243 additions and 4 deletions
@@ -30,6 +30,7 @@ change.type.arguments.to.0=Change type arguments to <{0}>
convert.0.to.float=Convert ''{0}'' to float
dataflow.message.array.index.out.of.bounds=Array index is out of bounds
dataflow.message.negative.array.size=Negative array size
dataflow.message.arraystore=Storing element of type <code>{0}</code> to array of <code>{1}</code> elements will produce <code>ArrayStoreException</code>
dataflow.message.assigning.null.notannotated=Assigning <code>null</code> value to non-annotated field
dataflow.message.assigning.null=<code>null</code> is assigned to a variable that is annotated with @NotNull
@@ -1734,14 +1734,20 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new PushInstruction(length, null, true));
// stack: ... var.length
final PsiExpression[] dimensions = expression.getArrayDimensions();
if (dimensions.length > 0) {
boolean sizeOnStack = false;
int dims = dimensions.length;
if (dims > 0) {
for (final PsiExpression dimension : dimensions) {
dimension.accept(this);
if (sizeOnStack) {
generateBoxingUnboxingInstructionFor(dimension, PsiType.INT);
}
DfaControlTransferValue transfer =
shouldHandleException() ?
myFactory.controlTransfer(myExceptionCache.get("java.lang.NegativeArraySizeException"), myTrapStack) : null;
for (int i = dims - 1; i >= 0; i--) {
addInstruction(new ArraySizeCheckInstruction(dimensions[i], transfer));
if (i != 0) {
addInstruction(new PopInstruction());
}
sizeOnStack = true;
}
}
else {
@@ -641,6 +641,9 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
holder.registerProblem(indexExpression, JavaAnalysisBundle.message("dataflow.message.array.index.out.of.bounds"));
}
});
visitor.negativeArraySizes().forEach(dimExpression -> {
holder.registerProblem(dimExpression, JavaAnalysisBundle.message("dataflow.message.negative.array.size"));
});
}
private static void reportArrayStoreProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) {
@@ -43,6 +43,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private final Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> myArrayStoreProblems = new HashMap<>();
private final Map<PsiMethodReferenceExpression, ConstantResult> myMethodReferenceResults = new HashMap<>();
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
private final Map<PsiExpression, ThreeState> myNegativeArraySizes = new HashMap<>();
private final Set<PsiElement> myReceiverMutabilityViolation = new HashSet<>();
private final Set<PsiElement> myArgumentMutabilityViolation = new HashSet<>();
private final Map<PsiExpression, Boolean> mySameValueAssigned = new HashMap<>();
@@ -193,6 +194,10 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals);
}
Stream<PsiExpression> negativeArraySizes() {
return StreamEx.ofKeys(myNegativeArraySizes, ThreeState.YES::equals);
}
StreamEx<PsiCallExpression> alwaysFailingCalls() {
return StreamEx.ofKeys(myFailingCalls, v -> v);
}
@@ -280,6 +285,11 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge);
}
@Override
protected void processArrayCreation(PsiExpression expression, boolean alwaysNegative) {
myNegativeArraySizes.merge(expression, ThreeState.fromBoolean(alwaysNegative), ThreeState::merge);
}
@Override
protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) {
if (assignmentExpression != null) {
@@ -304,4 +304,8 @@ public abstract class InstructionVisitor {
pushExpressionResult(runner.getFactory().getUnknown(), instruction, memState);
return nextInstruction(instruction, runner, memState);
}
public DfaInstructionState[] visitArraySizeCheck(ArraySizeCheckInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
return nextInstruction(instruction, runner, memState);
}
}
@@ -214,6 +214,10 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
protected void processArrayCreation(PsiExpression expression, boolean alwaysNegative) {
}
@Override
public DfaInstructionState[] visitMethodReference(MethodReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiMethodReferenceExpression expression = instruction.getExpression();
@@ -285,6 +289,42 @@ public class StandardInstructionVisitor extends InstructionVisitor {
return new DfaCallArguments(qualifier, arguments, MutationSignature.fromMethod(method));
}
@Override
public DfaInstructionState[] visitArraySizeCheck(ArraySizeCheckInstruction instruction,
DataFlowRunner runner, DfaMemoryState memState) {
DfaValue arraySize = memState.peek();
DfaControlTransferValue transfer = instruction.getNegativeSizeExceptionTransfer();
DfaCondition cond = arraySize.cond(RelationType.GE, runner.getFactory().getInt(0));
Instruction nextInstruction = runner.getInstruction(instruction.getIndex() + 1);
DfaInstructionState nextState = new DfaInstructionState(nextInstruction, memState);
if (cond.equals(DfaCondition.getTrue())) {
return new DfaInstructionState[]{nextState};
}
if (transfer == null) {
boolean hasNonNegative = memState.applyCondition(cond);
processArrayCreation(instruction.getExpression(), !hasNonNegative);
if (!hasNonNegative) {
return DfaInstructionState.EMPTY_ARRAY;
}
return new DfaInstructionState[]{nextState};
}
DfaMemoryState negativeSize = memState.createCopy();
boolean hasNonNegative = memState.applyCondition(cond);
boolean hasNegative = negativeSize.applyCondition(cond.negate());
List<DfaInstructionState> result = new ArrayList<>();
if (hasNonNegative) {
result.add(nextState);
}
if (hasNegative) {
List<DfaInstructionState> states = transfer.dispatch(negativeSize, runner);
for (DfaInstructionState negState : states) {
negState.getMemoryState().markEphemeral();
}
result.addAll(states);
}
return result.toArray(DfaInstructionState.EMPTY_ARRAY);
}
@Override
public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiType type = instruction.getCastTo();
@@ -0,0 +1,39 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ArraySizeCheckInstruction extends Instruction {
final @NotNull PsiExpression myExpression;
final @Nullable DfaControlTransferValue myTransferValue;
public ArraySizeCheckInstruction(@NotNull PsiExpression expression,
@Nullable DfaControlTransferValue value) {
myExpression = expression;
myTransferValue = value;
}
public @NotNull PsiExpression getExpression() {
return myExpression;
}
@Nullable
public DfaControlTransferValue getNegativeSizeExceptionTransfer() {
return myTransferValue;
}
@Override
public DfaInstructionState[] accept(DataFlowRunner runner,
DfaMemoryState stateBefore,
InstructionVisitor visitor) {
return visitor.visitArraySizeCheck(this, runner, stateBefore);
}
@Override
public String toString() {
return "CHECK_ARRAY_SIZE";
}
}
@@ -0,0 +1,135 @@
class ArrayNegativeSize {
private static final int VAL1 = -10;
private static int VAL2 = -10;
private static final int VAL3 = -10 * 2;
private static final short VAL4 = -10 * 2;
public static void main(String[] args) {
if (Math.random() > 0.5) {
int[] array1 = new int[<warning descr="Negative array size">-10</warning>];
}
if (Math.random() > 0.5) {
int[] array2 = createArray();
}
if (Math.random() > 0.5) {
int[] array3 = new int[]{};
}
if (Math.random() > 0.5) {
int[] array4 = new int[<warning descr="Negative array size">10 - 100</warning>];
}
if (Math.random() > 0.5) {
int[] array5 = new int[((int) (Integer.valueOf(Integer.MAX_VALUE).longValue() - 100)) + 100 - 10];
}
if (Math.random() > 0.5) {
int[] array5 = new int[<warning descr="Negative array size">((int) (Integer.valueOf(Integer.MAX_VALUE).longValue() - 100L)) + 100 + 10</warning>];
}
if (Math.random() > 0.5) {
int[] array6 = new int[((int) (Integer.valueOf(Integer.MAX_VALUE).longValue() - 100)) + 99];
}
if (Math.random() > 0.5) {
int[] array7 = new int[<warning descr="Negative array size">(int) (((long) ((Integer.MAX_VALUE - 100)) + 100) * 2)</warning>];
}
if (Math.random() > 0.5) {
int[] array8 = new int[num()];
}
if (Math.random() > 0.5) {
int[] array9 = new int[<warning descr="Negative array size">VAL1</warning>];
}
if (Math.random() > 0.5) {
int[] array10 = new int[VAL2];
}
if (Math.random() > 0.5) {
int[] array11 = new int[<warning descr="Negative array size">VAL3</warning>];
}
if (Math.random() > 0.5) {
int[] array12 = new int[<warning descr="Negative array size">VAL4</warning>];
}
if (Math.random() > 0.5) {
int[][] array13 = new int[0][<warning descr="Negative array size">-1</warning>];
}
if (Math.random() > 0.5) {
int[][][] array14 = new int[0][0][<warning descr="Negative array size">-1</warning>];
}
if (Math.random() > 0.5) {
int[][][] array15 = new int[-1][-2][<warning descr="Negative array size">-3</warning>];
}
if (Math.random() > 0.5) {
int[][][] array15 = new int[-1][<warning descr="Negative array size">-2</warning>][3];
}
if (Math.random() > 0.5) {
int[][][] array15 = new int[<warning descr="Negative array size">-1</warning>][2][3];
}
if (Math.random() > 0.5) {
int[] array16 = new int[<warning descr="Negative array size">-07</warning>];
}
if (Math.random() > 0.5) {
int[] array17 = new int[<warning descr="Negative array size">100 * -077</warning>];
}
if (Math.random() > 0.5) {
int[] array18 = new int[0x7fffffff];
}
if (Math.random() > 0.5) {
int[] array19 = new int[<warning descr="Negative array size">0x7fffffff + 1</warning>];
}
if (Math.random() > 0.5) {
int[] array20 = new int[0b1111111111111111111111111111111];
}
if (Math.random() > 0.5) {
int[] array21 = new int[<warning descr="Negative array size">0b1111111111111111111111111111111 + 1</warning>];
}
if (Math.random() > 0.5) {
action(new int[<warning descr="Negative array size">-1000000000</warning>]);
}
if (Math.random() > 0.5) {
action(new int[<warning descr="Negative array size">-0xcafe</warning>]);
}
if (Math.random() > 0.5) {
action(new int[<warning descr="Negative array size">(int) -10000000000000L</warning>]);
}
if (Math.random() > 0.5) {
action(new int[<warning descr="Negative array size">2147483647 + 1</warning>]);
}
if (Math.random() > 0.5) {
action(new int["".length() + 456]);
}
if (Math.random() > 0.5) {
action(new int[<warning descr="Negative array size">"".length() - 456</warning>]);
}
VAL2++;
}
private static int[] createArray() {
return new int[0];
}
private static void action(Object obj) {
}
private static int num() {
return -123;
}
final int[] array1 = new int[<warning descr="Negative array size">-10</warning>];
void foo(int size) {
int[] data = new int[size];
if (<warning descr="Condition 'size < 0' is always 'false'">size < 0</warning>) {
System.out.println("Impossible");
}
}
void tryCatch(int size) {
try {
int[] arr = new int[size];
} catch (NegativeArraySizeException e) {
if (<warning descr="Condition 'size >= 0' is always 'false'">size >= 0</warning>) {
System.out.println("impossible");
}
}
}
void testUnboxing(Integer len) {
int[] arr = new int[len];
long l = len.longValue();
if (<warning descr="Condition 'l < 0' is always 'false'">l < 0</warning>) {}
}
}
@@ -685,4 +685,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testDoubleArrayDiff() { doTest(); }
public void testInferenceInPrivateOrLocalClass() { doTest(); }
public void testArraysCopyOf() { doTest(); }
public void testArrayNegativeSize() { doTest(); }
}