diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index 570dd29b2aa1..4eac18c4bd1f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -714,23 +714,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return ((DfaFactMapValue)value).getFacts().intersect(factType, factValue) != null; } if (value instanceof DfaBinOpValue && factType == DfaFactType.RANGE && factValue != null) { - DfaBinOpValue sum = (DfaBinOpValue)value; - boolean isLong = PsiType.LONG.equals(sum.getType()); - LongRangeSet appliedRange = (LongRangeSet)factValue; - if (!isLong) { - appliedRange = appliedRange.intersect(LongRangeSet.fromType(PsiType.INT)); - } - DfaVariableValue left = sum.getLeft(); - DfaValue right = sum.getRight(); - LongRangeSet leftRange = getValueFact(left, DfaFactType.RANGE); - LongRangeSet rightRange = getValueFact(right, DfaFactType.RANGE); - if (leftRange == null || rightRange == null) return true; - LongRangeSet result = Objects.requireNonNull(leftRange.binOpFromToken(sum.getTokenType(), rightRange, isLong)); - if (!result.intersects(appliedRange)) return false; - boolean subtraction = sum.getOperation() == DfaBinOpValue.BinOp.MINUS; - LongRangeSet leftConstraint = subtraction ? rightRange.plus(appliedRange, isLong) : appliedRange.minus(rightRange, isLong); - LongRangeSet rightConstraint = subtraction ? leftRange.minus(appliedRange, isLong) : appliedRange.minus(leftRange, isLong); - return applyFact(left, DfaFactType.RANGE, leftConstraint) && applyFact(right, DfaFactType.RANGE, rightConstraint); + return propagateRangeBack((LongRangeSet)factValue, (DfaBinOpValue)value); } if (value instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue)value; @@ -746,6 +730,36 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return true; } + private boolean propagateRangeBack(@NotNull LongRangeSet factValue, @NotNull DfaBinOpValue binOp) { + boolean isLong = PsiType.LONG.equals(binOp.getType()); + LongRangeSet appliedRange = isLong ? factValue : factValue.intersect(LongRangeSet.fromType(PsiType.INT)); + DfaVariableValue left = binOp.getLeft(); + DfaValue right = binOp.getRight(); + LongRangeSet leftRange = getValueFact(left, DfaFactType.RANGE); + LongRangeSet rightRange = getValueFact(right, DfaFactType.RANGE); + if (leftRange == null || rightRange == null) return true; + LongRangeSet result = Objects.requireNonNull(leftRange.binOpFromToken(binOp.getTokenType(), rightRange, isLong)); + if (!result.intersects(appliedRange)) return false; + LongRangeSet leftConstraint = LongRangeSet.all(); + LongRangeSet rightConstraint = LongRangeSet.all(); + switch (binOp.getOperation()) { + case PLUS: + leftConstraint = appliedRange.minus(rightRange, isLong); + rightConstraint = appliedRange.minus(leftRange, isLong); + break; + case MINUS: + leftConstraint = rightRange.plus(appliedRange, isLong); + rightConstraint = leftRange.minus(appliedRange, isLong); + break; + case REM: + if (rightRange.min() == rightRange.max()) { + leftConstraint = LongRangeSet.fromRemainder(rightRange.min(), appliedRange.intersect(result)); + } + break; + } + return applyFact(left, DfaFactType.RANGE, leftConstraint) && applyFact(right, DfaFactType.RANGE, rightConstraint); + } + @Override public boolean applyContractCondition(DfaValue condition) { if (condition instanceof DfaRelationValue) { @@ -892,6 +906,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (left instanceof DfaBinOpValue) { DfaBinOpValue sum = (DfaBinOpValue)left; DfaBinOpValue.BinOp op = sum.getOperation(); + if (op != DfaBinOpValue.BinOp.PLUS && op != DfaBinOpValue.BinOp.MINUS) return true; LongRangeSet leftRange = getValueFact(sum.getLeft(), DfaFactType.RANGE); LongRangeSet rightRange = getValueFact(sum.getRight(), DfaFactType.RANGE); if (leftRange == null || rightRange == null) return true; @@ -1300,6 +1315,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } value = resolveVariableValue((DfaVariableValue)value); } + if (value instanceof DfaBinOpValue) { + return DfaFactMap.EMPTY.with(DfaFactType.RANGE, getValueFact(value, DfaFactType.RANGE)); + } return DfaFactMap.fromDfaValue(value); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java index 2418ebebbd76..9624dbb8f5c3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java @@ -154,6 +154,9 @@ public abstract class InstructionVisitor { public DfaInstructionState[] visitBox(BoxingInstruction instruction, DataFlowRunner runner, DfaMemoryState state) { DfaValue value = state.pop(); DfaValueFactory factory = runner.getFactory(); + if (value instanceof DfaBinOpValue) { + value = factory.getFactValue(DfaFactType.RANGE, state.getValueFact(value, DfaFactType.RANGE)); + } DfaValue boxed = factory.getBoxedFactory().createBoxed(value, instruction.getTargetType()); state.push(boxed == null ? factory.createTypeValue(instruction.getTargetType(), Nullability.NOT_NULL) : boxed); return nextInstruction(instruction, runner, state); 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 52e93e73280e..150256c6b2a0 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 @@ -802,6 +802,30 @@ public abstract class LongRangeSet { } } + /** + * Creates a LongRangeSet of values {@code x} for which {@code remainders.contains(x % mod)}. + * May include more values as well. + * + * @param mod a divisor + * @param remainders set of allowed remainders + * @return set of values which may produce supplied remainders when divided by mod. + */ + public static LongRangeSet fromRemainder(long mod, LongRangeSet remainders) { + if (remainders.isEmpty()) return empty(); + long min = remainders.min() > 0 ? 1 : Long.MIN_VALUE; + long max = remainders.max() < 0 ? -1 : Long.MAX_VALUE; + if (mod > 1 && mod <= Long.SIZE) { + long bits = remainders.contains(0) ? 1 : 0; + for(int rem = 1; rem < mod; rem++) { + if (remainders.contains(rem) || remainders.contains(rem - mod)) { + bits |= 1L << rem; + } + } + return modRange(min, max, mod, bits); + } + return range(min, max); + } + static final class Empty extends LongRangeSet { static final LongRangeSet EMPTY = new Empty(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java index faf9c1fa9390..18a4762c144a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaBinOpValue.java @@ -27,7 +27,7 @@ public class DfaBinOpValue extends DfaValue { private DfaBinOpValue(@NotNull DfaVariableValue left, @NotNull DfaValue right, boolean isLong, BinOp op) { super(left.getFactory()); - assert (right instanceof DfaConstValue && op == BinOp.PLUS) || right instanceof DfaVariableValue; + assert (right instanceof DfaConstValue && op != BinOp.MINUS) || (right instanceof DfaVariableValue && op != BinOp.REM); myLeft = left; myRight = right; myLong = isLong; @@ -115,6 +115,18 @@ public class DfaBinOpValue extends DfaValue { if (op == BinOp.MINUS && state.areEqual(left, right)) { return myFactory.getInt(0); } + if (op == BinOp.REM) { + if (left instanceof DfaVariableValue && right instanceof DfaConstValue) { + Object value = ((DfaConstValue)right).getValue(); + if (value instanceof Long) { + long divisor = ((Long)value).longValue(); + if (divisor > 1 && divisor <= Long.SIZE) { + return doCreate((DfaVariableValue)left, right, isLong, op); + } + } + } + return null; + } if (left instanceof DfaConstValue && (right instanceof DfaVariableValue || right instanceof DfaBinOpValue) && op == BinOp.PLUS) { return doCreate(right, left, state, isLong, op); } @@ -174,7 +186,7 @@ public class DfaBinOpValue extends DfaValue { } public enum BinOp { - PLUS("+", JavaTokenType.PLUS), MINUS("-", JavaTokenType.MINUS); + PLUS("+", JavaTokenType.PLUS), MINUS("-", JavaTokenType.MINUS), REM("%", JavaTokenType.PERC); private final String mySign; private final IElementType myTokenType; @@ -192,6 +204,7 @@ public class DfaBinOpValue extends DfaValue { public static BinOp fromTokenType(IElementType tokenType) { if (PLUS.getTokenType() == tokenType) return PLUS; if (MINUS.getTokenType() == tokenType) return MINUS; + if (REM.getTokenType() == tokenType) return REM; return null; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/Algebraic.java b/java/java-tests/testData/inspection/dataFlow/fixture/BackPropagation.java similarity index 99% rename from java/java-tests/testData/inspection/dataFlow/fixture/Algebraic.java rename to java/java-tests/testData/inspection/dataFlow/fixture/BackPropagation.java index 3416e2d84f24..cf4c3249a186 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/Algebraic.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/BackPropagation.java @@ -1,6 +1,6 @@ import java.util.*; -public class Algebraic { +public class BackPropagation { void testOverflowDetection(int[] arr1, int[] arr2, int offset) { int l1 = arr1.length; int l2 = arr2.length; diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/BackPropagationMod.java b/java/java-tests/testData/inspection/dataFlow/fixture/BackPropagationMod.java new file mode 100644 index 000000000000..e3fed5d11a8e --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/BackPropagationMod.java @@ -0,0 +1,58 @@ +import java.util.*; + +public class BackPropagationMod { + void testModSeries(int x) { + if(x % 2 == 0) {} + else if(x % 2 != 0) {} + + if(x >= 0) { + if(x % 3 == 0) {} + else if(x % 3 == 1) {} + else if(x % 3 == 2) {} + } + if(x % 2 == 0) {} + else if(x % 2 == 1) {} + else if(x % 2 == -1) {} + } + + void testGt(int[] arr) { + if (arr.length % 5 >= 2) { + + } else if(arr.length % 5 == 1) { + + } else if(arr.length % 5 == 0) { + + } + } + + void testWrongFizzBuzz() { + for (int i = 1; i <= 100; i++) { + if (i % 3 == 0) { + System.out.println("Fizz"); + } else if(i % 5 == 0) { + System.out.println("Buzz"); + } else if(i % 15 == 0) { + System.out.println("FizzBuzz"); + } else { + System.out.println(i); + } + } + } + + void testSign(int x) { + if (x % 2 == 1 && x < 0) {} + if (x % 2 == -1 && x > 0) {} + if (x % 2 > -1 && x < 0) {} + if (x % 2 > -1 && x > 0) {} + } + + // IDEABKL-6662 + void testMod510(StringBuilder sb, int pos) { + if (pos % 5 == 0) { + sb.append('.'); + } + else if (pos % 10 == 0) { + + } + } +} 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 2aa33e8a4d30..640ade8ae1fb 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -56,7 +56,8 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase { public void testManyAdditionsDoNotCauseExponentialBlowUp() { doTest(); } public void testBoxedRanges() { doTest(); } public void testLongRangeDiff() { doTest(); } - public void testAlgebraic() { doTest(); } + public void testBackPropagation() { doTest(); } public void testTwoArraysDiff() { doTest(); } public void testModRange() { doTest(); } + public void testBackPropagationMod() { 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 9dc238c71de5..1943dc668d86 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 @@ -734,6 +734,15 @@ public class LongRangeSetTest { assertEquals("{0..19}: <0, 1, 3, 5, 7, 9> mod 10", modRange(0, 11, 5, 0b1).unite(modRange(11, 20, 2, 0b10)).toString()); assertEquals("{0..100}", modRange(0, 100, 2, 0b1).unite(point(1)).toString()); } + + @Test + public void testFromRemainder() { + assertEquals("{-9223372036854775805..9223372036854775805}: <0> mod 5", fromRemainder(5, point(0)).toString()); + assertEquals("{1..Long.MAX_VALUE-1}: <1> mod 5", fromRemainder(5, point(1)).toString()); + assertEquals("{1..Long.MAX_VALUE}: <1, 2, 3, 4> mod 5", fromRemainder(5, range(1, 4)).toString()); + assertEquals("{Long.MIN_VALUE..-3}: <2> mod 5", fromRemainder(5, point(-3)).toString()); + assertEquals("{Long.MIN_VALUE..Long.MAX_VALUE}: <1, 2, 3, 4> mod 5", fromRemainder(5, range(1, 4).unite(range(-4, -1))).toString()); + } void checkAdd(LongRangeSet addend1, LongRangeSet addend2, boolean isLong, String expected) { LongRangeSet result = addend1.plus(addend2, isLong);