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