feat: initial commit
This commit is contained in:
commit
618a5a1ae7
16 changed files with 821 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/.direnv
|
111
Cargo.lock
generated
Normal file
111
Cargo.lock
generated
Normal file
|
@ -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"
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -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"
|
82
flake.lock
Normal file
82
flake.lock
Normal file
|
@ -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
|
||||||
|
}
|
54
flake.nix
Normal file
54
flake.nix
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
BIN
src/Test.class
Normal file
BIN
src/Test.class
Normal file
Binary file not shown.
17
src/Test.java
Normal file
17
src/Test.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//package dev.ashhhleyyy.jarr;
|
||||||
|
|
||||||
|
public class Test implements Runnable {
|
||||||
|
private final String beans = "hello, world!";
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
this.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String asdasd(long longBoi, int i, boolean b, Object o, float f, double d, short s, char c) {
|
||||||
|
return "haii";
|
||||||
|
}
|
||||||
|
}
|
27
src/attribute.rs
Normal file
27
src/attribute.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use nom::{bytes::complete::take, error::context, number::complete::{be_u16, be_u32}, sequence::tuple};
|
||||||
|
|
||||||
|
use crate::{constant_pool::{self, ConstantPool}, parse};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attribute {
|
||||||
|
pub name: String,
|
||||||
|
pub info: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
45
src/constant_pool.rs
Normal file
45
src/constant_pool.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::ConstantPoolEntry;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Classinfo {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConstantPool {
|
||||||
|
entries: Vec<ConstantPoolEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstantPool {
|
||||||
|
pub(crate) fn new(entries: Vec<ConstantPoolEntry>) -> 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<Classinfo> {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
41
src/field.rs
Normal file
41
src/field.rs
Normal file
|
@ -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<FieldAccessFlag>,
|
||||||
|
pub name: String,
|
||||||
|
pub descriptor: String,
|
||||||
|
pub attributes: Vec<Attribute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
56
src/java_utf8.rs
Normal file
56
src/java_utf8.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum InvalidJavaUtf8 {
|
||||||
|
NullCharacter,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(bytes: &[u8]) -> Result<String, InvalidJavaUtf8> {
|
||||||
|
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)
|
||||||
|
}
|
283
src/lib.rs
Normal file
283
src/lib.rs
Normal file
|
@ -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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<ClassAccessFlag>,
|
||||||
|
pub this_class: u16,
|
||||||
|
pub super_class: u16,
|
||||||
|
pub interfaces: Vec<Classinfo>,
|
||||||
|
pub fields: Vec<Field>,
|
||||||
|
pub methods: Vec<Method>,
|
||||||
|
pub attributes: Vec<Attribute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClassFile {
|
||||||
|
const MAGIC: &'static [u8] = &[0xCA, 0xFE, 0xBA, 0xBE];
|
||||||
|
|
||||||
|
pub fn parse(i: parse::Input) -> parse::Result<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
10
src/main.rs
Normal file
10
src/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::{env, error::Error, fs};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
60
src/method.rs
Normal file
60
src/method.rs
Normal file
|
@ -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<MethodAccessFlag>,
|
||||||
|
pub name: String,
|
||||||
|
pub descriptor: String,
|
||||||
|
pub attributes: Vec<Attribute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
21
src/parse.rs
Normal file
21
src/parse.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
pub type Input<'a> = &'a [u8];
|
||||||
|
pub type Result<'a, O> = nom::IResult<Input<'a>, O, nom::error::VerboseError<Input<'a>>>;
|
||||||
|
|
||||||
|
#[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<enumflags2::BitFlags<Self>> {
|
||||||
|
use nom::{
|
||||||
|
combinator::map_res,
|
||||||
|
error::{context, ErrorKind},
|
||||||
|
number::complete::$number_parser,
|
||||||
|
};
|
||||||
|
let parser = map_res($number_parser, |x| {
|
||||||
|
enumflags2::BitFlags::<Self>::from_bits(x).map_err(|_| ErrorKind::Alt)
|
||||||
|
});
|
||||||
|
context(stringify!($type), parser)(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue