jvm-class 0.1.0

Zero-copy parser for JVM class files (Java 1.1 - 8, major versions 45-52)
Documentation
#![allow(dead_code)]

use std::{
    fs,
    path::{Path, PathBuf},
};

pub fn fixture_dir() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/classes")
}

pub fn fixture(name: &str) -> Vec<u8> {
    let path = fixture_dir().join(name);

    fs::read(&path).unwrap_or_else(|error| {
        panic!(
            "cannot read {}: {error}\nrun tests/fixtures/build.sh to rebuild the fixtures",
            path.display()
        )
    })
}

pub fn all_fixtures() -> Vec<(String, Vec<u8>)> {
    let root = fixture_dir();
    let mut classes = Vec::new();
    let mut pending = vec![root.clone()];

    while let Some(directory) = pending.pop() {
        let entries = fs::read_dir(&directory)
            .unwrap_or_else(|error| panic!("cannot list {}: {error}", directory.display()));

        for entry in entries {
            let path = entry.expect("cannot read directory entry").path();

            if path.is_dir() {
                pending.push(path);
            } else if path.extension().is_some_and(|ext| ext == "class") {
                let name = path
                    .strip_prefix(&root)
                    .expect("fixture outside of the fixture directory")
                    .to_string_lossy()
                    .into_owned();

                classes.push((name, fs::read(&path).expect("cannot read fixture")));
            }
        }
    }

    classes.sort_by(|(left, _), (right, _)| left.cmp(right));

    classes
}

pub const JAVA_8: u16 = 52;

pub struct ClassBuilder {
    pub magic: u32,
    pub minor_version: u16,
    pub major_version: u16,
    pub constant_pool_count: Option<u16>,
    pub access_flags: u16,
    pub this_class: u16,
    pub super_class: u16,
    pub interfaces: Vec<u16>,
    pool: Vec<u8>,
    slots: u16,
    fields: Table,
    methods: Table,
    attributes: Table,
}

impl ClassBuilder {
    pub fn new() -> Self {
        let mut builder = Self {
            magic: 0xCAFE_BABE,
            minor_version: 0,
            major_version: JAVA_8,
            constant_pool_count: None,
            access_flags: 0x0021,
            this_class: 0,
            super_class: 0,
            interfaces: Vec::new(),
            pool: Vec::new(),
            slots: 1,
            fields: Table::new(),
            methods: Table::new(),
            attributes: Table::new(),
        };

        builder.this_class = builder.class_entry("Generated");
        builder.super_class = builder.class_entry("java/lang/Object");

        builder
    }

    pub fn raw_entry(&mut self, bytes: &[u8], slots: u16) -> u16 {
        let index = self.slots;

        self.pool.extend_from_slice(bytes);
        self.slots += slots;

        index
    }

    pub fn utf8_entry(&mut self, value: &str) -> u16 {
        self.utf8_bytes_entry(value.as_bytes())
    }

    pub fn utf8_bytes_entry(&mut self, bytes: &[u8]) -> u16 {
        let mut entry = vec![1];
        push_u16(&mut entry, bytes.len() as u16);
        entry.extend_from_slice(bytes);

        self.raw_entry(&entry, 1)
    }

    pub fn integer_entry(&mut self, value: i32) -> u16 {
        let mut entry = vec![3];
        entry.extend_from_slice(&value.to_be_bytes());

        self.raw_entry(&entry, 1)
    }

    pub fn float_entry(&mut self, value: f32) -> u16 {
        let mut entry = vec![4];
        entry.extend_from_slice(&value.to_bits().to_be_bytes());

        self.raw_entry(&entry, 1)
    }

    pub fn long_entry(&mut self, value: i64) -> u16 {
        let mut entry = vec![5];
        entry.extend_from_slice(&value.to_be_bytes());

        self.raw_entry(&entry, 2)
    }

    pub fn double_entry(&mut self, value: f64) -> u16 {
        let mut entry = vec![6];
        entry.extend_from_slice(&value.to_bits().to_be_bytes());

        self.raw_entry(&entry, 2)
    }

    pub fn class_entry(&mut self, name: &str) -> u16 {
        let name_index = self.utf8_entry(name);

        self.reference_entry(7, name_index, None)
    }

    pub fn string_entry(&mut self, value: &str) -> u16 {
        let value_index = self.utf8_entry(value);

        self.reference_entry(8, value_index, None)
    }

