mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
DFA: dispatch ClassCastException (IDEA-220077)
GitOrigin-RevId: e8b185f4300b4f319a7e6a06ee3000a56777ee92
This commit is contained in:
committed by
intellij-monorepo-bot
parent
25ebe168b4
commit
343a57fe88
+3
-5
@@ -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);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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;
|
||||
|
||||
+26
-17
@@ -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 {
|
||||
|
||||
+37
-10
@@ -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,
|
||||
|
||||
+15
-5
@@ -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(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user