DataFlowInspection: specific message for ioobe contracts, messages moved to resources

Fixes IDEA-180501 Confusing warning about list get method
This commit is contained in:
Tagir Valeev
2017-10-25 17:00:33 +07:00
parent d32433b876
commit 1f8341c882
7 changed files with 41 additions and 10 deletions
@@ -33,6 +33,13 @@ public abstract class ContractValue {
abstract DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments);
/**
* @return true if this contract value represents a bounds-checking condition
*/
boolean isBoundCheckingCondition() {
return false;
}
public static ContractValue qualifier() {
return Qualifier.INSTANCE;
}
@@ -160,6 +167,19 @@ public abstract class ContractValue {
myRelationType = type;
}
@Override
boolean isBoundCheckingCondition() {
switch (myRelationType) {
case LE:
case LT:
case GE:
case GT:
return true;
default:
return false;
}
}
@Override
DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) {
return factory.createCondition(myLeft.makeDfaValue(factory, arguments), myRelationType, myRight.makeDfaValue(factory, arguments));
@@ -393,13 +393,21 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
private static void reportAlwaysFailingCalls(ProblemsHolder holder,
DataFlowInstructionVisitor visitor,
HashSet<PsiElement> reportedAnchors) {
for (PsiCall call : visitor.getAlwaysFailingCalls()) {
if (TestUtils.isExceptionExpected(call)) continue;
visitor.getAlwaysFailingCalls().forEach((call, contracts) -> {
if (TestUtils.isExceptionExpected(call)) return;
PsiMethod method = call.resolveMethod();
if (method != null && reportedAnchors.add(call)) {
holder.registerProblem(getElementToHighlight(call), "The call to '#ref' always fails, according to its method contracts");
holder.registerProblem(getElementToHighlight(call), getContractMessage(contracts));
}
});
}
@NotNull
private static String getContractMessage(List<MethodContract> contracts) {
if (contracts.stream().allMatch(mc -> mc.getConditions().stream().allMatch(cv -> cv.isBoundCheckingCondition()))) {
return InspectionsBundle.message("dataflow.message.contract.fail.index");
}
return InspectionsBundle.message("dataflow.message.contract.fail");
}
@NotNull private static PsiElement getElementToHighlight(@NotNull PsiCall call) {
@@ -965,8 +973,9 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
return myOptionalQualifiers;
}
Collection<PsiCall> getAlwaysFailingCalls() {
return StreamEx.ofKeys(myFailingCalls, v -> v).map(MethodCallInstruction::getCallExpression).toList();
Map<PsiCall, List<MethodContract>> getAlwaysFailingCalls() {
return StreamEx.ofKeys(myFailingCalls, v -> v)
.mapToEntry(MethodCallInstruction::getCallExpression, MethodCallInstruction::getContracts).toMap();
}
boolean isAlwaysReturnsNotNull(Instruction[] instructions) {
@@ -26,6 +26,6 @@ public final class ArrayLength {
System.out.println("Impossible");
}
Arrays.fill(x, -1);
Arrays.<warning descr="The call to 'fill' always fails, according to its method contracts">fill</warning>(x, -1, -1, -1);
Arrays.<warning descr="The call to 'fill' always fails as index is out of bounds">fill</warning>(x, -1, -1, -1);
}
}
@@ -1,6 +1,6 @@
public class CustomContracts {
public void testSubstring(String s) {
if (s.<warning descr="The call to 'substring' always fails, according to its method contracts">substring</warning>(-1).length() == 0) {
if (s.<warning descr="The call to 'substring' always fails as index is out of bounds">substring</warning>(-1).length() == 0) {
System.out.println("Oops");
}
}
@@ -46,7 +46,7 @@ public class ForEachOverEmptyCollection {
}
if(!hasItem) {
System.out.println(
list.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>(<warning descr="Condition 'max == null' is always 'true'">max == null</warning> ? 0 : 1));
list.<warning descr="The call to 'get' always fails as index is out of bounds">get</warning>(<warning descr="Condition 'max == null' is always 'true'">max == null</warning> ? 0 : 1));
}
}
}
@@ -196,13 +196,13 @@ public class LongRangeKnownMethods {
void testEmptyListGet(List<String> list) {
if (list.isEmpty()) {
System.out.println(list.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>(0));
System.out.println(list.<warning descr="The call to 'get' always fails as index is out of bounds">get</warning>(0));
}
}
void testBoundError(List<String> list) {
if (list.size() < 10) {
System.out.println(list.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>(10));
System.out.println(list.<warning descr="The call to 'get' always fails as index is out of bounds">get</warning>(10));
}
}
@@ -64,6 +64,8 @@ dataflow.message.npe.field.access=Dereference of <code>#ref</code> #loc may prod
dataflow.message.cce=Casting <code>{0}</code> to <code>#ref</code> #loc may produce <code>java.lang.ClassCastException</code>
dataflow.message.arraystore=Storing element of type <code>{0}</code> to array of <code>{1}</code> elements may produce <code>java.lang.ArrayStoreException</code>
dataflow.message.redundant.instanceof=Condition <code>#ref</code> #loc is redundant and can be replaced with <code>!= null</code>
dataflow.message.contract.fail=The call to '#ref' always fails, according to its method contracts
dataflow.message.contract.fail.index=The call to '#ref' always fails as index is out of bounds
dataflow.message.constant.condition=Condition <code>#ref</code> #loc is always <code>{0}</code>
dataflow.message.constant.condition.when.reached=Condition <code>#ref</code> #loc is always <code>{0}</code> when reached
dataflow.message.loop.on.empty.array=Array <code>#ref</code> is always empty