Map contracts; more Collection contracts; minor API changes

This commit is contained in:
Tagir Valeev
2017-04-27 09:36:17 +07:00
parent a9122603fb
commit f4e3523f94
5 changed files with 90 additions and 14 deletions
@@ -41,6 +41,10 @@ public abstract class ContractValue {
return new Argument(index);
}
public ContractValue specialField(SpecialField field) {
return new Spec(this, field);
}
public static ContractValue constant(Object value, PsiType type) {
return new IndependentValue(factory -> factory.getConstFactory().createFromValue(value, type, null), String.valueOf(value));
}
@@ -57,8 +61,8 @@ public abstract class ContractValue {
return IndependentValue.NULL;
}
public static ContractValue specialField(ContractValue qualifier, SpecialField field) {
return new Spec(qualifier, field);
public static ContractValue zero() {
return IndependentValue.ZERO;
}
public static ContractValue condition(ContractValue left, DfaRelationValue.RelationType relation, ContractValue right) {
@@ -103,6 +107,8 @@ public abstract class ContractValue {
static final IndependentValue FALSE = new IndependentValue(factory -> factory.getConstFactory().getFalse(), "false");
static final IndependentValue PRESENT = new IndependentValue(factory -> factory.getOptionalFactory().getOptional(true), "present");
static final IndependentValue ABSENT = new IndependentValue(factory -> factory.getOptionalFactory().getOptional(false), "empty");
static final IndependentValue ZERO =
new IndependentValue(factory -> factory.getConstFactory().createFromValue(0, PsiType.INT, null), "0");
private final Function<DfaValueFactory, DfaValue> mySupplier;
private final String myPresentation;
@@ -41,7 +41,9 @@ public class CustomMethodHandlers {
private static final CallMapper<CustomMethodHandler> CUSTOM_METHOD_HANDLERS = new CallMapper<CustomMethodHandler>()
.register(instanceCall(JAVA_LANG_STRING, "indexOf", "lastIndexOf"),
(args, memState, factory) -> stringIndexOf(args.myQualifier, memState, factory))
(args, memState, factory) -> indexOf(args.myQualifier, memState, factory, SpecialField.STRING_LENGTH))
.register(instanceCall(JAVA_UTIL_LIST, "indexOf", "lastIndexOf"),
(args, memState, factory) -> indexOf(args.myQualifier, memState, factory, SpecialField.COLLECTION_SIZE))
.register(instanceCall(JAVA_LANG_STRING, "equals").parameterCount(1),
(args, memState, factory) -> stringEquals(args, memState, factory, false))
.register(instanceCall(JAVA_LANG_STRING, "equalsIgnoreCase").parameterCount(1),
@@ -105,10 +107,11 @@ public class CustomMethodHandlers {
return applyCondition(memState, trueRelation, DfaUnknownValue.getInstance(), falseRelation, factory.getBoolean(false));
}
private static List<DfaMemoryState> stringIndexOf(DfaValue qualifier,
DfaMemoryState memState,
DfaValueFactory factory) {
DfaValue length = SpecialField.STRING_LENGTH.createValue(factory, qualifier);
private static List<DfaMemoryState> indexOf(DfaValue qualifier,
DfaMemoryState memState,
DfaValueFactory factory,
SpecialField specialField) {
DfaValue length = specialField.createValue(factory, qualifier);
LongRangeSet range = memState.getRange(length);
long maxLen = range == null || range.isEmpty() ? Integer.MAX_VALUE : range.max();
return singleResult(memState, factory.getRangeFactory().create(LongRangeSet.range(-1, maxLen - 1)));
@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*;
import static com.intellij.codeInspection.dataFlow.StandardMethodContract.createConstraintArray;
@@ -38,6 +39,9 @@ import static com.intellij.codeInspection.dataFlow.StandardMethodContract.create
* @author peter
*/
public class HardcodedContracts {
private static final Pattern FIRST_OR_LAST = Pattern.compile("first|last");
private static final Pattern CONTAINS_KEY_VALUE = Pattern.compile("containsKey|containsValue");
public static List<MethodContract> getHardcodedContracts(@NotNull PsiMethod method, @Nullable PsiMethodCallExpression call) {
PsiClass owner = method.getContainingClass();
if (owner == null ||
@@ -96,10 +100,37 @@ public class HardcodedContracts {
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_COLLECTION, PsiType.BOOLEAN, "isEmpty")) {
return SpecialField.COLLECTION_SIZE.getEmptyContracts();
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_COLLECTION, null, "get", PsiType.INT)) {
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_COLLECTION, PsiType.BOOLEAN, "contains", (PsiType)null)) {
return Collections.singletonList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.COLLECTION_SIZE), RelationType.EQ, ContractValue.zero(), FALSE_VALUE));
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_SET, PsiType.BOOLEAN, "equals", (PsiType)null) ||
MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_LIST, PsiType.BOOLEAN, "equals", (PsiType)null)) {
return Collections.singletonList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.COLLECTION_SIZE), RelationType.NE,
ContractValue.argument(0).specialField(SpecialField.COLLECTION_SIZE), FALSE_VALUE));
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT)) {
return Arrays.asList(nonnegativeArgumentContract(0),
specialFieldRangeContract(0, RelationType.LT, SpecialField.COLLECTION_SIZE));
}
else if (MethodUtils.methodMatches(method, "java.util.SortedSet", null, FIRST_OR_LAST)) {
return Collections.singletonList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.COLLECTION_SIZE), RelationType.EQ,
ContractValue.zero(), THROW_EXCEPTION));
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_MAP, PsiType.BOOLEAN, "isEmpty")) {
return SpecialField.MAP_SIZE.getEmptyContracts();
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_MAP, PsiType.BOOLEAN, CONTAINS_KEY_VALUE, (PsiType)null)) {
return Collections.singletonList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.MAP_SIZE), RelationType.EQ, ContractValue.zero(), FALSE_VALUE));
}
else if (MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_MAP, PsiType.BOOLEAN, "equals", (PsiType)null)) {
return Collections.singletonList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.MAP_SIZE), RelationType.NE,
ContractValue.argument(0).specialField(SpecialField.MAP_SIZE), FALSE_VALUE));
}
else if ("org.apache.commons.lang.Validate".equals(className) ||
"org.apache.commons.lang3.Validate".equals(className) ||
"org.springframework.util.Assert".equals(className)) {
@@ -138,12 +169,12 @@ public class HardcodedContracts {
static MethodContract nonnegativeArgumentContract(int argNumber) {
return MethodContract
.singleConditionContract(ContractValue.argument(argNumber), RelationType.LT, ContractValue.constant(0, PsiType.INT), THROW_EXCEPTION);
.singleConditionContract(ContractValue.argument(argNumber), RelationType.LT, ContractValue.zero(), THROW_EXCEPTION);
}
static MethodContract specialFieldRangeContract(int index, RelationType type, SpecialField specialField) {
return MethodContract.singleConditionContract(ContractValue.argument(index), type.getNegated(),
ContractValue.specialField(ContractValue.qualifier(), specialField), THROW_EXCEPTION);
ContractValue.qualifier().specialField(specialField), THROW_EXCEPTION);
}
private static boolean isJunit(String className) {
@@ -40,7 +40,8 @@ public enum SpecialField {
return obj instanceof String ? factory.getConstFactory().createFromValue(((String)obj).length(), PsiType.INT, null) : null;
}
},
COLLECTION_SIZE(CommonClassNames.JAVA_UTIL_COLLECTION, "size", false, LongRangeSet.indexRange());
COLLECTION_SIZE(CommonClassNames.JAVA_UTIL_COLLECTION, "size", false, LongRangeSet.indexRange()),
MAP_SIZE(CommonClassNames.JAVA_UTIL_MAP, "size", false, LongRangeSet.indexRange());
private final String myClassName;
private final String myMethodName;
@@ -123,9 +124,8 @@ public enum SpecialField {
* @return a list of method contracts which equivalent to checking this special field for zero
*/
public List<MethodContract> getEmptyContracts() {
ContractValue thisValue = ContractValue.specialField(ContractValue.qualifier(), this);
ContractValue zeroValue = ContractValue.constant(0, PsiType.INT);
return Arrays.asList(MethodContract.singleConditionContract(thisValue, DfaRelationValue.RelationType.EQ, zeroValue,
ContractValue thisValue = ContractValue.qualifier().specialField(this);
return Arrays.asList(MethodContract.singleConditionContract(thisValue, DfaRelationValue.RelationType.EQ, ContractValue.zero(),
MethodContract.ValueConstraint.TRUE_VALUE),
MethodContract.trivialContract(MethodContract.ValueConstraint.FALSE_VALUE));
}
@@ -223,4 +223,40 @@ public class LongRangeKnownMethods {
}
else return "";
}
void testSetFirst(TreeSet<Integer> set) {
if (set.first() == 0 && <warning descr="Condition 'set.size() > 0' is always 'true' when reached">set.size() > 0</warning>) {
System.out.println("Impossible");
}
}
void testMap(HashMap<String, Integer> map) {
if(<warning descr="Condition 'map.isEmpty() && map.containsKey(\"xyz\")' is always 'false'">map.isEmpty() && <warning descr="Condition 'map.containsKey(\"xyz\")' is always 'false' when reached">map.containsKey("xyz")</warning></warning>) {
System.out.println("Impossible");
}
}
void testMapContainsValue(TreeMap<String, Integer> map) {
if(<warning descr="Condition 'map.containsValue(1) && map.size() < 1' is always 'false'">map.containsValue(1) && <warning descr="Condition 'map.size() < 1' is always 'false' when reached">map.size() < 1</warning></warning>) {
System.out.println("Impossible");
}
}
void testMapEquals(Map<String, String> map, Map<String, String> otherMap) {
if(<warning descr="Condition 'map.isEmpty() && otherMap.equals(map) && otherMap.containsValue(\"xyz\")' is always 'false'">map.isEmpty() && otherMap.equals(map) && <warning descr="Condition 'otherMap.containsValue(\"xyz\")' is always 'false'">otherMap.containsValue("xyz")</warning></warning>) {
System.out.println("Impossible");
}
}
void testListIndexOf(List<String> list) {
if(<warning descr="Condition 'list.size() == 10 && list.indexOf(\"xyz\") == 15' is always 'false'">list.size() == 10 && <warning descr="Condition 'list.indexOf(\"xyz\") == 15' is always 'false' when reached">list.indexOf("xyz") == 15</warning></warning>) {
System.out.println("Impossible");
}
}
void testGetUnknown(List<String> list, int index) {
if(<warning descr="Condition 'list.get(index).isEmpty() && list.isEmpty()' is always 'false'">list.get(index).isEmpty() && <warning descr="Condition 'list.isEmpty()' is always 'false' when reached">list.isEmpty()</warning></warning>) {
System.out.println("Impossible");
}
}
}