Skip to content

JavaStubifier: improve unloadable-annotation failure DX#1878

Merged
wmdietl merged 4 commits into
masterfrom
fix-stubifier-error-context
Jul 18, 2026
Merged

JavaStubifier: improve unloadable-annotation failure DX#1878
wmdietl merged 4 commits into
masterfrom
fix-stubifier-error-context

Conversation

@wmdietl

@wmdietl wmdietl commented Jul 17, 2026

Copy link
Copy Markdown
Member

While stubifying the JSpecify reference checker's annotated JDK, one stray source file with a JUnit annotation aborted the whole run with cannot load annotation org.junit.runners.Parameterized.Parameters ... — naming the annotation but not the file, which had to be found by grepping a 14k-file tree. And there was no way to proceed past it.

Two changes:

  1. File context in the failure. JavaStubifier.MinimizerCallback.process now wraps binaryStubWriter.process(cu) and rethrows with the absolute file path in the message. That layer is the one place that reliably knows the file (it's the SourceRoot.Callback parameter), and the single wrap covers every "Serialization failure" the writer can produce — BinaryStubWriter.process(CompilationUnit) is also called directly from tests with no file behind it, so the context does not belong there.

  2. Opt-in --skipUnloadableAnnotations flag. When passed to JavaStubifier, an annotation whose @Target cannot be read is omitted from the binary stub output — one deduped warning line to stderr naming annotation + file — and processing continues, mirroring the existing isUnloadablePlatformAnnotation drop-and-continue path in routeAnnotations. Default behavior (flag absent) is unchanged: hard fail, now with the file named. The flag's Javadoc spells out the trade-off: dropping a real qualifier silently changes checker behavior, so this is for known-irrelevant annotations (like a stray test file), not a default way to push through classpath problems.

Tests: two new cases in the existing JavaStubifierTest harness — default run fails with annotation and file path in the message; flagged run succeeds, warns, and the stub verifiably omits the annotation. Full :framework:test, assemble, and javadocDoclintAll pass on JDK 21. Real-world check: the reference checker's includeJSpecifyJDK stubification run against this branch succeeds.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EaHhVqLoJukm4kKsE7UfY4

…pt-in skip

Stubifying the JSpecify reference checker's annotated JDK hit a stray
source file with JUnit annotations, which aborted the whole run with a
message naming the annotation but not the source file being processed;
finding it required grepping a 14k-file tree. MinimizerCallback.process
is the layer that reliably knows the file (it is a SourceRoot.Callback
parameter, unlike BinaryStubWriter, which operates on a bare
CompilationUnit and is also called directly by tests with no file
behind it), so it now wraps any RuntimeException that escapes
BinaryStubWriter.process(cu) with that path, covering every
"Serialization failure" message the writer can produce, not just the
annotation-loading one.

Also add a `--skipUnloadableAnnotations` command-line flag. When set,
BinaryStubWriter drops an annotation whose @target it cannot read from
the binary stub output instead of aborting, printing one warning line
per annotation-and-file pair to stderr. This mirrors the existing
isUnloadablePlatformAnnotation drop-and-continue path, but is opt-in
and warned because -- unlike a platform annotation missing from the
stubifier's own narrow JVM -- the annotation could be a real qualifier
a checker's wider classpath would have resolved, so dropping it can
silently change the annotated JDK's meaning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 17, 2026 19:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

wmdietl and others added 3 commits July 17, 2026 17:31
PR #1878's CI failed on every JDK except 21 (cftests-junit,
cftests-junit-jdk21, cftests-nonjunit all red on 8/11/17/25/26/27).
Reproduced with ORG_GRADLE_PROJECT_useJdkVersion=8: compileTestJava
fails because PrintStream(OutputStream, boolean, Charset) and
ByteArrayOutputStream.toString(Charset) were only added in Java 10,
and this project's compileTestJava still targets Java 8
(sourceCompatibility/targetCompatibility = VERSION_1_8), for which
those symbols do not exist on the real JDK 8 boot classpath -- unlike
"--release 8" restrictions, a genuinely older JDK's classpath simply
lacks them. useJdkVersion=11/17/25/26/27 apparently also fail in CI
for the same reason, even though those JDKs do carry the Charset
overloads; useJdkVersion=21 is the only local run that reused the host
JVM directly (no toolchain), which is likely why it alone was unaffected.

Switch to the String-charset-name overloads instead, matching the
existing convention in CheckerMain.outputArgumentsToFile and
CheckerMain's jar-path decoding: @SuppressWarnings("JdkObsolete") on
the declaration, with an explicit Charset#name() argument so
-Xep:DefaultCharset:ERROR (enabled for useJdkVersion 21-26) has no
complaint either. Verified locally with
ORG_GRADLE_PROJECT_useJdkVersion=8 (previously failing to compile) and
=21 (must stay clean of errorprone) via
":framework:test --tests org.checkerframework.framework.stubifier.*".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wmdietl
wmdietl merged commit 0e17447 into master Jul 18, 2026
42 checks passed
@wmdietl
wmdietl deleted the fix-stubifier-error-context branch July 18, 2026 18:23
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.

2 participants