[rustjava-classfile-parse-error-propagation] fix: unreadable class files raise ClassFormatError instead of panicking - #3
Merged
Conversation
ClassDefinitionImpl::from_classfile unwrap()ed ClassInfo::parse and
assert_eq!ed the magic, so any corrupted or unsupported class file
(e.g. constant pool tags 15-18, which javac 9+ emits for string
concatenation and lambdas) crashed the whole process with a Rust panic
instead of raising a Java exception. "Class not found" was already a
clean NoClassDefFoundError; only "class unreadable" panicked.
- classfile: ClassInfo::parse now returns Result<_, ParseError> where
ParseError distinguishes truncation, magic mismatch, unsupported
constant pool tag N (with index), malformed data, and trailing bytes;
parse_utf8 no longer panics on invalid UTF-8
- jvm_rust: from_classfile propagates ParseError (unwrap/assert removed)
- java_runtime: new java/lang/ClassFormatError (extends LinkageError),
registered in the runtime class list
- define_class impls (RuntimeImpl, TestRuntime) convert ParseError into
jvm.exception("java/lang/ClassFormatError", cause) per the existing
NoClassDefFoundError convention
- tests/test_class_format.rs locks both audit repro cases (truncated
file, tag 18 mutation) plus bad magic, with a not-found control case
proving the two failure classes stay distinct; fixtures are derived
deterministically from test_data/Hello.class by byte manipulation
Unsupported tags are cleanly rejected, not implemented; invokedynamic
support remains out of scope.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Superset content covering both in-flight PRs so the later add/add merge resolves by taking this branch's version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jun025
pushed a commit
that referenced
this pull request
Jul 22, 2026
…-charset-exception Resolve STATE.md/REPORT.md add/add conflicts by adopting the superset: all task entries kept, classfile task marked merged at main 549b9eb. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
ClassDefinitionImpl::from_classfile— the single definition path every user class from the classpath/jar goes through — calledClassInfo::parse(data).unwrap()plusassert_eq!(magic). Any corrupted class file, or any class containing constant pool tags 15–18 (MethodHandle/MethodType/Dynamic/InvokeDynamic — which javac 9+ emits by default for string concatenation and lambdas), killed the entire process with a Rust panic. Since there is nocatch_unwindanywhere, an embedding host dies too. Meanwhile "class not found" was already a cleanjava.lang.NoClassDefFoundError— only "class unreadable" panicked.Changes
ClassInfo::parsereturnsResult<Self, ParseError>instead ofOption.ParseErrordistinguishes:Truncated,BadMagic(u32),UnsupportedConstantPoolTag { index, tag },Malformed,TrailingData.parse_utf8no longer panics on invalid UTF-8. Internal nom parsers untouched otherwise.from_classfilereturnsResult<Self, ParseError>;unwrap()/assert_eq!removed.java/lang/ClassFormatErrorruntime class (extendsLinkageError, mirrorsNoClassDefFoundError's structure), registered in the class list.define_class: on parse failure, raisejvm.exception("java/lang/ClassFormatError", <cause>)— the exact conventionNoClassDefFoundErroralready uses (jvm.rs:579). No new mechanism.ClassFormatError(notUnsupportedClassVersionError) is used for unsupported tags: per JVMS and HotSpot behavior, unknown constant tags produceClassFormatError: Unknown constant tag N;UnsupportedClassVersionErroris reserved for major/minor version mismatch, which is not what happens here.Evidence
Before fix (tests added first — both audit repro cases reproduce through the binary path):
(same for tag-18 mutation and bad magic; not-found control already passed)
After fix, the deployed binary:
tests/test_class_format.rs: 4 tests (truncated / tag 18 / bad magic / not-found control), fixtures derived deterministically from committedtest_data/Hello.classby byte manipulation at test time — no corrupted binaries committed.cargo test --allfully green (129 tests, 0 failures),cargo fmt --checkandcargo clippy --all-targetsclean.Merge note
This PR and #2 both add
STATE.md/REPORT.md(SOP files, new onmain). Whichever merges second will show an add/add conflict — resolve by mergingmaininto the remaining branch and taking this branch's superset versions.Non-goals (per ticket)
.get().unwrap()on dangling indices) — separate corruption class, out of scope.🤖 Generated with Claude Code