Skip to content

Class-file CLASS-retention annotations (RuntimeInvisibleAnnotations) are not read: SpotBugs @CheckForNull/@NonNull on library members #161

Closed

Description

@bnaudts

ClassSymbolScanner.loadAnnotations reads a library member's annotations through javac's symbols (symbol.getAnnotationMirrors()). Annotations with CLASS retention are stored in a class file's RuntimeInvisibleAnnotations attribute and do not show up there. kotlinc reads them, so Kotlin sees a nullness that maddi's analysis does not.

Example: jenkins remoting's JnlpConnectionState.getStash(Class<S>) carries edu.umd.cs.findbugs.annotations.CheckForNull (RuntimeInvisibleAnnotations, checked with javap).

  • kotlinc infers val state = event.getStash(State::class.java) as State?.
  • The nullability pass never seeds it: no cause in the jenkins causes run mentions a stapler or remoting annotation, although both use @CheckForNull and @NonNull widely.

Effect on the jenkins J2K trial (2026-10-20): DefaultJnlpSlaveReceiver.kt ×6, likely also Channels.kt:50/76 and JnlpSlaveAgentProtocol4.kt:76.

Type-use annotations in RuntimeVisibleTypeAnnotations (JSpecify) are a separate path, now carried by 21b739d on ws/maddi-null.

Possible fix: read RuntimeInvisibleAnnotations of a library member from the class file itself, through the JDK ClassFile API or maddi-java-bytecode, and add those annotations next to the visible ones.

🤖 Generated with Claude Code

Activity

  1. bnaudts commented on Nov 9, 2026

    @bnaudts
    ContributorAuthor

    Not reproducible on current devel, and the diagnosis was off: maddi does read CLASS-retention annotations.

    • Front end: javac's ClassReader attaches a class file's RuntimeInvisibleAnnotations and RuntimeInvisibleParameterAnnotations to the symbol, the same way it attaches the visible ones, so ClassSymbolScanner.loadAnnotations sees them through getAnnotationMirrors(). This holds even when the annotation's own class file is not on the class path, as with a compile-only spotbugs-annotations dependency. TestClassRetentionAnnotation (maddi 00d0725d7 on ws/maddi-bug) tests both cases.
    • Nullability: a library method's explicit nullable annotation seeds a call's result (Contracts.libraryReturnState → NullAnnotations.explicitState, which recognises CheckForNull by simple name).
    • jenkins, rerun 2026-11-09 (TestJavaToKotlinJenkinsNullability): DefaultJnlpSlaveReceiver.kt now prints val state = event.getStash(State::class.java)!!. getStash in remoting 3383 carries edu.umd.cs.findbugs.annotations.CheckForNull in RuntimeInvisibleAnnotations (checked with javap), so Kotlin sees the result as nullable. DefaultJnlpSlaveReceiver.kt, Channels.kt and JnlpSlaveAgentProtocol4.kt have no kotlinc errors left, only warnings.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions