Skip to content

Commit 78742c6

Browse files
committed
Add Queue.isEmpty/poll refinement
1 parent 68b7b63 commit 78742c6

3 files changed

Lines changed: 97 additions & 2 deletions

File tree

checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ public class NullnessNoInitAnnotatedTypeFactory
113113
private final ExecutableElement mapGet =
114114
TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv);
115115

116+
/** The Collection.isEmpty method. */
117+
private final ExecutableElement collectionIsEmpty =
118+
TreeUtils.getMethod("java.util.Collection", "isEmpty", 0, processingEnv);
119+
120+
/** The Queue.poll method. */
121+
private final ExecutableElement queuePoll =
122+
TreeUtils.getMethod("java.util.Queue", "poll", 0, processingEnv);
123+
116124
// List is in alphabetical order. If you update it, also update
117125
// ../../../../../../../../docs/manual/nullness-checker.tex
118126
// and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in
@@ -1072,4 +1080,24 @@ private AnnotationMirror ensuresNonNullAnno(String expression) {
10721080
public boolean isMapGet(Node node) {
10731081
return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv());
10741082
}
1083+
1084+
/**
1085+
* Returns true if {@code node} is an invocation of Collection.isEmpty.
1086+
*
1087+
* @param node a CFG node
1088+
* @return true if {@code node} is an invocation of Collection.isEmpty
1089+
*/
1090+
public boolean isCollectionIsEmpty(Node node) {
1091+
return NodeUtils.isMethodInvocation(node, collectionIsEmpty, getProcessingEnv());
1092+
}
1093+
1094+
/**
1095+
* Returns true if {@code node} is an invocation of Queue.poll.
1096+
*
1097+
* @param node a CFG node
1098+
* @return true if {@code node} is an invocation of Queue.poll
1099+
*/
1100+
public boolean isQueuePoll(Node node) {
1101+
return NodeUtils.isMethodInvocation(node, queuePoll, getProcessingEnv());
1102+
}
10751103
}

checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import java.util.List;
4141
import java.util.Map;
42+
import java.util.Queue;
4243

4344
import javax.lang.model.element.AnnotationMirror;
4445
import javax.lang.model.element.ExecutableElement;
@@ -81,6 +82,14 @@ public class NullnessNoInitTransfer
8182
*/
8283
protected final AnnotatedDeclaredType MAP_TYPE;
8384

85+
/**
86+
* Java's Queue interface.
87+
*
88+
* <p>The qualifiers in this type don't matter -- it is not used as a fully-annotated
89+
* AnnotatedDeclaredType, but just passed to asSuper().
90+
*/
91+
protected final AnnotatedDeclaredType QUEUE_TYPE;
92+
8493
/** The type factory for the nullness analysis that was passed to the constructor. */
8594
protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory;
8695

@@ -130,6 +139,14 @@ public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) {
130139
nullnessTypeFactory,
131140
false);
132141

142+
QUEUE_TYPE =
143+
(AnnotatedDeclaredType)
144+
AnnotatedTypeMirror.createType(
145+
TypesUtils.typeFromClass(
146+
Queue.class, analysis.getTypes(), elements),
147+
nullnessTypeFactory,
148+
false);
149+
133150
nonNullAssumptionAfterInvocation =
134151
!analysis.getTypeFactory()
135152
.getChecker()
@@ -464,6 +481,40 @@ public TransferResult<NullnessNoInitValue, NullnessNoInitStore> visitMethodInvoc
464481
}
465482
}
466483

484+
// Handle Collection.isEmpty(), mark receiver as non-empty in the false branch
485+
if (nullnessTypeFactory.isCollectionIsEmpty(n)) {
486+
AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree());
487+
AnnotatedDeclaredType queueType =
488+
AnnotatedTypes.asSuper(nullnessTypeFactory, receiverType, QUEUE_TYPE);
489+
// Only track isEmpty status for Queues, for poll()
490+
if (queueType != null) {
491+
JavaExpression receiverExpr = JavaExpression.fromNode(receiver);
492+
if (CFAbstractStore.canInsertJavaExpression(receiverExpr)) {
493+
NullnessNoInitStore thenStore = result.getThenStore();
494+
NullnessNoInitStore elseStore = result.getElseStore();
495+
elseStore.insertValue(receiverExpr, NONNULL);
496+
return new ConditionalTransferResult<>(
497+
result.getResultValue(), thenStore, elseStore);
498+
}
499+
}
500+
}
501+
502+
// Refine result to @NonNull if n is an invocation of Queue.poll(), the receiver is known to
503+
// be non-empty
504+
// and Queue element type is @NonNull
505+
if (nullnessTypeFactory.isQueuePoll(n)) {
506+
NullnessNoInitStore store = result.getRegularStore();
507+
JavaExpression receiverExpr = JavaExpression.fromNode(receiver);
508+
NullnessNoInitValue receiverValue = store.getValue(receiverExpr);
509+
if (receiverValue != null && receiverValue.getAnnotations().contains(NONNULL)) {
510+
AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree());
511+
if (!isElementTypeNullable(receiverType)) {
512+
makeNonNull(result, n);
513+
refineToNonNull(result);
514+
}
515+
}
516+
}
517+
467518
return result;
468519
}
469520

@@ -485,6 +536,24 @@ private boolean isValueTypeNullable(AnnotatedTypeMirror mapOrSubtype) {
485536
return valueType.hasAnnotation(NULLABLE);
486537
}
487538

539+
/**
540+
* Returns true if queueType's element type (the E type argument to Queue) is @Nullable.
541+
*
542+
* @param queueOrSubtype the Queue type, or a subtype
543+
* @return true if queueType's element type is @Nullable
544+
*/
545+
private boolean isElementTypeNullable(AnnotatedTypeMirror queueOrSubtype) {
546+
AnnotatedDeclaredType queueType =
547+
AnnotatedTypes.asSuper(nullnessTypeFactory, queueOrSubtype, QUEUE_TYPE);
548+
int numTypeArguments = queueType.getTypeArguments().size();
549+
if (numTypeArguments != 1) {
550+
throw new TypeSystemError(
551+
"Wrong number %d of type arguments: %s", numTypeArguments, queueType);
552+
}
553+
AnnotatedTypeMirror elementType = queueType.getTypeArguments().get(0);
554+
return elementType.hasAnnotation(NULLABLE);
555+
}
556+
488557
@Override
489558
public TransferResult<NullnessNoInitValue, NullnessNoInitStore> visitReturn(
490559
ReturnNode n, TransferInput<NullnessNoInitValue, NullnessNoInitStore> in) {

checker/tests/nullness/IsEmptyPoll.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Test case for Issue 399:
22
// https://github.com/typetools/checker-framework/issues/399
33

4-
// @skip-test until the issue is fixed
5-
64
import org.checkerframework.checker.nullness.qual.NonNull;
75
import org.checkerframework.checker.nullness.qual.Nullable;
86

0 commit comments

Comments
 (0)