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);