CustomMethodHandlers: support Math.max, Math.min, Math.abs; LongRangeSet: useless methods removed; more tests

This commit is contained in:
Tagir Valeev
2017-03-16 14:48:40 +07:00
parent 0afb882826
commit e27b0199fc
4 changed files with 244 additions and 53 deletions
@@ -28,9 +28,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
import static com.intellij.psi.CommonClassNames.*;
import static com.intellij.psi.JavaTokenType.*;
import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
/**
* @author Tagir Valeev
@@ -52,7 +52,21 @@ public class CustomMethodHandlers {
.register(instanceCall(JAVA_LANG_STRING, "startsWith").parameterCount(1),
(qualifier, args, memState, factory) -> stringStartsEnds(qualifier, args, memState, factory, false))
.register(instanceCall(JAVA_LANG_STRING, "endsWith").parameterCount(1),
(qualifier, args, memState, factory) -> stringStartsEnds(qualifier, args, memState, factory, true));
(qualifier, args, memState, factory) -> stringStartsEnds(qualifier, args, memState, factory, true))
.register(anyOf(staticCall(JAVA_LANG_MATH, "max").parameterTypes("int", "int"),
staticCall(JAVA_LANG_MATH, "max").parameterTypes("long", "long"),
staticCall(JAVA_LANG_INTEGER, "max").parameterTypes("int", "int"),
staticCall(JAVA_LANG_LONG, "max").parameterTypes("long", "long")),
(qualifier, args, memState, factory) -> mathMinMax(args, memState, factory, true))
.register(anyOf(staticCall(JAVA_LANG_MATH, "min").parameterTypes("int", "int"),
staticCall(JAVA_LANG_MATH, "min").parameterTypes("long", "long"),
staticCall(JAVA_LANG_INTEGER, "min").parameterTypes("int", "int"),
staticCall(JAVA_LANG_LONG, "min").parameterTypes("long", "long")),
(qualifier, args, memState, factory) -> mathMinMax(args, memState, factory, false))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("int"),
(qualifier, args, memState, factory) -> mathAbs(args, memState, factory, false))
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
(qualifier, args, memState, factory) -> mathAbs(args, memState, factory, true));
public static CustomMethodHandler find(PsiMethodCallExpression call) {
return CUSTOM_METHOD_HANDLERS.mapFirst(call);
@@ -116,6 +130,25 @@ public class CustomMethodHandlers {
return applyCondition(memState, trueRelation, factory.getBoolean(true), falseRelation, factory.getBoolean(false));
}
private static List<DfaMemoryState> mathMinMax(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean max) {
if(args == null || args.length != 2) return Collections.emptyList();
LongRangeSet first = memState.getRange(args[0]);
LongRangeSet second = memState.getRange(args[1]);
if (first == null || second == null || first.isEmpty() || second.isEmpty()) return Collections.emptyList();
LongRangeSet domain = max ? LongRangeSet.range(Math.max(first.min(), second.min()), Long.MAX_VALUE)
: LongRangeSet.range(Long.MIN_VALUE, Math.min(first.max(), second.max()));
LongRangeSet result = first.union(second).intersect(domain);
return singleResult(memState, factory.getRangeFactory().create(result));
}
private static List<DfaMemoryState> mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
DfaValue arg = ArrayUtil.getFirstElement(args);
if(arg == null) return Collections.emptyList();
LongRangeSet range = memState.getRange(arg);
if (range == null) return Collections.emptyList();
return singleResult(memState, factory.getRangeFactory().create(range.abs(isLong)));
}
private static List<DfaMemoryState> singleResult(DfaMemoryState state, DfaValue value) {
state.push(value);
return Collections.singletonList(state);
@@ -23,6 +23,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
/**
* An immutable set of long values optimized for small number of ranges.
@@ -40,27 +42,7 @@ public abstract class LongRangeSet {
*/
public abstract LongRangeSet subtract(LongRangeSet other);
public LongRangeSet gt(long value) {
return subtract(range(Long.MIN_VALUE, value));
}
public LongRangeSet ge(long value) {
return value == Long.MIN_VALUE ? this : subtract(range(Long.MIN_VALUE, value - 1));
}
public LongRangeSet lt(long value) {
return subtract(range(value, Long.MAX_VALUE));
}
public LongRangeSet le(long value) {
return value == Long.MAX_VALUE ? this : subtract(range(value + 1, Long.MAX_VALUE));
}
public LongRangeSet eq(long value) {
return contains(value) ? point(value) : Empty.EMPTY;
}
public LongRangeSet ne(long value) {
public LongRangeSet without(long value) {
return subtract(point(value));
}
@@ -144,8 +126,8 @@ public abstract class LongRangeSet {
}
if (JavaTokenType.NE.equals(relation)) {
long min = min();
if (min == max()) return Range.LONG_RANGE.subtract(this);
return Range.LONG_RANGE;
if (min == max()) return all().without(min);
return all();
}
if (JavaTokenType.GT.equals(relation)) {
long min = min();
@@ -164,6 +146,22 @@ public abstract class LongRangeSet {
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
*
* @param isLong whether {@link Math#abs(long)} is applied
* @return a new range
*/
public abstract LongRangeSet abs(boolean isLong);
/**
* Returns a stream of all values from this range. Be careful: could be huge
*
* @return a new stream
*/
public abstract LongStream stream();
/**
* @return an empty set
*/
@@ -171,6 +169,13 @@ public abstract class LongRangeSet {
return Empty.EMPTY;
}
/**
* @return a set containing all possible long values
*/
public static LongRangeSet all() {
return Range.LONG_RANGE;
}
/**
* Creates a set containing single given value
*
@@ -215,6 +220,10 @@ public abstract class LongRangeSet {
return from == to ? String.valueOf(from) : from + (to - from == 1 ? ", " : "..") + to;
}
static long minValue(boolean isLong) {
return isLong ? Long.MIN_VALUE : Integer.MIN_VALUE;
}
/**
* @return LongRangeSet describing possible array or string indices (from 0 to Integer.MAX_VALUE)
*/
@@ -248,7 +257,7 @@ public abstract class LongRangeSet {
return Range.INT_RANGE;
}
if (type.equals(PsiType.LONG)) {
return Range.LONG_RANGE;
return all();
}
}
return null;
@@ -309,6 +318,16 @@ public abstract class LongRangeSet {
return other.isEmpty();
}
@Override
public LongRangeSet abs(boolean isLong) {
return this;
}
@Override
public LongStream stream() {
return LongStream.empty();
}
@Override
long[] asRanges() {
return new long[0];
@@ -372,6 +391,16 @@ public abstract class LongRangeSet {
return other.isEmpty() || equals(other);
}
@Override
public LongRangeSet abs(boolean isLong) {
return myValue >= 0 || myValue == minValue(isLong) ? this : point(-myValue);
}
@Override
public LongStream stream() {
return LongStream.of(myValue);
}
@Override
long[] asRanges() {
return new long[] {myValue, myValue};
@@ -509,6 +538,35 @@ public abstract class LongRangeSet {
return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo;
}
@Override
public LongRangeSet abs(boolean isLong) {
if (myFrom >= 0) return this;
long minValue = minValue(isLong);
long low = myFrom, hi = myTo;
if (low <= minValue) {
low = minValue + 1;
}
if (myTo <= 0) {
hi = -low;
low = -myTo;
}
else {
hi = Math.max(-low, hi);
low = 0;
}
if (myFrom <= minValue) {
return new RangeSet(new long[]{minValue, minValue, low, hi});
}
else {
return new Range(low, hi);
}
}
@Override
public LongStream stream() {
return LongStream.rangeClosed(myFrom, myTo);
}
@Override
long[] asRanges() {
return new long[] {myFrom, myTo};
@@ -572,7 +630,7 @@ public abstract class LongRangeSet {
if (other instanceof Point || other instanceof Range) {
return other.intersect(this);
}
return subtract(Range.LONG_RANGE.subtract(other));
return subtract(all().subtract(other));
}
@Override
@@ -626,6 +684,22 @@ public abstract class LongRangeSet {
return other.subtract(this).isEmpty();
}
@Override
public LongRangeSet abs(boolean isLong) {
LongRangeSet result = all();
for (int i = 0; i < myRanges.length; i += 2) {
result = result.subtract(range(myRanges[i], myRanges[i + 1]).abs(isLong));
}
return all().subtract(result);
}
@Override
public LongStream stream() {
return IntStream.range(0, myRanges.length / 2)
.mapToObj(idx -> LongStream.rangeClosed(myRanges[idx * 2], myRanges[idx * 2 + 1]))
.reduce(LongStream::concat).orElseGet(LongStream::empty);
}
@Override
long[] asRanges() {
return myRanges;
@@ -80,8 +80,8 @@ public class LongRangeKnownMethods {
}
void testEqualsIgnoreCase(String s) {
if(!s.equalsIgnoreCase("xyz") || !s.isEmpty()) {
System.out.println("Always");
if(s.equalsIgnoreCase("xyz") && s.isEmpty()) {
System.out.println("Never");
}
}
@@ -91,4 +91,62 @@ public class LongRangeKnownMethods {
System.out.println("Impossible");
}
}
void testMax(int x) {
x = Math.max(x, 0);
if (<warning descr="Condition 'x > -1' is always 'true'">x > -1</warning>) {
System.out.println("Always");
}
if (x > 0) {
System.out.println("Not always");
}
}
void testMin(long x, long y) {
if (x < 10 && y > 10) {
y = Long.min(x, y);
if (<warning descr="Condition 'y > 20' is always 'false'">y > 20</warning>) {
System.out.println("Impossible");
}
}
if (y > 20) {
System.out.println("Possible");
}
}
void testMinMax(List<String> rows) {
int start = Integer.MAX_VALUE;
int end = -1;
for (int i = 0; i < rows.size(); i++) {
String row = rows.get(i);
if (!row.isEmpty()) {
start = Math.min(start, i);
end = Math.max(end, i);
}
}
if(end >= 0 && start < Integer.MAX_VALUE) {
System.out.println("Ok");
}
}
void testAbs(long x, int y) {
x = Math.abs(x);
y = Math.abs(y);
if (x == Long.MIN_VALUE) {
System.out.println("possible");
}
if (<warning descr="Condition 'x == Long.MIN_VALUE + 1' is always 'false'">x == Long.MIN_VALUE + 1</warning>) {
System.out.println("impossible");
}
if (<warning descr="Condition 'x == Integer.MIN_VALUE' is always 'false'">x == Integer.MIN_VALUE</warning>) {
System.out.println("impossible");
}
if (y == Integer.MIN_VALUE) {
System.out.println("possible");
} else if(<warning descr="Condition 'y < 0' is always 'false'">y < 0</warning>) {
System.out.println("impossible");
}
}
}
@@ -73,41 +73,36 @@ public class LongRangeSetTest {
assertTrue(point(10).subtract(range(-10, 20)).isEmpty());
assertTrue(point(10).subtract(range(-10, 10)).isEmpty());
assertEquals("{0..20}", range(0, 20).lt(30).toString());
assertEquals("{0..19}", range(0, 20).lt(20).toString());
assertEquals("{0..18}", range(0, 20).lt(19).toString());
assertEquals("{0}", range(0, 20).lt(1).toString());
assertTrue(range(0, 20).lt(0).isEmpty());
assertEquals("{0..20}", range(0, 20).subtract(range(30, Long.MAX_VALUE)).toString());
assertEquals("{0..19}", range(0, 20).subtract(range(20, Long.MAX_VALUE)).toString());
assertEquals("{0..18}", range(0, 20).subtract(range(19, Long.MAX_VALUE)).toString());
assertEquals("{0}", range(0, 20).subtract(range(1, Long.MAX_VALUE)).toString());
assertTrue(range(0, 20).subtract(range(0, Long.MAX_VALUE)).isEmpty());
LongRangeSet fullRange = range(Long.MIN_VALUE, Long.MAX_VALUE);
assertEquals("{-9223372036854775808}", fullRange.le(Long.MIN_VALUE).toString());
assertEquals(fullRange, fullRange.le(Long.MAX_VALUE));
assertEquals("{9223372036854775807}", fullRange.ge(Long.MAX_VALUE).toString());
assertEquals(fullRange, fullRange.ge(Long.MIN_VALUE));
assertTrue(fullRange.gt(Long.MAX_VALUE).isEmpty());
assertEquals(indexRange(), fromType(PsiType.INT).gt(-1));
assertTrue(fullRange.subtract(fullRange).isEmpty());
assertEquals(point(10), fullRange.eq(10));
assertTrue(range(30, 50).eq(10).isEmpty());
assertEquals("{-9223372036854775808}", all().subtract(range(Long.MIN_VALUE + 1, Long.MAX_VALUE)).toString());
assertEquals("{9223372036854775807}", all().subtract(range(Long.MIN_VALUE, Long.MAX_VALUE - 1)).toString());
assertTrue(all().subtract(range(Long.MIN_VALUE, Long.MAX_VALUE)).isEmpty());
assertEquals(indexRange(), fromType(PsiType.INT).subtract(range(Long.MIN_VALUE, (long)-1)));
assertTrue(all().subtract(all()).isEmpty());
}
@Test
public void testSets() {
assertEquals("{0..9, 11..20}", range(0, 20).ne(10).toString());
assertEquals("{0..9, 11..20}", range(0, 20).without(10).toString());
assertEquals("{0, 20}", range(0, 20).subtract(range(1, 19)).toString());
assertEquals("{0, 1, 19, 20}", range(0, 20).subtract(range(2, 18)).toString());
assertEquals("{0..9, 12..20}", range(0, 20).ne(10).ne(11).toString());
assertEquals("{0..9, 12..14, 16..20}", range(0, 20).ne(10).ne(11).ne(15).toString());
assertEquals("{0, 4..20}", range(0, 20).ne(3).ne(2).ne(1).toString());
assertEquals("{4..20}", range(0, 20).ne(3).ne(2).ne(1).ne(0).toString());
assertEquals("{0..9, 12..20}", range(0, 20).without(10).without(11).toString());
assertEquals("{0..9, 12..14, 16..20}", range(0, 20).without(10).without(11).without(15).toString());
assertEquals("{0, 4..20}", range(0, 20).without(3).without(2).without(1).toString());
assertEquals("{4..20}", range(0, 20).without(3).without(2).without(1).without(0).toString());
assertEquals("{0..2, 5..15, 19, 20}",
range(0, 20).subtract(range(3, 18).subtract(range(5, 15))).toString());
LongRangeSet first = fromType(PsiType.CHAR).ne(45);
LongRangeSet second = fromType(PsiType.CHAR).ne(32).ne(40).ne(44).ne(45).ne(46).ne(58).ne(59).ne(61);
LongRangeSet first = fromType(PsiType.CHAR).without(45);
LongRangeSet second =
fromType(PsiType.CHAR).without(32).without(40).without(44).without(45).without(46).without(58).without(59).without(61);
assertEquals("{0..44, 46..65535}", first.toString());
assertEquals("{0..31, 33..39, 41..43, 47..57, 60, 62..65535}", second.toString());
assertEquals("{32, 40, 44, 46, 58, 59, 61}", first.subtract(second).toString());
@@ -207,6 +202,18 @@ public class LongRangeSetTest {
}
assertEquals(message, intersection, right.subtract(fromType(PsiType.LONG).subtract(left)));
assertEquals(message, intersection, left.subtract(fromType(PsiType.LONG).subtract(right)));
intersection.stream().limit(1000).forEach(e -> {
assertTrue(left.contains(e));
assertTrue(right.contains(e));
});
lDiff.stream().limit(1000).forEach(e -> {
assertTrue(left.contains(e));
assertFalse(right.contains(e));
});
rDiff.stream().limit(1000).forEach(e -> {
assertFalse(left.contains(e));
assertTrue(right.contains(e));
});
switch (r.nextInt(3)) {
case 0:
data[idx] = lDiff;
@@ -242,4 +249,23 @@ public class LongRangeSetTest {
assertEquals(fromType(PsiType.LONG), range(100, 200).fromRelation(JavaTokenType.NE));
assertEquals("{-9223372036854775808..99, 101..9223372036854775807}", point(100).fromRelation(JavaTokenType.NE).toString());
}
@Test
public void testAbs() {
assertTrue(empty().abs(true).isEmpty());
assertEquals(point(Long.MAX_VALUE), point(Long.MIN_VALUE + 1).abs(true));
assertEquals(point(Long.MIN_VALUE), point(Long.MIN_VALUE).abs(true));
assertEquals(point(Integer.MIN_VALUE), point(Integer.MIN_VALUE).abs(false));
assertEquals(point(Integer.MAX_VALUE + 1L), point(Integer.MIN_VALUE).abs(true));
assertEquals(range(100, 200), range(100, 200).abs(true));
assertEquals(range(0, 200), range(-1, 200).abs(true));
assertEquals(range(0, 200), range(-200, 200).abs(false));
assertEquals(range(0, 201), range(-201, 200).abs(false));
assertEquals(range(0, Long.MAX_VALUE).union(point(Long.MIN_VALUE)), all().abs(true));
assertEquals(range(100, Integer.MAX_VALUE).union(point(Integer.MIN_VALUE)), range(Integer.MIN_VALUE, -100).abs(false));
assertEquals(range(100, Integer.MAX_VALUE + 1L), range(Integer.MIN_VALUE, -100).abs(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("{101..599, 701..1000}", set.abs(false).toString());
}
}