DataFlowInspection: detect possible ArrayStoreException

This commit is contained in:
Tagir Valeev
2017-10-10 09:37:12 +07:00
parent 2eae23ef90
commit cf615a6c99
5 changed files with 83 additions and 9 deletions
@@ -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<PsiMethodCallExpression, ThreeState> myOptionalCalls = new HashMap<>();
private final Map<PsiMethodCallExpression, ThreeState> myBooleanCalls = new HashMap<>();
private final Map<MethodCallInstruction, ThreeState> myOfNullableCalls = new HashMap<>();
private final Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> myArrayStoreProblems = new HashMap<>();
private final Map<PsiMethodReferenceExpression, DfaValue> myMethodReferenceResults = new HashMap<>();
private final Map<PsiArrayAccessExpression, ThreeState> myOutOfBoundsArrayAccesses = new HashMap<>();
private final List<PsiExpression> myOptionalQualifiers = new ArrayList<>();
@@ -935,6 +944,10 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
});
}
public Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> getArrayStoreProblems() {
return myArrayStoreProblems;
}
Map<PsiMethodCallExpression, ThreeState> 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<? extends MethodContract> contracts,
@@ -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,
@@ -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] <warning descr="Storing element of type 'int' to array of 'java.lang.String' elements may produce 'java.lang.ArrayStoreException'">=</warning> 123;
arr = args2;
arr[1] = 124;
arr[2] <warning descr="Storing element of type 'java.lang.String' to array of 'java.lang.Integer' elements may produce 'java.lang.ArrayStoreException'">=</warning> "foo";
arr = args;
arr[3] = "bar";
arr[4] <warning descr="Storing element of type 'int' to array of 'java.lang.String' elements may produce 'java.lang.ArrayStoreException'">=</warning> 125;
}
}
@@ -548,4 +548,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testGetterOfNullableFieldIsNotNull() { doTest(); }
public void testTryWithResourceExpressions() { doTest(); }
public void testArrayStoreProblems() { doTest(); }
}
@@ -78,6 +78,7 @@ dataflow.message.npe.array.access=Array access <code>#ref</code> #loc may produc
dataflow.message.npe.field.access.sure=Dereference of <code>#ref</code> #loc will produce <code>java.lang.NullPointerException</code>
dataflow.message.npe.field.access=Dereference of <code>#ref</code> #loc may produce <code>java.lang.NullPointerException</code>
dataflow.message.cce=Casting <code>{0}</code> to <code>#ref</code> #loc may produce <code>java.lang.ClassCastException</code>
dataflow.message.arraystore=Storing element of type <code>{0}</code> to array of <code>{1}</code> elements may produce <code>java.lang.ArrayStoreException</code>
dataflow.message.redundant.instanceof=Condition <code>#ref</code> #loc is redundant and can be replaced with <code>!= null</code>
dataflow.message.constant.condition=Condition <code>#ref</code> #loc is always <code>{0}</code>
dataflow.message.constant.condition.when.reached=Condition <code>#ref</code> #loc is always <code>{0}</code> when reached