diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java index 552d279b6b1e..51ab9a7d6ca8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractValue.java @@ -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 mySupplier; private final String myPresentation; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index cabd875f30f2..48768ebf42f9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -41,7 +41,9 @@ public class CustomMethodHandlers { private static final CallMapper CUSTOM_METHOD_HANDLERS = new CallMapper() .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 stringIndexOf(DfaValue qualifier, - DfaMemoryState memState, - DfaValueFactory factory) { - DfaValue length = SpecialField.STRING_LENGTH.createValue(factory, qualifier); + private static List 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))); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index d0dce0106379..24064ca672be 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -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 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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java index b31dc13453dd..e5ae08186214 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/SpecialField.java @@ -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 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)); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java index 62413972312d..c76ecc37c07b 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java @@ -223,4 +223,40 @@ public class LongRangeKnownMethods { } else return ""; } + + void testSetFirst(TreeSet set) { + if (set.first() == 0 && set.size() > 0) { + System.out.println("Impossible"); + } + } + + void testMap(HashMap map) { + if(map.isEmpty() && map.containsKey("xyz")) { + System.out.println("Impossible"); + } + } + + void testMapContainsValue(TreeMap map) { + if(map.containsValue(1) && map.size() < 1) { + System.out.println("Impossible"); + } + } + + void testMapEquals(Map map, Map otherMap) { + if(map.isEmpty() && otherMap.equals(map) && otherMap.containsValue("xyz")) { + System.out.println("Impossible"); + } + } + + void testListIndexOf(List list) { + if(list.size() == 10 && list.indexOf("xyz") == 15) { + System.out.println("Impossible"); + } + } + + void testGetUnknown(List list, int index) { + if(list.get(index).isEmpty() && list.isEmpty()) { + System.out.println("Impossible"); + } + } }