jvm-class 0.1.0

Zero-copy parser for JVM class files (Java 1.1 - 8, major versions 45-52)
Documentation
# jvm-class

[![crates.io](https://img.shields.io/crates/v/jvm-class.svg)](https://crates.io/crates/jvm-class)
[![docs.rs](https://docs.rs/jvm-class/badge.svg)](https://docs.rs/jvm-class)
[![license](https://img.shields.io/crates/l/jvm-class.svg)](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).