    pub fn name_and_type_entry(&mut self, name: &str, descriptor: &str) -> u16 {
        let name_index = self.utf8_entry(name);
        let descriptor_index = self.utf8_entry(descriptor);

        self.reference_entry(12, name_index, Some(descriptor_index))
    }

    pub fn method_ref_entry(&mut self, class: &str, name: &str, descriptor: &str) -> u16 {
        let class_index = self.class_entry(class);
        let name_and_type_index = self.name_and_type_entry(name, descriptor);

        self.reference_entry(10, class_index, Some(name_and_type_index))
    }

    pub fn method_handle_entry(&mut self, reference_kind: u8, reference_index: u16) -> u16 {
        let mut entry = vec![15, reference_kind];
        push_u16(&mut entry, reference_index);

        self.raw_entry(&entry, 1)
    }

    pub fn method_type_entry(&mut self, descriptor: &str) -> u16 {
        let descriptor_index = self.utf8_entry(descriptor);

        self.reference_entry(16, descriptor_index, None)
    }

    pub fn reference_entry(&mut self, tag: u8, first: u16, second: Option<u16>) -> u16 {
        let mut entry = vec![tag];
        push_u16(&mut entry, first);

        if let Some(second) = second {
            push_u16(&mut entry, second);
        }

        self.raw_entry(&entry, 1)
    }

    pub fn attribute(&mut self, name: &str, body: &[u8]) -> Vec<u8> {
        let name_index = self.utf8_entry(name);

        raw_attribute(name_index, body.len() as u32, body)
    }

    pub fn broken_attribute(&mut self, name: &str, declared: u32, body: &[u8]) -> Vec<u8> {
        let name_index = self.utf8_entry(name);

        raw_attribute(name_index, declared, body)
    }

    pub fn field(&mut self, access_flags: u16, name: &str, descriptor: &str, attrs: &[Vec<u8>]) {
        let member = self.member(access_flags, name, descriptor, attrs);

        self.fields.push(&member);
    }

    pub fn method(&mut self, access_flags: u16, name: &str, descriptor: &str, attrs: &[Vec<u8>]) {
        let member = self.member(access_flags, name, descriptor, attrs);

        self.methods.push(&member);
    }

    pub fn class_attribute(&mut self, attribute: &[u8]) {
        self.attributes.push(attribute);
    }

    fn member(
        &mut self,
        access_flags: u16,
        name: &str,
        descriptor: &str,
        attrs: &[Vec<u8>],
    ) -> Vec<u8> {
        let name_index = self.utf8_entry(name);
        let descriptor_index = self.utf8_entry(descriptor);

        let mut bytes = Vec::new();
        push_u16(&mut bytes, access_flags);
        push_u16(&mut bytes, name_index);
        push_u16(&mut bytes, descriptor_index);
        push_u16(&mut bytes, attrs.len() as u16);

        for attribute in attrs {
            bytes.extend_from_slice(attribute);
        }

        bytes
    }

    pub fn build(&self) -> Vec<u8> {
        let mut bytes = Vec::new();

        bytes.extend_from_slice(&self.magic.to_be_bytes());
        push_u16(&mut bytes, self.minor_version);
        push_u16(&mut bytes, self.major_version);

        push_u16(&mut bytes, self.constant_pool_count.unwrap_or(self.slots));
        bytes.extend_from_slice(&self.pool);

        push_u16(&mut bytes, self.access_flags);
        push_u16(&mut bytes, self.this_class);
        push_u16(&mut bytes, self.super_class);

        push_u16(&mut bytes, self.interfaces.len() as u16);
        for &interface in &self.interfaces {
            push_u16(&mut bytes, interface);
        }

        for table in [&self.fields, &self.methods, &self.attributes] {
            push_u16(&mut bytes, table.count);
            bytes.extend_from_slice(&table.bytes);
        }

        bytes
    }
}

struct Table {
    count: u16,
    bytes: Vec<u8>,
}

impl Table {
    fn new() -> Self {
        Self {
            count: 0,
            bytes: Vec::new(),
        }
    }

    fn push(&mut self, entry: &[u8]) {
        self.count += 1;
        self.bytes.extend_from_slice(entry);
    }
}

fn raw_attribute(name_index: u16, declared: u32, body: &[u8]) -> Vec<u8> {
    let mut bytes = Vec::new();

    push_u16(&mut bytes, name_index);
    bytes.extend_from_slice(&declared.to_be_bytes());
    bytes.extend_from_slice(body);

    bytes
}

pub fn push_u16(bytes: &mut Vec<u8>, value: u16) {
    bytes.extend_from_slice(&value.to_be_bytes());
}