From 9020534b91fd80ef5111d03f0aebd3e851f6956f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 11 Jan 2018 17:47:34 +0700 Subject: [PATCH] Dataflow: perform ranges addition (part of IDEA-184800) --- .../dataFlow/ControlFlowAnalyzer.java | 22 ++++- .../dataFlow/StandardInstructionVisitor.java | 14 ++- .../dataFlow/rangeSet/LongRangeSet.java | 85 +++++++++++++++++++ .../dataFlow/fixture/LongRangeAdd.java | 42 +++++++++ .../DataFlowRangeAnalysisTest.java | 2 + .../dataFlow/rangeSet/LongRangeSetTest.java | 46 ++++++++++ 6 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java 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 322b0a5552c4..2e74ae371dac 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 @@ -1171,7 +1171,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } private void generateOther(PsiPolyadicExpression expression, IElementType op, PsiExpression[] operands, PsiType type) { - op = substituteBinaryOperation(op, type); + op = substituteBinaryOperation(expression, op); PsiExpression lExpr = operands[0]; lExpr.accept(this); @@ -1190,13 +1190,29 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } @Nullable - private static IElementType substituteBinaryOperation(IElementType op, PsiType type) { - if (JavaTokenType.PLUS == op && (type == null || !type.equalsToText(JAVA_LANG_STRING))) { + private static IElementType substituteBinaryOperation(PsiPolyadicExpression expression, IElementType op) { + if (JavaTokenType.PLUS == op) { + PsiType type = expression.getType(); + if (type == null) return null; + if (type.equalsToText(JAVA_LANG_STRING)) return op; + if ((PsiType.INT.equals(type) || PsiType.LONG.equals(type)) && isAcceptableContextForMathOperation(expression)) return op; return null; } return op; } + private static boolean isAcceptableContextForMathOperation(PsiExpression context) { + PsiElement parent = context.getParent(); + while (parent != null && !(parent instanceof PsiAssignmentExpression) && !(parent instanceof PsiStatement) && !(parent instanceof PsiLambdaExpression)) { + if (parent instanceof PsiExpressionList) return true; + if (parent instanceof PsiBinaryExpression && DfaRelationValue.RelationType.fromElementType(((PsiBinaryExpression)parent).getOperationTokenType()) != null) { + return true; + } + parent = parent.getParent(); + } + return false; + } + private void acceptBinaryRightOperand(@Nullable IElementType op, PsiType type, PsiExpression lExpr, PsiType lType, PsiExpression rExpr, PsiType rType) { 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 3b0f9e2d5030..3f239384e396 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 @@ -611,7 +611,19 @@ public class StandardInstructionVisitor extends InstructionVisitor { } } else if (JavaTokenType.PLUS == opSign) { - result = instruction.getNonNullStringValue(runner.getFactory()); + PsiElement expr = instruction.getPsiAnchor(); + PsiType type = expr instanceof PsiExpression ? ((PsiExpression)expr).getType() : null; + if(PsiType.INT.equals(type) || PsiType.LONG.equals(type)) { + LongRangeSet left = memState.getValueFact(dfaLeft, DfaFactType.RANGE); + LongRangeSet right = memState.getValueFact(dfaRight, DfaFactType.RANGE); + if(left != null && right != null) { + result = runner.getFactory().getFactValue(DfaFactType.RANGE, left.add(right, PsiType.LONG.equals(type))); + } else { + result = DfaUnknownValue.getInstance(); + } + } else { + result = instruction.getNonNullStringValue(runner.getFactory()); + } } else { if (instruction instanceof InstanceofInstruction) { 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 57a08651c633..4f998228428f 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 @@ -155,8 +155,12 @@ public abstract class LongRangeSet { * @param isLong whether {@link Math#abs(long)} is applied * @return a new range */ + @NotNull public abstract LongRangeSet abs(boolean isLong); + @NotNull + public abstract LongRangeSet add(LongRangeSet other, boolean isLong); + /** * Returns a range which represents all the possible values after applying {@code x & y} operation for * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain @@ -165,6 +169,7 @@ public abstract class LongRangeSet { * @param other other set to perform bitwise-and with * @return a new range */ + @NotNull public LongRangeSet bitwiseAnd(LongRangeSet other) { if (this.isEmpty() || other.isEmpty()) return empty(); long[] left = splitAtZero(asRanges()); @@ -185,6 +190,7 @@ public abstract class LongRangeSet { return all().subtract(result); } + @NotNull abstract public LongRangeSet mod(LongRangeSet other); private static long[] splitAtZero(long[] ranges) { @@ -364,6 +370,10 @@ public abstract class LongRangeSet { return isLong ? Long.MIN_VALUE : Integer.MIN_VALUE; } + static long maxValue(boolean isLong) { + return isLong ? Long.MAX_VALUE : Integer.MAX_VALUE; + } + /** * @return LongRangeSet describing possible array or string indices (from 0 to Integer.MAX_VALUE) */ @@ -473,11 +483,19 @@ public abstract class LongRangeSet { return other.isEmpty(); } + @NotNull @Override public LongRangeSet abs(boolean isLong) { return this; } + @NotNull + @Override + public LongRangeSet add(LongRangeSet other, boolean isLong) { + return this; + } + + @NotNull @Override public LongRangeSet mod(LongRangeSet divisor) { return empty(); @@ -551,11 +569,24 @@ public abstract class LongRangeSet { return other.isEmpty() || equals(other); } + @NotNull @Override public LongRangeSet abs(boolean isLong) { return myValue >= 0 || myValue == minValue(isLong) ? this : point(-myValue); } + @NotNull + @Override + public LongRangeSet add(LongRangeSet other, boolean isLong) { + if (other.isEmpty()) return other; + if (other instanceof Point) { + long res = myValue + ((Point)other).myValue; + return point(isLong ? res : (int)res); + } + return other.add(this, isLong); + } + + @NotNull @Override public LongRangeSet mod(LongRangeSet divisor) { if (divisor.isEmpty() || divisor.equals(point(0))) return empty(); @@ -726,6 +757,7 @@ public abstract class LongRangeSet { return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo; } + @NotNull @Override public LongRangeSet abs(boolean isLong) { if (myFrom >= 0) return this; @@ -750,6 +782,47 @@ public abstract class LongRangeSet { } } + @NotNull + @Override + public LongRangeSet add(LongRangeSet other, boolean isLong) { + if (other.isEmpty()) return other; + if (isLong && equals(LONG_RANGE) || !isLong && equals(INT_RANGE)) return this; + if (other instanceof Point || other instanceof Range || (other instanceof RangeSet && ((RangeSet)other).myRanges.length > 6)) { + return add(myFrom, myTo, other.min(), other.max(), isLong); + } + long[] ranges = other.asRanges(); + LongRangeSet result = empty(); + for (int i = 0; i < ranges.length; i += 2) { + result = result.union(add(myFrom, myTo, ranges[i], ranges[i + 1], isLong)); + } + return result; + } + + @NotNull + private static LongRangeSet add(long from1, long to1, long from2, long to2, boolean isLong) { + long len1 = to1 - from1; // may overflow + long len2 = to2 - from2; // may overflow + if ((len1 < 0 || len2 < 0) && len1 + len2 + 1 >= 0) { // total length more than 2^32 + return isLong ? LONG_RANGE : INT_RANGE; + } + long from = from1 + from2; + long to = to1 + to2; + if (!isLong) { + if (to - from + 1 >= 0x1_0000_0000L) { + return INT_RANGE; + } + from = (int)from; + to = (int)to; + } + if (to < from) { + return new RangeSet(new long[]{minValue(isLong), to, from, maxValue(isLong)}); + } + else { + return range(from, to); + } + } + + @NotNull @Override public LongRangeSet mod(LongRangeSet divisor) { if (divisor.isEmpty() || divisor.equals(point(0))) return empty(); @@ -906,6 +979,7 @@ public abstract class LongRangeSet { return false; } + @NotNull @Override public LongRangeSet abs(boolean isLong) { LongRangeSet result = all(); @@ -915,6 +989,17 @@ public abstract class LongRangeSet { return all().subtract(result); } + @NotNull + @Override + public LongRangeSet add(LongRangeSet other, boolean isLong) { + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.union(range(myRanges[i], myRanges[i + 1]).add(other, isLong)); + } + return result; + } + + @NotNull @Override public LongRangeSet mod(LongRangeSet divisor) { if(divisor.isEmpty()) return empty(); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java new file mode 100644 index 000000000000..8180d90fad92 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java @@ -0,0 +1,42 @@ +import java.util.*; + +public class LongRangeAdd { + void test(int x, int y) { + if (x > 0 && x < 10) { + if (y > x + 10 && + y < 0){ + System.out.println("Impossible"); + } + } + } + + void testLoop() { + for (int i = 0; i < 10; i = i + 1) { + if (i + 1 == 15) { + System.out.println("Impossible"); + } + if (i + 1 == -10) { + System.out.println("Impossible"); + } + System.out.println(i); + } + } + + void testNestedLoop() { + for (int i = 0; i < 10; i = i + 1) { + for (int j = 0; j < 10; j = j + 1) { + if (i + j == 0) { + if (i != 0) { + System.out.println("never"); + } + if (j != 0) { + System.out.println("never"); + } + } + if (i + j == 20) { + System.out.println("never"); + } + } + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java index 3e98d1dfa601..b7ecfb4feedd 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -68,4 +68,6 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase { } public void testLongRangeMod() { doTest(); } + + public void testLongRangeAdd() { doTest(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java index 77788804167c..1505de26e806 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java @@ -350,4 +350,50 @@ public class LongRangeSetTest { assertFalse(range(0, 10).union(range(13, 20)).contains(range(9, 15))); assertTrue(range(0, 10).union(range(13, 20)).contains(range(2, 8).union(range(15, 17)))); } + + @Test + public void testAdd() { + checkAdd(empty(), empty(), true, "{}"); + checkAdd(empty(), point(0), true, "{}"); + checkAdd(empty(), range(0, 10), true, "{}"); + checkAdd(empty(), range(0, 10).union(range(15, 20)), true, "{}"); + + checkAdd(point(5), point(10), false, "{15}"); + checkAdd(point(Integer.MAX_VALUE), point(Integer.MAX_VALUE), false, "{-2}"); + checkAdd(point(Integer.MAX_VALUE), point(Integer.MAX_VALUE), true, "{" + 0xFFFF_FFFEL + "}"); + checkAdd(range(0, 10), point(10), false, "{10..20}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), point(1), true, "{2147483638..2147483648}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), point(1), false, "{-2147483648, 2147483638..2147483647}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), point(10), false, "{-2147483648..-2147483639, 2147483647}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), point(11), false, "{-2147483648..-2147483638}"); + + checkAdd(range(0, 10), range(20, 30), true, "{20..40}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), range(0, 10), true, "{2147483637..2147483657}"); + checkAdd(range(Integer.MAX_VALUE - 10, Integer.MAX_VALUE), range(0, 10), false, "{-2147483648..-2147483639, 2147483637..2147483647}"); + + checkAdd(range(10, 20).union(range(40, 50)), range(0, 3).union(range(5, 7)), true, "{10..27, 40..57}"); + + LongRangeSet intDomain = range(Integer.MIN_VALUE, Integer.MAX_VALUE); + assertEquals(intDomain, intDomain.add(point(20), false)); + assertEquals(intDomain.without(20), intDomain.without(0).add(point(20), false)); + assertEquals(all().without(20), all().without(0).add(point(20), true)); + assertEquals(intDomain, range(20, 30).union(range(40, 50)).add(intDomain, false)); + assertEquals(intDomain, range(Integer.MIN_VALUE, 2).add(range(-2, Integer.MAX_VALUE), false)); + assertEquals(all(), range(Long.MIN_VALUE, 2).add(range(-2, Long.MAX_VALUE), true)); + } + + void checkAdd(LongRangeSet addend1, LongRangeSet addend2, boolean isLong, String expected) { + LongRangeSet result = addend1.add(addend2, isLong); + assertEquals(result, addend2.add(addend1, isLong)); // commutative + assertEquals(expected, result.toString()); + String errors = addend1.stream() + .mapToObj(a -> addend2.stream() + .filter(b -> !result.contains(isLong ? a + b : (int)(a + b))) + .mapToObj(divisor -> a + " + " + divisor + " = " + (a + divisor))) + .flatMap(Function.identity()) + .collect(Collectors.joining("\n")); + if (!errors.isEmpty()) { + fail("Expected range " + expected + " is not satisfied:\n" + errors); + } + } } \ No newline at end of file