From 1ccfb9dc0de0619dd9bf1eddf940b4986098c3bd Mon Sep 17 00:00:00 2001 From: Nicholas Hioe Date: Sat, 21 Mar 2026 17:56:43 -0400 Subject: [PATCH 1/4] Add Queue.isEmpty/poll refinement --- .../NullnessNoInitAnnotatedTypeFactory.java | 28 +++++++++ .../nullness/NullnessNoInitTransfer.java | 63 +++++++++++++++++++ checker/tests/nullness/IsEmptyPoll.java | 2 - 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index befbe2fb9937..2419e4366fda 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -113,6 +113,14 @@ public class NullnessNoInitAnnotatedTypeFactory private final ExecutableElement mapGet = TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); + /** The Collection.isEmpty method. */ + private final ExecutableElement collectionIsEmpty = + TreeUtils.getMethod("java.util.Collection", "isEmpty", 0, processingEnv); + + /** The Queue.poll method. */ + private final ExecutableElement queuePoll = + TreeUtils.getMethod("java.util.Queue", "poll", 0, processingEnv); + // List is in alphabetical order. If you update it, also update // ../../../../../../../../docs/manual/nullness-checker.tex // and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in @@ -1072,4 +1080,24 @@ private AnnotationMirror ensuresNonNullAnno(String expression) { public boolean isMapGet(Node node) { return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); } + + /** + * Returns true if {@code node} is an invocation of Collection.isEmpty. + * + * @param node a CFG node + * @return true if {@code node} is an invocation of Collection.isEmpty + */ + public boolean isCollectionIsEmpty(Node node) { + return NodeUtils.isMethodInvocation(node, collectionIsEmpty, getProcessingEnv()); + } + + /** + * Returns true if {@code node} is an invocation of Queue.poll. + * + * @param node a CFG node + * @return true if {@code node} is an invocation of Queue.poll + */ + public boolean isQueuePoll(Node node) { + return NodeUtils.isMethodInvocation(node, queuePoll, getProcessingEnv()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java index 5eb2478f2458..7edb93e2c0ee 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Map; +import java.util.Queue; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; @@ -81,6 +82,14 @@ public class NullnessNoInitTransfer */ protected final AnnotatedDeclaredType MAP_TYPE; + /** + * Java's Queue interface. + * + *

The qualifiers in this type don't matter -- it is not used as a fully-annotated + * AnnotatedDeclaredType, but just passed to asSuper(). + */ + protected final AnnotatedDeclaredType QUEUE_TYPE; + /** The type factory for the nullness analysis that was passed to the constructor. */ protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory; @@ -130,6 +139,14 @@ public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) { nullnessTypeFactory, false); + QUEUE_TYPE = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + TypesUtils.typeFromClass( + Queue.class, analysis.getTypes(), elements), + nullnessTypeFactory, + false); + nonNullAssumptionAfterInvocation = !analysis.getTypeFactory() .getChecker() @@ -464,6 +481,34 @@ public TransferResult visitMethodInvoc } } + // Handle Collection.isEmpty(), mark receiver as non-empty in the false branch + if (nullnessTypeFactory.isCollectionIsEmpty(n)) { + JavaExpression receiverExpr = JavaExpression.fromNode(receiver); + if (CFAbstractStore.canInsertJavaExpression(receiverExpr)) { + NullnessNoInitStore thenStore = result.getThenStore(); + NullnessNoInitStore elseStore = result.getElseStore(); + elseStore.insertValue(receiverExpr, NONNULL); + return new ConditionalTransferResult<>( + result.getResultValue(), thenStore, elseStore); + } + } + + // Refine result to @NonNull if n is an invocation of Queue.poll(), the receiver is known to + // be non-empty + // and Queue element type is @NonNull + if (nullnessTypeFactory.isQueuePoll(n)) { + NullnessNoInitStore store = result.getRegularStore(); + JavaExpression receiverExpr = JavaExpression.fromNode(receiver); + NullnessNoInitValue receiverValue = store.getValue(receiverExpr); + if (receiverValue != null && receiverValue.getAnnotations().contains(NONNULL)) { + AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); + if (!isElementTypeNullable(receiverType)) { + makeNonNull(result, n); + refineToNonNull(result); + } + } + } + return result; } @@ -485,6 +530,24 @@ private boolean isValueTypeNullable(AnnotatedTypeMirror mapOrSubtype) { return valueType.hasAnnotation(NULLABLE); } + /** + * Returns true if queueType's element type (the E type argument to Queue) is @Nullable. + * + * @param queueOrSubtype the Queue type, or a subtype + * @return true if queueType's element type is @Nullable + */ + private boolean isElementTypeNullable(AnnotatedTypeMirror queueOrSubtype) { + AnnotatedDeclaredType queueType = + AnnotatedTypes.asSuper(nullnessTypeFactory, queueOrSubtype, QUEUE_TYPE); + int numTypeArguments = queueType.getTypeArguments().size(); + if (numTypeArguments != 1) { + throw new TypeSystemError( + "Wrong number %d of type arguments: %s", numTypeArguments, queueType); + } + AnnotatedTypeMirror elementType = queueType.getTypeArguments().get(0); + return elementType.hasAnnotation(NULLABLE); + } + @Override public TransferResult visitReturn( ReturnNode n, TransferInput in) { diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index 30f618aca32e..5238048529a7 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -1,8 +1,6 @@ // Test case for Issue 399: // https://github.com/typetools/checker-framework/issues/399 -// @skip-test until the issue is fixed - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; From 29323d63388561dc497fd353f4901ab1b2262c24 Mon Sep 17 00:00:00 2001 From: Nicholas Hioe Date: Fri, 27 Mar 2026 00:13:16 -0400 Subject: [PATCH 2/4] Add support for queue in store --- .../checker/nullness/NullnessNoInitStore.java | 91 +++++- .../nullness/NullnessNoInitTransfer.java | 35 +-- .../nullness-extra/issue5174/Issue5174.out | 288 ++++++++++++------ checker/tests/nullness/IsEmptyPoll.java | 86 ++++++ docs/CHANGELOG.md | 2 + 5 files changed, 387 insertions(+), 115 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java index 27fccb002789..80213f1718a3 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java @@ -1,18 +1,30 @@ package org.checkerframework.checker.nullness; +import com.sun.source.tree.MethodInvocationTree; + import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; +import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer; import org.checkerframework.dataflow.expression.FieldAccess; +import org.checkerframework.dataflow.expression.JavaExpression; +import org.checkerframework.dataflow.util.PurityUtils; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.qual.MonotonicQualifier; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.javacutil.TreeUtils; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.ExecutableElement; /** * In addition to the base class behavior, tracks whether {@link PolyNull} is known to be {@link @@ -40,6 +52,9 @@ public class NullnessNoInitStore extends CFAbstractStore initializedFields; + /** Receivers that are currently known to be non-empty queues. */ + protected Set nonEmptyQueueReceivers; + /** * Create a NullnessStore. * @@ -53,6 +68,7 @@ public NullnessNoInitStore( super(analysis, sequentialSemantics); isPolyNullNonNull = false; isPolyNullNull = false; + nonEmptyQueueReceivers = new HashSet<>(); } /** @@ -67,6 +83,72 @@ public NullnessNoInitStore(NullnessNoInitStore s) { if (s.initializedFields != null) { initializedFields = s.initializedFields; } + nonEmptyQueueReceivers = new HashSet<>(s.nonEmptyQueueReceivers); + } + + /** + * Marks the receiver as a queue known to be non-empty. + * + * @param receiver a queue receiver expression + */ + public void markQueueAsNonEmpty(JavaExpression receiver) { + nonEmptyQueueReceivers.add(receiver); + } + + /** + * Returns true if the receiver is known to be a non-empty queue. + * + * @param receiver a queue receiver expression + * @return true if the receiver is known to be a non-empty queue + */ + public boolean isQueueNonEmpty(JavaExpression receiver) { + return nonEmptyQueueReceivers.contains(receiver); + } + + @Override + public void updateForAssignment(Node n, @Nullable NullnessNoInitValue val) { + super.updateForAssignment(n, val); + JavaExpression expr = JavaExpression.fromNode(n); + if (expr != null) { + nonEmptyQueueReceivers.removeIf( + receiver -> receiver.containsSyntacticEqualJavaExpression(expr)); + } + } + + @Override + public void updateForMethodCall( + MethodInvocationNode methodInvocationNode, + GenericAnnotatedTypeFactory + atypeFactory, + NullnessNoInitValue val) { + super.updateForMethodCall(methodInvocationNode, atypeFactory, val); + + // Invalidate any non-empty queue information for side-effecting method calls. + MethodInvocationTree tree = methodInvocationNode.getTree(); + ExecutableElement method = TreeUtils.elementFromUse(tree); + boolean hasSideEffect = + !(atypeFactory.isSideEffectFree(method) + || PurityUtils.isSideEffectFree(atypeFactory, method)); + if (hasSideEffect) { + JavaExpression receiverExpr = + JavaExpression.fromNode(methodInvocationNode.getTarget().getReceiver()); + if (receiverExpr != null) { + nonEmptyQueueReceivers.removeIf( + queueReceiver -> + queueReceiver.containsSyntacticEqualJavaExpression(receiverExpr)); + } + + // Invalidate if queue is passed as an arguments, which makes it mutable. + List arguments = methodInvocationNode.getArguments(); + for (Node arg : arguments) { + JavaExpression argExpr = JavaExpression.fromNode(arg); + if (argExpr != null) { + nonEmptyQueueReceivers.removeIf( + queueReceiver -> + queueReceiver.containsSyntacticEqualJavaExpression(argExpr)); + } + } + } } @Override @@ -118,6 +200,8 @@ public NullnessNoInitStore leastUpperBound(NullnessNoInitStore other) { NullnessNoInitStore lub = super.leastUpperBound(other); lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull; lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull; + lub.nonEmptyQueueReceivers = new HashSet<>(nonEmptyQueueReceivers); + lub.nonEmptyQueueReceivers.retainAll(other.nonEmptyQueueReceivers); return lub; } @@ -131,7 +215,8 @@ protected boolean supersetOf(CFAbstractStore visitThrow( @Override public TransferResult visitMethodInvocation( MethodInvocationNode n, TransferInput in) { + Node receiver = n.getTarget().getReceiver(); + JavaExpression receiverExpr = JavaExpression.fromNode(receiver); + // Capture queue non-emptiness fact before superclass mutates the store when handling side + // effects. + boolean isNonEmptyQueuePoll = + nullnessTypeFactory.isQueuePoll(n) + && receiverExpr != null + && in.getRegularStore().isQueueNonEmpty(receiverExpr); + TransferResult result = super.visitMethodInvocation(n, in); @@ -437,7 +446,6 @@ public TransferResult visitMethodInvoc boolean isMethodSideEffectFree = nullnessTypeFactory.isSideEffectFree(method) || PurityUtils.isSideEffectFree(nullnessTypeFactory, method); - Node receiver = n.getTarget().getReceiver(); if (nonNullAssumptionAfterInvocation || isMethodSideEffectFree || !JavaExpression.fromNode(receiver).isAssignableByOtherCode()) { @@ -481,31 +489,24 @@ public TransferResult visitMethodInvoc } } - // Handle Collection.isEmpty(), mark receiver as non-empty in the false branch + // Handle Collection.isEmpty(): mark receiver as non-empty in the false branch. if (nullnessTypeFactory.isCollectionIsEmpty(n)) { - JavaExpression receiverExpr = JavaExpression.fromNode(receiver); - if (CFAbstractStore.canInsertJavaExpression(receiverExpr)) { + if (receiverExpr != null) { NullnessNoInitStore thenStore = result.getThenStore(); NullnessNoInitStore elseStore = result.getElseStore(); - elseStore.insertValue(receiverExpr, NONNULL); + elseStore.markQueueAsNonEmpty(receiverExpr); return new ConditionalTransferResult<>( result.getResultValue(), thenStore, elseStore); } } // Refine result to @NonNull if n is an invocation of Queue.poll(), the receiver is known to - // be non-empty - // and Queue element type is @NonNull - if (nullnessTypeFactory.isQueuePoll(n)) { - NullnessNoInitStore store = result.getRegularStore(); - JavaExpression receiverExpr = JavaExpression.fromNode(receiver); - NullnessNoInitValue receiverValue = store.getValue(receiverExpr); - if (receiverValue != null && receiverValue.getAnnotations().contains(NONNULL)) { - AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); - if (!isElementTypeNullable(receiverType)) { - makeNonNull(result, n); - refineToNonNull(result); - } + // be non-empty, and Queue element type is @NonNull + if (isNonEmptyQueuePoll) { + AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); + if (!isElementTypeNullable(receiverType)) { + makeNonNull(result, n); + refineToNonNull(result); } } diff --git a/checker/tests/nullness-extra/issue5174/Issue5174.out b/checker/tests/nullness-extra/issue5174/Issue5174.out index c9dd864292aa..6d1b49138901 100644 --- a/checker/tests/nullness-extra/issue5174/Issue5174.out +++ b/checker/tests/nullness-extra/issue5174/Issue5174.out @@ -134,7 +134,8 @@ Before: InitializationStore#22( Before: NullnessNoInitStore#32( isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -142,7 +143,8 @@ Before: NullnessNoInitStore#32( Before: NullnessNoInitStore#32( isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ "" [ StringLiteral ] > NV{@NonNull, String, poly nn/n=f/f} (this).sf = "" [ Assignment ] > NV{@NonNull, String, poly nn/n=f/f} @@ -151,7 +153,8 @@ Before: NullnessNoInitStore#32( Before: NullnessNoInitStore#33( Issue5174Super.sf > NV{@NonNull, String, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 7 -> 8 @@ -164,7 +167,8 @@ Before: NullnessNoInitStore#37( Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.f > NV{, S, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -175,7 +179,8 @@ Before: NullnessNoInitStore#37( Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.f > NV{, S, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ in [ LocalVariable ] > NV{, S, poly nn/n=f/f} return in [ Return ] > NV{@NonNull, boolean, poly nn/n=f/f} @@ -187,7 +192,8 @@ Before: NullnessNoInitStore#38( Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.f > NV{, S, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 13 -> 14 @@ -203,7 +209,8 @@ Before: NullnessNoInitStore#42( this > NV{@NonNull, Issue5174Super, poly nn/n=f/f} Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -213,7 +220,8 @@ Before: NullnessNoInitStore#42( this > NV{@NonNull, Issue5174Super, poly nn/n=f/f} Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this) [ ImplicitThis ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} (this). [ MethodAccess ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} @@ -224,7 +232,8 @@ Before: NullnessNoInitStore#43( this > NV{@NonNull, Issue5174Super, poly nn/n=f/f} Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this).() [ MethodInvocation ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -235,14 +244,16 @@ Before: then=NullnessNoInitStore#44( Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.() > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#45( f > NV{, S, poly nn/n=f/f} this > NV{@NonNull, Issue5174Super, poly nn/n=f/f} Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.() > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ expression statement super() [ ExpressionStatement ] this [ ExplicitThis ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} @@ -257,7 +268,8 @@ Before: NullnessNoInitStore#46( this > NV{@NonNull, Issue5174Super, poly nn/n=f/f} Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -268,7 +280,8 @@ Before: NullnessNoInitStore#51( Issue5174Super.sf > NV{@NonNull, Object, poly nn/n=f/f} this.f > NV{, S, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 20 -> 21 @@ -1043,7 +1056,8 @@ Before: NullnessNoInitStore#301( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1052,7 +1066,8 @@ Before: NullnessNoInitStore#301( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this) [ ImplicitThis ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} (this). [ MethodAccess ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1063,7 +1078,8 @@ Before: NullnessNoInitStore#302( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this).(f) [ MethodInvocation ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} @@ -1072,12 +1088,14 @@ Before: then=NullnessNoInitStore#303( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#304( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ expression statement super(f) [ ExpressionStatement ] @@ -1086,7 +1104,8 @@ Before: NullnessNoInitStore#305( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1095,7 +1114,8 @@ Before: NullnessNoInitStore#310( f > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 27 -> 28 @@ -1110,7 +1130,8 @@ Before: NullnessNoInitStore#317( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1119,7 +1140,8 @@ Before: NullnessNoInitStore#317( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] (this) [ ImplicitThis ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1131,7 +1153,8 @@ Before: NullnessNoInitStore#318( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this).methodInner(in) [ MethodInvocation ] > NV{, T, poly nn/n=f/f} @@ -1140,12 +1163,14 @@ Before: then=NullnessNoInitStore#319( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#320( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o = (this).methodInner(in) [ Assignment ] > NV{, T, poly nn/n=f/f} @@ -1154,7 +1179,8 @@ Before: NullnessNoInitStore#321( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1164,7 +1190,8 @@ Before: NullnessNoInitStore#326( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 34 -> 35 @@ -1179,7 +1206,8 @@ Before: NullnessNoInitStore#333( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1188,7 +1216,8 @@ Before: NullnessNoInitStore#333( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] this [ ExplicitThis ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1200,7 +1229,8 @@ Before: NullnessNoInitStore#334( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ this.methodInner(in) [ MethodInvocation ] > NV{, T, poly nn/n=f/f} @@ -1209,12 +1239,14 @@ Before: then=NullnessNoInitStore#335( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#336( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o = this.methodInner(in) [ Assignment ] > NV{, T, poly nn/n=f/f} @@ -1223,7 +1255,8 @@ Before: NullnessNoInitStore#337( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1233,7 +1266,8 @@ Before: NullnessNoInitStore#342( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 41 -> 42 @@ -1243,7 +1277,8 @@ Before: NullnessNoInitStore#342( Before: NullnessNoInitStore#349( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1251,7 +1286,8 @@ Before: NullnessNoInitStore#349( Before: NullnessNoInitStore#349( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] (this) [ ImplicitThis ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1263,7 +1299,8 @@ Before: NullnessNoInitStore#350( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 46 -> 47 @@ -1273,7 +1310,8 @@ Before: NullnessNoInitStore#350( Before: NullnessNoInitStore#354( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1281,7 +1319,8 @@ Before: NullnessNoInitStore#354( Before: NullnessNoInitStore#354( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] this [ ExplicitThis ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1293,7 +1332,8 @@ Before: NullnessNoInitStore#355( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 51 -> 52 @@ -1321,7 +1361,8 @@ Before: NullnessNoInitStore#355( Before: NullnessNoInitStore#359( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1329,7 +1370,8 @@ Before: NullnessNoInitStore#359( Before: NullnessNoInitStore#359( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] o [ LocalVariable ] @@ -1338,7 +1380,8 @@ o [ LocalVariable ] Before: NullnessNoInitStore#360( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super [ ClassName ] @@ -1346,7 +1389,8 @@ Issue5174Super [ ClassName ] Before: NullnessNoInitStore#361( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Super.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1357,7 +1401,8 @@ o [ LocalVariable ] Before: NullnessNoInitStore#362( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1366,7 +1411,8 @@ Before: NullnessNoInitStore#369( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub [ ClassName ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1375,7 +1421,8 @@ Before: NullnessNoInitStore#370( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Sub.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1387,7 +1434,8 @@ Before: NullnessNoInitStore#379( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super [ ClassName ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} @@ -1396,7 +1444,8 @@ Before: NullnessNoInitStore#380( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Super.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1407,7 +1456,8 @@ Before: NullnessNoInitStore#389( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 62 -> 63 @@ -1421,7 +1471,8 @@ Before: NullnessNoInitStore#389( Before: NullnessNoInitStore#400( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1429,7 +1480,8 @@ Before: NullnessNoInitStore#400( Before: NullnessNoInitStore#400( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this) [ ImplicitThis ] > NV{@NonNull, SubNested, poly nn/n=f/f} (this). [ MethodAccess ] > NV{@NonNull, SubNested, poly nn/n=f/f} @@ -1438,7 +1490,8 @@ Before: NullnessNoInitStore#400( Before: NullnessNoInitStore#401( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this).() [ MethodInvocation ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1447,12 +1500,14 @@ Before: then=NullnessNoInitStore#402( this > NV{@NonNull, SubNested, poly nn/n=f/f} this.() > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#403( this > NV{@NonNull, SubNested, poly nn/n=f/f} this.() > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ expression statement super() [ ExpressionStatement ] @@ -1460,7 +1515,8 @@ expression statement super() [ ExpressionStatement ] Before: NullnessNoInitStore#404( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1469,7 +1525,8 @@ Before: NullnessNoInitStore#409( this > NV{@NonNull, SubNested, poly nn/n=f/f} this.() > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 69 -> 70 @@ -1484,7 +1541,8 @@ Before: NullnessNoInitStore#416( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1493,7 +1551,8 @@ Before: NullnessNoInitStore#416( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] (this) [ ImplicitThis ] > NV{@NonNull, SubNested, poly nn/n=f/f} @@ -1505,7 +1564,8 @@ Before: NullnessNoInitStore#417( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ (this).methodInner(in) [ MethodInvocation ] > NV{, T, poly nn/n=f/f} @@ -1514,12 +1574,14 @@ Before: then=NullnessNoInitStore#418( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#419( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o = (this).methodInner(in) [ Assignment ] > NV{, T, poly nn/n=f/f} @@ -1528,7 +1590,8 @@ Before: NullnessNoInitStore#420( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1538,7 +1601,8 @@ Before: NullnessNoInitStore#425( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 76 -> 77 @@ -1563,7 +1627,8 @@ Before: NullnessNoInitStore#432( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1572,7 +1637,8 @@ Before: NullnessNoInitStore#432( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] @@ -1581,7 +1647,8 @@ Before: NullnessNoInitStore#433( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub [ ClassName ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1590,7 +1657,8 @@ Before: NullnessNoInitStore#434( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.this [ FieldAccess ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1599,7 +1667,8 @@ Before: NullnessNoInitStore#452( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Object, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1609,7 +1678,8 @@ Before: NullnessNoInitStore#442( this > NV{@NonNull, SubNested, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.this.methodInner [ MethodAccess ] @@ -1619,7 +1689,8 @@ Before: NullnessNoInitStore#445( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ in [ LocalVariable ] > NV{, T, poly nn/n=f/f} @@ -1629,7 +1700,8 @@ Before: NullnessNoInitStore#448( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.this.methodInner(in) [ MethodInvocation ] > NV{, T, poly nn/n=f/f} @@ -1639,13 +1711,15 @@ Before: then=NullnessNoInitStore#449( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false), + isPolyNullNull = false + nonEmptyQueueReceivers = []), else=NullnessNoInitStore#450( in > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o = Issue5174Sub.this.methodInner(in) [ Assignment ] > NV{, T, poly nn/n=f/f} @@ -1656,7 +1730,8 @@ Before: NullnessNoInitStore#457( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 89 -> 90 @@ -1666,7 +1741,8 @@ Before: NullnessNoInitStore#457( Before: NullnessNoInitStore#468( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1674,7 +1750,8 @@ Before: NullnessNoInitStore#468( Before: NullnessNoInitStore#468( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] (this) [ ImplicitThis ] > NV{@NonNull, SubNested, poly nn/n=f/f} @@ -1686,7 +1763,8 @@ Before: NullnessNoInitStore#469( o > NV{, T, poly nn/n=f/f} this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 94 -> 95 @@ -1706,7 +1784,8 @@ Before: NullnessNoInitStore#469( Before: NullnessNoInitStore#473( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1714,7 +1793,8 @@ Before: NullnessNoInitStore#473( Before: NullnessNoInitStore#473( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] @@ -1722,7 +1802,8 @@ o [ VariableDeclaration ] Before: NullnessNoInitStore#474( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub [ ClassName ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1730,7 +1811,8 @@ Issue5174Sub [ ClassName ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Before: NullnessNoInitStore#475( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.this [ FieldAccess ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1738,7 +1820,8 @@ Issue5174Sub.this [ FieldAccess ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/ Before: NullnessNoInitStore#476( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1747,7 +1830,8 @@ Before: NullnessNoInitStore#483( this > NV{@NonNull, SubNested, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.this.f [ FieldAccess ] > NV{, T, poly nn/n=f/f} @@ -1756,7 +1840,8 @@ Before: NullnessNoInitStore#486( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o = Issue5174Sub.this.f [ Assignment ] > NV{, T, poly nn/n=f/f} @@ -1766,7 +1851,8 @@ Before: NullnessNoInitStore#489( this > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} Issue5174Sub.class > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ 105 -> 106 @@ -1794,7 +1880,8 @@ Before: NullnessNoInitStore#489( Before: NullnessNoInitStore#498( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1802,7 +1889,8 @@ Before: NullnessNoInitStore#498( Before: NullnessNoInitStore#498( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ o [ VariableDeclaration ] o [ LocalVariable ] @@ -1811,7 +1899,8 @@ o [ LocalVariable ] Before: NullnessNoInitStore#499( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super [ ClassName ] @@ -1819,7 +1908,8 @@ Issue5174Super [ ClassName ] Before: NullnessNoInitStore#500( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Super.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1830,7 +1920,8 @@ o [ LocalVariable ] Before: NullnessNoInitStore#501( this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ @@ -1839,7 +1930,8 @@ Before: NullnessNoInitStore#508( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub [ ClassName ] > NV{@NonNull, Issue5174Sub, poly nn/n=f/f} @@ -1848,7 +1940,8 @@ Before: NullnessNoInitStore#509( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Sub.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Sub.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1860,7 +1953,8 @@ Before: NullnessNoInitStore#518( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super [ ClassName ] > NV{@NonNull, Issue5174Super, poly nn/n=f/f} @@ -1869,7 +1963,8 @@ Before: NullnessNoInitStore#519( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ Issue5174Super.sf [ FieldAccess ] > NV{@NonNull, Object, poly nn/n=f/f} o = Issue5174Super.sf [ Assignment ] > NV{@NonNull, Object, poly nn/n=f/f} @@ -1880,6 +1975,7 @@ Before: NullnessNoInitStore#528( o > NV{@NonNull, Object, poly nn/n=f/f} this > NV{@NonNull, SubNested, poly nn/n=f/f} isPolyNullNonNull = false - isPolyNullNull = false) + isPolyNullNull = false + nonEmptyQueueReceivers = []) ~~~~~~~~~ diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index 5238048529a7..3c0932149554 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -15,6 +15,20 @@ void mNonNull(Queue q) { } } + void noSideEffectMethod(Queue q) { + while (!q.isEmpty()) { + q.size(); + @NonNull String firstNode = q.poll(); + } + } + + void unrelatedClear(Queue q1, Queue q2) { + while (!q1.isEmpty()) { + q2.clear(); + @NonNull String firstNode = q1.poll(); + } + } + void mNullable(Queue<@Nullable String> q) { while (!q.isEmpty()) { // :: error: (assignment.type.incompatible) @@ -26,4 +40,76 @@ void mNoCheck(Queue<@Nullable String> q) { // :: error: (assignment.type.incompatible) @NonNull String firstNode = q.poll(); } + + void secondPoll(Queue q) { + while (!q.isEmpty()) { + @NonNull String firstNode = q.poll(); + // :: error: (assignment.type.incompatible) + @NonNull String secondNode = q.poll(); + } + } + + void replaceQueue(Queue q1, Queue q2) { + while (!q1.isEmpty()) { + q1 = q2; + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q1.poll(); + } + } + + void removeBeforePoll(Queue q) { + while (!q.isEmpty()) { + q.remove(); + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } + } + + void clearBeforePoll(Queue q) { + while (!q.isEmpty()) { + q.clear(); + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } + } + + void conditionalClearBeforePoll(Queue q, boolean bool) { + while (!q.isEmpty()) { + if (bool) { + q.clear(); + } + // :: error: (assignment.type.incompatible) + @NonNull String s = q.poll(); + } + } + + void indexPoll(Queue[] arr, int i) { + while (!arr[i].isEmpty()) { + i++; + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = arr[i].poll(); + } + } + + void clearViaArg(Queue q) { + q.clear(); + } + + void argMutate(Queue q) { + while (!q.isEmpty()) { + clearViaArg(q); + // :: error: (assignment.type.incompatible) + @NonNull String s = q.poll(); + } + } + + // Currently failing because the store does not track aliases of the queue receiver. + // void aliasClearBeforePoll(Queue q) { + // Queue a = q; + // while (!q.isEmpty()) { + // a.clear(); + // // should error once aliasing is tracked + // @NonNull String s = q.poll(); + // } + // } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 58c433a1a7ce..ede41e89510a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -38,6 +38,8 @@ The Nullness Checker now recognizes references to private, final fields with zer The `ClassBound` annotation can now be used with anonymous types. +The Nullness Checker now refines `Queue.poll()` to `@NonNull` after a false `isEmpty()` check for queues with `@NonNull` element types. + **Implementation details:** The `AbstractNodeVisitor` now has more summary methods, following the class hierarchy of `Node` and conceptual categories. From 0e630ad4715efcfa814fb0a8ebcf3d7eb54ceee1 Mon Sep 17 00:00:00 2001 From: Nicholas Hioe Date: Sun, 29 Mar 2026 19:52:40 -0400 Subject: [PATCH 3/4] consistent vars in tests --- checker/tests/nullness/IsEmptyPoll.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index 3c0932149554..fa55d8925c07 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -79,7 +79,7 @@ void conditionalClearBeforePoll(Queue q, boolean bool) { q.clear(); } // :: error: (assignment.type.incompatible) - @NonNull String s = q.poll(); + @NonNull String firstNode = q.poll(); } } @@ -99,7 +99,7 @@ void argMutate(Queue q) { while (!q.isEmpty()) { clearViaArg(q); // :: error: (assignment.type.incompatible) - @NonNull String s = q.poll(); + @NonNull String firstNode = q.poll(); } } @@ -109,7 +109,7 @@ void argMutate(Queue q) { // while (!q.isEmpty()) { // a.clear(); // // should error once aliasing is tracked - // @NonNull String s = q.poll(); + // @NonNull String firstNode = q.poll(); // } // } } From 0178723538ec98c62f8d36af0bed30fad4354ebb Mon Sep 17 00:00:00 2001 From: Nicholas Hioe Date: Sun, 29 Mar 2026 22:31:48 -0400 Subject: [PATCH 4/4] Make updateForMethodCall more conservative for queues --- .../checker/nullness/NullnessNoInitStore.java | 23 ++----------- checker/tests/nullness/IsEmptyPoll.java | 34 +++++++++---------- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java index 80213f1718a3..1b927056c38f 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -123,31 +122,15 @@ public void updateForMethodCall( NullnessNoInitValue val) { super.updateForMethodCall(methodInvocationNode, atypeFactory, val); - // Invalidate any non-empty queue information for side-effecting method calls. + // Conservatively invalidate all non-empty queue information for side-effecting method + // calls. MethodInvocationTree tree = methodInvocationNode.getTree(); ExecutableElement method = TreeUtils.elementFromUse(tree); boolean hasSideEffect = !(atypeFactory.isSideEffectFree(method) || PurityUtils.isSideEffectFree(atypeFactory, method)); if (hasSideEffect) { - JavaExpression receiverExpr = - JavaExpression.fromNode(methodInvocationNode.getTarget().getReceiver()); - if (receiverExpr != null) { - nonEmptyQueueReceivers.removeIf( - queueReceiver -> - queueReceiver.containsSyntacticEqualJavaExpression(receiverExpr)); - } - - // Invalidate if queue is passed as an arguments, which makes it mutable. - List arguments = methodInvocationNode.getArguments(); - for (Node arg : arguments) { - JavaExpression argExpr = JavaExpression.fromNode(arg); - if (argExpr != null) { - nonEmptyQueueReceivers.removeIf( - queueReceiver -> - queueReceiver.containsSyntacticEqualJavaExpression(argExpr)); - } - } + nonEmptyQueueReceivers.clear(); } } diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index fa55d8925c07..37e863f523c7 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -22,13 +22,6 @@ void noSideEffectMethod(Queue q) { } } - void unrelatedClear(Queue q1, Queue q2) { - while (!q1.isEmpty()) { - q2.clear(); - @NonNull String firstNode = q1.poll(); - } - } - void mNullable(Queue<@Nullable String> q) { while (!q.isEmpty()) { // :: error: (assignment.type.incompatible) @@ -83,6 +76,23 @@ void conditionalClearBeforePoll(Queue q, boolean bool) { } } + void aliasClearBeforePoll(Queue q) { + Queue a = q; + while (!q.isEmpty()) { + a.clear(); + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } + } + + void potentiallyRelatedMutation(Queue q1, Queue q2) { + while (!q1.isEmpty()) { + q2.clear(); + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q1.poll(); + } + } + void indexPoll(Queue[] arr, int i) { while (!arr[i].isEmpty()) { i++; @@ -102,14 +112,4 @@ void argMutate(Queue q) { @NonNull String firstNode = q.poll(); } } - - // Currently failing because the store does not track aliases of the queue receiver. - // void aliasClearBeforePoll(Queue q) { - // Queue a = q; - // while (!q.isEmpty()) { - // a.clear(); - // // should error once aliasing is tracked - // @NonNull String firstNode = q.poll(); - // } - // } }