diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java index db12be4fc2bf..ce2410bdfe39 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TrackingRunner.java @@ -156,6 +156,11 @@ public class TrackingRunner extends StandardDataFlowRunner { CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { return new CauseItem[0]; } + + @Nullable + DfaProblemType tryMerge(DfaProblemType other) { + return this.toString().equals(other.toString()) ? this : null; + } } public static class CauseItem { @@ -163,6 +168,12 @@ public class TrackingRunner extends StandardDataFlowRunner { final @NotNull DfaProblemType myProblem; final @Nullable SmartPsiFileRange myTarget; + private CauseItem(@NotNull List children, @NotNull DfaProblemType problem, @Nullable SmartPsiFileRange target) { + myChildren = children; + myProblem = problem; + myTarget = target; + } + CauseItem(@NotNull String problem, @Nullable PsiElement target) { this(new CustomDfaProblemType(problem), target); } @@ -270,9 +281,17 @@ public class TrackingRunner extends StandardDataFlowRunner { public CauseItem merge(CauseItem other) { if (this.equals(other)) return this; - if (Objects.equals(this.myTarget, other.myTarget) && getProblemName().equals(other.getProblemName())) { - if(tryMergeChildren(other.myChildren)) return this; - if(other.tryMergeChildren(this.myChildren)) return other; + if (Objects.equals(this.myTarget, other.myTarget)) { + if (myChildren.equals(other.myChildren)) { + DfaProblemType mergedProblem = myProblem.tryMerge(other.myProblem); + if (mergedProblem != null) { + return new CauseItem(myChildren, mergedProblem, myTarget); + } + } + if (getProblemName().equals(other.getProblemName())) { + if (tryMergeChildren(other.myChildren)) return this; + if (other.tryMergeChildren(this.myChildren)) return other; + } } return null; } @@ -350,6 +369,35 @@ public class TrackingRunner extends StandardDataFlowRunner { return myComplete ? "one of the following happens:" : "an execution might exist where:"; } } + + static class RangeDfaProblemType extends DfaProblemType { + final @NotNull String myTemplate; + final @NotNull LongRangeSet myRangeSet; + final @Nullable PsiPrimitiveType myType; + + RangeDfaProblemType(@NotNull String template, @NotNull LongRangeSet set, @Nullable PsiPrimitiveType type) { + myTemplate = template; + myRangeSet = set; + myType = type; + } + + @Nullable + @Override + DfaProblemType tryMerge(DfaProblemType other) { + if (other instanceof RangeDfaProblemType) { + RangeDfaProblemType rangeProblem = (RangeDfaProblemType)other; + if (myTemplate.equals(rangeProblem.myTemplate) && Objects.equals(myType, rangeProblem.myType)) { + return new RangeDfaProblemType(myTemplate, myRangeSet.unite(((RangeDfaProblemType)other).myRangeSet), myType); + } + } + return super.tryMerge(other); + } + + @Override + public String toString() { + return String.format(myTemplate, myRangeSet.getPresentationText(myType)); + } + } public static class ValueDfaProblemType extends DfaProblemType { @@ -933,7 +981,7 @@ public class TrackingRunner extends StandardDataFlowRunner { LongRangeSet result = operandRange.castTo((PsiPrimitiveType)type); if (range.equals(result)) { CauseItem cause = - new CauseItem("result of '(" + type.getCanonicalText() + ")' cast is " + range.getPresentationText(null), expression); + new CauseItem(new RangeDfaProblemType("result of '(" + type.getCanonicalText() + ")' cast is %s", range, null), expression); if (!operandRange.equals(LongRangeSet.fromType(operand.getType()))) { cause.addChildren(findRangeCause(operandPush, castedValue, operandRange, "cast operand is %s")); } @@ -963,8 +1011,9 @@ public class TrackingRunner extends StandardDataFlowRunner { LongRangeSet rightRange = rightSet.getFact(fromType); LongRangeSet result = leftRange.binOpFromToken(binOp.getOperationTokenType(), rightRange, isLong); if (range.equals(result)) { - CauseItem cause = new CauseItem("result of '" + binOp.getOperationSign().getText() + - "' is " + range.getPresentationText(type), factUse); + String sign = binOp.getOperationSign().getText(); + CauseItem cause = new CauseItem(new RangeDfaProblemType("result of '" + (sign.equals("%")?"%%":sign) + "' is %s", + range, ObjectUtils.tryCast(type, PsiPrimitiveType.class)), factUse); CauseItem leftCause = null, rightCause = null; if (!leftRange.equals(fromType)) { leftCause = findRangeCause(leftPush, leftVal, leftRange, "left operand is %s"); @@ -979,8 +1028,8 @@ public class TrackingRunner extends StandardDataFlowRunner { } } } - String rangeText = range.getPresentationText(expression != null ? expression.getType() : null); - CauseItem item = new CauseItem(String.format(template, rangeText), factUse); + PsiPrimitiveType type = expression != null ? ObjectUtils.tryCast(expression.getType(), PsiPrimitiveType.class) : null; + CauseItem item = new CauseItem(new RangeDfaProblemType(template, range, type), factUse); FactDefinition info = factUse.findFact(value, DfaFactType.RANGE); MemoryStateChange factDef = range.equals(info.myFact) ? info.myChange : null; if (factDef != null) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java index 44026674570c..a117e2317ea2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -1258,6 +1258,9 @@ public abstract class LongRangeSet { return ">= " + LongRangeSet.formatNumber(myFrom); } } + if (myTo - myFrom == 1) { + return myFrom + " or " + myTo; + } return "in " + toString(); } @@ -2006,6 +2009,9 @@ public abstract class LongRangeSet { return "!= " + diff.min(); } } + if (myRanges.length == 4 && myRanges[0] == myRanges[1] && myRanges[2] == myRanges[3]) { + return myRanges[0] + " or " + myRanges[2]; + } return "in " + toString(); } diff --git a/java/java-tests/testData/inspection/dataFlow/tracker/AssignTernaryNumeric.java b/java/java-tests/testData/inspection/dataFlow/tracker/AssignTernaryNumeric.java index 40ca65a09ae6..c4cb1e127cd3 100644 --- a/java/java-tests/testData/inspection/dataFlow/tracker/AssignTernaryNumeric.java +++ b/java/java-tests/testData/inspection/dataFlow/tracker/AssignTernaryNumeric.java @@ -1,10 +1,7 @@ /* -Value is always false (foo > 3; line#16) - One of the following happens: - Left operand is 1 (foo; line#16) - 'foo' was assigned (=; line#15) - or left operand is 2 (foo; line#16) - 'foo' was assigned (=; line#15) +Value is always false (foo > 3; line#13) + Left operand is 1 or 2 (foo; line#13) + 'foo' was assigned (=; line#12) */ import org.jetbrains.annotations.Nullable; diff --git a/java/java-tests/testData/inspection/dataFlow/tracker/CollectionSizeEquality.java b/java/java-tests/testData/inspection/dataFlow/tracker/CollectionSizeEquality.java new file mode 100644 index 000000000000..f2c2cd803639 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/tracker/CollectionSizeEquality.java @@ -0,0 +1,20 @@ +/* +Value is always false (l1 == l2; line#16) + Values cannot be equal because l1.size != l2.size + Left operand is 0 (l1; line#16) + Range is known from line #14 (!l1.isEmpty(); line#14) + and right operand is in {3..Integer.MAX_VALUE} (l2; line#16) + Range is known from line #15 (l2.size() < 3; line#15) + */ + +import java.util.List; + +class Test { + void test(List l1, List l2) { + if (!l1.isEmpty()) return; + if (l2.size() < 3) return; + if (l1 == l2) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTrackerTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTrackerTest.java index 4ae1409db0c8..da9fd237d820 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTrackerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTrackerTest.java @@ -164,4 +164,5 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa public void testSimpleContract() { doTest(); } public void testSimpleContract2() { doTest(); } public void testEqualsContract() { doTest(); } + public void testCollectionSizeEquality() { doTest(); } }