mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
DFA: back-propagate operations like "x mod c1 == c2"
Fixes IDEA-96216 Missing "Condition always false"
This commit is contained in:
+35
-17
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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);
|
||||
|
||||
+24
@@ -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();
|
||||
|
||||
|
||||
+15
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
@@ -0,0 +1,58 @@
|
||||
import java.util.*;
|
||||
|
||||
public class BackPropagationMod {
|
||||
void testModSeries(int x) {
|
||||
if(x % 2 == 0) {}
|
||||
else if(<warning descr="Condition 'x % 2 != 0' is always 'true'">x % 2 != 0</warning>) {}
|
||||
|
||||
if(x >= 0) {
|
||||
if(x % 3 == 0) {}
|
||||
else if(x % 3 == 1) {}
|
||||
else if(<warning descr="Condition 'x % 3 == 2' is always 'true'">x % 3 == 2</warning>) {}
|
||||
}
|
||||
if(x % 2 == 0) {}
|
||||
else if(x % 2 == 1) {}
|
||||
else if(<warning descr="Condition 'x % 2 == -1' is always 'true'">x % 2 == -1</warning>) {}
|
||||
}
|
||||
|
||||
void testGt(int[] arr) {
|
||||
if (arr.length % 5 >= 2) {
|
||||
|
||||
} else if(arr.length % 5 == 1) {
|
||||
|
||||
} else if(<warning descr="Condition 'arr.length % 5 == 0' is always 'true'">arr.length % 5 == 0</warning>) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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(<warning descr="Condition 'i % 15 == 0' is always 'false'">i % 15 == 0</warning>) {
|
||||
System.out.println("FizzBuzz");
|
||||
} else {
|
||||
System.out.println(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testSign(int x) {
|
||||
if (<warning descr="Condition 'x % 2 == 1 && x < 0' is always 'false'">x % 2 == 1 && <warning descr="Condition 'x < 0' is always 'false' when reached">x < 0</warning></warning>) {}
|
||||
if (<warning descr="Condition 'x % 2 == -1 && x > 0' is always 'false'">x % 2 == -1 && <warning descr="Condition 'x > 0' is always 'false' when reached">x > 0</warning></warning>) {}
|
||||
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 (<warning descr="Condition 'pos % 10 == 0' is always 'false'">pos % 10 == 0</warning>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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(); }
|
||||
}
|
||||
|
||||
+9
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user