Skip to content

[rustjava-classfile-parse-error-propagation] fix: unreadable class files raise ClassFormatError instead of panicking - #3

Merged
Jun025 merged 2 commits into
mainfrom
classfile-parse-error-propagation
Jul 22, 2026
Merged

[rustjava-classfile-parse-error-propagation] fix: unreadable class files raise ClassFormatError instead of panicking#3
Jun025 merged 2 commits into
mainfrom
classfile-parse-error-propagation

Conversation

@Jun025

@Jun025 Jun025 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What

ClassDefinitionImpl::from_classfile — the single definition path every user class from the classpath/jar goes through — called ClassInfo::parse(data).unwrap() plus assert_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 no catch_unwind anywhere, an embedding host dies too. Meanwhile "class not found" was already a clean java.lang.NoClassDefFoundError — only "class unreadable" panicked.

Changes

  • classfile: ClassInfo::parse returns Result<Self, ParseError> instead of Option. ParseError distinguishes: Truncated, BadMagic(u32), UnsupportedConstantPoolTag { index, tag }, Malformed, TrailingData. parse_utf8 no longer panics on invalid UTF-8. Internal nom parsers untouched otherwise.
  • jvm_rust: from_classfile returns Result<Self, ParseError>; unwrap()/assert_eq! removed.
  • java_runtime: new java/lang/ClassFormatError runtime class (extends LinkageError, mirrors NoClassDefFoundError's structure), registered in the class list.
  • RuntimeImpl / TestRuntime define_class: on parse failure, raise jvm.exception("java/lang/ClassFormatError", <cause>) — the exact convention NoClassDefFoundError already uses (jvm.rs:579). No new mechanism.

ClassFormatError (not UnsupportedClassVersionError) is used for unsupported tags: per JVMS and HotSpot behavior, unknown constant tags produce ClassFormatError: Unknown constant tag N; UnsupportedClassVersionError is 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):

thread 'test_truncated_class_raises_class_format_error' panicked at jvm_rust/src/class_definition.rs:100:44:
called `Option::unwrap()` on a `None` value

(same for tag-18 mutation and bad magic; not-found control already passed)

After fix, the deployed binary:

$ rust_java Truncated
Error: Java Exception:
java.lang.ClassFormatError: Truncated class file
	at java/net/URLClassLoader.defineClass...

$ rust_java BadTag        # first CP tag byte mutated 10 -> 18
Error: Java Exception:
java.lang.ClassFormatError: Unknown or unsupported constant pool tag 18 at index 1 in class file

$ rust_java NoSuchClass   # unchanged control
Error: Java Exception:
java.lang.NoClassDefFoundError: NoSuchClass
  • tests/test_class_format.rs: 4 tests (truncated / tag 18 / bad magic / not-found control), fixtures derived deterministically from committed test_data/Hello.class by byte manipulation at test time — no corrupted binaries committed.
  • cargo test --all fully green (129 tests, 0 failures), cargo fmt --check and cargo clippy --all-targets clean.

Merge note

This PR and #2 both add STATE.md/REPORT.md (SOP files, new on main). Whichever merges second will show an add/add conflict — resolve by merging main into the remaining branch and taking this branch's superset versions.

Non-goals (per ticket)

  • Actual invokedynamic/MethodHandle/lambda support — unsupported tags are cleanly rejected, not implemented.
  • Constant pool cross-reference validation (.get().unwrap() on dangling indices) — separate corruption class, out of scope.
  • No upstream (dlunch/RustJava) contact.

🤖 Generated with Claude Code

jun0 and others added 2 commits July 22, 2026 18:27
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
Jun025 merged commit 549b9eb into main Jul 22, 2026
@Jun025
Jun025 deleted the classfile-parse-error-propagation branch July 22, 2026 22:15
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>
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