Skip to content

Do not crash when a supertype's class file is absent - #8087

Open
smillst wants to merge 12 commits into
typetools:masterfrom
smillst:issue8055
Open

smillst wants to merge 12 commits into
typetools:masterfrom
smillst:issue8055

Conversation

@smillst

@smillst smillst commented Sep 4, 2026

Copy link
Copy Markdown
Member

Fixes #8055.

smillst and others added 7 commits September 3, 2026 11:18
`AnnotatedTypeFactory#getDeclAnnotations` already anticipated a
`CompletionFailure` from reading an element's annotations, but the `try` block
wrapped `results.add(annotation)` rather than the call that actually throws,
`elements.getAllAnnotationMirrors(elt)`.

That method walks the superclass chain to collect `@Inherited` annotations and
asks each superclass `isErroneous()`, which recurses into the superclass's type
arguments and completes them.  So reading the annotations of a classpath class
whose supertype has a type argument that is not on the classpath -- for example
`TableRowJsonCoder extends AtomicCoder<TableRow>` in Apache Beam, compiled
without `TableRow` -- throws.  javac itself never completes such a symbol and
reports nothing, so the crash appeared only under the Checker Framework, as one
of four unrelated-looking messages depending on where the exception surfaced.

Fall back to the annotations written on the element itself, losing only
inherited ones, and warn so that the user knows the classpath is incomplete.
The result is cached in `cacheDeclAnnos`, so the warning is issued once per
element rather than once per call.

This is half of issue typetools#8055: `checker/jtreg/missingclassfile` also exercises
shapes that go on to crash in `AnnotatedTypeMirror.createType`, which rejects
the `ERROR` type that javac leaves behind.  That is a separate fix, so the
jtreg test stays `@ignore`d for now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
When javac fails to complete a class -- because its class file is not on the
classpath -- `ClassSymbol.complete` does "quiet error recovery": it sets the
symbol's kind to ERR before rethrowing.  From then on every `ClassType` naming
that symbol reports `TypeKind.ERROR`, because `ClassType.getKind` consults the
symbol rather than itself.  `AnnotatedTypeMirror.createType` rejected such a
type with "input is not compilable".

That assertion no longer earns its keep.  The Checker Framework does not run on
a compilation unit that javac rejected, so an ERROR type reaching `createType`
is the classpath-gap case rather than uncompilable source.  `BoundsInitializer`
already has a `visitError` that hands the type to `createType`, which until now
could only throw.  Treat an ERROR type as the declared type it is.

With the previous commit this completes issue typetools#8055, so remove the test's
`@ignore`.  Its header comment is rewritten to describe both bugs in the past
tense and to state the expected outcome; the reference to
`ElementUtils#isElementFromByteCode` is corrected to `getSuperClass`, which is
where the `CompletionFailure` catch actually is; and the prose that sat between
`@run main DeleteMissingClassFile` and the next tag moves out of the tag block,
because jtreg was passing it to `main` as command-line arguments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
`AnnotatedTypeMirror.createType` wrapped an ERROR type in an `AnnotatedDeclaredType`
whose underlying type was the error type itself.  Such a type names a symbol whose
class file is not on the classpath, so its supertypes, members, and type arguments
cannot be read, and any later query about it can fail.

Build the `AnnotatedDeclaredType` from `java.lang.Object` instead, so that the rest of
the framework has a type it can answer questions about.

Verified with `taintingtest` (which includes `framework/tests/all-systems`) and
`nullnesstest`, and by replaying `checker/jtreg/missingclassfile` by hand -- jtreg is
not installed on this machine -- where all five compilations succeed with only the
expected `supertype.not.completed` warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
Three fixes to the CompletionFailure handler in getDeclAnnotations.

Retry getAllAnnotationMirrors instead of falling back to
elt.getAnnotationMirrors().  A completer runs at most once -- javac installs
NULL_COMPLETER before running it -- so the second call does not throw, and it
returns the annotations inherited from every superclass that could be read.
The fallback returned only the annotations written directly on elt.

