mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Dataflow: perform ranges addition (part of IDEA-184800)
This commit is contained in:
+19
-3
@@ -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) {
|
||||
|
||||
+13
-1
@@ -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) {
|
||||
|
||||
+85
@@ -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();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import java.util.*;
|
||||
|
||||
public class LongRangeAdd {
|
||||
void test(int x, int y) {
|
||||
if (x > 0 && x < 10) {
|
||||
if (<warning descr="Condition 'y > x + 10 && y < 0' is always 'false'">y > x + 10 &&
|
||||
<warning descr="Condition 'y < 0' is always 'false' when reached">y < 0</warning></warning>){
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testLoop() {
|
||||
for (int i = 0; i < 10; i = i + 1) {
|
||||
if (<warning descr="Condition 'i + 1 == 15' is always 'false'">i + 1 == 15</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
if (<warning descr="Condition 'i + 1 == -10' is always 'false'">i + 1 == -10</warning>) {
|
||||
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 (<warning descr="Condition 'i != 0' is always 'false'">i != 0</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
if (<warning descr="Condition 'j != 0' is always 'false'">j != 0</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
}
|
||||
if (<warning descr="Condition 'i + j == 20' is always 'false'">i + j == 20</warning>) {
|
||||
System.out.println("never");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -68,4 +68,6 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
|
||||
}
|
||||
|
||||
public void testLongRangeMod() { doTest(); }
|
||||
|
||||
public void testLongRangeAdd() { doTest(); }
|
||||
}
|
||||
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user