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 dbd790d3747c..4d2755f7cfe8 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
@@ -602,46 +602,23 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
DfaValue result = null;
- if (JavaTokenType.AND == opSign) {
+ 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.bitwiseAnd(right));
- }
- }
- else if (JavaTokenType.PERC == opSign) {
- 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.mod(right));
- }
- }
- else if (JavaTokenType.PLUS == opSign) {
- 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.plus(right, PsiType.LONG.equals(type)));
+ if (left != null && right != null) {
+ LongRangeSet resultRange = left.binOpFromToken(opSign, right, PsiType.LONG.equals(type));
+ if (resultRange != null) {
+ result = runner.getFactory().getFactValue(DfaFactType.RANGE, resultRange);
}
- } else {
+ }
+ }
+ if (result == null) {
+ if (JavaTokenType.PLUS == opSign) {
result = instruction.getNonNullStringValue(runner.getFactory());
}
- }
- else if (JavaTokenType.MINUS == opSign) {
- 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.minus(right, PsiType.LONG.equals(type)));
- }
- }
- }
- else {
- if (instruction instanceof InstanceofInstruction) {
+ else if (instruction instanceof InstanceofInstruction) {
handleInstanceof((InstanceofInstruction)instruction, dfaRight, dfaLeft);
}
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java
index 6ea884bb8899..1cc394eb3fe5 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java
@@ -33,7 +33,8 @@ import org.jetbrains.annotations.Nullable;
import static com.intellij.psi.JavaTokenType.*;
public class BinopInstruction extends BranchingInstruction {
- private static final TokenSet ourSignificantOperations = TokenSet.create(EQEQ, NE, LT, GT, LE, GE, INSTANCEOF_KEYWORD, PLUS, MINUS, AND, PERC);
+ private static final TokenSet ourSignificantOperations =
+ TokenSet.create(EQEQ, NE, LT, GT, LE, GE, INSTANCEOF_KEYWORD, PLUS, MINUS, AND, PERC, DIV, GTGT, GTGTGT);
private final IElementType myOperationSign;
private final Project myProject;
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 d29ebe7c181f..b2556e2d740c 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
@@ -4,11 +4,10 @@ package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.value.*;
-import com.intellij.psi.PsiAnnotation;
-import com.intellij.psi.PsiModifierListOwner;
-import com.intellij.psi.PsiPrimitiveType;
-import com.intellij.psi.PsiType;
+import com.intellij.psi.*;
+import com.intellij.psi.tree.IElementType;
import com.intellij.util.ThreeState;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -139,6 +138,43 @@ public abstract class LongRangeSet {
}
}
+ /**
+ * Performs a supported binary operation from token (defined in {@link JavaTokenType}).
+ *
+ * @param token a token which corresponds to the operation
+ * @param right a right-hand operand
+ * @param isLong true if operation should be performed on long types (otherwise int is assumed)
+ * @return the resulting LongRangeSet which covers possible results of the operation (probably including some more elements);
+ * or null if the supplied token is not supported.
+ */
+ @Contract("null, _, _ -> null")
+ @Nullable
+ public final LongRangeSet binOpFromToken(IElementType token, LongRangeSet right, boolean isLong) {
+ if (token == null) return null;
+ if (token.equals(JavaTokenType.PLUS)) {
+ return plus(right, isLong);
+ }
+ if (token.equals(JavaTokenType.MINUS)) {
+ return minus(right, isLong);
+ }
+ if (token.equals(JavaTokenType.AND)) {
+ return bitwiseAnd(right);
+ }
+ if (token.equals(JavaTokenType.PERC)) {
+ return mod(right);
+ }
+ if (token.equals(JavaTokenType.DIV)) {
+ return div(right, isLong);
+ }
+ if (token.equals(JavaTokenType.GTGT)) {
+ return shiftRight(right, isLong);
+ }
+ if (token.equals(JavaTokenType.GTGTGT)) {
+ return unsignedShiftRight(right, isLong);
+ }
+ return null;
+ }
+
/**
* Returns a range which represents all the possible values after applying {@link Math#abs(int)} or {@link Math#abs(long)}
* to the values from this set
@@ -211,8 +247,128 @@ public abstract class LongRangeSet {
return all().subtract(result);
}
+ /**
+ * 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 divisor set. The resulting set may contain
+ * some more values. Division by zero yields an empty set of possible results.
+ *
+ * @param divisor divisor set to divide by
+ * @param isLong whether the operation is performed on long type (if false, the int type is assumed). This only changes the special
+ * treatment of {@code MIN_VALUE/-1} division; other division results do not depend on the resulting type.
+ * @return a new range
+ */
@NotNull
- abstract public LongRangeSet mod(LongRangeSet other);
+ public LongRangeSet div(LongRangeSet divisor, boolean isLong) {
+ if (divisor.isEmpty() || divisor.equals(new Point(0))) return empty();
+ long[] left = splitAtZero(asRanges());
+ long[] right = splitAtZero(new long[]{divisor.min(), divisor.max()});
+ LongRangeSet result = empty();
+ for (int i = 0; i < left.length; i += 2) {
+ for (int j = 0; j < right.length; j += 2) {
+ result = result.union(divide(left[i], left[i + 1], right[j], right[j + 1], isLong));
+ }
+ }
+ return result;
+ }
+
+ @NotNull
+ private static LongRangeSet divide(long dividendMin, long dividendMax, long divisorMin, long divisorMax, boolean isLong) {
+ if (divisorMin == 0) {
+ if (divisorMax == 0) return empty();
+ divisorMin = 1;
+ }
+ if (dividendMin >= 0) {
+ return divisorMin > 0
+ ? range(dividendMin / divisorMax, dividendMax / divisorMin)
+ : range(dividendMax / divisorMax, dividendMin / divisorMin);
+ }
+ if (divisorMin > 0) {
+ return range(dividendMin / divisorMin, dividendMax / divisorMax);
+ }
+ long minValue = minValue(isLong);
+ if (dividendMin == minValue && divisorMax == -1) {
+ // MIN_VALUE/-1 = MIN_VALUE
+ return point(minValue)
+ .union(divisorMin == -1 ? empty() : range(dividendMin / divisorMin, dividendMin / (divisorMax - 1)))
+ .union(dividendMax == minValue ? empty() : range(dividendMax / divisorMin, (dividendMin + 1) / divisorMax));
+ }
+ return range(dividendMax / divisorMin, dividendMin / divisorMax);
+ }
+
+ /**
+ * 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 shiftSize set. The resulting set may contain
+ * some more values.
+ *
+ * @param shiftSize set of possible shift sizes (number of bits to shift to the right)
+ * @param isLong whether the operation is performed on long type (if false, the int type is assumed).
+ * @return a new range
+ */
+ @NotNull
+ public LongRangeSet shiftRight(LongRangeSet shiftSize, boolean isLong) {
+ if (isEmpty() || shiftSize.isEmpty()) return empty();
+ int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1;
+ if (shiftSize.min() < 0 || shiftSize.max() > maxShift) {
+ shiftSize = shiftSize.bitwiseAnd(point(maxShift));
+ }
+ long min = shiftSize.min();
+ long max = shiftSize.max();
+ LongRangeSet negative = intersect(range(minValue(isLong), -1));
+ LongRangeSet positive = intersect(range(0, maxValue(isLong)));
+ return positive.shrPositive(min, max, isLong)
+ .union(point(-1).minus(point(-1).minus(negative, isLong).shrPositive(min, max, isLong), 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 shiftSize set. The resulting set may contain
+ * some more values.
+ *
+ * @param shiftSize set of possible shift sizes (number of bits to shift to the right)
+ * @param isLong whether the operation is performed on long type (if false, the int type is assumed).
+ * @return a new range
+ */
+ @NotNull
+ public LongRangeSet unsignedShiftRight(LongRangeSet shiftSize, boolean isLong) {
+ if (isEmpty() || shiftSize.isEmpty()) return empty();
+ int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1;
+ if (shiftSize.min() < 0 || shiftSize.max() > maxShift) {
+ shiftSize = shiftSize.bitwiseAnd(point(maxShift));
+ }
+ long min = shiftSize.min();
+ long max = shiftSize.max();
+ LongRangeSet negative = intersect(range(minValue(isLong), -1));
+ LongRangeSet positive = intersect(range(0, maxValue(isLong)));
+ LongRangeSet result = positive.shrPositive(min, max, isLong);
+ if (min == 0) {
+ result = result.union(negative);
+ if (max == 0) return result;
+ min++;
+ }
+ // for x < 0, y > 0, x >>> y = (MAX_VALUE - ((-1-x) >> 1)) >> (y-1)
+ return result.union(point(maxValue(isLong)).minus(point(-1).minus(negative, isLong).shrPositive(1, 1, isLong), isLong)
+ .shrPositive(min - 1, max - 1, isLong));
+ }
+
+ private LongRangeSet shrPositive(long min, long max, boolean isLong) {
+ if (isEmpty()) return empty();
+ int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1;
+ if (max == maxShift) {
+ return min == max ? point(0) : point(0).union(div(range(1L << min, 1L << (max - 1)), isLong));
+ }
+ return div(range(1L << min, 1L << max), 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 divisor set. The resulting set may contain
+ * some more values. Division by zero yields an empty set of possible results.
+ *
+ * @param divisor divisor set to divide by
+ * @return a new range
+ */
+ @NotNull
+ abstract public LongRangeSet mod(LongRangeSet divisor);
private static long[] splitAtZero(long[] ranges) {
for (int i = 0; i < ranges.length; i += 2) {
diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeDivShift.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeDivShift.java
new file mode 100644
index 000000000000..16965971e3d9
--- /dev/null
+++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeDivShift.java
@@ -0,0 +1,39 @@
+import java.util.*;
+
+public class LongRangeDivShift {
+ void test(int[] arr, int x) {
+ if(arr.length / 2 < 0) {
+ System.out.println("Impossible");
+ }
+ }
+
+ void signTest(int x, int y) {
+ if(x > 0 && y < 0 && x/y > 0) {
+ System.out.println("Impossible");
+ }
+ }
+
+ void shift(long x) {
+ long a = x >> 32;
+ if(a < Integer.MIN_VALUE || a > Integer.MAX_VALUE) {
+ System.out.println("Impossible");
+ }
+ }
+
+ void shiftUnsigned(int x) {
+ x = x >>> 16;
+ if(x >= 0 && x <= 0xFFFF) {
+ char c = (char)x;
+ }
+ }
+
+ static final int RESIZE_STAMP_SHIFT = 16;
+ static final int MAX_RESIZERS = 65535;
+
+ void testCHM(int sc, int rs) {
+ if (sc < 0) {
+ if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
+ sc == rs + MAX_RESIZERS) {}
+ }
+ }
+}
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 35f73304d368..5c2bf9ee0393 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java
@@ -61,6 +61,7 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
}
public void testLongRangeMod() { doTest(); }
+ public void testLongRangeDivShift() { doTest(); }
public void testLongRangePlusMinus() { doTest(); }
public void testFebruary31() { 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 b6afbcd0db30..1cfaeae6f830 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
@@ -341,6 +341,70 @@ public class LongRangeSetTest {
checkMod(range(Long.MAX_VALUE - 3, Long.MAX_VALUE), point(Long.MAX_VALUE), "{0..Long.MAX_VALUE-1}");
}
+ @Test
+ public void testDiv() {
+ assertEquals(empty(), empty().div(all(), true));
+ assertEquals(empty(), all().div(empty(), true));
+ assertEquals(empty(), point(1).div(empty(), true));
+ assertEquals(empty(), point(1).div(point(3), true).div(empty(), true));
+ assertEquals(all(), all().div(all(), true));
+ assertEquals(empty(), all().div(point(0), true));
+ assertEquals(all(), all().div(point(1), true));
+ assertEquals(all(), all().div(point(-1), true));
+ assertEquals(point(11), point(110).div(point(10), true));
+
+ checkDiv(range(1, 20), range(1, 5), true, "{0..20}");
+ checkDiv(range(1, 20), range(-5, -1), true, "{-20..0}");
+ checkDiv(range(-20, -1), range(1, 5), true, "{-20..0}");
+ checkDiv(range(-20, -1), range(-5, -1), true, "{0..20}");
+ checkDiv(range(-10, 10), range(2, 4), true, "{-5..5}");
+ checkDiv(range(100, 120), range(-2, 2), true, "{-120..-50, 50..120}");
+ checkDiv(range(Integer.MIN_VALUE, Integer.MIN_VALUE + 20), range(-2, 2), true,
+ "{Integer.MIN_VALUE..-1073741814, 1073741814..2147483648}");
+ checkDiv(range(Integer.MIN_VALUE, Integer.MIN_VALUE + 20), range(-2, 2), false,
+ "{Integer.MIN_VALUE..-1073741814, 1073741814..Integer.MAX_VALUE}");
+ checkDiv(range(Integer.MIN_VALUE, Integer.MIN_VALUE + 20), range(-2, -1), true,
+ "{1073741814..2147483648}");
+ checkDiv(range(Integer.MIN_VALUE, Integer.MIN_VALUE + 20), range(-2, -1), false,
+ "{Integer.MIN_VALUE, 1073741814..Integer.MAX_VALUE}");
+ }
+
+ @Test
+ public void testShr() {
+ assertEquals(empty(), empty().shiftRight(all(), true));
+ assertEquals(empty(), all().shiftRight(empty(), true));
+ assertEquals(all(), all().shiftRight(all(), true));
+ assertEquals(fromType(PsiType.INT), all().shiftRight(point(32), true));
+ assertEquals(fromType(PsiType.SHORT), fromType(PsiType.INT).shiftRight(point(16), false));
+ assertEquals(fromType(PsiType.BYTE), fromType(PsiType.INT).shiftRight(point(24), false));
+ assertEquals(range(-1, 0), fromType(PsiType.INT).shiftRight(point(31), false));
+
+ checkShr(range(-20, 20), point(31), false, "{-1, 0}");
+ checkShr(range(-20, 20), point(31), true, "{-1, 0}");
+ checkShr(range(-20, 20), range(1, 3), true, "{-10..10}");
+ checkShr(range(-20, 20), range(3, 5), true, "{-3..2}");
+ checkShr(range(1000000, 1000020), range(3, 5), true, "{31250..125002}");
+ }
+
+ @Test
+ public void testUShr() {
+ assertEquals(empty(), empty().unsignedShiftRight(all(), true));
+ assertEquals(empty(), all().unsignedShiftRight(empty(), true));
+ assertEquals(all(), all().unsignedShiftRight(all(), true));
+ assertEquals(range(0, 4294967295L), all().unsignedShiftRight(point(32), true));
+ assertEquals(fromType(PsiType.CHAR), fromType(PsiType.INT).unsignedShiftRight(point(16), false));
+ assertEquals(range(0, 255), fromType(PsiType.INT).unsignedShiftRight(point(24), false));
+ assertEquals(range(0, 1), fromType(PsiType.INT).unsignedShiftRight(point(31), false));
+
+ checkUShr(range(-20, 20), point(31), false, "{0, 1}");
+ checkUShr(range(-20, 20), point(31), true, "{0, 8589934591}");
+ checkUShr(range(-20, 20), range(1, 3), true, "{0..10, 2305843009213693949..Long.MAX_VALUE}");
+ checkUShr(range(-20, 20), range(1, 3), false, "{0..10, 536870909..Integer.MAX_VALUE}");
+ checkUShr(range(-20, 20), range(3, 5), true, "{0..2, 576460752303423487..2305843009213693951}");
+ checkUShr(range(-20, 20), range(3, 5), false, "{0..2, 134217727..536870911}");
+ checkUShr(range(1000000, 1000020), range(3, 5), true, "{31250..125002}");
+ }
+
@Test
public void testContains() {
assertTrue(range(0, 10).contains(5));
@@ -385,18 +449,33 @@ public class LongRangeSetTest {
void checkAdd(LongRangeSet addend1, LongRangeSet addend2, boolean isLong, String expected) {
LongRangeSet result = addend1.plus(addend2, isLong);
assertEquals(result, addend2.plus(addend1, isLong)); // commutative
- checkBinOp(addend1, addend2, result, x -> true, isLong ? Long::sum : (a, b) -> (int)(a + b), expected);
+ checkBinOp(addend1, addend2, result, x -> true, isLong ? Long::sum : (a, b) -> (int)(a + b), expected, "+");
}
void checkMod(LongRangeSet dividendRange, LongRangeSet divisorRange, String expected) {
LongRangeSet result = dividendRange.mod(divisorRange);
- checkBinOp(dividendRange, divisorRange, result, divisor -> divisor != 0, (a, b) -> a % b, expected);
+ checkBinOp(dividendRange, divisorRange, result, divisor -> divisor != 0, (a, b) -> a % b, expected, "%");
+ }
+
+ void checkDiv(LongRangeSet dividendRange, LongRangeSet divisorRange, boolean isLong, String expected) {
+ LongRangeSet result = dividendRange.div(divisorRange, isLong);
+ checkBinOp(dividendRange, divisorRange, result, divisor -> divisor != 0, (a, b) -> isLong ? a / b : ((int)a / (int)b), expected, "/");
+ }
+
+ void checkShr(LongRangeSet arg, LongRangeSet shiftSize, boolean isLong, String expected) {
+ LongRangeSet result = arg.shiftRight(shiftSize, isLong);
+ checkBinOp(arg, shiftSize, result, x -> true, (a, b) -> isLong ? a >> b : ((int)a >> (int)b), expected, ">>");
+ }
+
+ void checkUShr(LongRangeSet arg, LongRangeSet shiftSize, boolean isLong, String expected) {
+ LongRangeSet result = arg.unsignedShiftRight(shiftSize, isLong);
+ checkBinOp(arg, shiftSize, result, x -> true, (a, b) -> isLong ? a >>> b : ((int)a >>> (int)b), expected, ">>>");
}
void checkBitwiseAnd(LongRangeSet range1, LongRangeSet range2, String expected) {
LongRangeSet result = range1.bitwiseAnd(range2);
assertEquals(result, range2.bitwiseAnd(range1)); // commutative
- checkBinOp(range1, range2, result, x -> true, (a, b) -> a & b, expected);
+ checkBinOp(range1, range2, result, x -> true, (a, b) -> a & b, expected, "&");
}
void checkBinOp(LongRangeSet op1,
@@ -404,13 +483,14 @@ public class LongRangeSetTest {
LongRangeSet result,
LongPredicate filter,
LongBinaryOperator operator,
- String expected) {
+ String expected,
+ String sign) {
assertEquals(expected, result.toString());
String errors = op1.stream()
.mapToObj(a -> op2.stream()
.filter(filter)
.filter(b -> !result.contains(operator.applyAsLong(a, b)))
- .mapToObj(b -> a + " + " + b + " = " + operator.applyAsLong(a, b)))
+ .mapToObj(b -> a + " " + sign + " " + b + " = " + operator.applyAsLong(a, b)))
.flatMap(Function.identity())
.collect(Collectors.joining("\n"));
if (!errors.isEmpty()) {