# jvm-class
[](https://crates.io/crates/jvm-class)
[](https://docs.rs/jvm-class)
[](LICENSE)
A JVM class file parser written in Rust.
`jvm-class` turns the bytes of a `.class` file into a typed structure: constant pool,
access flags, fields, methods and attributes. Parsing borrows from the input buffer,
so string data, bytecode and unknown attribute payloads are slices, not copies.
## Features
- Zero-copy: `Class<'a>` borrows the input slice; no allocation for UTF-8 data or code arrays.
- Strict validation: magic, class file version, constant pool bounds and slot usage,
attribute lengths and constant pool entry kinds are all checked, and trailing bytes are an error.
- CESU-8 aware: constant pool strings are exposed as `Cesu8Bytes`, decoded on demand
(Java's modified UTF-8, including surrogate pairs and the two-byte encoded NUL).
- Typed attributes: all attributes defined up to Java SE 8 are parsed into enum variants;
unrecognized ones are kept verbatim as `AttributeInfo::Unknown`.
- No `unsafe`, two small dependencies (`bitflags`, `cesu8`).
## Supported class file versions
Major versions **45 to 52** — Java 1.1 through Java 8. Anything outside that range is
rejected with `Error::UnsupportedVersion`.
## Installation
```sh
cargo add jvm-class
```
Or add it manually:
```toml
[dependencies]
jvm-class = "0.1"
```
## Usage
```rust
use jvm_class::{Class, MethodAccessFlags};
let bytes = std::fs::read("Sample.class")?;
let class = Class::parse(&bytes)?;
println!("class {} (major version {})", class.name()?, class.major_version);
for method in &class.methods {
let name = method.name(&class.constant_pool)?;
let descriptor = method.descriptor(&class.constant_pool)?;
let is_static = method.access_flags.contains(MethodAccessFlags::STATIC);
println!(" {}{name}{descriptor}", if is_static { "static " } else { "" });
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
Bytecode and the exception table live in the `Code` attribute:
```rust
use jvm_class::{AttributeInfo, Class};
# let bytes = std::fs::read("Sample.class")?;
# let class = Class::parse(&bytes)?;
for method in &class.methods {
for attribute in &method.attributes {
if let AttributeInfo::Code { max_stack, max_locals, code, .. } = attribute {
println!(
"{}: {} bytes of bytecode, max_stack={max_stack}, max_locals={max_locals}",
method.name(&class.constant_pool)?,
code.len(),
);
}
}
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
The constant pool is indexed exactly like in the class file — 1-based, with `Long` and
`Double` taking two slots. Reading the unusable second slot is an error rather than a panic:
```rust
use jvm_class::{Class, ConstantPoolInfo};
# let bytes = std::fs::read("Sample.class")?;
# let class = Class::parse(&bytes)?;
for (index, entry) in class.constant_pool.iter() {
if let ConstantPoolInfo::Utf8 { bytes } = entry {
println!("#{index} = {}", bytes.decode()?);
}
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
If you need to parse a class embedded in a larger buffer, use `Class::try_read` with a
`Cursor` instead of `Class::parse`; it leaves the remaining bytes in the cursor rather
than failing with `Error::TrailingBytes`.
## Error handling
Every fallible operation returns `jvm_class::Result<T>`, an alias for
`std::result::Result<T, jvm_class::Error>`. `Error` implements `Display` and
`std::error::Error`, and each variant carries the offending value — the tag, index,
version or byte count that caused the failure.
## Minimum supported Rust version
Rust 1.85 (edition 2024).
## Testing
The test suite parses real class files compiled from the Java sources in
`tests/fixtures/java`, plus hand-built malformed classes that exercise every error variant.
```sh
cargo test
```
To rebuild the fixtures you need a `javac` that supports `--release 8` on `PATH`:
```sh
tests/fixtures/build.sh
```
## License
MIT — see [LICENSE](LICENSE).