Hardcoded contracts for Queue.peek/poll

Fixes IDEA-189790 Incorrect code analysis for peek() in Deque<E>
This commit is contained in:
Tagir Valeev
2018-04-12 16:51:33 +07:00
parent e6749fcfe1
commit 0d09c94eba
4 changed files with 33 additions and 3 deletions
@@ -21,6 +21,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.MethodUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NotNull;
@@ -52,6 +53,8 @@ public class HardcodedContracts {
ContractValue.argument(2), THROW_EXCEPTION)
);
private static final CallMatcher QUEUE_POLL = instanceCall("java.util.Queue", "poll").parameterCount(0);
private static StandardMethodContract standardContract(MethodContract.ValueConstraint returnValue,
MethodContract.ValueConstraint... args) {
return new StandardMethodContract(args, returnValue);
@@ -120,7 +123,11 @@ public class HardcodedContracts {
.register(staticCall(JAVA_UTIL_ARRAYS, "binarySearch", "fill", "parallelPrefix", "parallelSort", "sort", "spliterator", "stream"),
(call, cnt) -> cnt >= 3 ? ARRAY_RANGE_CONTRACTS : null)
.register(staticCall("org.mockito.ArgumentMatchers", "argThat").parameterCount(1),
ContractProvider.single(() -> new StandardMethodContract(new MethodContract.ValueConstraint[] {ANY_VALUE}, ANY_VALUE)));
ContractProvider.single(() -> new StandardMethodContract(new MethodContract.ValueConstraint[]{ANY_VALUE}, ANY_VALUE)))
.register(instanceCall("java.util.Queue", "peek", "poll").parameterCount(0),
(call, paramCount) -> Arrays.asList(MethodContract.singleConditionContract(
ContractValue.qualifier().specialField(SpecialField.COLLECTION_SIZE), RelationType.EQ,
ContractValue.zero(), NULL_VALUE), MethodContract.trivialContract(ANY_VALUE)));
public static List<MethodContract> getHardcodedContracts(@NotNull PsiMethod method, @Nullable PsiMethodCallExpression call) {
PsiClass owner = method.getContainingClass();
@@ -362,6 +369,9 @@ public class HardcodedContracts {
if (JAVA_UTIL_ARRAYS.equals(className)) {
return name.equals("binarySearch") || name.equals("spliterator") || name.equals("stream");
}
if (QUEUE_POLL.methodMatches(method)) {
return false;
}
return true;
}
@@ -0,0 +1,21 @@
import java.util.*;
import java.util.concurrent.*;
import org.jetbrains.annotations.*;
class ThisAsVariable {
void check(Queue<String> queue) {
if(!queue.isEmpty()) {
System.out.println(queue.peek().length() + queue.peek().length());
}
}
void checkPoll(Queue<String> queue) {
if(!queue.isEmpty()) {
System.out.println(queue.poll().length() + queue.poll().<warning descr="Method invocation 'length' may produce 'java.lang.NullPointerException'">length</warning>());
}
}
void notCheck(Queue<String> queue) {
System.out.println(queue.peek().<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>());
}
}
@@ -225,4 +225,5 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testMethodReferenceBoundToNullable() { doTestWithCustomAnnotations(); }
public void testEscapeAnalysis() { doTest(); }
public void testThisAsVariable() { doTest(); }
public void testQueuePeek() { doTest(); }
}
@@ -1826,13 +1826,11 @@
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.Queue E poll()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="mutates" val="&quot;this&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.Queue E remove()'>
<annotation name='org.jetbrains.annotations.Contract'>