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 2e74ae371dac..47ec0d0cebf2 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 @@ -28,15 +28,13 @@ import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; +import com.intellij.psi.util.InheritanceUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FList; import com.siyeh.ig.numeric.UnnecessaryExplicitNumericCastInspection; -import com.siyeh.ig.psiutils.CountingLoop; -import com.siyeh.ig.psiutils.ExpectedTypeUtils; -import com.siyeh.ig.psiutils.ExpressionUtils; -import com.siyeh.ig.psiutils.VariableAccessUtils; +import com.siyeh.ig.psiutils.*; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -1192,17 +1190,16 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { @Nullable 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; + if (TypeUtils.isJavaLangString(expression.getType()) || isAcceptableContextForMathOperation(expression)) return op; return null; } + if (JavaTokenType.MINUS == op && !isAcceptableContextForMathOperation(expression)) return null; return op; } - private static boolean isAcceptableContextForMathOperation(PsiExpression context) { - PsiElement parent = context.getParent(); + private static boolean isAcceptableContextForMathOperation(PsiExpression expression) { + PsiType type = expression.getType(); + PsiElement parent = expression.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) { @@ -1721,6 +1718,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (expression.getOperationTokenType() == JavaTokenType.EXCL) { addInstruction(new NotInstruction()); } + else if (expression.getOperationTokenType() == JavaTokenType.MINUS && (PsiType.INT.equals(type) || PsiType.LONG.equals(type))) { + addInstruction(new PushInstruction(myFactory.getConstFactory().createFromValue(PsiTypesUtil.getDefaultValue(type), type, null), null)); + addInstruction(new SwapInstruction()); + addInstruction(new BinopInstruction(expression.getOperationTokenType(), expression, myProject)); + } else { addInstruction(new PopInstruction()); pushUnknown(); 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 3f239384e396..478656b28ce0 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 @@ -617,14 +617,23 @@ public class StandardInstructionVisitor extends InstructionVisitor { 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(); + result = runner.getFactory().getFactValue(DfaFactType.RANGE, left.plus(right, PsiType.LONG.equals(type))); } } else { 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) { 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 e5eb2822d2d5..6ea884bb8899 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,7 @@ 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, AND, PERC); + private static final TokenSet ourSignificantOperations = TokenSet.create(EQEQ, NE, LT, GT, LE, GE, INSTANCEOF_KEYWORD, PLUS, MINUS, AND, PERC); 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 4f998228428f..96189835fe4d 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 @@ -158,8 +158,38 @@ public abstract class LongRangeSet { @NotNull public abstract LongRangeSet abs(boolean isLong); + /** + * Returns a range which represents all the possible values after applying unary minus + * to the values from this set + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ @NotNull - public abstract LongRangeSet add(LongRangeSet other, boolean isLong); + public abstract LongRangeSet negate(boolean isLong); + + /** + * Returns a range which represents all the possible values after performing an addition between any value from this range + * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. + * Guaranteed to be commutative. + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ + @NotNull + public abstract LongRangeSet plus(LongRangeSet other, boolean isLong); + + /** + * Returns a range which represents all the possible values after performing an addition between any value from this range + * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ + @NotNull + public LongRangeSet minus(LongRangeSet other, boolean isLong) { + return plus(other.negate(isLong), isLong); + } /** * Returns a range which represents all the possible values after applying {@code x & y} operation for @@ -491,7 +521,13 @@ public abstract class LongRangeSet { @NotNull @Override - public LongRangeSet add(LongRangeSet other, boolean isLong) { + public LongRangeSet negate(boolean isLong) { + return this; + } + + @NotNull + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { return this; } @@ -577,13 +613,19 @@ public abstract class LongRangeSet { @NotNull @Override - public LongRangeSet add(LongRangeSet other, boolean isLong) { + public LongRangeSet negate(boolean isLong) { + return myValue == minValue(isLong) ? this : point(-myValue); + } + + @NotNull + @Override + public LongRangeSet plus(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); + return other.plus(this, isLong); } @NotNull @@ -784,22 +826,35 @@ public abstract class LongRangeSet { @NotNull @Override - public LongRangeSet add(LongRangeSet other, boolean isLong) { + public LongRangeSet negate(boolean isLong) { + long minValue = minValue(isLong); + if (myFrom <= minValue) { + if (myTo >= maxValue(isLong)) { + return isLong ? LONG_RANGE : INT_RANGE; + } + return new RangeSet(new long[]{minValue, minValue, -myTo, -(minValue + 1)}); + } + return new Range(-myTo, -myFrom); + } + + @NotNull + @Override + public LongRangeSet plus(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); + return plus(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)); + result = result.union(plus(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) { + private static LongRangeSet plus(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 @@ -991,10 +1046,20 @@ public abstract class LongRangeSet { @NotNull @Override - public LongRangeSet add(LongRangeSet other, boolean isLong) { + public LongRangeSet negate(boolean isLong) { + LongRangeSet result = all(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.subtract(range(myRanges[i], myRanges[i + 1]).negate(isLong)); + } + return all().subtract(result); + } + + @NotNull + @Override + public LongRangeSet plus(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)); + result = result.union(range(myRanges[i], myRanges[i + 1]).plus(other, isLong)); } return result; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangePlusMinus.java similarity index 62% rename from java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java rename to java/java-tests/testData/inspection/dataFlow/fixture/LongRangePlusMinus.java index 8180d90fad92..205982d45004 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeAdd.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangePlusMinus.java @@ -1,6 +1,6 @@ import java.util.*; -public class LongRangeAdd { +public class LongRangePlusMinus { void test(int x, int y) { if (x > 0 && x < 10) { if (y > x + 10 && @@ -39,4 +39,34 @@ public class LongRangeAdd { } } } + + void testMinus(int offset) { + if(offset <= 0) return; + if(offset - 1 >= 0) { + System.out.println("always"); + } + } + + void testMinusInLoop() { + for(int i=0; i<10; i++) { + for(int j=11; j<20; j++) { + if(j - i < 0) { + System.out.println("Impossible"); + } + } + } + } + + void testNegateInLoop(int[] arr) { + int x = 1; + for(int val : arr) { + x = -x; + if (val == x) { + System.out.println("ok"); + if (val > 3) { + System.out.println("Impossible"); + } + } + } + } } 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 731dc1c248d6..6a0ae36b4e86 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowRangeAnalysisTest.java @@ -69,6 +69,6 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase { public void testLongRangeMod() { doTest(); } - public void testLongRangeAdd() { 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 1505de26e806..755e325ca823 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 @@ -23,6 +23,8 @@ import org.junit.Test; import java.util.Random; import java.util.function.Function; +import java.util.function.LongBinaryOperator; +import java.util.function.LongPredicate; import java.util.stream.Collectors; import static com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet.*; @@ -272,6 +274,26 @@ public class LongRangeSetTest { assertEquals("{101..599, 701..1000}", set.abs(false).toString()); } + @Test + public void testNegate() { + assertTrue(empty().negate(true).isEmpty()); + assertEquals(point(Long.MAX_VALUE), point(Long.MIN_VALUE + 1).negate(true)); + assertEquals(point(Long.MIN_VALUE), point(Long.MIN_VALUE).negate(true)); + assertEquals(point(Integer.MIN_VALUE), point(Integer.MIN_VALUE).negate(false)); + assertEquals(point(Integer.MAX_VALUE + 1L), point(Integer.MIN_VALUE).negate(true)); + assertEquals(range(-200, -100), range(100, 200).negate(true)); + assertEquals(range(-200, 1), range(-1, 200).negate(true)); + assertEquals(range(-200, 200), range(-200, 200).negate(false)); + assertEquals(range(-200, 201), range(-201, 200).negate(false)); + assertEquals(all(), all().negate(true)); + assertEquals(range(100, Integer.MAX_VALUE).union(point(Integer.MIN_VALUE)), range(Integer.MIN_VALUE, -100).negate(false)); + assertEquals(point(Long.MAX_VALUE).union(point(Long.MIN_VALUE)), range(Long.MIN_VALUE, Long.MIN_VALUE + 1).negate(true)); + assertEquals(range(100, Integer.MAX_VALUE + 1L), range(Integer.MIN_VALUE, -100).negate(true)); + LongRangeSet set = range(-900, 1000).subtract(range(-800, -600)).subtract(range(-300, 100)).subtract(range(500, 700)); + assertEquals("{-900..-801, -599..-301, 101..499, 701..1000}", set.toString()); + assertEquals("{-1000..-701, -499..-101, 301..599, 801..900}", set.negate(false).toString()); + } + @Test public void testBitwiseAnd() { assertTrue(empty().bitwiseAnd(all()).isEmpty()); @@ -292,13 +314,6 @@ public class LongRangeSetTest { checkBitwiseAnd(range(-30, -20).union(range(20, 33)), point(-10).union(point(10)), "{-32..-26, 0..62}"); } - void checkBitwiseAnd(LongRangeSet range1, LongRangeSet range2, String expected) { - LongRangeSet result = range1.bitwiseAnd(range2); - assertEquals(expected, result.toString()); - assertTrue( - range1.stream().mapToObj(l1 -> range2.stream().map(l2 -> l1 & l2)).flatMapToLong(Function.identity()).allMatch(result::contains)); - } - @Test public void testMod() { assertEquals(empty(), empty().mod(all())); @@ -326,21 +341,6 @@ public class LongRangeSetTest { checkMod(range(Long.MAX_VALUE - 3, Long.MAX_VALUE), point(Long.MAX_VALUE), "{0..9223372036854775806}"); } - void checkMod(LongRangeSet dividendRange, LongRangeSet divisorRange, String expected) { - LongRangeSet result = dividendRange.mod(divisorRange); - assertEquals(expected, result.toString()); - String errors = dividendRange.stream() - .mapToObj(dividend -> divisorRange.stream() - .filter(divisor -> divisor != 0) - .filter(divisor -> !result.contains(dividend % divisor)) - .mapToObj(divisor -> dividend + " % " + divisor + " = " + (dividend % divisor))) - .flatMap(Function.identity()) - .collect(Collectors.joining("\n")); - if (!errors.isEmpty()) { - fail("Expected range " + expected + " is not satisfied:\n" + errors); - } - } - @Test public void testContains() { assertTrue(range(0, 10).contains(5)); @@ -374,26 +374,49 @@ public class LongRangeSetTest { 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)); + assertEquals(intDomain, intDomain.plus(point(20), false)); + assertEquals(intDomain.without(20), intDomain.without(0).plus(point(20), false)); + assertEquals(all().without(20), all().without(0).plus(point(20), true)); + assertEquals(intDomain, range(20, 30).union(range(40, 50)).plus(intDomain, false)); + assertEquals(intDomain, range(Integer.MIN_VALUE, 2).plus(range(-2, Integer.MAX_VALUE), false)); + assertEquals(all(), range(Long.MIN_VALUE, 2).plus(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 + 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); + } + + 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); + } + + 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); + } + + void checkBinOp(LongRangeSet op1, + LongRangeSet op2, + LongRangeSet result, + LongPredicate filter, + LongBinaryOperator operator, + String expected) { 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))) + 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))) .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