DFA: dispatch ClassCastException (IDEA-220077)

GitOrigin-RevId: e8b185f4300b4f319a7e6a06ee3000a56777ee92
This commit is contained in:
Tagir Valeev
2019-08-09 15:03:05 +03:00
committed by intellij-monorepo-bot
parent 25ebe168b4
commit 343a57fe88
7 changed files with 126 additions and 39 deletions
@@ -125,10 +125,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
return myFactory;
}
PsiElement getContext() {
return myCodeFragment;
}
@NotNull
private PsiClassType createClassType(GlobalSearchScope scope, String fqn) {
PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(fqn, scope);
@@ -2050,7 +2046,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
final PsiTypeElement typeElement = castExpression.getCastType();
if (typeElement != null && operand != null && operand.getType() != null && !(typeElement.getType() instanceof PsiPrimitiveType)) {
addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType()));
DfaControlTransferValue transfer =
shouldHandleException() ? myFactory.controlTransfer(myExceptionCache.get("java.lang.ClassCastException"), myTrapStack) : null;
addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType(), transfer));
}
finishElement(castExpression);
}
@@ -701,8 +701,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
}
private void reportFailingCasts(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
for (TypeCastInstruction instruction : visitor.getClassCastExceptionInstructions()) {
PsiTypeCastExpression typeCast = instruction.getExpression();
for (PsiTypeCastExpression typeCast : visitor.getFailingCastExpressions()) {
PsiExpression operand = typeCast.getOperand();
PsiTypeElement castType = typeCast.getCastType();
assert castType != null;
@@ -30,7 +30,7 @@ import static com.intellij.util.ObjectUtils.tryCast;
final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowInstructionVisitor");
private final Map<NullabilityProblemKind.NullabilityProblem<?>, StateInfo> myStateInfos = new LinkedHashMap<>();
private final Set<TypeCastInstruction> myCCEInstructions = new HashSet<>();
private final Map<PsiTypeCastExpression, StateInfo> myClassCastProblems = new HashMap<>();
private final Map<PsiCallExpression, Boolean> myFailingCalls = new HashMap<>();
private final Map<PsiExpression, ConstantResult> myConstantExpressions = new HashMap<>();
private final Map<PsiElement, ThreeState> myOfNullableCalls = new HashMap<>();
@@ -128,15 +128,12 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
@Override
protected void onInstructionProducesCCE(TypeCastInstruction instruction) {
myCCEInstructions.add(instruction);
protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) {
myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible);
}
StreamEx<NullabilityProblemKind.NullabilityProblem<?>> 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);
return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport);
}
public Map<PsiAssignmentExpression, Pair<PsiType, PsiType>> getArrayStoreProblems() {
@@ -155,8 +152,8 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
return myMethodReferenceResults;
}
Set<TypeCastInstruction> getClassCastExceptionInstructions() {
return myCCEInstructions;
StreamEx<PsiTypeCastExpression> getFailingCastExpressions() {
return StreamEx.ofKeys(myClassCastProblems, StateInfo::shouldReport);
}
Set<PsiElement> getMutabilityViolations(boolean receiver) {
@@ -289,12 +286,7 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
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()) {
if (ok) info.normalOk = true;
else info.normalNpe = true;
}
info.update(state, ok);
return ok;
}
@@ -316,9 +308,26 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
}
private static class StateInfo {
boolean ephemeralNpe;
boolean normalNpe;
boolean ephemeralException;
boolean normalException;
boolean normalOk;
void update(DfaMemoryState state, boolean ok) {
if (state.isEphemeral()) {
if (!ok) ephemeralException = true;
}
else {
if (ok) normalOk = true;
else normalException = true;
}
}
boolean shouldReport() {
// non-ephemeral exceptions should be reported
// ephemeral exceptions 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 normalException || ephemeralException && !normalOk;
}
}
private class ExpressionVisitor extends JavaElementVisitor {
@@ -259,22 +259,49 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@Override
public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiType type = instruction.getCastTo();
DfaControlTransferValue transfer = instruction.getCastExceptionTransfer();
final DfaValueFactory factory = runner.getFactory();
PsiType fromType = instruction.getCasted().getType();
if (fromType != null && type.isConvertibleFrom(fromType) && !memState.castTopOfStack(factory.createDfaType(type))) {
onInstructionProducesCCE(instruction);
}
DfaPsiType dfaType = factory.createDfaType(type);
boolean castPossible = true;
List<DfaInstructionState> result = new ArrayList<>();
if (transfer != null) {
DfaMemoryState castFail = memState.createCopy();
if (fromType != null && type.isConvertibleFrom(fromType)) {
if (!memState.castTopOfStack(dfaType)) {
castPossible = false;
} else {
result.add(new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState));
DfaValue value = memState.pop();
pushExpressionResult(value, instruction, memState);
}
}
DfaValue value = castFail.peek();
DfaValue notNullCondition = factory.createCondition(value, RelationType.NE, factory.getConstFactory().getNull());
DfaValue notTypeCondition = factory.createCondition(value, RelationType.IS_NOT, factory.createTypeValue(type, Nullability.NOT_NULL));
if (castFail.applyCondition(notNullCondition) && castFail.applyCondition(notTypeCondition)) {
List<DfaInstructionState> states = transfer.dispatch(castFail, runner);
for (DfaInstructionState cceState : states) {
cceState.getMemoryState().markEphemeral();
}
result.addAll(states);
}
} else {
if (fromType != null && type.isConvertibleFrom(fromType)) {
if (!memState.castTopOfStack(dfaType)) {
castPossible = false;
}
}
DfaValue value = memState.pop();
if (type instanceof PsiPrimitiveType) {
value = DfaUtil.boxUnbox(value, type);
result.add(new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState));
DfaValue value = memState.pop();
pushExpressionResult(value, instruction, memState);
}
pushExpressionResult(value, instruction, memState);
return nextInstruction(instruction, runner, memState);
onTypeCast(instruction.getExpression(), memState, castPossible);
return result.toArray(DfaInstructionState.EMPTY_ARRAY);
}
protected void onInstructionProducesCCE(TypeCastInstruction instruction) {}
protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) {}
protected void beforeMethodCall(@NotNull PsiExpression expression,
@NotNull DfaCallArguments arguments,
@@ -16,24 +16,34 @@
package com.intellij.codeInspection.dataFlow.instructions;
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.codeInspection.dataFlow.*;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeCastExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TypeCastInstruction extends Instruction implements ExpressionPushingInstruction {
private final PsiTypeCastExpression myCastExpression;
private final PsiExpression myCasted;
private final PsiType myCastTo;
private final @Nullable DfaControlTransferValue myTransferValue;
public TypeCastInstruction(PsiTypeCastExpression castExpression, PsiExpression casted, PsiType castTo) {
public TypeCastInstruction(PsiTypeCastExpression castExpression,
PsiExpression casted,
PsiType castTo,
@Nullable DfaControlTransferValue value) {
assert !(castTo instanceof PsiPrimitiveType);
myCastExpression = castExpression;
myCasted = casted;
myCastTo = castTo;
myTransferValue = value;
}
@Nullable
public DfaControlTransferValue getCastExceptionTransfer() {
return myTransferValue;
}
public PsiExpression getCasted() {
@@ -0,0 +1,43 @@
class X {
public static void main(final String[] args) {
final Object obj = getObject();
String str = null;
try {
str = (String) obj;
} catch (final AssertionError ignored) {
if (obj instanceof String) {}
if (str != null) {}
} catch (final ClassCastException ignored) {
if (<warning descr="Condition 'obj instanceof String' is always 'false'">obj instanceof String</warning>) {}
if (<warning descr="Condition 'obj == null' is always 'false'">obj == null</warning>) {}
if (<warning descr="Condition 'str != null' is always 'false'">str != null</warning>) {}
}
if (str != null) {
System.out.println("str = " + str);
}
}
private static native Object getObject();
void testDoubleCatch(Object obj) {
try {
System.out.println(((String)obj).length());
}
catch (Exception ex) {}
try {
System.out.println(((String)obj).trim());
}
catch (Exception ex) {}
}
void testFinally(Object obj) {
try {
System.out.println(((String)obj).length());
}
catch (Exception ex) {}
finally {
System.out.println(((String)obj).trim());
}
}
}
@@ -665,4 +665,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testFieldRewrittenInInner() { doTest(); }
public void testArrayElementLocality() { doTest(); }
public void testOverwrittenParameter() { doTest(); }
public void testClassCastExceptionDispatch() { doTest(); }
}