This is belt and braces: for a type declaration,
inheritOverriddenDeclAnnosFromTypeDecl already re-adds the @inherited
annotations of every direct supertype, and for any other element kind javac's
getAllAnnotationMirrors returns exactly elt.getAnnotationMirrors(), so neither
path could observe the difference.  The retry does cover the isParsing early
return, which skips inheritOverriddenDeclAnnosFromTypeDecl.

Issue the warning after fromEle has been established, and swallow a nested
CompletionFailure.  Reporting a warning resolves @SuppressWarnings and
@AnnotatedFor on elt and on every enclosing element, which is the very thing
that raises CompletionFailure in issues 309 and 348; on a classpath with more
than one gap, handling one missing class could throw a fresh CompletionFailure
out of getDeclAnnotations.  The fallback prints through checker.message,
because SourceChecker.reportUnsuppressed rejects a null source despite its
javadoc.

Rename the message key supertype.not.completed to class.not.completed.  The
catch also covers a failure to complete elt itself -- getAllAnnotationMirrors
starts with sym.getAnnotationMirrors(), which completes elt -- so a message
blaming "a supertype" could name the wrong class and send the user looking for
a supertype that is fine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
Commit 14017cf built the AnnotatedDeclaredType for an ERROR type out of
java.lang.Object, on the theory that a symbol whose class file is absent cannot
answer questions about itself.  No failure motivated it, and it broke two
things.

createType's documented postcondition -- "an AnnotatedTypeMirror whose
underlying type is type" -- no longer held, so code that zips the underlying
type's type arguments against the ATM's positionally paired unrelated types.
For lib.SuperTypeArg extends Box<Missing>, createType(Box<Missing>) returned an
ATM whose getUnderlyingType().getTypeArguments().get(0) was lib.Missing but
whose getTypeArguments().get(0).getUnderlyingType() was java.lang.Object;
SupertypeFinder.supertypesFromElement then handed the erroneous Missing symbol
to addComputedTypeAnnotations together with an Object ATM.

The Object type also has no type parameters, so
BoundsInitializer.InitializerVisitor.visitDeclared built an ATM from Object and
then set a non-empty type-argument list onto it.  That path is reachable
without visitError: a javac ClassType whose tsym.kind is ERR reports
TypeKind.ERROR but still dispatches to visitDeclared.

Fold the ERROR arm into the DECLARED arm instead.  The cast is sound:
javax.lang.model.type.ErrorType extends DeclaredType, javac's Type$ErrorType
extends ClassType, and in this case the object is a ClassType already.

Guard the one cast that the old BugInCF used to protect.  getTypeArguments'
declaration branch casts createType's result to AnnotatedTypeVariable, which
now yields a bare ClassCastException rather than a message naming the type;
throw a BugInCF that names the type argument and its kind.  The other casts
that the review flagged, in BoundsInitializer.visitTypeVariable and
visitWildcard, cannot see an ERROR type: visitor dispatch is by TypeKind, so
their argument always matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
A bare @compile checks only the exit status, so the test passed whether the
warning was issued once, five times, or not at all.  Give every compilation a
.goal file, following the convention of checker/jtreg/SymbolNotFoundErrors.java.

-XDrawDiagnostics is required, not cosmetic: without it a diagnostic that has a
source position prints the absolute path that jtreg passed to javac, which
would make the golden files machine-specific.

Add InheritedAnno.java, the one compilation that is expected to fail, together
with lib.QualParam and lib.SubOfQualParam.  Its error is the assertion:
SubOfQualParam inherits @HasQualifierParameter from QualParam, whose own
supertype is the one that cannot be read, and that annotation is what makes the
assignment an error.  Without it the file compiles cleanly.

The review also asked the test to detect that SuperTypeArg's supertype had
silently become Box<Object>.  That is not observable from source: any
expression that surfaces the type argument makes javac itself need the class
file and report "error: cannot access Missing" with no processor involved.  The
property that makes the bug possible is the same one that hides the
substitution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
Record the new class.not.completed warning, the change to
AnnotatedTypeMirror.createType's treatment of an ERROR type, and the closed
issue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU4K6ckax3NwbgG9LTFEcX
@smillst smillst self-assigned this Sep 4, 2026
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: bf07c361-b814-401f-a43f-b1c76430cb2b

📥 Commits

