JSpecify mode: a `@Nullable` element of an array initializer is accepted where the component type excludes null (`new Object[] {null}`)

Open
#1,960 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
Half a day
Newbie friendliness
58/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
tooling

Research direction

Start with the existing check in NullAway.matchAssignment for ArrayAccessTree targets, which is where the a[0] = n diagnostic on line 19 comes from, and compare it with how NewArrayTree initializers are handled today. Add a JSpecify test in nullaway/src/test/java/com/uber/nullaway/jspecify/ covering the new Object[] {null}, new Object[] {n} and Object[] a = {n} cases. Done when those three lines report a diagnostic, while new @Nullable Object[] {null} and @Nullable Object[] a = {n} remain accepted.

Written by the indexing model from the issue text.

Description

false negative jspecify

With JSpecify mode on, a @Nullable value written as an element of an array initializer is accepted where the array's component type excludes null: new Object[] {null}, new Object[] {n} and Object[] a = {n} with n declared @Nullable Object produce no diagnostic. The same value written as a[0] = n is reported as "Writing @Nullable expression into array with @NonNull contents".

Versions: NullAway master at bc2fee556 (0.14.3-SNAPSHOT) and the 0.14.2 release. Error Prone 2.50.0, JDK 21.0.9. Flags: -XepOpt:NullAway:JSpecifyMode=true -XDaddTypeAnnotationsToSymbol=true -XepOpt:NullAway:OnlyNullMarked=true.

Code sample. I ran it as a test in nullaway/src/test/java/com/uber/nullaway/jspecify/, with makeTestHelperWithArgs(JSpecifyJavacConfig.withJSpecifyModeArgs(Arrays.asList("-XepOpt:NullAway:OnlyNullMarked=true"))). Each // BUG marker sits on the line above the statement it expects a diagnostic on.

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
class Test {
  static Object[] literal() {
    // BUG: Diagnostic contains: Writing @Nullable expression into array
    return new Object[] {null};
  }
  static Object[] nullable(@Nullable Object n) {
    // BUG: Diagnostic contains: Writing @Nullable expression into array
    return new Object[] {n};
  }
  static void shorthand(@Nullable Object n) {
    // BUG: Diagnostic contains: Writing @Nullable expression into array
    Object[] a = {n};
  }
  static void element(Object[] a, @Nullable Object n) {
    // BUG: Diagnostic contains: Writing @Nullable expression into array
    a[0] = n;
  }
}

Expected: a diagnostic on each initializer element that may be null, on lines 7, 11 and 15, as there is one on line 19 today:

Writing @Nullable expression into array with @NonNull contents.

The grounding is NullAway's own check for line 19: in @NullMarked code Object[] has a non-null component type, and an initializer element is written into that component exactly as a[0] = n is. Reusing the line 19 message is one possible shape; the requirement is the report. An initializer whose type admits null elements, new @Nullable Object[] {null} or @Nullable Object[] a = {n}, stays accepted.

Actual: only line 19 is reported, on master and on 0.14.2. The test fails with:

Did not see an error on line 7 matching Writing @Nullable expression into array. All errors:
/Test.java:19: warning: [NullAway] Writing @Nullable expression into array with @NonNull contents.
    a[0] = n;
         ^
    (see http://t.uber.com/nullaway )

Analysis, a guess; the report stands without it. The check for line 19 lives in NullAway.matchAssignment, under tree.getVariable() instanceof ArrayAccessTree, and compares mayBeNullExpr of the right-hand side against isArrayElementNullable of the array's type. NullAway implements no NewArrayTreeMatcher, so the elements of NewArrayTree.getInitializers() never reach a comparable check; the same two predicates applied to each initializer, with the component nullability taken from the NewArrayTree's type (or from the declared type for the {n} shorthand), would report lines 7, 11 and 15.

Related: the same gap hides a type-variable value with a nullable bound written into such an array, which is also a case of the documented "checking of generic class implementations" limitation (wiki, JSpecify Support); the CastToNonNullMethod side of that is #1961.

Assisted-by: Claude Code (claude-opus-5-5)

Dominant language
Java
Stars
4.1k
Forks
379
Avg merge
1d 2h
Merged PRs (30d)
106

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from uber/NullAway

All issues in uber/NullAway

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.