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()) {