Skip to content

Keep Checker Framework annotations out of the published bytecode - #1332

Merged
seongahjo merged 2 commits into
mainfrom
sa/checker-framework-verify-task
Sep 7, 2026
Merged

Keep Checker Framework annotations out of the published bytecode#1332
seongahjo merged 2 commits into
mainfrom
sa/checker-framework-verify-task

Conversation

@seongahjo

Copy link
Copy Markdown
Contributor

Summary

Keep Checker Framework annotations out of the published bytecode

(Optional): Description

The Checker Framework's javac writes the type qualifiers it infers into the class files it produces. We never wrote them in source, but 139 of the 147 classes in fixture-monkey shipped with @UnknownKeyFor, @NonNull and @Initialized on method returns and parameters. checker-qual is declared compileOnly, so it reaches no POM and consumers cannot resolve those annotation classes.

That went unnoticed since 1.1.16 because javac, and Kotlin through 2.3, silently drop an annotation they cannot resolve. Kotlin 2.4 reports it as an error whenever it has to infer a type that carries one, so the idiomatic calls stop compiling:

e: Type annotation class 'org.checkerframework.checker.nullness.qual.UnknownKeyFor'
       of the inferred type is inaccessible.

Measured against 1.2.1 from Maven Central:

Kotlin register(T::class.java) { it... }, thenApply { obj, builder -> }
2.0.21, 2.2.21, 2.3.0, 2.3.21 compiles
2.4.0, 2.4.20 fails

How Has This Been Tested?

  • existing tests

The Checker Framework's javac writes the annotations it infers into the
class files, so 139 of the 147 classes in fixture-monkey shipped with
@UnknownKeyFor and friends while checker-qual never reached a POM. Kotlin
2.4 turned that into a compile error for every consumer (#1318).

Run the check in a checkerFrameworkMain task whose output nothing
packages, and turn the framework off for the tasks that feed a jar. check
depends on the new task, so a nullness error still fails the build.
Both modules switched the checker off for the whole project from inside
the multi-release loop. Now that the check runs in a task of its own, the
multi-release compile tasks are untouched and the flag can go.

The 13 findings it surfaces split in two. Flow the checker cannot follow
takes a method-level suppression beside requireNonNull, as elsewhere in
this codebase. The rest are types that really do hold null and now say so:
a map of nullable values, a JSON null literal, a builder callers pass as
null, a ThreadLocal that starts empty.
@seongahjo seongahjo added this to the 1.2.3 milestone Sep 7, 2026
@seongahjo
seongahjo merged commit 73d5a63 into main Sep 7, 2026
13 checks passed
@seongahjo
seongahjo deleted the sa/checker-framework-verify-task branch September 7, 2026 13:45
seongahjo added a commit that referenced this pull request Sep 7, 2026
Backport of #1332 to the 1.1.x line. The Checker Framework's javac wrote
the annotations it inferred into the class files, so 169 of the classes in
fixture-monkey 1.1.22 shipped with @UnknownKeyFor and friends while
checker-qual never reached a POM. Kotlin 2.4 turned that into a compile
error for every consumer (#1318).

Run the check in a checkerFrameworkMain task whose output nothing packages,
and turn the framework off for the tasks that feed a jar. check depends on
the new task, so a nullness error still fails the build.

fixture-monkey-api and object-farm-api keep the skipCheckerFramework flag
they already set on this branch, so the set of type-checked modules is
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@seongahjo seongahjo modified the milestones: 1.2.3, 1.1.23 Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant