JSpecify mode: `? super E` containment accepts a non-null argument when the receiver is `Sink<? extends V>` and `V` may be null
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 22/100
Research direction
Start with CheckIdenticalNullabilityVisitor.superWildcardContains and TypeSubstitutionUtils.memberType, which the report says hands the containment check a wildcard lower bound where a capture is expected. Add a test in nullaway/src/test/java/com/uber/nullaway/jspecify/ modeled on WildcardTests, covering the throughWildcardReceiver case, and confirm it reports the same error as the direct case. Done means the Sink<? extends V> receiver case is flagged without changing the existing direct-receiver results.
Written by the indexing model from the issue text.
Description
With JSpecify mode on, passing a Box<Object> to a method that takes Box<? super E> is not reported when the method is called on a Sink<? extends V> receiver and V is declared V extends @Nullable Object. The method may store a null E into the box, so a null reaches a Box<Object> that excludes one. The same call on a Sink<V> receiver is reported once #1834 is applied.
Versions: NullAway master at 2b25d0fc8 (0.14.3-SNAPSHOT), the 0.14.2 release, and the head of #1834 (b84ffe738), where only the direct-receiver line below is reported. Error Prone 2.50.0, JDK 21.0.9. Flags: -XepOpt:NullAway:JSpecifyMode=true -XDaddTypeAnnotationsToSymbol=true -XepOpt:NullAway:JSpecifyExperimental=true -XepOpt:NullAway:OnlyNullMarked=true.
Code sample. I ran it as a test in nullaway/src/test/java/com/uber/nullaway/jspecify/, with the helper WildcardTests uses: makeTestHelperWithArgs(JSpecifyJavacConfig.withJSpecifyModeArgs(Arrays.asList("-XepOpt:NullAway:OnlyNullMarked=true"))).
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
class Test {
static class Box<E extends @Nullable Object> {
E e;
Box(E e) { this.e = e; }
void set(E e) { this.e = e; }
E get() { return e; }
}
interface Sink<E extends @Nullable Object> {
void put(Box<? super E> b);
}
static class C<V extends @Nullable Object> {
void throughWildcardReceiver(Sink<? extends V> s, Box<Object> b) {
s.put(b);
}
void direct(Sink<V> s, Box<Object> b) {
s.put(b);
}
}
}
Expected: a diagnostic on the s.put(b) line of throughWildcardReceiver (line 16), of the form #1834 gives the direct line (line 19):
incompatible types: Box<Object> cannot be converted to Box<? super V>
The member type of put on a Sink<? extends V> is put(Box<? super capture of ? extends V>), and under JSpecify containment a ? super S contains Object only where S is a nullness subtype of Object. The capture's upper bound is V, whose declared bound admits null, so it is not. JSpecify's own samples/SuperVsObject.java marks a Receiver<Object> returned as Receiver<? super T> with T extends @Nullable Object as jspecify_nullness_mismatch; this is the same containment one receiver further out.
Actual: no diagnostic on either line on master and on 0.14.2. With #1834 applied, line 19 is reported and line 16 is not. With a // BUG: Diagnostic contains: incompatible types marker inserted above line 16, which shifts it to line 17, the test fails with:
Did not see an error on line 17 matching incompatible types. There were no errors.
Analysis, a guess; the report stands without it. With a print added to CheckIdenticalNullabilityVisitor.superWildcardContains on the head of #1834, the formal parameter type that reaches the containment check for line 16 is Test.Box<? super ? extends V>: TypeSubstitutionUtils.memberType replaces E with the receiver's wildcard rather than with its capture, so the lower bound of the formal is the wildcard ? extends V. The nullness check has no case for a wildcard in that position and treats it as a class type, which is null-exclusive. For line 19 the lower bound is V itself and is judged by its declared bound. A fix could capture-convert the receiver before memberType, or resolve a wildcard lower bound to its effective upper bound before the nullness check.
Related: #1834 reports the direct-receiver case. #1854 and #1847 are also about values seen through a wildcard receiver, in the other direction: a nullable upper bound lost on a field read through ? super, and a false positive on Consumer<@Nullable V> through ?, ? extends, and ? super receivers. #1846 (closed) is the crash on a value read through a ? super receiver.
Assisted-by: Claude Code (claude-fable-5-1)
- Dominant language
- Java
- Stars
- 4.1k
- Forks
- 379
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 106
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from uber/NullAway
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
uber/NullAway#2145 · 1 reaction ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
uber/NullAway#2058 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
uber/NullAway#1899 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 15/100
uber/NullAway#2160 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
johanhaleby/occurrent#1505 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FasterXML/jackson-databind#6443 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
finos/fluxnova-bpm-platform#387 ·
Maintainers usually reply within 4 days
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100