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 f451e157fe25..c4081fecbd55 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 @@ -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); } 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 7374c40c297b..fb47e2fa92cd 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 @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java index 01bfec80bb91..03a5752ec917 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInstructionVisitor.java @@ -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, StateInfo> myStateInfos = new LinkedHashMap<>(); - private final Set myCCEInstructions = new HashSet<>(); + private final Map myClassCastProblems = new HashMap<>(); private final Map myFailingCalls = new HashMap<>(); private final Map myConstantExpressions = new HashMap<>(); private final Map 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> 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> getArrayStoreProblems() { @@ -155,8 +152,8 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor { return myMethodReferenceResults; } - Set getClassCastExceptionInstructions() { - return myCCEInstructions; + StreamEx getFailingCastExpressions() { + return StreamEx.ofKeys(myClassCastProblems, StateInfo::shouldReport); } Set 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 { 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 d9687c8a48ed..b67ce30a4b3b 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 @@ -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 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 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, diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java index 480d3a0cb913..180b2a6e8708 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/TypeCastInstruction.java @@ -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() { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ClassCastExceptionDispatch.java b/java/java-tests/testData/inspection/dataFlow/fixture/ClassCastExceptionDispatch.java new file mode 100644 index 000000000000..b8c25fe17123 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ClassCastExceptionDispatch.java @@ -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 (obj instanceof String) {} + if (obj == null) {} + if (str != null) {} + } + 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()); + } + } +} \ 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 3baaa7d3d2dd..f533821a1c4d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -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(); } }