Reviewing files that changed from the base of the PR and between afe9395 and d60722f.

📒 Files selected for processing (1)
  • docs/CHANGELOG.md

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The framework now reports a class.not.completed warning when it cannot read a class file needed for declaration annotations. It falls back to directly declared annotations. AnnotatedTypeMirror.createType now supports ERROR types and validates type arguments. The issue 8055 jtreg test is enabled, expanded with missing-classfile fixtures, and checked against golden diagnostics. The changelog records these changes.

Suggested reviewers: mernst

Priority: ➖ Normal

Change: Bug fix

Merge Risk: ⚪ Minimal · up to d6072

The change handles the reported missing-class-file path and adds targeted regression coverage without an unresolved merge-blocking risk.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #8055 requires the CompletionFailure crash in the missing-classfile case to be fixed. AnnotatedTypeFactory#getDeclAnnotations now catches Symbol.CompletionFailure, retains available annota…
Out of Scope Changes check ✅ Passed The changes stay within issue #8055. The implementation changes prevent the reported crash. The new library fixtures, regression test, golden outputs, diagnostic message, and changelog entry support o…
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 6 files. (1 skipped: 1 u…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

smillst and others added 2 commits September 8, 2026 08:30
The comment justified the retry with "a completer runs at most once, so the
second call does not throw".  That is true of a single symbol -- Symbol#complete
installs NULL_COMPLETER before running the completer -- but getAllAnnotationMirrors
walks the whole superclass chain, and the first call aborts at the first symbol
that fails.  On its own, that reason leaves the retry free to walk further than
the first call did and to throw on a symbol it never reached, which is why the
try/catch around the second call is not redundant with the claim above it.

What actually stops the retry in the same place is ClassSymbol#complete's quiet
error recovery: it sets the symbol's type to an ErrorType before rethrowing, so
the walk's sup.isErroneous() check breaks out exactly where the first walk threw.

Verified on JDK 21 with a probe annotation processor that calls
getAllAnnotationMirrors three times on one element, over a class directory with
class files deleted as in checker/jtreg/missingclassfile: a missing supertype
type argument, a missing annotation type, missing Class- and enum-valued
annotation elements, and combinations of those at two depths in the chain.  Every
shape throws on the first call only.  The shape that would defeat the retry -- a
failure completing an annotation type, which would not make the supertype
erroneous and so would let the walk continue -- does not arise, because
ClassReader tolerates an absent annotation class file silently.

Keep the inner catch: it is three lines, its unreachability rests on every
completer preserving ClassSymbol#complete's error recovery, and a CompletionFailure
escaping getDeclAnnotations is the issue 8055 crash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDgr6aZpKDFnqw43VfHbLL
@smillst
smillst requested a review from mernst September 8, 2026 16:50
@smillst smillst assigned mernst and unassigned smillst Sep 8, 2026
Comment thread docs/CHANGELOG.md Outdated
### User-visible changes

New warning `class.not.completed` is issued when a class file that is needed to
compute an element's declaration annotations cannot be read. Previously, the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is no need for the second sentence. Let's keep the changelog concise, so that people will actually read it.

Comment thread docs/CHANGELOG.md Outdated

Renamed `AnnotatedTypes.innerMostType()` to `innermostComponentType()`.

`AnnotatedTypeMirror.createType()` no longer throws `BugInCF` for a type whose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ordinary bug fixes don't go in the changelog.

Comment thread docs/CHANGELOG.md Outdated

### Closed issues

\#8055.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Closed issues are filled in later. When they appear in pull requests, they lead to a lot of unnecessary conflicts.

Comment thread framework/src/main/java/org/checkerframework/framework/source/messages.properties Outdated
// enclosing elements, which can fail to read a class file too.
checker.message(
Diagnostic.Kind.WARNING,
"Cannot read a class file that is needed to compute the declaration annotations of %s:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems to duplicate text in messages.properties.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It does, but the comment above explains that it can't use the normal reporting method that will use messages.properties.

@mernst mernst assigned smillst and unassigned mernst Sep 10, 2026
@smillst smillst assigned mernst and unassigned smillst Sep 14, 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.

com.sun.tools.javac.code.Symbol$CompletionFailure crash

2 participants