From 618a5a1ae7ac85fe8a1ef5e578befa68ceb72a53 Mon Sep 17 00:00:00 2001 From: Ashhhleyyy Date: Sun, 14 Apr 2024 21:52:00 +0100 Subject: [PATCH] feat: initial commit --- .envrc | 1 + .gitignore | 2 + Cargo.lock | 111 +++++++++++++++++ Cargo.toml | 11 ++ flake.lock | 82 +++++++++++++ flake.nix | 54 +++++++++ src/Test.class | Bin 0 -> 525 bytes src/Test.java | 17 +++ src/attribute.rs | 27 +++++ src/constant_pool.rs | 45 +++++++ src/field.rs | 41 +++++++ src/java_utf8.rs | 56 +++++++++ src/lib.rs | 283 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 ++ src/method.rs | 60 +++++++++ src/parse.rs | 21 ++++ 16 files changed, 821 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/Test.class create mode 100644 src/Test.java create mode 100644 src/attribute.rs create mode 100644 src/constant_pool.rs create mode 100644 src/field.rs create mode 100644 src/java_utf8.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/method.rs create mode 100644 src/parse.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6abfe1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.direnv diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fdfb13d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,111 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "derive-try-from-primitive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumflags2" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "jarr" +version = "0.1.0" +dependencies = [ + "derive-try-from-primitive", + "enumflags2", + "nom", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9cf2b5a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "jarr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +derive-try-from-primitive = "1.0.0" +enumflags2 = "0.7.8" +nom = "7.1.3" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ccb6a0a --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "crane": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1706473964, + "narHash": "sha256-Fq6xleee/TsX6NbtoRuI96bBuDHMU57PrcK9z1QEKbk=", + "owner": "ipetkov", + "repo": "crane", + "rev": "c798790eabec3e3da48190ae3698ac227aab770c", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1706683685, + "narHash": "sha256-FtPPshEpxH/ewBOsdKBNhlsL2MLEFv1hEnQ19f/bFsQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5ad9903c16126a7d949101687af0aa589b1d7d3d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9f1da02 --- /dev/null +++ b/flake.nix @@ -0,0 +1,54 @@ +{ + description = "java class file schenanigans in rust"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + crane = { + url = "github:ipetkov/crane"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, crane, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + + deps = with pkgs; [ + ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + pkgs.libiconv + ]; + + craneLib = crane.lib.${system}; + jarr = craneLib.buildPackage { + src = craneLib.cleanCargoSource (craneLib.path ./.); + + buildInputs = deps; + }; + in + { + checks = { + inherit jarr; + }; + + packages.default = jarr; + + apps.default = flake-utils.lib.mkApp { + drv = jarr; + }; + + devShells.default = craneLib.devShell { + checks = self.checks.${system}; + + packages = with pkgs; [ + rust-analyzer + jdk8 + ] ++ deps; + }; + }); +} diff --git a/src/Test.class b/src/Test.class new file mode 100644 index 0000000000000000000000000000000000000000..fd2d155db14152adc540c2e28f3579118b9dc04d GIT binary patch literal 525 zcmYjNO;5r=6r6=Zp%lUTiKvm2KvXW?FkUbu8ls614&D}Nprme-((2#RgPM5o2l%6m zvtWRPtU%1=1Iu@}f(DWVO~8Fj~Qo)k4Qp?rr=uaKoM)_^cR_ zJB^46W5ppk9r6`8t=?QetjOFAChlX@Y+rR-wUzQh-RcH$_~^DhO_}YH@=SZQVIxm( zW7qeClcSd)^oJE2Sy(oTx?_x`=P~ICf#ULBb3xv9S-Ovv(wTTG?j+ukeB+UOV6@l-k{r=PwDUhynjwf, +} + +impl Attribute { + pub fn parse<'a>(i: parse::Input<'a>, constant_pool: &ConstantPool) -> parse::Result<'a, Self> { + let (i, (name_info, length)) = tuple(( + context("name_info", be_u16), + context("length", be_u32), + ))(i)?; + + let name = constant_pool.get_utf8(name_info as usize).unwrap(); + + let (i, info) = context("info", take(length as usize))(i)?; + + Ok((i, Self { + name: name.to_owned(), + info: info.to_vec(), + })) + } +} diff --git a/src/constant_pool.rs b/src/constant_pool.rs new file mode 100644 index 0000000..05c5c94 --- /dev/null +++ b/src/constant_pool.rs @@ -0,0 +1,45 @@ +use crate::ConstantPoolEntry; + +#[derive(Debug)] +pub struct Classinfo { + pub name: String, +} + +#[derive(Debug)] +pub struct ConstantPool { + entries: Vec, +} + +impl ConstantPool { + pub(crate) fn new(entries: Vec) -> Self { + Self { + entries, + } + } + + fn get(&self, index: usize) -> Option<&ConstantPoolEntry> { + self.entries.get(index - 1) + } + + pub fn get_utf8(&self, index: usize) -> Option<&str> { + self.get(index).and_then(|entry| match entry { + ConstantPoolEntry::Utf8(s) => Some(s.as_str()), + _ => None + }) + } + + pub fn get_class_info(&self, index: usize) -> Option { + self.get(index).and_then(|entry| match entry { + ConstantPoolEntry::Class(name_index) => self.get_utf8(*name_index as usize) + .map(|s| Classinfo { name: s.to_owned() }), + _ => None, + }) + } + + pub fn get_string(&self, index: usize) -> Option<&str> { + self.get(index).and_then(|entry| match entry { + ConstantPoolEntry::String { string_index } => self.get_utf8(*string_index as usize), + _ => None, + }) + } +} diff --git a/src/field.rs b/src/field.rs new file mode 100644 index 0000000..b465e27 --- /dev/null +++ b/src/field.rs @@ -0,0 +1,41 @@ +use enumflags2::BitFlags; +use nom::{error::context, number::complete::be_u16, sequence::tuple}; + +use crate::{attribute::Attribute, constant_pool::ConstantPool, parse, FieldAccessFlag}; + +#[derive(Debug)] +pub struct Field { + pub access_flags: BitFlags, + pub name: String, + pub descriptor: String, + pub attributes: Vec, +} + +impl Field { + pub fn parse<'a>(i: parse::Input<'a>, constant_pool: &ConstantPool) -> parse::Result<'a, Self> { + let (mut i, (access_flags, name_index, descriptor_index, attributes_count)) = tuple(( + context("access_flags", FieldAccessFlag::parse), + context("name_index", be_u16), + context("descriptor_index", be_u16), + context("attributes_count", be_u16), + ))(i)?; + + // TODO: not shit error handling + let name = constant_pool.get_utf8(name_index as usize).expect("invalid class file").to_owned(); + let descriptor = constant_pool.get_utf8(descriptor_index as usize).expect("invalid class file").to_owned(); + + let mut attributes = Vec::with_capacity(attributes_count as usize); + for _ in 0..attributes_count { + let attribute = Attribute::parse(i, constant_pool)?; + i = attribute.0; + attributes.push(attribute.1); + } + + Ok((i, Self { + access_flags, + name, + descriptor, + attributes, + })) + } +} diff --git a/src/java_utf8.rs b/src/java_utf8.rs new file mode 100644 index 0000000..54879ee --- /dev/null +++ b/src/java_utf8.rs @@ -0,0 +1,56 @@ +#[derive(Clone, Debug)] +pub enum InvalidJavaUtf8 { + NullCharacter, + Invalid, +} + +pub fn parse(bytes: &[u8]) -> Result { + let mut result = String::with_capacity(bytes.len()); + let mut position = 0; + while position < bytes.len() { + let x = bytes[position]; + if x == 0 { + return Err(InvalidJavaUtf8::NullCharacter); + } + position += 1; + if x < 0x80 { + result.push(x as char); + } else { + let x = x as u32; + let y = bytes[position] as u32; + position += 1; + let c = if (x & 0b11100000) == 0b11000000 && (y & 0b11000000) == 0b10000000 { + let c = ((x & 0x1f) << 6) + (y & 0x3f); + char::from_u32(c).expect("invalid utf8") + } else { + let x = x as u32; + let y = y as u32; + let z = bytes[position] as u32; + position += 1; + if (x & 0b11110000) == 0b11100000 && (y & 0b11000000) == 0b10000000 && (z & 0b11000000) == 0b10000000 { + let c = ((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f); + char::from_u32(c).expect("invalid utf8") + } else { + let u = x; + let v = y; + let w = z; + let x = bytes[position] as u32; + position += 1; + let y = bytes[position] as u32; + position += 1; + let z = bytes[position] as u32; + position += 1; + if u == 0b11101101 && (v & 0b11110000) == 0b10100000 && (w & 0b11000000) == 0b10000000 && x == 0b11101101 && (y & 0xf0) == 0b10110000 && (z & 0b11000000) == 0b10000000 { + let c = 0x10000 + ((v & 0x0f) << 16) + ((w & 0x3f) << 10) + ((y & 0x0f) << 6) + (z & 0x3f); + char::from_u32(c).expect("invalid utf8") + } else { + return Err(InvalidJavaUtf8::Invalid); + } + } + }; + result.push(c); + } + } + result.shrink_to_fit(); + Ok(result) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..61c0350 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,283 @@ +use attribute::Attribute; +use constant_pool::{Classinfo, ConstantPool}; +use derive_try_from_primitive::TryFromPrimitive; +use field::Field; +use method::Method; +use nom::{bytes::complete::{tag, take}, combinator::{map, map_res}, error::{context, ErrorKind}, number::complete::{be_u16, be_u8, le_u16}, sequence::tuple}; +use enumflags2::{bitflags, BitFlags}; + +pub mod attribute; +pub mod constant_pool; +pub mod field; +pub mod java_utf8; +pub mod method; +pub mod parse; + +pub struct HexDump<'a>(&'a [u8]); + +use std::fmt; +impl<'a> fmt::Debug for HexDump<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for &x in self.0.iter().take(20) { + write!(f, "{:02x} ", x)?; + } + Ok(()) + } +} + +#[derive(Debug, TryFromPrimitive)] +#[repr(u8)] +pub enum ConstantPoolEntryType { + Class = 7, + Fieldref = 9, + Methodref = 10, + InterfaceMethodref = 11, + String = 8, + Integer = 3, + Float = 4, + Long = 5, + Double = 6, + NameAndType = 12, + Utf8 = 1, + MethodHandle = 15, + MethodType = 16, + InvokeDynamic = 18, +} + +impl ConstantPoolEntryType { + pub fn parse(i: parse::Input) -> parse::Result { + map_res(be_u8, |x| Self::try_from(x).map_err(|_| ErrorKind::Alt))(i) + } +} + +#[derive(Debug)] +pub enum ConstantPoolEntry { + Class(u16), + Fieldref { + class_index: u16, + name_and_type_index: u16, + }, + Methodref { + class_index: u16, + name_and_type_index: u16, + }, + InterfaceMethodref { + class_index: u16, + name_and_type_index: u16, + }, + String { + string_index: u16, + }, + Integer, + Float, + Long, + Double, + NameAndType { + name_index: u16, + descriptor_index: u16, + }, + Utf8(String), + MethodHandle, + InvokeDynamic, +} + +impl ConstantPoolEntry { + pub fn parse(i: parse::Input) -> parse::Result { + let (i, ty) = context("cp_info tag", ConstantPoolEntryType::parse)(i)?; + let (i, result) = match ty { + ConstantPoolEntryType::Class => { + let (i, name_index) = context("Class_info name_index", be_u16)(i)?; + (i, Self::Class(name_index)) + }, + ConstantPoolEntryType::Fieldref => { + let (i, (class_index, name_and_type_index)) = tuple(( + context("Fieldref_info class_index", be_u16), + context("Fieldref_info name_and_type_index", be_u16), + ))(i)?; + (i, Self::Fieldref { class_index, name_and_type_index }) + }, + ConstantPoolEntryType::Methodref => { + let (i, (class_index, name_and_type_index)) = tuple(( + context("Methodref_info class_index", be_u16), + context("Methodref_info name_and_type_index", be_u16), + ))(i)?; + (i, Self::Methodref { class_index, name_and_type_index }) + }, + ConstantPoolEntryType::InterfaceMethodref => { + let (i, (class_index, name_and_type_index)) = tuple(( + context("InterfaceMethodref_info class_index", be_u16), + context("InterfaceMethodref_info name_and_type_index", be_u16), + ))(i)?; + (i, Self::InterfaceMethodref { class_index, name_and_type_index }) + }, + ConstantPoolEntryType::Utf8 => { + let (i, length) = context("Utf8_info length", be_u16)(i)?; + let (i, data) = context("Uft8_info bytes", take(length as usize))(i)?; + let parsed = java_utf8::parse(data).expect("beans"); + (i, Self::Utf8(parsed)) + }, + ConstantPoolEntryType::NameAndType => { + let (i, (name_index, descriptor_index)) = tuple(( + context("NameAndType_info name_index", be_u16), + context("NameAndType_info descriptor_index", be_u16), + ))(i)?; + (i, Self::NameAndType { name_index, descriptor_index }) + } + ConstantPoolEntryType::String => { + let (i, string_index) = context("String_info string_index", be_u16)(i)?; + (i, Self::String { string_index }) + } + v => unimplemented!("{v:?}"), + }; + Ok((i, result)) + } +} + +#[bitflags] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ClassAccessFlag { + Public = 0x001, + Final = 0x0010, + Super = 0x0020, + Interface = 0x0200, + Abstract = 0x0400, + Synthetic = 0x1000, + Annotation = 0x2000, + Enum = 0x4000, +} + +#[bitflags] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum FieldAccessFlag { + Public = 0x0001, + Private = 0x0002, + Protected = 0x0004, + Static = 0x0008, + Final = 0x0010, + Volatile = 0x0040, + Transient = 0x0080, + Synthetic = 0x1000, + Enum = 0x4000, +} + +impl_parse_for_enumflags!(ClassAccessFlag, be_u16); +impl_parse_for_enumflags!(FieldAccessFlag, be_u16); + +#[derive(Debug)] +pub struct ClassFile { + pub minor_version: u16, + pub major_version: u16, + pub constant_pool: ConstantPool, + pub access_flags: BitFlags, + pub this_class: u16, + pub super_class: u16, + pub interfaces: Vec, + pub fields: Vec, + pub methods: Vec, + pub attributes: Vec, +} + +impl ClassFile { + const MAGIC: &'static [u8] = &[0xCA, 0xFE, 0xBA, 0xBE]; + + pub fn parse(i: parse::Input) -> parse::Result { + let (i, _) = context("magic", tag(Self::MAGIC))(i)?; + let (mut i, (minor_version, major_version, constant_pool_count)) = tuple(( + context("minor_version", be_u16), + context("major_version", be_u16), + context("constant_pool_count", be_u16), + ))(i)?; + + let constant_pool_count = constant_pool_count - 1; + + let mut constant_pool_entries = Vec::with_capacity(constant_pool_count as usize); + for j in 0..constant_pool_count { + let res = context("constant_pool", ConstantPoolEntry::parse)(i)?; + i = res.0; + constant_pool_entries.push(res.1); + } + + let constant_pool = ConstantPool::new(constant_pool_entries); + + let (mut i, (access_flags, this_class, super_class, interfaces_count)) = tuple(( + context("access_flags", ClassAccessFlag::parse), + context("this_class", be_u16), + context("super_class", be_u16), + context("interfaces_count", be_u16), + ))(i)?; + + let mut interfaces = Vec::with_capacity(interfaces_count as usize); + for _ in 0..interfaces_count { + let cp_index = context("interface", be_u16)(i)?; + i = cp_index.0; + let cp_index = cp_index.1; + // TODO: Not shit error handling + interfaces.push(constant_pool.get_class_info(cp_index as usize).expect("invalid class file")); + } + + let (mut i, fields_count) = context("fields_count", be_u16)(i)?; + let mut fields = Vec::with_capacity(fields_count as usize); + for _ in 0..fields_count { + let field = Field::parse(i, &constant_pool)?; + i = field.0; + let field = field.1; + fields.push(field); + } + + let (mut i, methods_count) = context("methods_count", be_u16)(i)?; + let mut methods = Vec::with_capacity(methods_count as usize); + for _ in 0..methods_count { + let method = Method::parse(i, &constant_pool)?; + i = method.0; + let method = method.1; + methods.push(method); + } + + let (mut i, attributes_count) = context("attributes_count", be_u16)(i)?; + let mut attributes = Vec::with_capacity(attributes_count as usize); + for _ in 0..attributes_count { + let attribute = Attribute::parse(i, &constant_pool)?; + i = attribute.0; + let attribute = attribute.1; + attributes.push(attribute); + } + + Ok((i, Self { + minor_version, + major_version, + constant_pool, + access_flags, + this_class, + super_class, + interfaces, + fields, + methods, + attributes, + })) + } + + pub fn parse_or_print_error(i: parse::Input) -> Option { + match Self::parse(i) { + Ok((_, file)) => Some(file), + Err(nom::Err::Failure(err)) | Err(nom::Err::Error(err)) => { + eprintln!("Parsing failed:"); + for (input, err) in err.errors { + use nom::Offset; + let offset = i.offset(input); + eprintln!("{:?} at position {}:", err, offset); + eprintln!("{:>08x}: {:?}", offset, HexDump(input)); + } + None + } + Err(_) => panic!("unexpected nom error"), + } + } +} + +impl ClassFile { + pub fn this_class(&self) -> Classinfo { + self.constant_pool.get_class_info(self.this_class as usize).expect("invalid class file") + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ab40588 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,10 @@ +use std::{env, error::Error, fs}; + +fn main() -> Result<(), Box> { + let input_path = env::args().nth(1).expect("usage: jarr FILE"); + let input = fs::read(&input_path)?; + let file = jarr::ClassFile::parse_or_print_error(&input).unwrap(); + println!("{file:#?}"); + println!("{:?}", file.this_class()); + Ok(()) +} diff --git a/src/method.rs b/src/method.rs new file mode 100644 index 0000000..a54d8c0 --- /dev/null +++ b/src/method.rs @@ -0,0 +1,60 @@ +use enumflags2::{bitflags, BitFlags}; +use nom::{error::context, number::complete::be_u16, sequence::tuple}; + +use crate::{attribute::Attribute, constant_pool::ConstantPool, impl_parse_for_enumflags, parse}; + +#[bitflags] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum MethodAccessFlag { + Public = 0x0001, + Private = 0x0002, + Protected = 0x004, + Static = 0x0008, + Final = 0x0010, + Synchronized = 0x0020, + Bridge = 0x0040, + Varargs = 0x0080, + Native = 0x0100, + Abstract = 0x0400, + Strict = 0x0800, + Synthetic = 0x1000, +} + +impl_parse_for_enumflags!(MethodAccessFlag, be_u16); + +#[derive(Debug)] +pub struct Method { + pub access_flags: BitFlags, + pub name: String, + pub descriptor: String, + pub attributes: Vec, +} + +impl Method { + pub fn parse<'a>(i: parse::Input<'a>, constant_pool: &ConstantPool) -> parse::Result<'a, Self> { + let (mut i, (access_flags, name_index, descriptor_index, attributes_count)) = tuple(( + context("access_flags", MethodAccessFlag::parse), + context("name_index", be_u16), + context("descriptor_index", be_u16), + context("attributes_count", be_u16), + ))(i)?; + + let name = constant_pool.get_utf8(name_index as usize).expect("invalid class file").to_owned(); + let descriptor = constant_pool.get_utf8(descriptor_index as usize).expect("invalid class file").to_owned(); + + let mut attributes = Vec::with_capacity(attributes_count as usize); + for _ in 0..attributes_count { + let attribute = Attribute::parse(i, constant_pool)?; + i = attribute.0; + attributes.push(attribute.1); + } + + Ok((i, Self { + access_flags, + name, + descriptor, + attributes, + })) + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..7f91087 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,21 @@ +pub type Input<'a> = &'a [u8]; +pub type Result<'a, O> = nom::IResult, O, nom::error::VerboseError>>; + +#[macro_export] +macro_rules! impl_parse_for_enumflags { + ($type: ident, $number_parser: ident) => { + impl $type { + pub fn parse(i: crate::parse::Input) -> crate::parse::Result> { + use nom::{ + combinator::map_res, + error::{context, ErrorKind}, + number::complete::$number_parser, + }; + let parser = map_res($number_parser, |x| { + enumflags2::BitFlags::::from_bits(x).map_err(|_| ErrorKind::Alt) + }); + context(stringify!($type), parser)(i) + } + } + }; +}