mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-209947 Explain CCE warnings; merge explanations from different states
GitOrigin-RevId: b83e85b4712257fd536bca806790e2878bbf89a8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c64967f41b
commit
c11388a534
+3
-2
@@ -682,14 +682,15 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
reporter.registerProblem(toHighlight, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private static void reportFailingCasts(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
|
||||
private void reportFailingCasts(ProblemReporter reporter, DataFlowInstructionVisitor visitor) {
|
||||
for (TypeCastInstruction instruction : visitor.getClassCastExceptionInstructions()) {
|
||||
PsiTypeCastExpression typeCast = instruction.getExpression();
|
||||
PsiExpression operand = typeCast.getOperand();
|
||||
PsiTypeElement castType = typeCast.getCastType();
|
||||
assert castType != null;
|
||||
assert operand != null;
|
||||
reporter.registerProblem(castType, InspectionsBundle.message("dataflow.message.cce", operand.getText()));
|
||||
reporter.registerProblem(castType, InspectionsBundle.message("dataflow.message.cce", operand.getText()),
|
||||
createExplainFix(typeCast, new TrackingRunner.CastDfaProblemType()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+70
-9
@@ -75,14 +75,17 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
StandardInstructionVisitor visitor = new StandardInstructionVisitor();
|
||||
RunnerResult result = runner.analyzeMethodRecursively(body, visitor, ignoreAssertions);
|
||||
if (result != RunnerResult.OK) return Collections.emptyList();
|
||||
List<CauseItem> results = new ArrayList<>();
|
||||
CauseItem cause = null;
|
||||
for (MemoryStateChange history : runner.myHistoryForContext) {
|
||||
CauseItem root = findCauseChain(expression, history, type);
|
||||
if (!results.contains(root)) {
|
||||
results.add(root);
|
||||
if (cause == null) {
|
||||
cause = root;
|
||||
} else {
|
||||
cause = cause.merge(root);
|
||||
if (cause == null) return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return results;
|
||||
return Collections.singletonList(cause);
|
||||
}
|
||||
|
||||
public abstract static class DfaProblemType {
|
||||
@@ -182,7 +185,59 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
public String toString() {
|
||||
return myProblem.toString().replaceFirst("#ref$", "here");
|
||||
}
|
||||
|
||||
public CauseItem merge(CauseItem other) {
|
||||
if (this.equals(other)) return this;
|
||||
if (Objects.equals(this.myTarget, other.myTarget) && this.myProblem.toString().equals(other.myProblem.toString())) {
|
||||
if(tryMergeChildren(other.myChildren)) return this;
|
||||
if(other.tryMergeChildren(this.myChildren)) return other;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean tryMergeChildren(List<CauseItem> children) {
|
||||
if (myChildren.isEmpty()) return false;
|
||||
if (myChildren.size() != 1 || !(myChildren.get(0).myProblem instanceof PossibleExecutionDfaProblemType)) {
|
||||
if (children.size() == myChildren.size()) {
|
||||
List<CauseItem> merged = StreamEx.zip(myChildren, children, CauseItem::merge).toList();
|
||||
if (!merged.contains(null)) {
|
||||
myChildren.clear();
|
||||
myChildren.addAll(merged);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
insertIntoHierarchy(new CauseItem(new PossibleExecutionDfaProblemType(), (PsiElement)null));
|
||||
}
|
||||
CauseItem mergePoint = myChildren.get(0);
|
||||
if (children.isEmpty()) {
|
||||
((PossibleExecutionDfaProblemType)mergePoint.myProblem).myComplete = false;
|
||||
}
|
||||
mergePoint.myChildren.addAll(children);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void insertIntoHierarchy(CauseItem intermediate) {
|
||||
intermediate.myChildren.addAll(myChildren);
|
||||
myChildren.clear();
|
||||
myChildren.add(intermediate);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CastDfaProblemType extends DfaProblemType {
|
||||
public String toString() {
|
||||
return "Cast may fail";
|
||||
}
|
||||
}
|
||||
|
||||
static class PossibleExecutionDfaProblemType extends DfaProblemType {
|
||||
boolean myComplete = true;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myComplete ? "One of the following happens:" : "An execution might exist where...";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ValueDfaProblemType extends DfaProblemType {
|
||||
final Object myValue;
|
||||
@@ -215,7 +270,6 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
Cause for possible NPE
|
||||
Cause for AIOOBE
|
||||
Cause for "Contract always fails"
|
||||
Cause for possible CCE
|
||||
Cause for "modifying an immutable collection"
|
||||
Cause for "Collection is always empty" (separate inspection now)
|
||||
TODO: 2. Describe causes in more cases:
|
||||
@@ -236,6 +290,13 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
CauseItem[] causes = findConstantValueCause(expression, history, expectedValue);
|
||||
root.addChildren(causes);
|
||||
}
|
||||
if (type instanceof CastDfaProblemType && expression instanceof PsiTypeCastExpression) {
|
||||
PsiType expressionType = expression.getType();
|
||||
MemoryStateChange operandPush = history.findExpressionPush(((PsiTypeCastExpression)expression).getOperand());
|
||||
if (operandPush != null) {
|
||||
root.addChildren(findTypeCause(operandPush, expressionType, false));
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -335,8 +396,8 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
PsiTypeElement typeElement = instanceOfExpression.getCheckType();
|
||||
if (typeElement != null) {
|
||||
PsiType type = typeElement.getType();
|
||||
CauseItem[] causeItem = findTypeCause(operandHistory, type, value);
|
||||
if (causeItem != null) return causeItem;
|
||||
CauseItem causeItem = findTypeCause(operandHistory, type, value);
|
||||
if (causeItem != null) return new CauseItem[]{causeItem};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,7 +405,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static CauseItem[] findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) {
|
||||
private static CauseItem findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) {
|
||||
PsiExpression operand = Objects.requireNonNull(operandHistory.getExpression());
|
||||
DfaValue operandValue = operandHistory.myTopOfStack;
|
||||
DfaPsiType wanted = operandValue.getFactory().createDfaType(type);
|
||||
@@ -362,7 +423,7 @@ public class TrackingRunner extends StandardDataFlowRunner {
|
||||
if (prevExplanation == null) {
|
||||
CauseItem causeItem = new CauseItem(explanation, operand);
|
||||
causeItem.addChildren(new CauseItem("Type of '" + operand.getText() + "' is known from #ref", causeLocation));
|
||||
return new CauseItem[]{causeItem};
|
||||
return causeItem;
|
||||
}
|
||||
explanation = prevExplanation;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Value is always false (x == null)
|
||||
One of the following happens:
|
||||
'x' was assigned (new Object())
|
||||
Expression cannot be null as it's newly created object (new Object())
|
||||
'x' was assigned ("foo")
|
||||
Expression cannot be null as it's literal ("foo")
|
||||
*/
|
||||
|
||||
class Test {
|
||||
void test(boolean b) {
|
||||
Object x;
|
||||
if (b) {
|
||||
x = new Object();
|
||||
} else {
|
||||
x = "foo";
|
||||
}
|
||||
if (<selection>x == null</selection>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Value is always false (y == null)
|
||||
'y' was assigned (x)
|
||||
One of the following happens:
|
||||
'x' was assigned (new Object())
|
||||
Expression cannot be null as it's newly created object (new Object())
|
||||
'x' was assigned ("foo")
|
||||
Expression cannot be null as it's literal ("foo")
|
||||
*/
|
||||
|
||||
class Test {
|
||||
void test(boolean b) {
|
||||
Object x;
|
||||
if (b) {
|
||||
x = new Object();
|
||||
} else {
|
||||
x = "foo";
|
||||
}
|
||||
Object y = x;
|
||||
if (<selection>y == null</selection>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Cast may fail ((Integer)x)
|
||||
An object type is exactly String which is not a subtype of Integer (x)
|
||||
Type of 'x' is known from line #9 (x instanceof String)
|
||||
*/
|
||||
|
||||
class Test {
|
||||
void test(Object x) {
|
||||
if (x instanceof String) {
|
||||
System.out.println(<selection>(Integer)x</selection>);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Cast may fail ((Integer)x)
|
||||
An execution might exist where...
|
||||
An object type is exactly Double which is not a subtype of Integer (x)
|
||||
Type of 'x' is known from line #14 (x instanceof Double)
|
||||
An object type is exactly String which is not a subtype of Integer (x)
|
||||
Type of 'x' is known from line #12 (x instanceof String)
|
||||
*/
|
||||
|
||||
class Test {
|
||||
void test(Object x) {
|
||||
if (x instanceof String) {
|
||||
}
|
||||
if (x instanceof Double) {
|
||||
}
|
||||
System.out.println(<selection>(Integer)x</selection>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Cast may fail ((Integer)x)
|
||||
An execution might exist where...
|
||||
An object type is exactly String which is not a subtype of Integer (x)
|
||||
Type of 'x' is known from line #10 (x instanceof String)
|
||||
*/
|
||||
|
||||
class Test {
|
||||
void test(Object x) {
|
||||
if (x instanceof String) {
|
||||
}
|
||||
System.out.println(<selection>(Integer)x</selection>);
|
||||
}
|
||||
}
|
||||
+20
-7
@@ -54,13 +54,8 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
|
||||
assertNotNull("Failed to find element at selection: " + selectedText, element);
|
||||
assertTrue("Selected element is not an expression: " + selectedText, element instanceof PsiExpression);
|
||||
PsiExpression expression = (PsiExpression)element;
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression);
|
||||
assertNotNull("No common dataflow result for expression: " + selectedText, result);
|
||||
Set<Object> values = result.getExpressionValues(expression);
|
||||
assertEquals("No single value for expression: "+selectedText, 1, values.size());
|
||||
Object singleValue = values.iterator().next();
|
||||
List<TrackingRunner.CauseItem> items = TrackingRunner.findProblemCause(
|
||||
true, false, expression, new TrackingRunner.ValueDfaProblemType(singleValue));
|
||||
TrackingRunner.DfaProblemType problemType = getProblemType(selectedText, expression);
|
||||
List<TrackingRunner.CauseItem> items = TrackingRunner.findProblemCause(true, false, expression, problemType);
|
||||
String dump = StreamEx.of(items).map(item -> item.dump(getEditor().getDocument())).joining("\n----\n");
|
||||
PsiComment firstComment = PsiTreeUtil.findChildOfType(file, PsiComment.class);
|
||||
if (firstComment == null) {
|
||||
@@ -94,6 +89,19 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
|
||||
assertEquals(text, actual);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static TrackingRunner.DfaProblemType getProblemType(String selectedText, PsiExpression expression) {
|
||||
if (expression instanceof PsiTypeCastExpression) {
|
||||
return new TrackingRunner.CastDfaProblemType();
|
||||
}
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression);
|
||||
assertNotNull("No common dataflow result for expression: " + selectedText, result);
|
||||
Set<Object> values = result.getExpressionValues(expression);
|
||||
assertEquals("No single value for expression: "+selectedText, 1, values.size());
|
||||
Object singleValue = values.iterator().next();
|
||||
return new TrackingRunner.ValueDfaProblemType(singleValue);
|
||||
}
|
||||
|
||||
public void testDitto() { doTest(); }
|
||||
public void testConstants() { doTest(); }
|
||||
public void testSimpleDeref() { doTest(); }
|
||||
@@ -124,4 +132,9 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
|
||||
public void testNotNullObvious() { doTest(); }
|
||||
public void testNotNullAssignmentInside() { doTest(); }
|
||||
public void testIndexOfPlusOne() { doTest(); }
|
||||
public void testWrongCastSimple() { doTest(); }
|
||||
public void testWrongCastTwoStates() { doTest(); }
|
||||
public void testWrongCastThreeStates() { doTest(); }
|
||||
public void testIfBothNotNull() { doTest(); }
|
||||
public void testIfBothNotNullReassign() { doTest(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user