From 89fd4cec4cb842dc9d323dd4b5be26664e794d53 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Thu, 27 Nov 2025 22:41:48 -0600 Subject: [PATCH 01/27] add InnerClasses parsing --- Cargo.toml | 4 +- java-assets/compile.sh | 1 + .../compiled-classes/InnerClasses$1.class | Bin 0 -> 1184 bytes .../InnerClasses$1EnglishGreeting.class | Bin 0 -> 1209 bytes .../compiled-classes/InnerClasses$2.class | Bin 0 -> 1176 bytes .../InnerClasses$HelloWorld.class | Bin 0 -> 251 bytes .../compiled-classes/InnerClasses.class | Bin 0 -> 641 bytes java-assets/src/InnerClasses.java | 47 +++++++++++++ src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 34 ++++++++- src/attribute_info/types.rs | 30 ++++++++ src/lib.rs | 35 +++++----- src/types.rs | 1 + tests/classfile.rs | 66 +++++++----------- tests/code_attribute.rs | 59 +++++++++++++++- 15 files changed, 215 insertions(+), 63 deletions(-) create mode 100644 java-assets/compiled-classes/InnerClasses$1.class create mode 100644 java-assets/compiled-classes/InnerClasses$1EnglishGreeting.class create mode 100644 java-assets/compiled-classes/InnerClasses$2.class create mode 100644 java-assets/compiled-classes/InnerClasses$HelloWorld.class create mode 100644 java-assets/compiled-classes/InnerClasses.class create mode 100644 java-assets/src/InnerClasses.java diff --git a/Cargo.toml b/Cargo.toml index d1aa695..244e8e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "classfile-parser" description = "A parser for Java Class files written in rust" version = "0.3.9" -edition = "2021" +edition = "2024" authors = ["Nick Palmer "] keywords = ["java", "parsing", "class", "classfile"] categories = ["parser-implementations"] @@ -12,6 +12,6 @@ license = "MIT" exclude = ["java-assets/out/**/*"] [dependencies] -nom = "7" +nom = "^7" bitflags = "^2.3" cesu8 = "^1.1" diff --git a/java-assets/compile.sh b/java-assets/compile.sh index 2ce8fb1..f703842 100755 --- a/java-assets/compile.sh +++ b/java-assets/compile.sh @@ -25,6 +25,7 @@ javac -d java-assets/compiled-classes/ -g:none java-assets/src/Factorial.java javac -d java-assets/compiled-classes/ java-assets/src/Instructions.java javac -d java-assets/compiled-classes/ java-assets/src/UnicodeStrings.java javac -d java-assets/compiled-classes/ java-assets/src/DeprecatedAnnotation.java +javac -d java-assets/compiled-classes/ java-assets/src/InnerClasses.java javac -g -d java-assets/compiled-classes/ java-assets/src/LocalVariableTable.java javac -d java-assets/compiled-classes/ java-assets/src/HelloWorld.java diff --git a/java-assets/compiled-classes/InnerClasses$1.class b/java-assets/compiled-classes/InnerClasses$1.class new file mode 100644 index 0000000000000000000000000000000000000000..7410ad0c4c94402eaa2861bf16de0efb2469acda GIT binary patch literal 1184 zcmaJ=TTc@~7(D}}+m;1NvE0GRMcZlp5zTbZ?e*xIU+XQ;hYawEz4^e^Si9C}9S9(W< zkB29!Y6PMij^~)y0=?PXehe{z0i&BHkD0{s?j8v_^=7<}(}$}hPx2vo4J zCJ^ytT?wSh*QqPUcf6xwg3P3iVWb4?BVQ>~(RHOgqSM)Kcy2$AJ4jm?wK0bA8)7Tx zfl+m8q%yMGWJecvS;(-QlJ#PWJD#*Lg=vAYx;#}S?Ny~YaLh4hfsvjG1n%Efxm!#E zv$$&^W8+>Zf26g!n*{}|Mp)kUEX=dy4P&;I>*@pTyETE~Y`ODaF}F|GO1efLhRTko zcAE7=5dA_whm}(a#L#)7+*Hp^dN@E++;<%ybfY0iuDAZ;+ci# zq027>7Os!n@y_(ADzs0&ZTyz38ttD8eCf3O3fQi&Exnq{YgE?ywAmyK2D`eOPJrJ1V zK-ffl_j2@Mf$NK$Jz(r%?9cy%$fv9yh=1iLFDu_68Av07Q36S0md{E4%p%Hn$e$-9 z%x#f-fuVo7;{;9+z(%|xtX~yO=a*RP8%FZq5x*n|7nr%G54LnpQRy^0bA~a;Dm}g3^GPR3s^Z2cL#^QWmx|&F&P_H-CnI zL!+4R;1BRe8Ncl|X-kO@Gv8d!`OY_IX8!!W{0(3WZ%u?SWFTxIf+$1sl%Mmw!(}c1 z@%U6!G(&XVmbTtt7|LW1V~8fMI|d-AKChZMxN$Udkhb* zQywhF#5C?1n6Yr*FMq6kxPCzfqv6Xt(!d-^_W86c97i4bb~21+O1*o9>>;^UR8{gJ zQL?4jYu1m2d%%w!T1kEo`b1Utxl6Wc;Zn(wws#C>SvB2?*s*=wXm83DzdXveEh~=l z$on9Pfn{>O7ib~xzlNB{TkQ&IxFP+N=n-7fN;T5gi7Q@$G%R9h8VVBFQL$9i&)LFAp7`%p_l|JG@ zt0As|f9k;p&%BWBS?Z)Ha>ps#S`^xjU3t!C_vQ?|W>^?F<8`GQ*kqXgKa_r4a8At7 z#|VL8mKywDM*R@A2{eR%?@F$Bbd%DlGb;O<$fT3i3#8WQ(gK^<~F^naw;8A;J39A%il>RBT$5^J>B(Y73uc*xuwh)bB9UFLqiGSlzCv^Y- literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/InnerClasses$2.class b/java-assets/compiled-classes/InnerClasses$2.class new file mode 100644 index 0000000000000000000000000000000000000000..7f2815c93a9545e1117a14759c5a7531c9d4e590 GIT binary patch literal 1176 zcmaJ=YflqF6g^YgZo8~diWR{ZLXoySswlpSkI+bJQjw$tKKN;9C$O+P)9g-3`puu= z-_R%~eDDYOql|aEO}e$jO=j=x>^b+IIWzb7pUYnWw(-_P3}Xi377|D@WKQ`xFFRZw zmp>kzil$~rZrRe-uNlS)#e)=53<V*baGPPq}Pxm%AT#&(N2jv}UV3A4=8B-IEqYjnlv>2ue_2|D!@qk>bsuuY$S+}Lw?YKw6 z+vi6Ptz+#_4H@JMo^=N*IDP#v!+cI*H*6-~Jw$fIoUWYbYTc^@V*utv^D zffmZa@uz&=?J1<`uJCnD`DANmTPf{pkGDg*pTc9>WKB7Ib)HQov1#Hdo*8%^xcq`4 zKR9w*o~tua4o^Nbeup=;^12LPqL%l8VTBr(Er+gAU8%E9yE1wVk(D9hezz^IfPaeM z!+TvRMwa?1s@!oJwiXq#bZ|}vwiuR1c660c1JovW{|}{x3U`HhYJ(UU7HA-7B6^R} zNFYz^OEg=i7^65|`UzIb8b6T!N}qID=^cd3tZsNfIiXFxlBq6)R{$!MXC}*2`d!i oSj9RX_EuK$h+>M;KcTUKHTs$(wn;H~76|(?nL-7x@CLJg0jcd9dH?_b literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/InnerClasses$HelloWorld.class b/java-assets/compiled-classes/InnerClasses$HelloWorld.class new file mode 100644 index 0000000000000000000000000000000000000000..c5831ef7ef8b8f5a18779cc2ebf64527bafd65c7 GIT binary patch literal 251 zcmYk0Jqp4=6ohAuni!2gg?Isr*q8&@DTsn-5zsouM@U$AVR!X#79PMui7z%<4R82n z-n`zACx9geJ{kg(tx{xGGP9QKbVE|=lQ#0=qbV?~vU`@uOclxDTv6@>T7@BE_2Su4 zp!dB}eI>0(U>xs$iPV`=6^j}RQ++cztxL(!=>Oxq<^_U1S+~*F`G~mnpWyUwRU6>f hJRfNAz82VE^%1b+p;P+^(fw)<5o^c^u0+2bd;o&JIZglo literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/InnerClasses.class b/java-assets/compiled-classes/InnerClasses.class new file mode 100644 index 0000000000000000000000000000000000000000..9e42ecd35235b147a900f5e1c19ec400dd2438a4 GIT binary patch literal 641 zcmZ8e+fEZv6kWTwDW^kkSU^gv)~ZD=6<#qs&`>mK>qCvkH>dQZ4jE>WnZd-*^2tCG zKfsS7tW!f;dXjVYb*;Vk+5hft{{lG08-)at7E(6S$Owv;&QGW9IsT;mX>_SCVnOD; z>$~xZAlcgaVj(N2ee``Dc04DFbksaN^CzAg{WuS`j$MDEU_lwWoS@X|E#@8@f>l9& z(b+syu%Rpzmjg!%1*ja>@WevJ#=7Zn;}Q0Q_PpS05PD-ldSW`rVpEXnhI(v(TQ;iL z7TB}cFu2lzuLYG>Z$9WS4%zmsw>mQy`N`A?#sn04uCE8zS0f#Mc19jO*~s}dt1eK( z;5xj}UDx6j>xt2VI-K3ffuN8CFUf+nULZ8j8=_ODARBMKz>sZ zJm+h|Mi&AfQokdK6us}Ju)Z;Ur8k9blze}Rf>BC?-5Znzd*zKAJWXJKX3bga+#&dV zE2yB%I#ukFe-As@M;iy^Z!n&~OEf7JtF3WWHc@rj4cZ3(ir3~fr|@4euZGu|>^-yX BeVqUR literal 0 HcmV?d00001 diff --git a/java-assets/src/InnerClasses.java b/java-assets/src/InnerClasses.java new file mode 100644 index 0000000..6bbdc7b --- /dev/null +++ b/java-assets/src/InnerClasses.java @@ -0,0 +1,47 @@ +public class InnerClasses { + interface HelloWorld { + public void greet(); + public void greetSomeone(String someone); + } + + public void sayHello() { + class EnglishGreeting implements HelloWorld { + String name = "world"; + public void greet() { + greetSomeone("world"); + } + public void greetSomeone(String someone) { + name = someone; + System.out.println("Hello " + name); + } + } + + HelloWorld englishGreeting = new EnglishGreeting(); + + HelloWorld frenchGreeting = new HelloWorld() { + String name = "tout le monde"; + public void greet() { + greetSomeone("tout le monde"); + } + public void greetSomeone(String someone) { + name = someone; + System.out.println("Salut " + name); + } + }; + + HelloWorld spanishGreeting = new HelloWorld() { + String name = "mundo"; + public void greet() { + greetSomeone("mundo"); + } + + public void greetSomeone(String someone) { + name = someone; + System.out.println("Hola, " + name); + } + }; + englishGreeting.greet(); + frenchGreeting.greetSomeone("Fred"); + spanishGreeting.greet(); + } +} diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 509ae8b..a7eceb8 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -9,5 +9,6 @@ pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::method_parameters_attribute_parser; +pub use self::parser::inner_classes_attribute_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 1c42aee..dd6a3f5 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -7,8 +7,8 @@ use nom::{ Err as BaseErr, }; -use crate::attribute_info::types::StackMapFrame::*; use crate::attribute_info::*; +use crate::{attribute_info::types::StackMapFrame::*}; // Using a type alias here evades a Clippy warning about complex types. type Err = BaseErr>; @@ -94,6 +94,38 @@ pub fn parameters_parser(input: &[u8]) -> Result<(&[u8], ParameterAttribute), Er )) } +pub fn inner_classes_attribute_parser( + input: &[u8], +) -> Result<(&[u8], InnerClassesAttribute), Err<&[u8]>> { + let (input, number_of_classes) = be_u16(input)?; + let (input, classes) = count(inner_class_info_parser, number_of_classes as usize)(input)?; + let ret = ( + input, + InnerClassesAttribute { + number_of_classes, + classes, + }, + ); + + Ok(ret) +} + +pub fn inner_class_info_parser(input: &[u8]) -> Result<(&[u8], InnerClassInfo), Err<&[u8]>> { + let (input, inner_class_info_index) = be_u16(input)?; + let (input, outer_class_info_index) = be_u16(input)?; + let (input, inner_name_index) = be_u16(input)?; + let (input, inner_class_access_flags) = be_u16(input)?; + Ok(( + input, + InnerClassInfo { + inner_class_info_index, + outer_class_info_index, + inner_name_index, + inner_class_access_flags, + }, + )) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 2a690cd..9bc1300 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -37,6 +37,36 @@ pub struct ParameterAttribute { pub access_flags: u16, } +#[derive(Clone, Debug)] +pub struct InnerClassesAttribute { + pub number_of_classes: u16, + pub classes: Vec, +} + +#[derive(Clone, Debug)] +pub struct InnerClassInfo { + pub inner_class_info_index: u16, + pub outer_class_info_index: u16, + pub inner_name_index: u16, + pub inner_class_access_flags: u16, +} + +bitflags! { + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub struct InnerClassAccessFlags: u16 { + const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. + const PRIVATE = 0x0002; // Declared private; may not be accessed from outside its package. + const PROTECTED = 0x0004; // Declared praotected; may only be accessed within children. + const STATIC = 0x0008; // Declared static. + const FINAL = 0x0010; // Declared final; no subclasses allowed. + const INTERFACE = 0x0200; // Is an interface, not a class. + const ABSTRACT = 0x0400; // Declared abstract; must not be instantiated. + const SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code. + const ANNOTATION = 0x2000; // Declared as an annotation type. + const ENUM = 0x4000; // Declared as an enum type. + } +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/src/lib.rs b/src/lib.rs index 26a7f23..ffea935 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,34 +46,35 @@ pub fn parse_class(class_name: &str) -> Result { }; let mut reader = BufReader::new(file); - parse_class_from_reader(&mut reader, display.to_string()) + parse_class_from_reader(&mut reader) } /// Attempt to parse a class file given a reader that implements the std::io::Read trait. -/// The file_path parameter is only used in case of errors to provide reasonable error -/// messages. +/// Parameters shouldn't be passed for the sole purpose of debug output, this should be +/// abstracted instead. +/// OLD: The file_path parameter is only used in case of errors to provide +/// reasonable error messages. /// /// ```rust /// let mut reader = "this_will_be_parsed_as_classfile".as_bytes(); -/// let result = classfile_parser::parse_class_from_reader(&mut reader, "path/to/Java.class".to_string()); +/// let result = classfile_parser::parse_class_from_reader(&mut reader); /// assert!(result.is_err()); /// ``` -pub fn parse_class_from_reader( - reader: &mut T, - file_path: String, -) -> Result { +pub fn parse_class_from_reader(reader: &mut T) -> Result { let mut class_bytes = Vec::new(); - if let Err(why) = reader.read_to_end(&mut class_bytes) { - return Err(format!( - "Unable to read {}: {}", - file_path, - &why.to_string() - )); - } + reader + .read_to_end(&mut class_bytes) + .expect("cannot continue, read_to_end failed"); let parsed_class = class_parser(&class_bytes); match parsed_class { - Ok((_, c)) => Ok(c), - _ => Err(format!("Failed to parse classfile {}", file_path)), + Ok((a, c)) => { + if !a.is_empty() { + eprintln!("Warning: not all bytes were consumed when parsing classfile, {} bytes remaining", a.len()); + } + + Ok(c) + } + Err(e) => Err(format!("Failed to parse classfile: {}", e)), } } diff --git a/src/types.rs b/src/types.rs index b1bd1bd..324cec7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -33,6 +33,7 @@ bitflags! { const SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code. const ANNOTATION = 0x2000; // Declared as an annotation type. const ENUM = 0x4000; // Declared as an enum type. + const MODULE = 0x8000; // Declared as a module type. } } diff --git a/tests/classfile.rs b/tests/classfile.rs index a807a85..5266cbb 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -17,32 +17,25 @@ fn test_valid_class() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "Code" { - code_const_index = (const_index + 1) as u16; - } + if let ConstantInfo::Utf8(ref c) = *const_item { + if c.utf8_string == "Code" { + code_const_index = (const_index + 1) as u16; } - _ => {} } } println!("Code index = {}", code_const_index); println!("Interfaces:"); - let mut interface_index = 0; - for i in &c.interfaces { + for (interface_index, i) in c.interfaces.iter().enumerate() { println!( "\t[{}] = const[{}] = {:?}", interface_index, i, c.const_pool[(i - 1) as usize] ); - - interface_index += 1; } println!("Fields:"); - let mut field_index = 0; - for f in &c.fields { + for (field_index, f) in c.fields.iter().enumerate() { println!( "\t[{}] Name(const[{}] = {:?}) - access({:?})", field_index, @@ -50,11 +43,9 @@ fn test_valid_class() { c.const_pool[(f.name_index - 1) as usize], f.access_flags ); - field_index += 1; } println!("Methods:"); - let mut method_index = 0; - for m in &c.methods { + for (method_index, m) in c.methods.iter().enumerate() { println!( "\t[{}] Name(const[{}] = {:?}) - access({:?})", method_index, @@ -62,7 +53,6 @@ fn test_valid_class() { c.const_pool[(m.name_index - 1) as usize], m.access_flags ); - method_index += 1; for a in &m.attributes { if a.attribute_name_index == code_const_index { @@ -98,28 +88,25 @@ fn test_utf_string_constants() { let mut found_utf_unpaired_string = false; for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" { - found_utf_maths_string = true; - } - if c.utf8_string - == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" - { - found_utf_runes_string = true; - } - if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" - { - found_utf_braille_string = true; - } - if c.utf8_string == "\0𠜎" { - found_utf_modified_string = true; - } - if c.utf8_string == "X���X" && c.bytes.len() == 5 { - found_utf_unpaired_string = true; - } + if let ConstantInfo::Utf8(ref c) = *const_item { + if c.utf8_string == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" { + found_utf_maths_string = true; + } + if c.utf8_string + == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + { + found_utf_runes_string = true; + } + if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" + { + found_utf_braille_string = true; + } + if c.utf8_string == "\0𠜎" { + found_utf_modified_string = true; + } + if c.utf8_string == "X���X" && c.bytes.len() == 5 { + found_utf_unpaired_string = true; } - _ => {} } } @@ -140,10 +127,7 @@ fn test_utf_string_constants() { fn test_malformed_class() { let malformed_class = include_bytes!("../java-assets/compiled-classes/malformed.class"); let res = class_parser(malformed_class); - match res { - Result::Ok((_, _)) => panic!("The file is not valid and shouldn't be parsed"), - _ => {} - }; + if let Result::Ok((_, _)) = res { panic!("The file is not valid and shouldn't be parsed") }; } // #[test] diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 43bbfa1..d8e0f20 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -1,6 +1,8 @@ extern crate classfile_parser; -use classfile_parser::attribute_info::{code_attribute_parser, method_parameters_attribute_parser}; +use classfile_parser::attribute_info::{ + InnerClassAccessFlags, code_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser +}; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, @@ -105,7 +107,7 @@ fn method_parameters() { assert_eq!( lookup_string( &class, - method_parameters.parameters.get(0).unwrap().name_index + method_parameters.parameters.first().unwrap().name_index ), Some("a".to_string()) ); @@ -118,6 +120,59 @@ fn method_parameters() { ); } +#[test] +fn inner_classes() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + dbg!(&class); + + for attr in &class.attributes { + match lookup_string(&class, attr.attribute_name_index) { + Some(x) if x == "InnerClasses" => { + let (_, inner_class_attrs) = inner_classes_attribute_parser(&attr.info).unwrap(); + + assert_eq!( + inner_class_attrs.number_of_classes, + 4 + ); + + assert_eq!( + inner_class_attrs.number_of_classes, + inner_class_attrs.classes.len() as u16 + ); + + for c in inner_class_attrs.classes { + dbg!(&class.const_pool[(c.inner_class_info_index-1) as usize]); + + // only == 0 when this class is a top-level class or interface, or when it's + // a local class or an anonymous class. + if c.outer_class_info_index != 0 { + assert_ne!(c.inner_class_info_index, c.outer_class_info_index); + + dbg!(&class.const_pool[(c.outer_class_info_index-1) as usize]); + } + + // only == 0 when this class is anonymous + if c.inner_name_index != 0 { + dbg!(&class.const_pool[(c.inner_name_index-1) as usize]); + } + + dbg!(InnerClassAccessFlags::from_bits_truncate( + c.inner_class_access_flags + )); + } + //uncomment to see dbg output from above + //assert_eq!(1, 2); + }, + Some(_) => {}, + None => panic!( + "Could not find attribute name for index {}", + attr.attribute_name_index + ), + } + } +} + #[test] fn local_variable_table() { // The class was not compiled with "javac -g" From cbbe8dcddb55b0904e7d71390d9318172b954774 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Thu, 27 Nov 2025 22:45:36 -0600 Subject: [PATCH 02/27] forgot `cargo fmt` on last commit --- src/attribute_info/mod.rs | 2 +- src/attribute_info/parser.rs | 227 +++++++++++++------------------- src/code_attribute/parser.rs | 47 +++---- src/constant_info/parser.rs | 4 +- src/field_info/parser.rs | 19 ++- src/lib.rs | 7 +- src/method_info/parser.rs | 19 ++- src/parser.rs | 37 +++--- tests/attr_bootstrap_methods.rs | 19 ++- tests/classfile.rs | 24 +++- tests/code_attribute.rs | 76 +++++------ 11 files changed, 214 insertions(+), 267 deletions(-) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index a7eceb8..6137280 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -8,7 +8,7 @@ pub use self::parser::bootstrap_methods_attribute_parser; pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; pub use self::parser::exceptions_attribute_parser; -pub use self::parser::method_parameters_attribute_parser; pub use self::parser::inner_classes_attribute_parser; +pub use self::parser::method_parameters_attribute_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index dd6a3f5..08d3cf3 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,14 +1,14 @@ use nom::{ + Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u16, be_u32, be_u8}, - Err as BaseErr, + number::complete::{be_u8, be_u16, be_u32}, }; +use crate::attribute_info::types::StackMapFrame::*; use crate::attribute_info::*; -use crate::{attribute_info::types::StackMapFrame::*}; // Using a type alias here evades a Clippy warning about complex types. type Err = BaseErr>; @@ -17,14 +17,11 @@ pub fn attribute_parser(input: &[u8]) -> Result<(&[u8], AttributeInfo), Err<&[u8 let (input, attribute_name_index) = be_u16(input)?; let (input, attribute_length) = be_u32(input)?; let (input, info) = take(attribute_length)(input)?; - Ok(( - input, - AttributeInfo { - attribute_name_index, - attribute_length, - info: info.to_owned(), - }, - )) + Ok((input, AttributeInfo { + attribute_name_index, + attribute_length, + info: info.to_owned(), + })) } pub fn exception_entry_parser(input: &[u8]) -> Result<(&[u8], ExceptionEntry), Err<&[u8]>> { @@ -32,15 +29,12 @@ pub fn exception_entry_parser(input: &[u8]) -> Result<(&[u8], ExceptionEntry), E let (input, end_pc) = be_u16(input)?; let (input, handler_pc) = be_u16(input)?; let (input, catch_type) = be_u16(input)?; - Ok(( - input, - ExceptionEntry { - start_pc, - end_pc, - handler_pc, - catch_type, - }, - )) + Ok((input, ExceptionEntry { + start_pc, + end_pc, + handler_pc, + catch_type, + })) } pub fn code_attribute_parser(input: &[u8]) -> Result<(&[u8], CodeAttribute), Err<&[u8]>> { @@ -53,19 +47,16 @@ pub fn code_attribute_parser(input: &[u8]) -> Result<(&[u8], CodeAttribute), Err count(exception_entry_parser, exception_table_length as usize)(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok(( - input, - CodeAttribute { - max_stack, - max_locals, - code_length, - code: code.to_owned(), - exception_table_length, - exception_table, - attributes_count, - attributes, - }, - )) + Ok((input, CodeAttribute { + max_stack, + max_locals, + code_length, + code: code.to_owned(), + exception_table_length, + exception_table, + attributes_count, + attributes, + })) } pub fn method_parameters_attribute_parser( @@ -73,25 +64,19 @@ pub fn method_parameters_attribute_parser( ) -> Result<(&[u8], MethodParametersAttribute), Err<&[u8]>> { let (input, parameters_count) = be_u8(input)?; let (input, parameters) = count(parameters_parser, parameters_count as usize)(input)?; - Ok(( - input, - MethodParametersAttribute { - parameters_count, - parameters, - }, - )) + Ok((input, MethodParametersAttribute { + parameters_count, + parameters, + })) } pub fn parameters_parser(input: &[u8]) -> Result<(&[u8], ParameterAttribute), Err<&[u8]>> { let (input, name_index) = be_u16(input)?; let (input, access_flags) = be_u16(input)?; - Ok(( - input, - ParameterAttribute { - name_index, - access_flags, - }, - )) + Ok((input, ParameterAttribute { + name_index, + access_flags, + })) } pub fn inner_classes_attribute_parser( @@ -99,13 +84,10 @@ pub fn inner_classes_attribute_parser( ) -> Result<(&[u8], InnerClassesAttribute), Err<&[u8]>> { let (input, number_of_classes) = be_u16(input)?; let (input, classes) = count(inner_class_info_parser, number_of_classes as usize)(input)?; - let ret = ( - input, - InnerClassesAttribute { - number_of_classes, - classes, - }, - ); + let ret = (input, InnerClassesAttribute { + number_of_classes, + classes, + }); Ok(ret) } @@ -115,15 +97,12 @@ pub fn inner_class_info_parser(input: &[u8]) -> Result<(&[u8], InnerClassInfo), let (input, outer_class_info_index) = be_u16(input)?; let (input, inner_name_index) = be_u16(input)?; let (input, inner_class_access_flags) = be_u16(input)?; - Ok(( - input, - InnerClassInfo { - inner_class_info_index, - outer_class_info_index, - inner_name_index, - inner_class_access_flags, - }, - )) + Ok((input, InnerClassInfo { + inner_class_info_index, + outer_class_info_index, + inner_name_index, + inner_class_access_flags, + })) } fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -162,25 +141,19 @@ fn same_locals_1_stack_item_frame_extended_parser( ) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; let (input, stack) = verification_type_parser(input)?; - Ok(( - input, - SameLocals1StackItemFrameExtended { - frame_type, - offset_delta, - stack, - }, - )) + Ok((input, SameLocals1StackItemFrameExtended { + frame_type, + offset_delta, + stack, + })) } fn chop_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; - Ok(( - input, - ChopFrame { - frame_type, - offset_delta, - }, - )) + Ok((input, ChopFrame { + frame_type, + offset_delta, + })) } fn same_frame_extended_parser( @@ -188,26 +161,20 @@ fn same_frame_extended_parser( frame_type: u8, ) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; - Ok(( - input, - SameFrameExtended { - frame_type, - offset_delta, - }, - )) + Ok((input, SameFrameExtended { + frame_type, + offset_delta, + })) } fn append_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; let (input, locals) = count(verification_type_parser, (frame_type - 251) as usize)(input)?; - Ok(( - input, - AppendFrame { - frame_type, - offset_delta, - locals, - }, - )) + Ok((input, AppendFrame { + frame_type, + offset_delta, + locals, + })) } fn full_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -216,17 +183,14 @@ fn full_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFra let (input, locals) = count(verification_type_parser, number_of_locals as usize)(input)?; let (input, number_of_stack_items) = be_u16(input)?; let (input, stack) = count(verification_type_parser, number_of_stack_items as usize)(input)?; - Ok(( - input, - FullFrame { - frame_type, - offset_delta, - number_of_locals, - locals, - number_of_stack_items, - stack, - }, - )) + Ok((input, FullFrame { + frame_type, + offset_delta, + number_of_locals, + locals, + number_of_stack_items, + stack, + })) } fn stack_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -253,13 +217,10 @@ pub fn stack_map_table_attribute_parser( ) -> Result<(&[u8], StackMapTableAttribute), Err<&[u8]>> { let (input, number_of_entries) = be_u16(input)?; let (input, entries) = count(stack_map_frame_entry_parser, number_of_entries as usize)(input)?; - Ok(( - input, - StackMapTableAttribute { - number_of_entries, - entries, - }, - )) + Ok((input, StackMapTableAttribute { + number_of_entries, + entries, + })) } pub fn exceptions_attribute_parser( @@ -267,39 +228,30 @@ pub fn exceptions_attribute_parser( ) -> Result<(&[u8], ExceptionsAttribute), Err<&[u8]>> { let (input, exception_table_length) = be_u16(input)?; let (input, exception_table) = count(be_u16, exception_table_length as usize)(input)?; - Ok(( - input, - ExceptionsAttribute { - exception_table_length, - exception_table, - }, - )) + Ok((input, ExceptionsAttribute { + exception_table_length, + exception_table, + })) } pub fn constant_value_attribute_parser( input: &[u8], ) -> Result<(&[u8], ConstantValueAttribute), Err<&[u8]>> { let (input, constant_value_index) = be_u16(input)?; - Ok(( - input, - ConstantValueAttribute { - constant_value_index, - }, - )) + Ok((input, ConstantValueAttribute { + constant_value_index, + })) } fn bootstrap_method_parser(input: &[u8]) -> Result<(&[u8], BootstrapMethod), Err<&[u8]>> { let (input, bootstrap_method_ref) = be_u16(input)?; let (input, num_bootstrap_arguments) = be_u16(input)?; let (input, bootstrap_arguments) = count(be_u16, num_bootstrap_arguments as usize)(input)?; - Ok(( - input, - BootstrapMethod { - bootstrap_method_ref, - num_bootstrap_arguments, - bootstrap_arguments, - }, - )) + Ok((input, BootstrapMethod { + bootstrap_method_ref, + num_bootstrap_arguments, + bootstrap_arguments, + })) } pub fn bootstrap_methods_attribute_parser( @@ -308,13 +260,10 @@ pub fn bootstrap_methods_attribute_parser( let (input, num_bootstrap_methods) = be_u16(input)?; let (input, bootstrap_methods) = count(bootstrap_method_parser, num_bootstrap_methods as usize)(input)?; - Ok(( - input, - BootstrapMethodsAttribute { - num_bootstrap_methods, - bootstrap_methods, - }, - )) + Ok((input, BootstrapMethodsAttribute { + num_bootstrap_methods, + bootstrap_methods, + })) } pub fn sourcefile_attribute_parser( diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index 3cdafcd..7d6a8d8 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -1,12 +1,12 @@ use crate::code_attribute::types::Instruction; use nom::{ + Err as BaseErr, IResult, Offset, bytes::complete::{tag, take}, combinator::{complete, fail, map, success}, error::Error, multi::{count, many0}, - number::complete::{be_i16, be_i32, be_i8, be_u16, be_u32, be_u8}, + number::complete::{be_i8, be_i16, be_i32, be_u8, be_u16, be_u32}, sequence::{pair, preceded, tuple}, - Err as BaseErr, IResult, Offset, }; use super::{LocalVariableTableAttribute, LocalVariableTableItem}; @@ -38,15 +38,12 @@ fn tableswitch_parser(input: &[u8]) -> IResult<&[u8], Instruction> { let (input, low) = be_i32(input)?; let (input, high) = be_i32(input)?; let (input, offsets) = count(be_i32, (high - low + 1) as usize)(input)?; - Ok(( - input, - Instruction::Tableswitch { - default, - low, - high, - offsets, - }, - )) + Ok((input, Instruction::Tableswitch { + default, + low, + high, + offsets, + })) } pub fn code_parser(outer_input: &[u8]) -> IResult<&[u8], Vec<(usize, Instruction)>> { @@ -303,13 +300,10 @@ pub fn local_variable_table_parser( variable_table_item_parser, local_variable_table_length as usize, )(input)?; - Ok(( - input, - LocalVariableTableAttribute { - local_variable_table_length, - items, - }, - )) + Ok((input, LocalVariableTableAttribute { + local_variable_table_length, + items, + })) } pub fn variable_table_item_parser( @@ -320,14 +314,11 @@ pub fn variable_table_item_parser( let (input, name_index) = be_u16(input)?; let (input, descriptor_index) = be_u16(input)?; let (input, index) = be_u16(input)?; - Ok(( - input, - LocalVariableTableItem { - start_pc, - length, - name_index, - descriptor_index, - index, - }, - )) + Ok((input, LocalVariableTableItem { + start_pc, + length, + name_index, + descriptor_index, + index, + })) } diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index bf062dd..b7eda20 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -1,10 +1,10 @@ use crate::constant_info::*; use nom::{ + Err, bytes::complete::take, combinator::map, error::{Error, ErrorKind}, - number::complete::{be_f32, be_f64, be_i32, be_i64, be_u16, be_u8}, - Err, + number::complete::{be_f32, be_f64, be_i32, be_i64, be_u8, be_u16}, }; fn utf8_constant(input: &[u8]) -> Utf8Constant { diff --git a/src/field_info/parser.rs b/src/field_info/parser.rs index 6eed08f..080a8e6 100644 --- a/src/field_info/parser.rs +++ b/src/field_info/parser.rs @@ -1,4 +1,4 @@ -use nom::{multi::count, number::complete::be_u16, IResult}; +use nom::{IResult, multi::count, number::complete::be_u16}; use crate::attribute_info::attribute_parser; @@ -10,14 +10,11 @@ pub fn field_parser(input: &[u8]) -> IResult<&[u8], FieldInfo> { let (input, descriptor_index) = be_u16(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok(( - input, - FieldInfo { - access_flags: FieldAccessFlags::from_bits_truncate(access_flags), - name_index, - descriptor_index, - attributes_count, - attributes, - }, - )) + Ok((input, FieldInfo { + access_flags: FieldAccessFlags::from_bits_truncate(access_flags), + name_index, + descriptor_index, + attributes_count, + attributes, + })) } diff --git a/src/lib.rs b/src/lib.rs index ffea935..f0ad88e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! A parser for [Java Classfiles](https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-4.html) use std::fs::File; -use std::io::{prelude::*, BufReader}; +use std::io::{BufReader, prelude::*}; use std::path::Path; #[macro_use] @@ -70,7 +70,10 @@ pub fn parse_class_from_reader(reader: &mut T) -> Result { if !a.is_empty() { - eprintln!("Warning: not all bytes were consumed when parsing classfile, {} bytes remaining", a.len()); + eprintln!( + "Warning: not all bytes were consumed when parsing classfile, {} bytes remaining", + a.len() + ); } Ok(c) diff --git a/src/method_info/parser.rs b/src/method_info/parser.rs index 01b023f..bea98cd 100644 --- a/src/method_info/parser.rs +++ b/src/method_info/parser.rs @@ -1,4 +1,4 @@ -use nom::{multi::count, number::complete::be_u16, IResult}; +use nom::{IResult, multi::count, number::complete::be_u16}; use crate::attribute_info::attribute_parser; @@ -10,14 +10,11 @@ pub fn method_parser(input: &[u8]) -> IResult<&[u8], MethodInfo> { let (input, descriptor_index) = be_u16(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok(( - input, - MethodInfo { - access_flags: MethodAccessFlags::from_bits_truncate(access_flags), - name_index, - descriptor_index, - attributes_count, - attributes, - }, - )) + Ok((input, MethodInfo { + access_flags: MethodAccessFlags::from_bits_truncate(access_flags), + name_index, + descriptor_index, + attributes_count, + attributes, + })) } diff --git a/src/parser.rs b/src/parser.rs index 4bc75ef..c2fbc5a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -46,24 +46,21 @@ pub fn class_parser(input: &[u8]) -> IResult<&[u8], ClassFile> { let (input, methods) = count(method_parser, methods_count as usize)(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok(( - input, - ClassFile { - minor_version, - major_version, - const_pool_size, - const_pool, - access_flags: ClassAccessFlags::from_bits_truncate(access_flags), - this_class, - super_class, - interfaces_count, - interfaces, - fields_count, - fields, - methods_count, - methods, - attributes_count, - attributes, - }, - )) + Ok((input, ClassFile { + minor_version, + major_version, + const_pool_size, + const_pool, + access_flags: ClassAccessFlags::from_bits_truncate(access_flags), + this_class, + super_class, + interfaces_count, + interfaces, + fields_count, + fields, + methods_count, + methods, + attributes_count, + attributes, + })) } diff --git a/tests/attr_bootstrap_methods.rs b/tests/attr_bootstrap_methods.rs index 11c5bfe..8cb9871 100644 --- a/tests/attr_bootstrap_methods.rs +++ b/tests/attr_bootstrap_methods.rs @@ -11,7 +11,19 @@ fn test_attribute_bootstrap_methods() { "../java-assets/compiled-classes/BootstrapMethods.class" )) { Ok((_, c)) => { - println!("Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", c.major_version, c.minor_version, c.const_pool_size, c.this_class, c.super_class, c.interfaces_count, c.fields_count, c.methods_count, c.attributes_count, c.access_flags); + println!( + "Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", + c.major_version, + c.minor_version, + c.const_pool_size, + c.this_class, + c.super_class, + c.interfaces_count, + c.fields_count, + c.methods_count, + c.attributes_count, + c.access_flags + ); let mut bootstrap_method_const_index = 0; @@ -70,7 +82,10 @@ fn should_have_no_bootstrap_method_attr_if_no_invoke_dynamic() { match *const_item { ConstantInfo::Utf8(ref c) => { if c.utf8_string == "BootstrapMethods" { - assert!(false, "Should not have found a BootstrapMethods constant in a class not requiring it") + assert!( + false, + "Should not have found a BootstrapMethods constant in a class not requiring it" + ) } } _ => {} diff --git a/tests/classfile.rs b/tests/classfile.rs index 5266cbb..523015f 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -10,7 +10,19 @@ fn test_valid_class() { let res = class_parser(valid_class); match res { Result::Ok((_, c)) => { - println!("Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", c.major_version, c.minor_version, c.const_pool_size, c.this_class, c.super_class, c.interfaces_count, c.fields_count, c.methods_count, c.attributes_count, c.access_flags); + println!( + "Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", + c.major_version, + c.minor_version, + c.const_pool_size, + c.this_class, + c.super_class, + c.interfaces_count, + c.fields_count, + c.methods_count, + c.attributes_count, + c.access_flags + ); let mut code_const_index = 0; @@ -92,13 +104,11 @@ fn test_utf_string_constants() { if c.utf8_string == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" { found_utf_maths_string = true; } - if c.utf8_string - == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + if c.utf8_string == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" { found_utf_runes_string = true; } - if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" - { + if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" { found_utf_braille_string = true; } if c.utf8_string == "\0𠜎" { @@ -127,7 +137,9 @@ fn test_utf_string_constants() { fn test_malformed_class() { let malformed_class = include_bytes!("../java-assets/compiled-classes/malformed.class"); let res = class_parser(malformed_class); - if let Result::Ok((_, _)) = res { panic!("The file is not valid and shouldn't be parsed") }; + if let Result::Ok((_, _)) = res { + panic!("The file is not valid and shouldn't be parsed") + }; } // #[test] diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index d8e0f20..e58e74a 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -1,11 +1,12 @@ extern crate classfile_parser; use classfile_parser::attribute_info::{ - InnerClassAccessFlags, code_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser + InnerClassAccessFlags, code_attribute_parser, inner_classes_attribute_parser, + method_parameters_attribute_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, }; use classfile_parser::method_info::MethodAccessFlags; @@ -30,28 +31,19 @@ fn test_wide() { #[test] fn test_alignment() { let instructions = vec![ - ( - 3, - vec![ - 0xaa, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, - ], - ), - ( - 0, - vec![ - 0xaa, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, - ], - ), + (3, vec![ + 0xaa, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, + ]), + (0, vec![ + 0xaa, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, + ]), ]; - let expected = Ok(( - &[][..], - Instruction::Tableswitch { - default: 10, - low: 20, - high: 21, - offsets: vec![30, 31], - }, - )); + let expected = Ok((&[][..], Instruction::Tableswitch { + default: 10, + low: 20, + high: 21, + offsets: vec![30, 31], + })); for (address, instruction) in instructions { assert_eq!(expected, instruction_parser(&instruction, address)); } @@ -60,10 +52,10 @@ fn test_alignment() { #[test] fn test_incomplete() { let code = &[0x59, 0x59, 0xc4, 0x15]; // dup, dup, - let expected = Ok(( - &[0xc4, 0x15][..], - vec![(0, Instruction::Dup), (1, Instruction::Dup)], - )); + let expected = Ok((&[0xc4, 0x15][..], vec![ + (0, Instruction::Dup), + (1, Instruction::Dup), + ])); assert_eq!(expected, code_parser(code)); } @@ -131,10 +123,7 @@ fn inner_classes() { Some(x) if x == "InnerClasses" => { let (_, inner_class_attrs) = inner_classes_attribute_parser(&attr.info).unwrap(); - assert_eq!( - inner_class_attrs.number_of_classes, - 4 - ); + assert_eq!(inner_class_attrs.number_of_classes, 4); assert_eq!( inner_class_attrs.number_of_classes, @@ -142,19 +131,19 @@ fn inner_classes() { ); for c in inner_class_attrs.classes { - dbg!(&class.const_pool[(c.inner_class_info_index-1) as usize]); + dbg!(&class.const_pool[(c.inner_class_info_index - 1) as usize]); // only == 0 when this class is a top-level class or interface, or when it's // a local class or an anonymous class. if c.outer_class_info_index != 0 { assert_ne!(c.inner_class_info_index, c.outer_class_info_index); - - dbg!(&class.const_pool[(c.outer_class_info_index-1) as usize]); + + dbg!(&class.const_pool[(c.outer_class_info_index - 1) as usize]); } // only == 0 when this class is anonymous if c.inner_name_index != 0 { - dbg!(&class.const_pool[(c.inner_name_index-1) as usize]); + dbg!(&class.const_pool[(c.inner_name_index - 1) as usize]); } dbg!(InnerClassAccessFlags::from_bits_truncate( @@ -163,8 +152,8 @@ fn inner_classes() { } //uncomment to see dbg output from above //assert_eq!(1, 2); - }, - Some(_) => {}, + } + Some(_) => {} None => panic!( "Could not find attribute name for index {}", attr.attribute_name_index @@ -219,14 +208,11 @@ fn local_variable_table() { .collect(); // All used types in method code block of last method - assert_eq!( - types, - vec![ - "LLocalVariableTable;".to_string(), - "Ljava/util/HashMap;".to_string(), - "I".to_string() - ] - ); + assert_eq!(types, vec![ + "LLocalVariableTable;".to_string(), + "Ljava/util/HashMap;".to_string(), + "I".to_string() + ]); } #[test] From f579846c6016e5241416b16634fc84786ac2d43f Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 28 Nov 2025 00:19:07 -0600 Subject: [PATCH 03/27] add EnclosingMethod attribute parsing --- Cargo.toml | 3 ++ src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 11 +++++ src/attribute_info/types.rs | 6 +++ tests/code_attribute.rs | 81 ++++++++++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 244e8e6..f547898 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,6 @@ exclude = ["java-assets/out/**/*"] nom = "^7" bitflags = "^2.3" cesu8 = "^1.1" + +[dev-dependencies] +assert_matches = "1.5.0" diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 6137280..8bcbd8c 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -7,6 +7,7 @@ pub use self::parser::attribute_parser; pub use self::parser::bootstrap_methods_attribute_parser; pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; +pub use self::parser::enclosing_method_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; pub use self::parser::method_parameters_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 08d3cf3..5e81b16 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -105,6 +105,17 @@ pub fn inner_class_info_parser(input: &[u8]) -> Result<(&[u8], InnerClassInfo), })) } +pub fn enclosing_method_parser( + input: &[u8], +) -> Result<(&[u8], EnclosingMethodAttribute), Err<&[u8]>> { + let (input, class_index) = be_u16(input)?; + let (input, method_index) = be_u16(input)?; + Ok((input, EnclosingMethodAttribute { + class_index, + method_index, + })) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 9bc1300..0b9d210 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -67,6 +67,12 @@ bitflags! { } } +#[derive(Clone, Debug)] +pub struct EnclosingMethodAttribute { + pub class_index: u16, + pub method_index: u16, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index e58e74a..65dd1fb 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -1,13 +1,18 @@ +#![feature(assert_matches)] + extern crate classfile_parser; +use std::assert_matches::assert_matches; + use classfile_parser::attribute_info::{ - InnerClassAccessFlags, code_attribute_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, + InnerClassAccessFlags, code_attribute_parser, enclosing_method_parser, + inner_classes_attribute_parser, method_parameters_attribute_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, }; +use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; #[test] @@ -116,7 +121,6 @@ fn method_parameters() { fn inner_classes() { let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses.class"); let (_, class) = class_parser(class_bytes).unwrap(); - dbg!(&class); for attr in &class.attributes { match lookup_string(&class, attr.attribute_name_index) { @@ -162,6 +166,77 @@ fn inner_classes() { } } +#[test] +// test for enclosing method attribute, which only applies to local and anonymous classes +fn enclosing_method() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses$2.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + for attr in &class.attributes { + match lookup_string(&class, attr.attribute_name_index) { + Some(x) if x == "EnclosingMethod" => { + assert_eq!(attr.attribute_length, 4); + + let (_, inner_class_attrs) = enclosing_method_parser(&attr.info).unwrap(); + + match &class.const_pool[(inner_class_attrs.class_index - 1) as usize] { + classfile_parser::constant_info::ConstantInfo::Class(class_constant) => { + let _expected = String::from("InnerClasses"); + assert_matches!( + &class.const_pool[(class_constant.name_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + bytes: _, + }) + ); + dbg!(&class.const_pool[(class_constant.name_index - 1) as usize]); + } + _ => panic!("Expected Class constant"), + } + + match &class.const_pool[(inner_class_attrs.method_index - 1) as usize] { + classfile_parser::constant_info::ConstantInfo::NameAndType( + name_and_type_constant, + ) => { + let mut _expected = String::from("sayHello"); + assert_matches!( + &class.const_pool[(name_and_type_constant.name_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + bytes: _, + }) + ); + dbg!(&class.const_pool[(name_and_type_constant.name_index - 1) as usize]); + + _expected = String::from("()V"); + assert_matches!( + &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + bytes: _, + }) + ); + dbg!( + &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize] + ); + } + _ => panic!("Expected NameAndType constant"), + } + + //uncomment to see dbg output from above + //assert_eq!(1, 2); + } + Some(_) => {} + None => panic!( + "Could not find attribute name for index {}", + attr.attribute_name_index + ), + } + } +} + #[test] fn local_variable_table() { // The class was not compiled with "javac -g" From 685702f2f042bf80bd18ada6608576bff28e9198 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 28 Nov 2025 10:32:57 -0600 Subject: [PATCH 04/27] add(?) synthetic attribute struct and a test case; this didn't really need a custom struct for parsing --- src/attribute_info/types.rs | 5 +++ tests/code_attribute.rs | 84 ++++++++++++++++++++++++++----------- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 0b9d210..0543146 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -73,6 +73,11 @@ pub struct EnclosingMethodAttribute { pub method_index: u16, } +// in all reality this struct isn't required b/c it's zero sized +// "Synthetic" is a marker attribute +#[derive(Clone, Debug)] +pub struct SyntheticAttribute {} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 65dd1fb..47e7473 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,12 +5,12 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - InnerClassAccessFlags, code_attribute_parser, enclosing_method_parser, - inner_classes_attribute_parser, method_parameters_attribute_parser, + code_attribute_parser, enclosing_method_parser, inner_classes_attribute_parser, + method_parameters_attribute_parser, InnerClassAccessFlags, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -36,19 +36,28 @@ fn test_wide() { #[test] fn test_alignment() { let instructions = vec![ - (3, vec![ - 0xaa, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, - ]), - (0, vec![ - 0xaa, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, - ]), + ( + 3, + vec![ + 0xaa, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, + ], + ), + ( + 0, + vec![ + 0xaa, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 30, 0, 0, 0, 31, + ], + ), ]; - let expected = Ok((&[][..], Instruction::Tableswitch { - default: 10, - low: 20, - high: 21, - offsets: vec![30, 31], - })); + let expected = Ok(( + &[][..], + Instruction::Tableswitch { + default: 10, + low: 20, + high: 21, + offsets: vec![30, 31], + }, + )); for (address, instruction) in instructions { assert_eq!(expected, instruction_parser(&instruction, address)); } @@ -57,10 +66,10 @@ fn test_alignment() { #[test] fn test_incomplete() { let code = &[0x59, 0x59, 0xc4, 0x15]; // dup, dup, - let expected = Ok((&[0xc4, 0x15][..], vec![ - (0, Instruction::Dup), - (1, Instruction::Dup), - ])); + let expected = Ok(( + &[0xc4, 0x15][..], + vec![(0, Instruction::Dup), (1, Instruction::Dup)], + )); assert_eq!(expected, code_parser(code)); } @@ -237,6 +246,30 @@ fn enclosing_method() { } } +#[test] +fn synthetic_attribute() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses$2.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let synthetic_attr = class + .attributes + .iter() + .filter( + |attribute_info| match lookup_string(&class, attribute_info.attribute_name_index) { + Some(s) if s == "Synethic" => true, + Some(_) => false, + None => panic!( + "Could not find attribute name for index {}", + attribute_info.attribute_name_index + ), + }, + ) + .collect::>(); + + for attr in &synthetic_attr { + assert_eq!(attr.attribute_length, 0); + } +} + #[test] fn local_variable_table() { // The class was not compiled with "javac -g" @@ -283,11 +316,14 @@ fn local_variable_table() { .collect(); // All used types in method code block of last method - assert_eq!(types, vec![ - "LLocalVariableTable;".to_string(), - "Ljava/util/HashMap;".to_string(), - "I".to_string() - ]); + assert_eq!( + types, + vec![ + "LLocalVariableTable;".to_string(), + "Ljava/util/HashMap;".to_string(), + "I".to_string() + ] + ); } #[test] From 26ed5eed90d80745e91e752838485ea2ca62c839 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 28 Nov 2025 11:02:02 -0600 Subject: [PATCH 05/27] add signature parsing --- src/attribute_info/mod.rs | 3 +- src/attribute_info/parser.rs | 243 ++++++++++++++++++++++------------- src/attribute_info/types.rs | 5 + tests/code_attribute.rs | 46 ++++++- 4 files changed, 197 insertions(+), 100 deletions(-) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 8bcbd8c..6ad6da1 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -7,9 +7,10 @@ pub use self::parser::attribute_parser; pub use self::parser::bootstrap_methods_attribute_parser; pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; -pub use self::parser::enclosing_method_parser; +pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; pub use self::parser::method_parameters_attribute_parser; +pub use self::parser::signature_attribute_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 5e81b16..c4f3bcf 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,10 +1,10 @@ use nom::{ - Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u8, be_u16, be_u32}, + number::complete::{be_u16, be_u32, be_u8}, + Err as BaseErr, }; use crate::attribute_info::types::StackMapFrame::*; @@ -17,11 +17,14 @@ pub fn attribute_parser(input: &[u8]) -> Result<(&[u8], AttributeInfo), Err<&[u8 let (input, attribute_name_index) = be_u16(input)?; let (input, attribute_length) = be_u32(input)?; let (input, info) = take(attribute_length)(input)?; - Ok((input, AttributeInfo { - attribute_name_index, - attribute_length, - info: info.to_owned(), - })) + Ok(( + input, + AttributeInfo { + attribute_name_index, + attribute_length, + info: info.to_owned(), + }, + )) } pub fn exception_entry_parser(input: &[u8]) -> Result<(&[u8], ExceptionEntry), Err<&[u8]>> { @@ -29,12 +32,15 @@ pub fn exception_entry_parser(input: &[u8]) -> Result<(&[u8], ExceptionEntry), E let (input, end_pc) = be_u16(input)?; let (input, handler_pc) = be_u16(input)?; let (input, catch_type) = be_u16(input)?; - Ok((input, ExceptionEntry { - start_pc, - end_pc, - handler_pc, - catch_type, - })) + Ok(( + input, + ExceptionEntry { + start_pc, + end_pc, + handler_pc, + catch_type, + }, + )) } pub fn code_attribute_parser(input: &[u8]) -> Result<(&[u8], CodeAttribute), Err<&[u8]>> { @@ -47,16 +53,19 @@ pub fn code_attribute_parser(input: &[u8]) -> Result<(&[u8], CodeAttribute), Err count(exception_entry_parser, exception_table_length as usize)(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok((input, CodeAttribute { - max_stack, - max_locals, - code_length, - code: code.to_owned(), - exception_table_length, - exception_table, - attributes_count, - attributes, - })) + Ok(( + input, + CodeAttribute { + max_stack, + max_locals, + code_length, + code: code.to_owned(), + exception_table_length, + exception_table, + attributes_count, + attributes, + }, + )) } pub fn method_parameters_attribute_parser( @@ -64,19 +73,25 @@ pub fn method_parameters_attribute_parser( ) -> Result<(&[u8], MethodParametersAttribute), Err<&[u8]>> { let (input, parameters_count) = be_u8(input)?; let (input, parameters) = count(parameters_parser, parameters_count as usize)(input)?; - Ok((input, MethodParametersAttribute { - parameters_count, - parameters, - })) + Ok(( + input, + MethodParametersAttribute { + parameters_count, + parameters, + }, + )) } pub fn parameters_parser(input: &[u8]) -> Result<(&[u8], ParameterAttribute), Err<&[u8]>> { let (input, name_index) = be_u16(input)?; let (input, access_flags) = be_u16(input)?; - Ok((input, ParameterAttribute { - name_index, - access_flags, - })) + Ok(( + input, + ParameterAttribute { + name_index, + access_flags, + }, + )) } pub fn inner_classes_attribute_parser( @@ -84,10 +99,13 @@ pub fn inner_classes_attribute_parser( ) -> Result<(&[u8], InnerClassesAttribute), Err<&[u8]>> { let (input, number_of_classes) = be_u16(input)?; let (input, classes) = count(inner_class_info_parser, number_of_classes as usize)(input)?; - let ret = (input, InnerClassesAttribute { - number_of_classes, - classes, - }); + let ret = ( + input, + InnerClassesAttribute { + number_of_classes, + classes, + }, + ); Ok(ret) } @@ -97,23 +115,34 @@ pub fn inner_class_info_parser(input: &[u8]) -> Result<(&[u8], InnerClassInfo), let (input, outer_class_info_index) = be_u16(input)?; let (input, inner_name_index) = be_u16(input)?; let (input, inner_class_access_flags) = be_u16(input)?; - Ok((input, InnerClassInfo { - inner_class_info_index, - outer_class_info_index, - inner_name_index, - inner_class_access_flags, - })) + Ok(( + input, + InnerClassInfo { + inner_class_info_index, + outer_class_info_index, + inner_name_index, + inner_class_access_flags, + }, + )) } -pub fn enclosing_method_parser( +pub fn enclosing_method_attribute_parser( input: &[u8], ) -> Result<(&[u8], EnclosingMethodAttribute), Err<&[u8]>> { let (input, class_index) = be_u16(input)?; let (input, method_index) = be_u16(input)?; - Ok((input, EnclosingMethodAttribute { - class_index, - method_index, - })) + Ok(( + input, + EnclosingMethodAttribute { + class_index, + method_index, + }, + )) +} + +pub fn signature_attribute_parser(input: &[u8]) -> Result<(&[u8], SignatureAttribute), Err<&[u8]>> { + let (input, signature_index) = be_u16(input)?; + Ok((input, SignatureAttribute { signature_index })) } fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -152,19 +181,25 @@ fn same_locals_1_stack_item_frame_extended_parser( ) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; let (input, stack) = verification_type_parser(input)?; - Ok((input, SameLocals1StackItemFrameExtended { - frame_type, - offset_delta, - stack, - })) + Ok(( + input, + SameLocals1StackItemFrameExtended { + frame_type, + offset_delta, + stack, + }, + )) } fn chop_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; - Ok((input, ChopFrame { - frame_type, - offset_delta, - })) + Ok(( + input, + ChopFrame { + frame_type, + offset_delta, + }, + )) } fn same_frame_extended_parser( @@ -172,20 +207,26 @@ fn same_frame_extended_parser( frame_type: u8, ) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; - Ok((input, SameFrameExtended { - frame_type, - offset_delta, - })) + Ok(( + input, + SameFrameExtended { + frame_type, + offset_delta, + }, + )) } fn append_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { let (input, offset_delta) = be_u16(input)?; let (input, locals) = count(verification_type_parser, (frame_type - 251) as usize)(input)?; - Ok((input, AppendFrame { - frame_type, - offset_delta, - locals, - })) + Ok(( + input, + AppendFrame { + frame_type, + offset_delta, + locals, + }, + )) } fn full_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -194,14 +235,17 @@ fn full_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFra let (input, locals) = count(verification_type_parser, number_of_locals as usize)(input)?; let (input, number_of_stack_items) = be_u16(input)?; let (input, stack) = count(verification_type_parser, number_of_stack_items as usize)(input)?; - Ok((input, FullFrame { - frame_type, - offset_delta, - number_of_locals, - locals, - number_of_stack_items, - stack, - })) + Ok(( + input, + FullFrame { + frame_type, + offset_delta, + number_of_locals, + locals, + number_of_stack_items, + stack, + }, + )) } fn stack_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { @@ -228,10 +272,13 @@ pub fn stack_map_table_attribute_parser( ) -> Result<(&[u8], StackMapTableAttribute), Err<&[u8]>> { let (input, number_of_entries) = be_u16(input)?; let (input, entries) = count(stack_map_frame_entry_parser, number_of_entries as usize)(input)?; - Ok((input, StackMapTableAttribute { - number_of_entries, - entries, - })) + Ok(( + input, + StackMapTableAttribute { + number_of_entries, + entries, + }, + )) } pub fn exceptions_attribute_parser( @@ -239,30 +286,39 @@ pub fn exceptions_attribute_parser( ) -> Result<(&[u8], ExceptionsAttribute), Err<&[u8]>> { let (input, exception_table_length) = be_u16(input)?; let (input, exception_table) = count(be_u16, exception_table_length as usize)(input)?; - Ok((input, ExceptionsAttribute { - exception_table_length, - exception_table, - })) + Ok(( + input, + ExceptionsAttribute { + exception_table_length, + exception_table, + }, + )) } pub fn constant_value_attribute_parser( input: &[u8], ) -> Result<(&[u8], ConstantValueAttribute), Err<&[u8]>> { let (input, constant_value_index) = be_u16(input)?; - Ok((input, ConstantValueAttribute { - constant_value_index, - })) + Ok(( + input, + ConstantValueAttribute { + constant_value_index, + }, + )) } fn bootstrap_method_parser(input: &[u8]) -> Result<(&[u8], BootstrapMethod), Err<&[u8]>> { let (input, bootstrap_method_ref) = be_u16(input)?; let (input, num_bootstrap_arguments) = be_u16(input)?; let (input, bootstrap_arguments) = count(be_u16, num_bootstrap_arguments as usize)(input)?; - Ok((input, BootstrapMethod { - bootstrap_method_ref, - num_bootstrap_arguments, - bootstrap_arguments, - })) + Ok(( + input, + BootstrapMethod { + bootstrap_method_ref, + num_bootstrap_arguments, + bootstrap_arguments, + }, + )) } pub fn bootstrap_methods_attribute_parser( @@ -271,10 +327,13 @@ pub fn bootstrap_methods_attribute_parser( let (input, num_bootstrap_methods) = be_u16(input)?; let (input, bootstrap_methods) = count(bootstrap_method_parser, num_bootstrap_methods as usize)(input)?; - Ok((input, BootstrapMethodsAttribute { - num_bootstrap_methods, - bootstrap_methods, - })) + Ok(( + input, + BootstrapMethodsAttribute { + num_bootstrap_methods, + bootstrap_methods, + }, + )) } pub fn sourcefile_attribute_parser( diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 0543146..41fdb96 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -78,6 +78,11 @@ pub struct EnclosingMethodAttribute { #[derive(Clone, Debug)] pub struct SyntheticAttribute {} +#[derive(Clone, Debug)] +pub struct SignatureAttribute { + pub signature_index: u16, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 47e7473..5959c4b 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,8 +5,8 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, enclosing_method_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, InnerClassAccessFlags, + code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, + method_parameters_attribute_parser, signature_attribute_parser, InnerClassAccessFlags, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ @@ -164,7 +164,7 @@ fn inner_classes() { )); } //uncomment to see dbg output from above - //assert_eq!(1, 2); + //assert!(false); } Some(_) => {} None => panic!( @@ -186,7 +186,7 @@ fn enclosing_method() { Some(x) if x == "EnclosingMethod" => { assert_eq!(attr.attribute_length, 4); - let (_, inner_class_attrs) = enclosing_method_parser(&attr.info).unwrap(); + let (_, inner_class_attrs) = enclosing_method_attribute_parser(&attr.info).unwrap(); match &class.const_pool[(inner_class_attrs.class_index - 1) as usize] { classfile_parser::constant_info::ConstantInfo::Class(class_constant) => { @@ -235,7 +235,7 @@ fn enclosing_method() { } //uncomment to see dbg output from above - //assert_eq!(1, 2); + //assert!(false); } Some(_) => {} None => panic!( @@ -250,7 +250,7 @@ fn enclosing_method() { fn synthetic_attribute() { let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses$2.class"); let (_, class) = class_parser(class_bytes).unwrap(); - let synthetic_attr = class + let synthetic_attrs = class .attributes .iter() .filter( @@ -265,11 +265,43 @@ fn synthetic_attribute() { ) .collect::>(); - for attr in &synthetic_attr { + for attr in &synthetic_attrs { assert_eq!(attr.attribute_length, 0); } } +//works on both method attributes and ClassFile attributes +#[test] +fn signature_attribute() { + let class_bytes = + include_bytes!("../java-assets/compiled-classes/BootstrapMethods.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let signature_attrs = class + .methods + .iter() + .flat_map(|method_info| &method_info.attributes) + .filter( + |attribute_info| match lookup_string(&class, attribute_info.attribute_name_index) { + Some(s) if s == "Signature" => {eprintln!("Got a signature attr!"); true}, + Some(_) => false, + None => panic!( + "Could not find attribute name for index {}", + attribute_info.attribute_name_index + ), + }, + ) + .collect::>(); + + for attr in &signature_attrs { + let (_, signature_attr) = signature_attribute_parser(&attr.info).unwrap(); + let signature_string = lookup_string(&class, signature_attr.signature_index).unwrap(); + dbg!(signature_string); + } + + //uncomment to see dbg output from above + //assert!(false); +} + #[test] fn local_variable_table() { // The class was not compiled with "javac -g" From 120b9a4552ecdc30e1fab88dab1d62a31769f228 Mon Sep 17 00:00:00 2001 From: Sam V Date: Mon, 1 Dec 2025 22:56:13 -0600 Subject: [PATCH 06/27] start migrating to binrw for class writeability --- Cargo.toml | 1 + src/attribute_info/types.rs | 5 +++++ src/constant_info/parser.rs | 7 +++---- src/constant_info/types.rs | 21 +++++++++++++++++-- src/field_info/types.rs | 11 ++++++++-- src/method_info/types.rs | 12 +++++++++-- src/parser.rs | 37 ++++++++++++++++++--------------- src/types.rs | 26 ++++++++++++----------- tests/attr_bootstrap_methods.rs | 4 ++-- tests/attr_stack_map_table.rs | 2 +- tests/classfile.rs | 15 +++++++------ tests/code_attribute.rs | 15 ++++++------- 12 files changed, 101 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f547898..a91e7b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ exclude = ["java-assets/out/**/*"] nom = "^7" bitflags = "^2.3" cesu8 = "^1.1" +binrw = "0.15.0" [dev-dependencies] assert_matches = "1.5.0" diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 41fdb96..71f9ffc 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -1,7 +1,12 @@ +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct AttributeInfo { pub attribute_name_index: u16, pub attribute_length: u32, + #[br(args { count: attribute_length.try_into().unwrap() })] pub info: Vec, } diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index b7eda20..531cb01 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -1,18 +1,17 @@ use crate::constant_info::*; use nom::{ - Err, bytes::complete::take, combinator::map, error::{Error, ErrorKind}, - number::complete::{be_f32, be_f64, be_i32, be_i64, be_u8, be_u16}, + number::complete::{be_f32, be_f64, be_i32, be_i64, be_u16, be_u8}, + Err, }; fn utf8_constant(input: &[u8]) -> Utf8Constant { let utf8_string = cesu8::from_java_cesu8(input).unwrap_or_else(|_| String::from_utf8_lossy(input)); Utf8Constant { - utf8_string: utf8_string.to_string(), - bytes: input.to_owned(), + utf8_string: utf8_string.to_string().into(), } } diff --git a/src/constant_info/types.rs b/src/constant_info/types.rs index fde08a3..3b0e749 100644 --- a/src/constant_info/types.rs +++ b/src/constant_info/types.rs @@ -1,4 +1,7 @@ +use binrw::{binrw, NullString, NullWideString}; + #[derive(Clone, Debug)] +#[binrw] pub enum ConstantInfo { Utf8(Utf8Constant), Integer(IntegerConstant), @@ -18,77 +21,91 @@ pub enum ConstantInfo { } #[derive(Clone, Debug)] +#[binrw] pub struct Utf8Constant { - pub utf8_string: String, - pub bytes: Vec, + pub utf8_string: NullWideString, + // pub bytes: Vec, } #[derive(Clone, Debug)] +#[binrw] pub struct IntegerConstant { pub value: i32, } #[derive(Clone, Debug)] +#[binrw] pub struct FloatConstant { pub value: f32, } #[derive(Clone, Debug)] +#[binrw] pub struct LongConstant { pub value: i64, } #[derive(Clone, Debug)] +#[binrw] pub struct DoubleConstant { pub value: f64, } #[derive(Clone, Debug)] +#[binrw] pub struct ClassConstant { pub name_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct StringConstant { pub string_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct FieldRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct InterfaceMethodRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct NameAndTypeConstant { pub name_index: u16, pub descriptor_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodHandleConstant { pub reference_kind: u8, pub reference_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodTypeConstant { pub descriptor_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct InvokeDynamicConstant { pub bootstrap_method_attr_index: u16, pub name_and_type_index: u16, diff --git a/src/field_info/types.rs b/src/field_info/types.rs index ca4e3e2..91cf144 100644 --- a/src/field_info/types.rs +++ b/src/field_info/types.rs @@ -1,17 +1,24 @@ use crate::attribute_info::AttributeInfo; +use binrw::binrw; #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct FieldInfo { pub access_flags: FieldAccessFlags, pub name_index: u16, pub descriptor_index: u16, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct FieldAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct FieldAccessFlags: u16 { + impl FieldAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const PRIVATE = 0x0002; // Declared private; usable only within the defining class. const PROTECTED = 0x0004; // Declared protected; may be accessed within subclasses. diff --git a/src/method_info/types.rs b/src/method_info/types.rs index 4d316ba..411950c 100644 --- a/src/method_info/types.rs +++ b/src/method_info/types.rs @@ -1,17 +1,25 @@ use crate::attribute_info::AttributeInfo; +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct MethodInfo { pub access_flags: MethodAccessFlags, pub name_index: u16, pub descriptor_index: u16, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct MethodAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct MethodAccessFlags: u16 { + impl MethodAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const PRIVATE = 0x0002; // Declared private; accessible only within the defining class. const PROTECTED = 0x0004; // Declared protected; may be accessed within subclasses. diff --git a/src/parser.rs b/src/parser.rs index c2fbc5a..4bc75ef 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -46,21 +46,24 @@ pub fn class_parser(input: &[u8]) -> IResult<&[u8], ClassFile> { let (input, methods) = count(method_parser, methods_count as usize)(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok((input, ClassFile { - minor_version, - major_version, - const_pool_size, - const_pool, - access_flags: ClassAccessFlags::from_bits_truncate(access_flags), - this_class, - super_class, - interfaces_count, - interfaces, - fields_count, - fields, - methods_count, - methods, - attributes_count, - attributes, - })) + Ok(( + input, + ClassFile { + minor_version, + major_version, + const_pool_size, + const_pool, + access_flags: ClassAccessFlags::from_bits_truncate(access_flags), + this_class, + super_class, + interfaces_count, + interfaces, + fields_count, + fields, + methods_count, + methods, + attributes_count, + attributes, + }, + )) } diff --git a/src/types.rs b/src/types.rs index 324cec7..8616790 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,28 +3,40 @@ use crate::constant_info::ConstantInfo; use crate::field_info::FieldInfo; use crate::method_info::MethodInfo; +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big, magic = b"\xca\xfe\xba\xbe")] pub struct ClassFile { pub minor_version: u16, pub major_version: u16, pub const_pool_size: u16, + #[br(args { count: const_pool_size.into() })] pub const_pool: Vec, pub access_flags: ClassAccessFlags, pub this_class: u16, pub super_class: u16, pub interfaces_count: u16, + #[br(args { count: interfaces_count.into() })] pub interfaces: Vec, pub fields_count: u16, + #[br(args { count: fields_count.into() })] pub fields: Vec, pub methods_count: u16, + #[br(args { count: methods_count.into() })] pub methods: Vec, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct ClassAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct ClassAccessFlags: u16 { + impl ClassAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const FINAL = 0x0010; // Declared final; no subclasses allowed. const SUPER = 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction. @@ -36,13 +48,3 @@ bitflags! { const MODULE = 0x8000; // Declared as a module type. } } - -#[cfg(test)] -#[allow(dead_code)] -trait TraitTester: - Copy + Clone + PartialEq + Eq + PartialOrd + Ord + ::std::hash::Hash + ::std::fmt::Debug -{ -} - -#[cfg(test)] -impl TraitTester for ClassAccessFlags {} diff --git a/tests/attr_bootstrap_methods.rs b/tests/attr_bootstrap_methods.rs index 8cb9871..3a7a0e0 100644 --- a/tests/attr_bootstrap_methods.rs +++ b/tests/attr_bootstrap_methods.rs @@ -32,7 +32,7 @@ fn test_attribute_bootstrap_methods() { println!("\t[{}] = {:?}", (const_index + 1), const_item); match *const_item { ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "BootstrapMethods" { + if c.utf8_string.to_string() == "BootstrapMethods" { if bootstrap_method_const_index != 0 { assert!( false, @@ -81,7 +81,7 @@ fn should_have_no_bootstrap_method_attr_if_no_invoke_dynamic() { for (_, const_item) in c.const_pool.iter().enumerate() { match *const_item { ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "BootstrapMethods" { + if c.utf8_string.to_string() == "BootstrapMethods" { assert!( false, "Should not have found a BootstrapMethods constant in a class not requiring it" diff --git a/tests/attr_stack_map_table.rs b/tests/attr_stack_map_table.rs index a6bd130..799331a 100644 --- a/tests/attr_stack_map_table.rs +++ b/tests/attr_stack_map_table.rs @@ -20,7 +20,7 @@ fn test_attribute_stack_map_table() { println!("\t[{}] = {:?}", (const_index + 1), const_item); match *const_item { ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "StackMapTable" { + if c.utf8_string.to_string() == "StackMapTable" { if stack_map_table_index != 0 { assert!( false, diff --git a/tests/classfile.rs b/tests/classfile.rs index 523015f..e888898 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -30,7 +30,7 @@ fn test_valid_class() { for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); if let ConstantInfo::Utf8(ref c) = *const_item { - if c.utf8_string == "Code" { + if c.utf8_string.to_string() == "Code" { code_const_index = (const_index + 1) as u16; } } @@ -101,20 +101,23 @@ fn test_utf_string_constants() { for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); if let ConstantInfo::Utf8(ref c) = *const_item { - if c.utf8_string == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" { + if c.utf8_string.to_string() == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" + { found_utf_maths_string = true; } - if c.utf8_string == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + if c.utf8_string.to_string() + == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" { found_utf_runes_string = true; } - if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" { + if c.utf8_string.to_string() == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" + { found_utf_braille_string = true; } - if c.utf8_string == "\0𠜎" { + if c.utf8_string.to_string() == "\0𠜎" { found_utf_modified_string = true; } - if c.utf8_string == "X���X" && c.bytes.len() == 5 { + if c.utf8_string.to_string() == "X���X" && c.utf8_string.len() == 5 { found_utf_unpaired_string = true; } } diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 5959c4b..3e6e05a 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -93,7 +93,9 @@ fn test_class() { fn lookup_string(c: &classfile_parser::ClassFile, index: u16) -> Option { let con = &c.const_pool[(index - 1) as usize]; match con { - classfile_parser::constant_info::ConstantInfo::Utf8(utf8) => Some(utf8.utf8_string.clone()), + classfile_parser::constant_info::ConstantInfo::Utf8(utf8) => { + Some(utf8.utf8_string.to_string()) + } _ => None, } } @@ -195,7 +197,6 @@ fn enclosing_method() { &class.const_pool[(class_constant.name_index - 1) as usize], ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { utf8_string: _expected, - bytes: _, }) ); dbg!(&class.const_pool[(class_constant.name_index - 1) as usize]); @@ -212,7 +213,6 @@ fn enclosing_method() { &class.const_pool[(name_and_type_constant.name_index - 1) as usize], ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { utf8_string: _expected, - bytes: _, }) ); dbg!(&class.const_pool[(name_and_type_constant.name_index - 1) as usize]); @@ -223,7 +223,6 @@ fn enclosing_method() { [(name_and_type_constant.descriptor_index - 1) as usize], ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { utf8_string: _expected, - bytes: _, }) ); dbg!( @@ -273,8 +272,7 @@ fn synthetic_attribute() { //works on both method attributes and ClassFile attributes #[test] fn signature_attribute() { - let class_bytes = - include_bytes!("../java-assets/compiled-classes/BootstrapMethods.class"); + let class_bytes = include_bytes!("../java-assets/compiled-classes/BootstrapMethods.class"); let (_, class) = class_parser(class_bytes).unwrap(); let signature_attrs = class .methods @@ -282,7 +280,10 @@ fn signature_attribute() { .flat_map(|method_info| &method_info.attributes) .filter( |attribute_info| match lookup_string(&class, attribute_info.attribute_name_index) { - Some(s) if s == "Signature" => {eprintln!("Got a signature attr!"); true}, + Some(s) if s == "Signature" => { + eprintln!("Got a signature attr!"); + true + } Some(_) => false, None => panic!( "Could not find attribute name for index {}", From ca17e15b8edd5b0bf617b588bec1d9d202f6ab8e Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Tue, 2 Dec 2025 14:55:32 -0600 Subject: [PATCH 07/27] add runtime visible annotations parsing --- java-assets/compile.sh | 1 + src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 94 ++++++++++++++++++++++++++++++++++++ src/attribute_info/types.rs | 41 ++++++++++++++++ tests/code_attribute.rs | 58 +++++++++++++++++++++- 5 files changed, 194 insertions(+), 1 deletion(-) diff --git a/java-assets/compile.sh b/java-assets/compile.sh index f703842..cea4c97 100755 --- a/java-assets/compile.sh +++ b/java-assets/compile.sh @@ -26,6 +26,7 @@ javac -d java-assets/compiled-classes/ java-assets/src/Instructions.java javac -d java-assets/compiled-classes/ java-assets/src/UnicodeStrings.java javac -d java-assets/compiled-classes/ java-assets/src/DeprecatedAnnotation.java javac -d java-assets/compiled-classes/ java-assets/src/InnerClasses.java +javac -d java-assets/compiled-classes/ java-assets/src/Annotations.java javac -g -d java-assets/compiled-classes/ java-assets/src/LocalVariableTable.java javac -d java-assets/compiled-classes/ java-assets/src/HelloWorld.java diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 6ad6da1..444cdaa 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -11,6 +11,7 @@ pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; pub use self::parser::method_parameters_attribute_parser; +pub use self::parser::runtime_visible_annotations_attribute_parser; pub use self::parser::signature_attribute_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index c4f3bcf..ce4323a 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -145,6 +145,100 @@ pub fn signature_attribute_parser(input: &[u8]) -> Result<(&[u8], SignatureAttri Ok((input, SignatureAttribute { signature_index })) } +pub fn runtime_visible_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeVisibleTypeAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + +fn annotation_parser(input: &[u8]) -> Result<(&[u8], RuntimeAnnotation), Err<&[u8]>> { + let (input, type_index) = be_u16(input)?; + let (input, num_element_value_pairs) = be_u16(input)?; + eprintln!("Parsing annotation with type index = {}, and {} element value pairs", type_index, num_element_value_pairs); + let (input, element_value_pairs) = + count(element_value_pair_parser, num_element_value_pairs as usize)(input)?; + Ok(( + input, + RuntimeAnnotation { + type_index, + num_element_value_pairs, + element_value_pairs, + }, + )) +} + +fn element_value_pair_parser(input: &[u8]) -> Result<(&[u8], ElementValuePair), Err<&[u8]>> { + let (input, element_name_index) = be_u16(input)?; + let (input, value) = element_value_parser(input)?; + Ok(( + input, + ElementValuePair { + element_name_index, + value, + }, + )) +} + +fn array_value_parser(input: &[u8]) -> Result<(&[u8], ElementArrayValue), Err<&[u8]>> { + let (input, num_values) = be_u16(input)?; + let (input, values) = count(element_value_parser, num_values as usize)(input)?; + Ok((input, ElementArrayValue { num_values, values })) +} + +fn element_value_parser(input: &[u8]) -> Result<(&[u8], ElementValue), Err<&[u8]>> { + let (input, tag) = be_u8(input)?; + eprintln!("Element value parsing: tag = {}", tag as char); + + match tag as char { + 'B' | 'C' | 'I' | 'S' | 'Z' | 'D' | 'F' | 'J' | 's' => { + let (input, const_value_index) = be_u16(input)?; + eprintln!("Element value parsing: const_value_index = {}", const_value_index); + Ok((input, ElementValue::ConstValueIndex { tag: tag as char, value: const_value_index })) + } + 'e' => { + let (input, enum_const_value) = enum_const_value_parser(input)?; + eprintln!("Element value parsing: enum_const_value = {:?}", enum_const_value); + Ok((input, ElementValue::EnumConst(enum_const_value))) + } + 'c' => { + let (input, class_info_index) = be_u16(input)?; + eprintln!("Element value parsing: class_info_index = {}", class_info_index); + Ok((input, ElementValue::ClassInfoIndex(class_info_index))) + } + '@' => { + let (input, annotation_value) = annotation_parser(input)?; + eprintln!("Element value parsing: annotation_value = {:?}", annotation_value); + Ok((input, ElementValue::AnnotationValue(annotation_value))) + } + '[' => { + let (input, array_value) = array_value_parser(input)?; + eprintln!("Element value parsing: array_value = {:?}", array_value); + Ok((input, ElementValue::ElementArray(array_value))) + } + _ => Result::Err(Err::Error(error_position!(input, ErrorKind::NoneOf))), + } +} + +fn enum_const_value_parser(input: &[u8]) -> Result<(&[u8], EnumConstValue), Err<&[u8]>> { + let (input, type_name_index) = be_u16(input)?; + let (input, const_name_index) = be_u16(input)?; + Ok(( + input, + EnumConstValue { + type_name_index, + const_name_index, + }, + )) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 71f9ffc..543ae06 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -88,6 +88,47 @@ pub struct SignatureAttribute { pub signature_index: u16, } +#[derive(Clone, Debug)] +pub struct RuntimeVisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeAnnotation { + pub type_index: u16, + pub num_element_value_pairs: u16, + pub element_value_pairs: Vec, +} + +#[derive(Clone, Debug)] +pub struct ElementValuePair { + pub element_name_index: u16, + pub value: ElementValue, +} + +#[derive(Clone, Debug)] +pub enum ElementValue { + // pub tag: u8, + ConstValueIndex { tag: char, value: u16 }, + EnumConst(EnumConstValue), + ClassInfoIndex(u16), + AnnotationValue(RuntimeAnnotation), + ElementArray(ElementArrayValue), +} + +#[derive(Clone, Debug)] +pub struct ElementArrayValue { + pub num_values: u16, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct EnumConstValue { + pub type_name_index: u16, + pub const_name_index: u16, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 3e6e05a..16e07fb 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -6,7 +6,9 @@ use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, signature_attribute_parser, InnerClassAccessFlags, + method_parameters_attribute_parser, runtime_visible_annotations_attribute_parser, + signature_attribute_parser, ElementValue, ElementValuePair, InnerClassAccessFlags, + RuntimeAnnotation, RuntimeVisibleTypeAnnotationsAttribute, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ @@ -359,6 +361,60 @@ fn local_variable_table() { ); } +#[test] +fn runtime_visible_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_annotations_attribute.len(), 1); + let f = runtime_visible_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = RuntimeVisibleTypeAnnotationsAttribute { + num_annotations: 1, + annotations: vec![RuntimeAnnotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![ElementValuePair { + element_name_index: 31, + value: ElementValue::ConstValueIndex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 30); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 31 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 32); + } + _ => panic!("Expected ConstValueIndex"), + } +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); From 22e85aa6fb783cafefe686ebb93b691ec91ed09d Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Tue, 2 Dec 2025 14:57:00 -0600 Subject: [PATCH 08/27] update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3f54dad..37f537b 100644 --- a/README.md +++ b/README.md @@ -92,11 +92,11 @@ fn main() { - [x] Exceptions - [x] BootstrapMethods - [ ] Critical for Java SE - - [ ] InnerClasses - - [ ] EnclosingMethod - - [ ] Synthetic - - [ ] Signature - - [ ] RuntimeVisibleAnnotations + - [x] InnerClasses + - [x] EnclosingMethod + - [x] Synthetic + - [x] Signature + - [x] RuntimeVisibleAnnotations - [ ] RuntimeInvisibleAnnotations - [ ] RuntimeVisibleParameterAnnotations - [ ] RuntimeInvisibleParameterAnnotations From 3e10ad26ebefbc203e5e054e628fb149f0ff50fb Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Tue, 2 Dec 2025 15:05:18 -0600 Subject: [PATCH 09/27] add java assets for annotations test --- .../Annotations$InvisibleAtRuntime.class | Bin 0 -> 419 bytes .../Annotations$VisibleAtRuntime.class | Bin 0 -> 417 bytes .../compiled-classes/Annotations.class | Bin 0 -> 881 bytes java-assets/src/Annotations.java | 25 ++++++++++++++++++ 4 files changed, 25 insertions(+) create mode 100644 java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class create mode 100644 java-assets/compiled-classes/Annotations$VisibleAtRuntime.class create mode 100644 java-assets/compiled-classes/Annotations.class create mode 100644 java-assets/src/Annotations.java diff --git a/java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class new file mode 100644 index 0000000000000000000000000000000000000000..ca06a61b46b89ab4b1aca05755b08d7b95cbe739 GIT binary patch literal 419 zcmah_!AiqG5Pee{w@s_Y+KU(UAVDwY13bloSSTtDc;AMlY*}|9y9xMf9{d15N*tv$ z0S_K#XP9~O=I#6Zdj9}$hp9l1aF$!^f(+VOe>t~}_IhPh9+tHY`c(<^3CTt_GBeVy zv!~TYl|f*iaNc(1jw5U72vH+Vtq9Yr>qQGLLZ$8cmJk=Nu1a;U4P%ma(ck=l38z~P zFI!E!Bc5>4A?#dVs-Rf6vrM1<19^5vmv0P=W{bQi2!luE!-Mnuy<@_tBZx4bTdS&> zk=`plKl$raN+9fUMC>lHYXSqV`WW)tLyQsU1D=mLgyQlLN57*46V5TW*#?}X!*6bL Ba)x+^yfX-!7xtfeUxVR(LVSA*xF&~|x6h~};=a&@B(W8!+zU;cmzM}HbB zO`9X0aM~bjT+dWctlL?pPyT^CI-~P928!%?_B6ep5V|wv!>#lD!F@u%A&D@UTC0k& zk=`p75Lc=yrxF5Ti=)Hd61yhQ<*JPyzb!=QbKc?kfI}!Qcd_?7igCa>;x^ObFzI~* D3bAp9 literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/Annotations.class b/java-assets/compiled-classes/Annotations.class new file mode 100644 index 0000000000000000000000000000000000000000..d8bac835587d7d5d4dced7042de7d12bab2ec825 GIT binary patch literal 881 zcmZuwT~8B16g|_g?Z-lED+NTbXss<&>U)iek{C%^G}hD@pSE;@Lw0A)?zRa(OCKQ7 z#2?^~GM;H6T?!9-@0~m6oO|ca?B9RC{s4G`7kMO*w2-oqMn<4?=1iTA>*&+Y`_Y*^ zi3Bn)l~&O!fn>8a%pr@Mg}jXd>>KdGc^FBLD1SoIT5l$#{LY6!>1YrI(($@^tfFY4 zWaA#n0+l;30@ioKa5Y7mbCp>;lu-J#+ZtN9FHm@`wI4Z=@^zR;1<5Sd1yz(&y^vxd&qz?VD6e#wTmd6usB!f@R$Yo~p<3vZwlS36MCSnx? zc6za8{TdNnF}ecjspC#4lAV$r&h)mfc8|2lEi2Y;C@wHD&78VsvTMa(nL^{$x+ z^pu*+*n@bLjjaBj%ZZgI6uFX+>Imk>V~us*HX22!-VNKy>6P5~p4{RwOT z0xJu&dCnltVv%h|1@d Date: Tue, 2 Dec 2025 22:21:43 -0600 Subject: [PATCH 10/27] add runtime invisible annotations parsing --- src/attribute_info/parser.rs | 49 +++++++++++++++++++++++++++----- src/attribute_info/types.rs | 6 ++++ tests/code_attribute.rs | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 7 deletions(-) diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index ce4323a..9fd049c 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -159,10 +159,27 @@ pub fn runtime_visible_annotations_attribute_parser( )) } +pub fn runtime_invisible_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeInvisibleTypeAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + fn annotation_parser(input: &[u8]) -> Result<(&[u8], RuntimeAnnotation), Err<&[u8]>> { let (input, type_index) = be_u16(input)?; let (input, num_element_value_pairs) = be_u16(input)?; - eprintln!("Parsing annotation with type index = {}, and {} element value pairs", type_index, num_element_value_pairs); + eprintln!( + "Parsing annotation with type index = {}, and {} element value pairs", + type_index, num_element_value_pairs + ); let (input, element_value_pairs) = count(element_value_pair_parser, num_element_value_pairs as usize)(input)?; Ok(( @@ -198,24 +215,42 @@ fn element_value_parser(input: &[u8]) -> Result<(&[u8], ElementValue), Err<&[u8] eprintln!("Element value parsing: tag = {}", tag as char); match tag as char { - 'B' | 'C' | 'I' | 'S' | 'Z' | 'D' | 'F' | 'J' | 's' => { + 'B' | 'C' | 'I' | 'S' | 'Z' | 'D' | 'F' | 'J' | 's' => { let (input, const_value_index) = be_u16(input)?; - eprintln!("Element value parsing: const_value_index = {}", const_value_index); - Ok((input, ElementValue::ConstValueIndex { tag: tag as char, value: const_value_index })) + eprintln!( + "Element value parsing: const_value_index = {}", + const_value_index + ); + Ok(( + input, + ElementValue::ConstValueIndex { + tag: tag as char, + value: const_value_index, + }, + )) } 'e' => { let (input, enum_const_value) = enum_const_value_parser(input)?; - eprintln!("Element value parsing: enum_const_value = {:?}", enum_const_value); + eprintln!( + "Element value parsing: enum_const_value = {:?}", + enum_const_value + ); Ok((input, ElementValue::EnumConst(enum_const_value))) } 'c' => { let (input, class_info_index) = be_u16(input)?; - eprintln!("Element value parsing: class_info_index = {}", class_info_index); + eprintln!( + "Element value parsing: class_info_index = {}", + class_info_index + ); Ok((input, ElementValue::ClassInfoIndex(class_info_index))) } '@' => { let (input, annotation_value) = annotation_parser(input)?; - eprintln!("Element value parsing: annotation_value = {:?}", annotation_value); + eprintln!( + "Element value parsing: annotation_value = {:?}", + annotation_value + ); Ok((input, ElementValue::AnnotationValue(annotation_value))) } '[' => { diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 543ae06..ed2552f 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -94,6 +94,12 @@ pub struct RuntimeVisibleTypeAnnotationsAttribute { pub annotations: Vec, } +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + #[derive(Clone, Debug)] pub struct RuntimeAnnotation { pub type_index: u16, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 16e07fb..a13d568 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -415,6 +415,60 @@ fn runtime_visible_annotations() { } } +#[test] +fn runtime_invisible_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleAnnotations")) + .collect::>(); + + assert_eq!(runtime_invisible_annotations_attribute.len(), 1); + let f = runtime_invisible_annotations_attribute.first().unwrap(); + + let invisible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = RuntimeVisibleTypeAnnotationsAttribute { + num_annotations: 1, + annotations: vec![RuntimeAnnotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![ElementValuePair { + element_name_index: 31, + value: ElementValue::ConstValueIndex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 34); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 31 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 35); + } + _ => panic!("Expected ConstValueIndex"), + } +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); From 37da5874bc723130d9e256805be242b271fa90fc Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Tue, 2 Dec 2025 22:42:51 -0600 Subject: [PATCH 11/27] add runtime (in)visible paramter parsing, haven't finished testing yet --- src/attribute_info/mod.rs | 3 + src/attribute_info/parser.rs | 28 +++++++++ src/attribute_info/types.rs | 12 ++++ tests/code_attribute.rs | 117 +++++++++++++++++++++++++++++++++-- 4 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 444cdaa..cbade60 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -11,7 +11,10 @@ pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; pub use self::parser::method_parameters_attribute_parser; +pub use self::parser::runtime_invisible_annotations_attribute_parser; +pub use self::parser::runtime_invisible_parameter_annotations_attribute_parser; pub use self::parser::runtime_visible_annotations_attribute_parser; +pub use self::parser::runtime_visible_parameter_annotations_attribute_parser; pub use self::parser::signature_attribute_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 9fd049c..a77c3cc 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -173,6 +173,34 @@ pub fn runtime_invisible_annotations_attribute_parser( )) } +pub fn runtime_visible_parameter_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeVisibleTypeAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + +pub fn runtime_invisible_parameter_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeInvisibleTypeAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + fn annotation_parser(input: &[u8]) -> Result<(&[u8], RuntimeAnnotation), Err<&[u8]>> { let (input, type_index) = be_u16(input)?; let (input, num_element_value_pairs) = be_u16(input)?; diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index ed2552f..9acd4b1 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -100,6 +100,18 @@ pub struct RuntimeInvisibleTypeAnnotationsAttribute { pub annotations: Vec, } +#[derive(Clone, Debug)] +pub struct RuntimeVisibleParameterAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleParameterAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + #[derive(Clone, Debug)] pub struct RuntimeAnnotation { pub type_index: u16, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index a13d568..ed9143a 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -6,9 +6,10 @@ use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, runtime_visible_annotations_attribute_parser, - signature_attribute_parser, ElementValue, ElementValuePair, InnerClassAccessFlags, - RuntimeAnnotation, RuntimeVisibleTypeAnnotationsAttribute, + method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, + runtime_visible_annotations_attribute_parser, signature_attribute_parser, ElementValue, + ElementValuePair, InnerClassAccessFlags, RuntimeAnnotation, + RuntimeVisibleTypeAnnotationsAttribute, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ @@ -429,7 +430,7 @@ fn runtime_invisible_annotations() { assert_eq!(runtime_invisible_annotations_attribute.len(), 1); let f = runtime_invisible_annotations_attribute.first().unwrap(); - let invisible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let invisible_annotations = runtime_invisible_annotations_attribute_parser(&f.info); let inner = &invisible_annotations.unwrap(); assert!(&inner.0.is_empty()); @@ -469,6 +470,114 @@ fn runtime_invisible_annotations() { } } +#[test] +fn runtime_visible_parameter_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleParameterAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_annotations_attribute.len(), 1); + let f = runtime_visible_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = runtimevisibletypeannotationsattribute { + num_annotations: 1, + annotations: vec![runtimeannotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![elementvaluepair { + element_name_index: 31, + value: elementvalue::constvalueindex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 30); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 31 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 32); + } + _ => panic!("expected constvalueindex"), + } +} + +#[test] +fn runtime_invisible_parameter_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleParameterAnnotations")) + .collect::>(); + + assert_eq!(runtime_invisible_annotations_attribute.len(), 1); + let f = runtime_invisible_annotations_attribute.first().unwrap(); + + let invisible_annotations = runtime_invisible_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = runtimevisibletypeannotationsattribute { + num_annotations: 1, + annotations: vec![runtimeannotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![elementvaluepair { + element_name_index: 31, + value: elementvalue::constvalueindex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 34); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 31 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 35); + } + _ => panic!("expected constvalueindex"), + } +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); From a593242decce2c4054456a0a8f4a9b7af949a50d Mon Sep 17 00:00:00 2001 From: Sam V Date: Wed, 3 Dec 2025 16:24:55 -0600 Subject: [PATCH 12/27] add runtime parameter parsing --- .../Annotations$ParamInvisibleAtRuntime.class | Bin 0 -> 524 bytes .../Annotations$ParamVisibleAtRuntime.class | Bin 0 -> 522 bytes .../compiled-classes/Annotations.class | Bin 881 -> 1314 bytes java-assets/src/Annotations.java | 21 +++- src/attribute_info/parser.rs | 34 +++--- src/attribute_info/types.rs | 8 +- src/code_attribute/parser.rs | 43 ++++--- src/constant_info/parser.rs | 4 +- src/constant_info/types.rs | 2 +- src/field_info/parser.rs | 17 +-- src/method_info/parser.rs | 17 +-- tests/code_attribute.rs | 107 ++++++------------ 12 files changed, 126 insertions(+), 127 deletions(-) create mode 100644 java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class create mode 100644 java-assets/compiled-classes/Annotations$ParamVisibleAtRuntime.class diff --git a/java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class new file mode 100644 index 0000000000000000000000000000000000000000..36cae7aec060f8309e673c8f3cfbc1f3491e7296 GIT binary patch literal 524 zcmaix&rSkC4934AxcpO5P-2YHs3Cfh2k^8Kj3lUJmw53skcJFdX3fq(!mD}k0emQ9 zF@)uS@i5b7`s>$r`t|+s3E&=e4;jLxZ%piz)3LEvJ(Z|Q$IP_VgNS_>OpVhM_K+o1 z#%iXTkusy^%V5ky=V6<0mcFW$P&3seT(@o*fCEASc2*og-CPRMEkua4s z(cUg#!tt_)*JV$uB9?HzCb(8TV8_yTY(&2C57LVxJ)8@eYjyoFB%E$e=&NMJPPmKB z@pi-$DcYaEvp|KOANWu0emfxKpV_*{*vfy^2&J_N3FVG4oU|fkEz3OhKmNZ#AZ*FW y$>KCLT)<_~qmeq@=fiz^vGHZ5bNq996K7bEp zEJj%lBpznkOn?2ioqm43e*n0}frA|3%+)%!%7(Ev7k!ne=}TzBVZ^TWQ*Fa3JIE7i z6E#!qNa=C=X*l7Lb+Ab|$zIh`rkyDg3Nsa@OlVwQJ!bI0CZQhR5Xyr%O-6hlM#9vx zM034>35UNu{PnafVhE=zf-BX1wk&Jkm0%1c= yLDojH3kOB<@+irjLm3sZN8(k5aTr_J#?Hch1u<&a#oiBCM^kKBB7{2jTcvM9rjc|2 literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/Annotations.class b/java-assets/compiled-classes/Annotations.class index d8bac835587d7d5d4dced7042de7d12bab2ec825..71701fef60be863036a3779a33ff4cda177401ab 100644 GIT binary patch literal 1314 zcmZ`(T~8B16g@)=+m>2Nks|V83#AmO)Nc_GMG_+kAQ6KxJ}of8CA+g`cT2+0(g#R1 z@dx;$jAynbY}@r==kC38?zv}X?#!RRKYs&wgUt;3kT8(Uq8|eS`7`s}Y}lqcZM;7^ zlgEL;z`CWZU_&5LTR2Q3g|vZ87K6xkzz;6`KsrRZO(u=*ghZCx*!L_I90Z;;owW>d z7&4I0Vi*O1kr<3X|2LgMidu^!6hi__ovtk$YKq%gjOu(NMB2*0n84tsQf^=dmaF^> zib$kzm*U?^+ji?IOcGxD3uYhY(~aEG*w_Z^3d`v1|o_CrgtK4t-xl%12Q0@VCuiL;2 zo?;XhN5zAjjAFW31hU&oNpH(GeP8+lWB*ab3lO{g0)^hX+>Gu*D6`G`c*%hT*j*Ul zSf)2cD`@){uOPnjhpoX;Pq#*148k?Ep&tj@p&u}oE^&*deu%fpXS-!>z7JooT%Xr8tTE_4%6hB|#&ThRh zUYNMVRJV{hzD`x-SvA2VQ2ir3rp+LUCwPVxtYV%1Oz0Q!vYKYJhz;5m+E=uzw6Ck_ Ee*xi6vj6}9 delta 388 zcmYLF%TB^j6r9TyT8d!O0^$P&LFA=n=R!BSav^R^G{(fF2?Q~kKs*kSZ1hl1E+JcAJHBcVKslfxk45W#|Ix?L)ik zUArBBU%dOzkrPq(cV$U5{Y%*rx<8U>|3{`D4MeyJAR!P%jJU!oPZs28eFE`HA+Sme zu7Nos-(X7+EptFHLVbhM7$Z4LYoV`0;aYhn66{;!Ss$W+;?(f4F?C9$GB%09P?N2a zyzmL*VuDP&sb|N?&1R><>n!*SNFm3n!gvH*sFGt;H8ikIt%uscP9;vqfLV$E1NS*L Ae*gdg diff --git a/java-assets/src/Annotations.java b/java-assets/src/Annotations.java index 95a3314..5676159 100644 --- a/java-assets/src/Annotations.java +++ b/java-assets/src/Annotations.java @@ -1,5 +1,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; public class Annotations { @Retention(RetentionPolicy.RUNTIME) @@ -12,14 +14,27 @@ public class Annotations { String value(); } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface ParamVisibleAtRuntime { + String value(); + } + + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.PARAMETER) + public @interface ParamInvisibleAtRuntime { + String value(); + } + @VisibleAtRuntime(value = "visisble") @InvisibleAtRuntime(value = "invisible") - public void myMethod(String s) { - System.out.println(s); + public void myMethod(@ParamVisibleAtRuntime(value = "visible") String v, @ParamInvisibleAtRuntime(value = "invisible") String i) { + System.out.print(v); + System.out.println(i); } public void main(String[] args) { Annotations annotations = new Annotations(); - annotations.myMethod("Hello, World!"); + annotations.myMethod("Hello,", " World!"); } } diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index a77c3cc..f22919e 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,10 +1,10 @@ use nom::{ + Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u16, be_u32, be_u8}, - Err as BaseErr, + number::complete::{be_u8, be_u16, be_u32}, }; use crate::attribute_info::types::StackMapFrame::*; @@ -175,28 +175,34 @@ pub fn runtime_invisible_annotations_attribute_parser( pub fn runtime_visible_parameter_annotations_attribute_parser( input: &[u8], -) -> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { - let (input, num_annotations) = be_u16(input)?; - let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; +) -> Result<(&[u8], RuntimeVisibleParameterAnnotationsAttribute), Err<&[u8]>> { + let (input, num_parameters) = be_u8(input)?; + let (input, parameter_annotations) = count( + runtime_visible_annotations_attribute_parser, + num_parameters as usize, + )(input)?; Ok(( input, - RuntimeVisibleTypeAnnotationsAttribute { - num_annotations, - annotations, + RuntimeVisibleParameterAnnotationsAttribute { + num_parameters, + parameter_annotations, }, )) } pub fn runtime_invisible_parameter_annotations_attribute_parser( input: &[u8], -) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { - let (input, num_annotations) = be_u16(input)?; - let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; +) -> Result<(&[u8], RuntimeInvisibleParameterAnnotationsAttribute), Err<&[u8]>> { + let (input, num_parameters) = be_u8(input)?; + let (input, parameter_annotations) = count( + runtime_invisible_annotations_attribute_parser, + num_parameters as usize, + )(input)?; Ok(( input, - RuntimeInvisibleTypeAnnotationsAttribute { - num_annotations, - annotations, + RuntimeInvisibleParameterAnnotationsAttribute { + num_parameters, + parameter_annotations, }, )) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 9acd4b1..07dc73e 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -102,14 +102,14 @@ pub struct RuntimeInvisibleTypeAnnotationsAttribute { #[derive(Clone, Debug)] pub struct RuntimeVisibleParameterAnnotationsAttribute { - pub num_annotations: u16, - pub annotations: Vec, + pub num_parameters: u8, + pub parameter_annotations: Vec, } #[derive(Clone, Debug)] pub struct RuntimeInvisibleParameterAnnotationsAttribute { - pub num_annotations: u16, - pub annotations: Vec, + pub num_parameters: u8, + pub parameter_annotations: Vec, } #[derive(Clone, Debug)] diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index 7d6a8d8..29d2693 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -38,12 +38,15 @@ fn tableswitch_parser(input: &[u8]) -> IResult<&[u8], Instruction> { let (input, low) = be_i32(input)?; let (input, high) = be_i32(input)?; let (input, offsets) = count(be_i32, (high - low + 1) as usize)(input)?; - Ok((input, Instruction::Tableswitch { - default, - low, - high, - offsets, - })) + Ok(( + input, + Instruction::Tableswitch { + default, + low, + high, + offsets, + }, + )) } pub fn code_parser(outer_input: &[u8]) -> IResult<&[u8], Vec<(usize, Instruction)>> { @@ -300,10 +303,13 @@ pub fn local_variable_table_parser( variable_table_item_parser, local_variable_table_length as usize, )(input)?; - Ok((input, LocalVariableTableAttribute { - local_variable_table_length, - items, - })) + Ok(( + input, + LocalVariableTableAttribute { + local_variable_table_length, + items, + }, + )) } pub fn variable_table_item_parser( @@ -314,11 +320,14 @@ pub fn variable_table_item_parser( let (input, name_index) = be_u16(input)?; let (input, descriptor_index) = be_u16(input)?; let (input, index) = be_u16(input)?; - Ok((input, LocalVariableTableItem { - start_pc, - length, - name_index, - descriptor_index, - index, - })) + Ok(( + input, + LocalVariableTableItem { + start_pc, + length, + name_index, + descriptor_index, + index, + }, + )) } diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index 531cb01..be7a40f 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -1,10 +1,10 @@ use crate::constant_info::*; use nom::{ + Err, bytes::complete::take, combinator::map, error::{Error, ErrorKind}, - number::complete::{be_f32, be_f64, be_i32, be_i64, be_u16, be_u8}, - Err, + number::complete::{be_f32, be_f64, be_i32, be_i64, be_u8, be_u16}, }; fn utf8_constant(input: &[u8]) -> Utf8Constant { diff --git a/src/constant_info/types.rs b/src/constant_info/types.rs index 3b0e749..378edbe 100644 --- a/src/constant_info/types.rs +++ b/src/constant_info/types.rs @@ -1,4 +1,4 @@ -use binrw::{binrw, NullString, NullWideString}; +use binrw::{NullString, NullWideString, binrw}; #[derive(Clone, Debug)] #[binrw] diff --git a/src/field_info/parser.rs b/src/field_info/parser.rs index 080a8e6..1a7a9dd 100644 --- a/src/field_info/parser.rs +++ b/src/field_info/parser.rs @@ -10,11 +10,14 @@ pub fn field_parser(input: &[u8]) -> IResult<&[u8], FieldInfo> { let (input, descriptor_index) = be_u16(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok((input, FieldInfo { - access_flags: FieldAccessFlags::from_bits_truncate(access_flags), - name_index, - descriptor_index, - attributes_count, - attributes, - })) + Ok(( + input, + FieldInfo { + access_flags: FieldAccessFlags::from_bits_truncate(access_flags), + name_index, + descriptor_index, + attributes_count, + attributes, + }, + )) } diff --git a/src/method_info/parser.rs b/src/method_info/parser.rs index bea98cd..0f66fd0 100644 --- a/src/method_info/parser.rs +++ b/src/method_info/parser.rs @@ -10,11 +10,14 @@ pub fn method_parser(input: &[u8]) -> IResult<&[u8], MethodInfo> { let (input, descriptor_index) = be_u16(input)?; let (input, attributes_count) = be_u16(input)?; let (input, attributes) = count(attribute_parser, attributes_count as usize)(input)?; - Ok((input, MethodInfo { - access_flags: MethodAccessFlags::from_bits_truncate(access_flags), - name_index, - descriptor_index, - attributes_count, - attributes, - })) + Ok(( + input, + MethodInfo { + access_flags: MethodAccessFlags::from_bits_truncate(access_flags), + name_index, + descriptor_index, + attributes_count, + attributes, + }, + )) } diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index ed9143a..304fa64 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,15 +5,16 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, - runtime_visible_annotations_attribute_parser, signature_attribute_parser, ElementValue, - ElementValuePair, InnerClassAccessFlags, RuntimeAnnotation, - RuntimeVisibleTypeAnnotationsAttribute, + ElementValue, InnerClassAccessFlags, code_attribute_parser, enclosing_method_attribute_parser, + inner_classes_attribute_parser, method_parameters_attribute_parser, + runtime_invisible_annotations_attribute_parser, + runtime_invisible_parameter_annotations_attribute_parser, + runtime_visible_annotations_attribute_parser, + runtime_visible_parameter_annotations_attribute_parser, signature_attribute_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -399,18 +400,18 @@ fn runtime_visible_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 30); + assert_eq!(inner.1.annotations[0].type_index, 36); assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); assert_eq!( inner.1.annotations[0].element_value_pairs[0].element_name_index, - 31 + 37 ); match inner.1.annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 32); + assert_eq!(value, 38); } _ => panic!("Expected ConstValueIndex"), } @@ -453,18 +454,18 @@ fn runtime_invisible_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 34); + assert_eq!(inner.1.annotations[0].type_index, 40); assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); assert_eq!( inner.1.annotations[0].element_value_pairs[0].element_name_index, - 31 + 37 ); match inner.1.annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 35); + assert_eq!(value, 41); } _ => panic!("Expected ConstValueIndex"), } @@ -484,43 +485,24 @@ fn runtime_visible_parameter_annotations() { assert_eq!(runtime_visible_annotations_attribute.len(), 1); let f = runtime_visible_annotations_attribute.first().unwrap(); - let visible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let visible_annotations = runtime_visible_parameter_annotations_attribute_parser(&f.info); let inner = &visible_annotations.unwrap(); assert!(&inner.0.is_empty()); - /* - let should_be = runtimevisibletypeannotationsattribute { - num_annotations: 1, - annotations: vec![runtimeannotation { - type_index: 30, - num_element_value_pairs: 1, - element_value_pairs: vec![elementvaluepair { - element_name_index: 31, - value: elementvalue::constvalueindex { - tag: 's', - value: 32, - }, - }], - }], - }; - */ + assert_eq!(inner.1.num_parameters, 2); + assert_eq!(inner.1.parameter_annotations.len(), 2); + assert_eq!(inner.1.parameter_annotations[0].num_annotations, 1); + assert_eq!(inner.1.parameter_annotations[0].annotations.len(), 1); - assert_eq!(inner.1.num_annotations, 1); - assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 30); - assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); - assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); - assert_eq!( - inner.1.annotations[0].element_value_pairs[0].element_name_index, - 31 - ); - - match inner.1.annotations[0].element_value_pairs[0].value { + match inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 32); + assert_eq!(value, 44); } - _ => panic!("expected constvalueindex"), + _ => panic!( + "expected ConstValueIndex, got {:?}", + inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value + ), } } @@ -538,43 +520,24 @@ fn runtime_invisible_parameter_annotations() { assert_eq!(runtime_invisible_annotations_attribute.len(), 1); let f = runtime_invisible_annotations_attribute.first().unwrap(); - let invisible_annotations = runtime_invisible_annotations_attribute_parser(&f.info); + let invisible_annotations = runtime_invisible_parameter_annotations_attribute_parser(&f.info); let inner = &invisible_annotations.unwrap(); assert!(&inner.0.is_empty()); - /* - let should_be = runtimevisibletypeannotationsattribute { - num_annotations: 1, - annotations: vec![runtimeannotation { - type_index: 30, - num_element_value_pairs: 1, - element_value_pairs: vec![elementvaluepair { - element_name_index: 31, - value: elementvalue::constvalueindex { - tag: 's', - value: 32, - }, - }], - }], - }; - */ + assert_eq!(inner.1.num_parameters, 2); + assert_eq!(inner.1.parameter_annotations.len(), 2); + assert_eq!(inner.1.parameter_annotations[1].num_annotations, 1); + assert_eq!(inner.1.parameter_annotations[1].annotations.len(), 1); - assert_eq!(inner.1.num_annotations, 1); - assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 34); - assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); - assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); - assert_eq!( - inner.1.annotations[0].element_value_pairs[0].element_name_index, - 31 - ); - - match inner.1.annotations[0].element_value_pairs[0].value { + match inner.1.parameter_annotations[1].annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 35); + assert_eq!(value, 41); } - _ => panic!("expected constvalueindex"), + _ => panic!( + "expected ConstValueIndex, got {:?}", + inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value + ), } } From 14987260e91eee69d8de9684ec105676abdb1e74 Mon Sep 17 00:00:00 2001 From: Sam V Date: Wed, 3 Dec 2025 16:26:11 -0600 Subject: [PATCH 13/27] add clippy fixes --- src/constant_info/parser.rs | 34 +++++++++++++++++----------------- src/constant_info/types.rs | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index be7a40f..7cd196b 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -15,43 +15,43 @@ fn utf8_constant(input: &[u8]) -> Utf8Constant { } } -fn const_utf8(input: &[u8]) -> ConstantInfoResult { +fn const_utf8(input: &[u8]) -> ConstantInfoResult<'_> { let (input, length) = be_u16(input)?; let (input, constant) = map(take(length), utf8_constant)(input)?; Ok((input, ConstantInfo::Utf8(constant))) } -fn const_integer(input: &[u8]) -> ConstantInfoResult { +fn const_integer(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_i32(input)?; Ok((input, ConstantInfo::Integer(IntegerConstant { value }))) } -fn const_float(input: &[u8]) -> ConstantInfoResult { +fn const_float(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_f32(input)?; Ok((input, ConstantInfo::Float(FloatConstant { value }))) } -fn const_long(input: &[u8]) -> ConstantInfoResult { +fn const_long(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_i64(input)?; Ok((input, ConstantInfo::Long(LongConstant { value }))) } -fn const_double(input: &[u8]) -> ConstantInfoResult { +fn const_double(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_f64(input)?; Ok((input, ConstantInfo::Double(DoubleConstant { value }))) } -fn const_class(input: &[u8]) -> ConstantInfoResult { +fn const_class(input: &[u8]) -> ConstantInfoResult<'_> { let (input, name_index) = be_u16(input)?; Ok((input, ConstantInfo::Class(ClassConstant { name_index }))) } -fn const_string(input: &[u8]) -> ConstantInfoResult { +fn const_string(input: &[u8]) -> ConstantInfoResult<'_> { let (input, string_index) = be_u16(input)?; Ok((input, ConstantInfo::String(StringConstant { string_index }))) } -fn const_field_ref(input: &[u8]) -> ConstantInfoResult { +fn const_field_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -63,7 +63,7 @@ fn const_field_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_ref(input: &[u8]) -> ConstantInfoResult { +fn const_method_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -75,7 +75,7 @@ fn const_method_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult { +fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -87,7 +87,7 @@ fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_name_and_type(input: &[u8]) -> ConstantInfoResult { +fn const_name_and_type(input: &[u8]) -> ConstantInfoResult<'_> { let (input, name_index) = be_u16(input)?; let (input, descriptor_index) = be_u16(input)?; Ok(( @@ -99,7 +99,7 @@ fn const_name_and_type(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_handle(input: &[u8]) -> ConstantInfoResult { +fn const_method_handle(input: &[u8]) -> ConstantInfoResult<'_> { let (input, reference_kind) = be_u8(input)?; let (input, reference_index) = be_u16(input)?; Ok(( @@ -111,7 +111,7 @@ fn const_method_handle(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_type(input: &[u8]) -> ConstantInfoResult { +fn const_method_type(input: &[u8]) -> ConstantInfoResult<'_> { let (input, descriptor_index) = be_u16(input)?; Ok(( input, @@ -119,7 +119,7 @@ fn const_method_type(input: &[u8]) -> ConstantInfoResult { )) } -fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult { +fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult<'_> { let (input, bootstrap_method_attr_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -134,7 +134,7 @@ fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult { type ConstantInfoResult<'a> = Result<(&'a [u8], ConstantInfo), Err>>; type ConstantInfoVecResult<'a> = Result<(&'a [u8], Vec), Err>>; -fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult { +fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult<'_> { match const_type { 1 => const_utf8(input), 3 => const_integer(input), @@ -154,13 +154,13 @@ fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult { } } -fn single_constant_parser(input: &[u8]) -> ConstantInfoResult { +fn single_constant_parser(input: &[u8]) -> ConstantInfoResult<'_> { let (input, const_type) = be_u8(input)?; let (input, const_block) = const_block_parser(input, const_type)?; Ok((input, const_block)) } -pub fn constant_parser(i: &[u8], const_pool_size: usize) -> ConstantInfoVecResult { +pub fn constant_parser(i: &[u8], const_pool_size: usize) -> ConstantInfoVecResult<'_> { let mut index = 0; let mut input = i; let mut res = Vec::with_capacity(const_pool_size); diff --git a/src/constant_info/types.rs b/src/constant_info/types.rs index 378edbe..f66612b 100644 --- a/src/constant_info/types.rs +++ b/src/constant_info/types.rs @@ -1,4 +1,4 @@ -use binrw::{NullString, NullWideString, binrw}; +use binrw::{NullWideString, binrw}; #[derive(Clone, Debug)] #[binrw] From 772e1f31942a91d4703fc7535fc1a3e5fb0f4387 Mon Sep 17 00:00:00 2001 From: Sam V Date: Wed, 3 Dec 2025 16:26:59 -0600 Subject: [PATCH 14/27] update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 37f537b..aa28a06 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ use classfile_parser::class_parser; fn main() { let classfile_bytes = include_bytes!("../path/to/JavaClass.class"); - + match class_parser(classfile_bytes) { Ok((_, class_file)) => { println!( @@ -97,9 +97,9 @@ fn main() { - [x] Synthetic - [x] Signature - [x] RuntimeVisibleAnnotations - - [ ] RuntimeInvisibleAnnotations - - [ ] RuntimeVisibleParameterAnnotations - - [ ] RuntimeInvisibleParameterAnnotations + - [x] RuntimeInvisibleAnnotations + - [x] RuntimeVisibleParameterAnnotations + - [x] RuntimeInvisibleParameterAnnotations - [ ] RuntimeVisibleTypeAnnotations - [ ] RuntimeInvisibleTypeAnnotations - [ ] AnnotationDefault From 368ef7d5f0e1fc04381851a8ae5a35d1e7e42fa5 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 13:24:04 -0600 Subject: [PATCH 15/27] rename runtime vis & invis attrs to proper names --- src/attribute_info/parser.rs | 12 ++++++------ src/attribute_info/types.rs | 8 ++++---- tests/attr_bootstrap_methods.rs | 34 +++++++++------------------------ tests/attr_stack_map_table.rs | 29 ++++++++++------------------ tests/classfile.rs | 4 +--- 5 files changed, 30 insertions(+), 57 deletions(-) diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index f22919e..4e034ab 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,10 +1,10 @@ use nom::{ - Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u8, be_u16, be_u32}, + number::complete::{be_u16, be_u32, be_u8}, + Err as BaseErr, }; use crate::attribute_info::types::StackMapFrame::*; @@ -147,12 +147,12 @@ pub fn signature_attribute_parser(input: &[u8]) -> Result<(&[u8], SignatureAttri pub fn runtime_visible_annotations_attribute_parser( input: &[u8], -) -> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { +) -> Result<(&[u8], RuntimeVisibleAnnotationsAttribute), Err<&[u8]>> { let (input, num_annotations) = be_u16(input)?; let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; Ok(( input, - RuntimeVisibleTypeAnnotationsAttribute { + RuntimeVisibleAnnotationsAttribute { num_annotations, annotations, }, @@ -161,12 +161,12 @@ pub fn runtime_visible_annotations_attribute_parser( pub fn runtime_invisible_annotations_attribute_parser( input: &[u8], -) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { +) -> Result<(&[u8], RuntimeInvisibleAnnotationsAttribute), Err<&[u8]>> { let (input, num_annotations) = be_u16(input)?; let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; Ok(( input, - RuntimeInvisibleTypeAnnotationsAttribute { + RuntimeInvisibleAnnotationsAttribute { num_annotations, annotations, }, diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 07dc73e..ff30d35 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -89,13 +89,13 @@ pub struct SignatureAttribute { } #[derive(Clone, Debug)] -pub struct RuntimeVisibleTypeAnnotationsAttribute { +pub struct RuntimeVisibleAnnotationsAttribute { pub num_annotations: u16, pub annotations: Vec, } #[derive(Clone, Debug)] -pub struct RuntimeInvisibleTypeAnnotationsAttribute { +pub struct RuntimeInvisibleAnnotationsAttribute { pub num_annotations: u16, pub annotations: Vec, } @@ -103,13 +103,13 @@ pub struct RuntimeInvisibleTypeAnnotationsAttribute { #[derive(Clone, Debug)] pub struct RuntimeVisibleParameterAnnotationsAttribute { pub num_parameters: u8, - pub parameter_annotations: Vec, + pub parameter_annotations: Vec, } #[derive(Clone, Debug)] pub struct RuntimeInvisibleParameterAnnotationsAttribute { pub num_parameters: u8, - pub parameter_annotations: Vec, + pub parameter_annotations: Vec, } #[derive(Clone, Debug)] diff --git a/tests/attr_bootstrap_methods.rs b/tests/attr_bootstrap_methods.rs index 3a7a0e0..bed0471 100644 --- a/tests/attr_bootstrap_methods.rs +++ b/tests/attr_bootstrap_methods.rs @@ -30,19 +30,11 @@ fn test_attribute_bootstrap_methods() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string.to_string() == "BootstrapMethods" { - if bootstrap_method_const_index != 0 { - assert!( - false, - "Should not find more than one BootstrapMethods constant" - ); - } - bootstrap_method_const_index = (const_index + 1) as u16; + if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "BootstrapMethods" { + if bootstrap_method_const_index != 0 { + panic!("Should not find more than one BootstrapMethods constant"); } - } - _ => {} + bootstrap_method_const_index = (const_index + 1) as u16; } } assert_ne!(bootstrap_method_const_index, 0); @@ -52,7 +44,7 @@ fn test_attribute_bootstrap_methods() { bootstrap_method_const_index ); - for (_, attribute_item) in c.attributes.iter().enumerate() { + for attribute_item in c.attributes.iter() { if attribute_item.attribute_name_index == bootstrap_method_const_index { match bootstrap_methods_attribute_parser(&attribute_item.info) { Ok((_, bsma)) => { @@ -66,7 +58,7 @@ fn test_attribute_bootstrap_methods() { } } - assert!(false, "Should not get to here"); + panic!("Should not get to here"); } _ => panic!("Not a valid class file"), } @@ -78,17 +70,9 @@ fn should_have_no_bootstrap_method_attr_if_no_invoke_dynamic() { "../java-assets/compiled-classes/BasicClass.class" )) { Ok((_, c)) => { - for (_, const_item) in c.const_pool.iter().enumerate() { - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string.to_string() == "BootstrapMethods" { - assert!( - false, - "Should not have found a BootstrapMethods constant in a class not requiring it" - ) - } - } - _ => {} + for const_item in c.const_pool.iter() { + if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "BootstrapMethods" { + panic!("Should not have found a BootstrapMethods constant in a class not requiring it") } } } diff --git a/tests/attr_stack_map_table.rs b/tests/attr_stack_map_table.rs index 799331a..30ad912 100644 --- a/tests/attr_stack_map_table.rs +++ b/tests/attr_stack_map_table.rs @@ -18,19 +18,13 @@ fn test_attribute_stack_map_table() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string.to_string() == "StackMapTable" { - if stack_map_table_index != 0 { - assert!( - false, - "Should not find more than one StackMapTable constant" - ); - } - stack_map_table_index = (const_index + 1) as u16; - } + if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "StackMapTable" { + if stack_map_table_index != 0 { + panic!( + "Should not find more than one StackMapTable constant" + ); } - _ => {} + stack_map_table_index = (const_index + 1) as u16; } } println!("Methods:"); @@ -54,16 +48,13 @@ fn test_attribute_stack_map_table() { println!("Code Attrs:"); for (idx, code_attr) in code.attributes.iter().enumerate() { println!("\t[{}] = {:?}", idx, code_attr); - match *code_attr { - AttributeInfo { + let AttributeInfo { ref attribute_name_index, attribute_length: _, info: _, - } => { - if attribute_name_index == &stack_map_table_index { - stack_map_table_attr_index = idx; - } - } + } = *code_attr; + if attribute_name_index == &stack_map_table_index { + stack_map_table_attr_index = idx; } } diff --git a/tests/classfile.rs b/tests/classfile.rs index e888898..14bb38d 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -29,12 +29,10 @@ fn test_valid_class() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - if let ConstantInfo::Utf8(ref c) = *const_item { - if c.utf8_string.to_string() == "Code" { + if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "Code" { code_const_index = (const_index + 1) as u16; } } - } println!("Code index = {}", code_const_index); println!("Interfaces:"); From 9c16763c1d2798496f8395e02d38a43d9c71c435 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 19:57:14 -0600 Subject: [PATCH 16/27] add runtime visible type annotations parsing --- .../Annotations$TypeInvisibleAtRuntime.class | Bin 0 -> 521 bytes .../Annotations$TypeVisibleAtRuntime.class | Bin 0 -> 519 bytes .../compiled-classes/Annotations.class | Bin 1314 -> 1759 bytes java-assets/src/Annotations.java | 15 ++ src/attribute_info/mod.rs | 2 + src/attribute_info/parser.rs | 150 ++++++++++++++++++ src/attribute_info/types.rs | 76 +++++++++ tests/code_attribute.rs | 78 +++++++-- 8 files changed, 308 insertions(+), 13 deletions(-) create mode 100644 java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class create mode 100644 java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class diff --git a/java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class new file mode 100644 index 0000000000000000000000000000000000000000..856365ed25c65d52386bcd2b820d281a9e02931b GIT binary patch literal 521 zcmaix%}PQ+6vzLkwCh)8X+}g85>lIdfL10(f|PhIh!!!*K}N>8ac2~KwH7@<4;39l zye@(JdhtM$>r6_u2?kOdQ?K z0w$a+Yj|1Kv`XR#7i$66viI1t)E!v~U;78?Mn(_k0;ZdtMi>&#HaqlHJYp|gb+f%? zcp^y`^Ajl3f9@P30k-`7~ literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class new file mode 100644 index 0000000000000000000000000000000000000000..bbb27216d4d284db3cc8f16f61f2bc06a84b477d GIT binary patch literal 519 zcmaix-AY115Xb*x+VQKhv?3yk2&tQVfUYczgfh`%5M9J7gRHE3;_NE;YF+dIJybMC zdAtz1*xh00H~;zi{CfWYaE<){DZ)wJ+Q=)fBkRt>*&9D;r~8KMzB932k2ye^P#USJ zsu^X6wa5O52R^_$;W+tK3zb?TNXSf;nJ}SpesQ0`T_0;Zydvbg(Ig)5tv14xl0bJk zhY1J2HT>1INaCdMCBdca9ri4BM^@yk{~+zi=)p|DpwnxG&4&izXmv=a;vst>PgnaJ zhR2e2K0kr7;d8t3+Uts5w#ClhMNS4*ArzKcBov$0a(rWyb1V&2R!zT3AgsyB$j(SM u5g;p88acUB$fF?dp;$#>0>%b5u{AeeK#UT$vGW6#u`6$0JcKg#s=04-{gA)_ literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/Annotations.class b/java-assets/compiled-classes/Annotations.class index 71701fef60be863036a3779a33ff4cda177401ab..d8be0515f2988c286b8c0ed4774c8f44bac660fe 100644 GIT binary patch delta 750 zcmZ`$%}x_x6g_u_QYn9pKp`Q7Qc5jUDbyBFKq$YqR%(D46Bni?O*5nn#*QRxBt8Q9 zwybdJ3P>Pf!2|dbE`1B*nIDR2<6^#Z?>*;y=g!Po;8$nh=bxKf04sQN9!SfFzC)vC ze5{tUrg>nMEaSl3J^EA@JS^;$4ok^u$*d%Itj~s7nHLOge=#j%zx>wq*vflfO%N&k z(?)GteUriC4vSUT3lj7j=6^zmz58=_f~JIMQ$k&vwLCW$?Q#;m9IaYb3-a#rRPs`M z!7Uq?Y`(&eWkI-6=e>44$d48ETZ_w@3hFW0P_z1|yj1V?DPPEW_Y=K$px3S%jr<1c ztJbf+>$zhM8b%O-V3fb56Vd}j*^Si@BlXCk;&p1=r6!=W@S&Awg5(fUh`xzy$TVyx{i^cbbz delta 344 zcmYLDyH3ME5S+Eoc^o1NDJ&=uxrCVD1PBnoB)m%`N{}e&>F8+qhG=^VzJQ1#5=eXi z65qg|M9g6j#qI3O&hFgT?31-0)7N(ZXE+_(zUVNx(q}2XjDmBK9ZgN!> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, type_annotations) = count(type_annotation_parser, num_annotations as usize)(input)?; + + Ok(( + input, + RuntimeVisibleTypeAnnotationsAttribute { + num_annotations, + type_annotations, + }, + )) +} + +pub fn runtime_invisible_type_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, type_annotations) = count(type_annotation_parser, num_annotations as usize)(input)?; + + Ok(( + input, + RuntimeInvisibleTypeAnnotationsAttribute { + num_annotations, + type_annotations, + }, + )) +} +pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), Err<&[u8]>> { + let (input, target_type) = be_u8(input)?; + let mut target_info: TargetInfo = TargetInfo::Empty; + match target_type { + 0x0 | 0x1 => { + let (input, type_parameter_index) = be_u8(input)?; + target_info = TargetInfo::TypeParameter { + type_parameter_index, + }; + } + 0x10 => { + let (input, supertype_index) = be_u16(input)?; + target_info = TargetInfo::SuperType { supertype_index }; + } + 0x11..=0x12 => { + let (input, type_parameter_index) = be_u8(input)?; + let (input, bound_index) = be_u8(input)?; + target_info = TargetInfo::TypeParameterBound { type_parameter_index, bound_index } + } + 0x13..=0x15 => { + // Empty target_info + } + 0x16 => { + let (input, formal_parameter_index) = be_u8(input)?; + target_info = TargetInfo::FormalParameter { + formal_parameter_index, + }; + } + 0x17 => { + let (input, throws_type_index) = be_u16(input)?; + target_info = TargetInfo::Throws { throws_type_index }; + } + 0x40 | 0x41 => { + let (input, table_length) = be_u16(input)?; + let (input, tables) = count( + local_variable_table_annotation_parser, + table_length as usize, + )(input)?; + target_info = TargetInfo::LocalVar { + table_length, + tables, + }; + } + 0x42 => { + let (input, exception_table_index) = be_u16(input)?; + target_info = TargetInfo::Catch { + exception_table_index, + } + } + 0x43..=0x46 => { + let (input, offset) = be_u16(input)?; + target_info = TargetInfo::Offset { offset } + } + 0x47..=0x4B => { + let (input, offset) = be_u16(input)?; + let (input, type_argument_index) = be_u8(input)?; + target_info = TargetInfo::TypeArgument { + offset, + type_argument_index, + }; + } + _ => { + eprintln!( + "Parsing RuntimeVisibleTypeAnnotationsAttribute with target_type = {}", + target_type + ); + } + } + let (input, target_path) = target_path_parser(input)?; + let (input, type_index) = be_u16(input)?; + let (input, num_element_value_pairs) = be_u16(input)?; + let (input, element_value_pairs) = + count(element_value_pair_parser, num_element_value_pairs as usize)(input)?; + + Ok(( + input, + TypeAnnotation { + target_type, + target_info, + target_path, + type_index, + num_element_value_pairs, + element_value_pairs, + }, + )) +} + +fn target_path_parser(input: &[u8]) -> Result<(&[u8], TypePath), Err<&[u8]>> { + let (input, path_length) = be_u8(input)?; + let (input, paths) = count( + |input| { + let (input, type_path_kind) = be_u8(input)?; + let (input, type_argument_index) = be_u8(input)?; + Ok(( + input, + TypePathEntry { + type_path_kind, + type_argument_index, + }, + )) + }, + path_length as usize, + )(input)?; + Ok((input, TypePath { path_length, paths })) +} + +pub fn local_variable_table_annotation_parser( + input: &[u8], +) -> Result<(&[u8], LocalVarTableAnnotation), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, length) = be_u16(input)?; + let (input, index) = be_u16(input)?; + Ok(( + input, + LocalVarTableAnnotation { + start_pc, + length, + index, + }, + )) +} fn annotation_parser(input: &[u8]) -> Result<(&[u8], RuntimeAnnotation), Err<&[u8]>> { let (input, type_index) = be_u16(input)?; let (input, num_element_value_pairs) = be_u16(input)?; diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index ff30d35..01f4a74 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -112,6 +112,82 @@ pub struct RuntimeInvisibleParameterAnnotationsAttribute { pub parameter_annotations: Vec, } +#[derive(Clone, Debug)] +pub struct RuntimeVisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub type_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub type_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct TypeAnnotation { + pub target_type: u8, + pub target_info: TargetInfo, + pub target_path: TypePath, + pub type_index: u16, + pub num_element_value_pairs: u16, + pub element_value_pairs: Vec, +} + +#[derive(Clone, Debug)] +pub enum TargetInfo { + TypeParameter { + type_parameter_index: u8, + }, + SuperType { + supertype_index: u16, + }, + TypeParameterBound { + type_parameter_index: u8, + bound_index: u8, + }, + Empty, + FormalParameter { + formal_parameter_index: u8, + }, + Throws { + throws_type_index: u16, + }, + LocalVar { + table_length: u16, + tables: Vec, + }, + Catch { + exception_table_index: u16, + }, + Offset { + offset: u16, + }, + TypeArgument { + offset: u16, + type_argument_index: u8, + }, +} + +#[derive(Clone, Debug)] +pub struct TypePath { + pub path_length: u8, + pub paths: Vec, +} + +#[derive(Clone, Debug)] +pub struct TypePathEntry { + pub type_path_kind: u8, + pub type_argument_index: u8, +} + +#[derive(Clone, Debug)] +pub struct LocalVarTableAnnotation { + pub start_pc: u16, + pub length: u16, + pub index: u16, +} + #[derive(Clone, Debug)] pub struct RuntimeAnnotation { pub type_index: u16, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 304fa64..a08441b 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,16 +5,11 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - ElementValue, InnerClassAccessFlags, code_attribute_parser, enclosing_method_attribute_parser, - inner_classes_attribute_parser, method_parameters_attribute_parser, - runtime_invisible_annotations_attribute_parser, - runtime_invisible_parameter_annotations_attribute_parser, - runtime_visible_annotations_attribute_parser, - runtime_visible_parameter_annotations_attribute_parser, signature_attribute_parser, + code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, ElementValue, InnerClassAccessFlags, TargetInfo }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -400,7 +395,7 @@ fn runtime_visible_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 36); + assert_eq!(inner.1.annotations[0].type_index, 46); assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); assert_eq!( @@ -411,7 +406,7 @@ fn runtime_visible_annotations() { match inner.1.annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 38); + assert_eq!(value, 47); } _ => panic!("Expected ConstValueIndex"), } @@ -454,7 +449,7 @@ fn runtime_invisible_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.annotations.len(), 1); - assert_eq!(inner.1.annotations[0].type_index, 40); + assert_eq!(inner.1.annotations[0].type_index, 49); assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); assert_eq!( @@ -465,7 +460,7 @@ fn runtime_invisible_annotations() { match inner.1.annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 41); + assert_eq!(value, 50); } _ => panic!("Expected ConstValueIndex"), } @@ -497,7 +492,7 @@ fn runtime_visible_parameter_annotations() { match inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 44); + assert_eq!(value, 53); } _ => panic!( "expected ConstValueIndex, got {:?}", @@ -532,7 +527,7 @@ fn runtime_invisible_parameter_annotations() { match inner.1.parameter_annotations[1].annotations[0].element_value_pairs[0].value { ElementValue::ConstValueIndex { tag, value } => { assert_eq!(tag, 's'); - assert_eq!(value, 41); + assert_eq!(value, 50); } _ => panic!( "expected ConstValueIndex, got {:?}", @@ -541,6 +536,63 @@ fn runtime_invisible_parameter_annotations() { } } +#[test] +fn runtime_visible_type_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_type_annotations_attribute = class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleTypeAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_type_annotations_attribute.len(), 1); + let f = runtime_visible_type_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_type_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.type_annotations.len(), 1); + assert_eq!(inner.1.type_annotations[0].target_type, 19); + assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); + assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); + assert_eq!(inner.1.type_annotations[0].type_index, 36); + assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37); + assert_matches!(inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { tag: 's', value: 38 }); +} + +#[test] +fn runtime_invisible_type_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_type_annotations_attribute = class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleTypeAnnotations")) + .collect::>(); + assert_eq!(runtime_invisible_type_annotations_attribute.len(), 1); + let f = runtime_invisible_type_annotations_attribute.first().unwrap(); + + let invisible_annotations = runtime_visible_type_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.type_annotations.len(), 1); + assert_eq!(inner.1.type_annotations[0].target_type, 19); + assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); + assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); + assert_eq!(inner.1.type_annotations[0].type_index, 41); + assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37); + assert_matches!(inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { tag: 's', value: 42 }); +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); From b53fd0fd8907567a630eb90b1e204eb193dd8a79 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 20:20:27 -0600 Subject: [PATCH 17/27] update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aa28a06..7acf373 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,8 @@ fn main() { - [x] RuntimeInvisibleAnnotations - [x] RuntimeVisibleParameterAnnotations - [x] RuntimeInvisibleParameterAnnotations - - [ ] RuntimeVisibleTypeAnnotations - - [ ] RuntimeInvisibleTypeAnnotations + - [x] RuntimeVisibleTypeAnnotations + - [x] RuntimeInvisibleTypeAnnotations - [ ] AnnotationDefault - [X] MethodParameters - [ ] Useful but not critical From a5a793a810363ee7445daff45f914e986b78b04e Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 20:47:20 -0600 Subject: [PATCH 18/27] add default annotation parsing --- .../Annotations$VisibleAtRuntime.class | Bin 417 -> 467 bytes .../compiled-classes/Annotations.class | Bin 1759 -> 1759 bytes java-assets/compiled-classes/BasicClass.class | Bin 1017 -> 1017 bytes .../compiled-classes/BootstrapMethods.class | Bin 1279 -> 1279 bytes .../DeprecatedAnnotation.class | Bin 645 -> 645 bytes java-assets/compiled-classes/Factorial.class | Bin 224 -> 224 bytes java-assets/compiled-classes/HelloWorld.class | Bin 426 -> 426 bytes .../compiled-classes/Instructions.class | Bin 735 -> 735 bytes .../compiled-classes/LocalVariableTable.class | Bin 701 -> 701 bytes .../compiled-classes/UnicodeStrings.class | Bin 756 -> 756 bytes java-assets/compiled-classes/malformed.class | Bin 426 -> 426 bytes java-assets/src/Annotations.java | 2 +- src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 11 +-- src/attribute_info/types.rs | 2 + tests/attr_bootstrap_methods.rs | 20 ++++-- tests/attr_stack_map_table.rs | 16 ++--- tests/classfile.rs | 8 ++- tests/code_attribute.rs | 65 ++++++++++++++++-- 19 files changed, 95 insertions(+), 30 deletions(-) diff --git a/java-assets/compiled-classes/Annotations$VisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$VisibleAtRuntime.class index 40525ff4260ce65bf6e01a2089604fb3b2a6d326..cff9b279982e2af98f12d400ff433ea256713a32 100644 GIT binary patch delta 131 zcmZ3;e3_Z+)W2Q(7#J9w7{n%W)u{_Q=H=y=B$j06=eeY&C6?xtFfs_GKv)WiaJh+X z^BILFt1-ITGcs@jbu$+;a51nja08i43_J|HK$@3 delta 82 zcmcc2ypWme)W2Q(7#J9w7(^y=)lJ+umr-!CKclM|0|N^KCs3S;fs26~Nb@o diff --git a/java-assets/compiled-classes/Annotations.class b/java-assets/compiled-classes/Annotations.class index d8be0515f2988c286b8c0ed4774c8f44bac660fe..78ced4f771eaad6711001426f1910a1c2dfa79fe 100644 GIT binary patch delta 67 zcmcc5d!KhhBC8A|gCc_x10#bn0|Nsu1EVklg9?zRTFjuvz&N> V5?ceO83PA{IfDR$#pF)5FaTU53KIYT diff --git a/java-assets/compiled-classes/BasicClass.class b/java-assets/compiled-classes/BasicClass.class index ec642255c15436c98abacbff587c6b4d5525159c..cab2379ead9ea9ddb1246f2f90a44aa689c7d34c 100644 GIT binary patch delta 17 Zcmey#{*#^K)W2Q(7#J9wHgbGq1^`IP2RHx# delta 17 Zcmey#{*#^K)W2Q(7#J8FH*$Pr1^`IK2R8r! diff --git a/java-assets/compiled-classes/BootstrapMethods.class b/java-assets/compiled-classes/BootstrapMethods.class index 166a793a7ed20755663a6ee942941752b499e456..54154acf50da2e52d57bfb3ffd2b2ce2fcddab28 100644 GIT binary patch delta 17 Zcmey*`Ja>H)W2Q(7#J9wHgbGt0RTzU2TuS1 delta 17 Zcmey*`Ja>H)W2Q(7#J8FH*$Pu0RTzP2TlM0 diff --git a/java-assets/compiled-classes/DeprecatedAnnotation.class b/java-assets/compiled-classes/DeprecatedAnnotation.class index dfaba31c95fd6655e82cb09e8a7ffb8135163f8a..106fdeb509f9fc0c69c68aa7319108a2c4ddab56 100644 GIT binary patch delta 17 YcmZo=ZDr*+^>5cc1_lPFjU2U1068TE*8l(j delta 17 YcmZo=ZDr*+^>5cc1_lPljU2U1068E9)&Kwi diff --git a/java-assets/compiled-classes/Factorial.class b/java-assets/compiled-classes/Factorial.class index bbb56bcedc12853c10a6432562c926efde0d22df..9709e7f6cfeaa8ff9dfa01043c993a4f228fdc0e 100644 GIT binary patch delta 16 YcmaFB_<)h))W2Q(7#J9wCUV>W06l{Sz5oCK delta 16 YcmaFB_<)h))W2Q(7#J8FCvw~X06l*Oy#N3J diff --git a/java-assets/compiled-classes/HelloWorld.class b/java-assets/compiled-classes/HelloWorld.class index c4d915c45bade82ec6eb5e9232ee433ab4733cc4..230ca2b54bbae9175336934ca47c523d87cdae54 100644 GIT binary patch delta 17 ZcmZ3*yo#CQ)W2Q(7#J9wHgYUt1OPqK20;J- delta 17 ZcmZ3*yo#CQ)W2Q(7#J8FH*zdu1OPqF20#D+ diff --git a/java-assets/compiled-classes/Instructions.class b/java-assets/compiled-classes/Instructions.class index c1f08b4254e6a64ac7370a985ff20b4b96c841f7..3cb696cd57cdeeb32638538ceee33b7fbac2bac5 100644 GIT binary patch delta 17 Zcmcc5dY_f!)W2Q(7#J9wHga5N0suuB2IT+% delta 17 Zcmcc5dY_f!)W2Q(7#J8FH*#EO0suu62IK$$ diff --git a/java-assets/compiled-classes/LocalVariableTable.class b/java-assets/compiled-classes/LocalVariableTable.class index ff0af9ab970c440b8c7482a6657bc4aaadd5fde8..33d8448d05a8b1308fc8317a01195de61e2c23c1 100644 GIT binary patch delta 17 ZcmdnXx|fyX)W2Q(7#J9wHgarb0sugj27dqm delta 17 ZcmdnXx|fyX)W2Q(7#J8FH*#!c0suge27Ukl diff --git a/java-assets/compiled-classes/UnicodeStrings.class b/java-assets/compiled-classes/UnicodeStrings.class index 2330623d73e1be97d1cf7f9709d2bd908fe86d3c..243ac6b3c1086083469d99323d4642bc426b1c50 100644 GIT binary patch delta 17 Zcmeyu`h}I_)W2Q(7#J9wHgddS0su$j2P6Oh delta 17 Zcmeyu`h}I_)W2Q(7#J8FH*&mT0su$e2O|Ig diff --git a/java-assets/compiled-classes/malformed.class b/java-assets/compiled-classes/malformed.class index 4089af7cc213316766729c1552a11399520dd74c..202b7c9c6adeccffde020abd9b6170a347c21da2 100644 GIT binary patch delta 17 ZcmZ3*yo#CQ-r9Zd85kIxHgYUt1OPoK1~~u# delta 17 ZcmZ3*yo#CQ-r9Zd85kHGH*zdu1OPoF1~>o! diff --git a/java-assets/src/Annotations.java b/java-assets/src/Annotations.java index 4160d76..03f7765 100644 --- a/java-assets/src/Annotations.java +++ b/java-assets/src/Annotations.java @@ -9,7 +9,7 @@ public class Annotations { @Retention(RetentionPolicy.RUNTIME) public @interface VisibleAtRuntime { - String value(); + String value() default "default annotation"; } @Retention(RetentionPolicy.CLASS) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 3ad02e3..8066866 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -7,6 +7,7 @@ pub use self::parser::attribute_parser; pub use self::parser::bootstrap_methods_attribute_parser; pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; +pub use self::parser::element_value_parser; pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 1964698..cfe9e5e 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,10 +1,10 @@ use nom::{ + Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u16, be_u32, be_u8}, - Err as BaseErr, + number::complete::{be_u8, be_u16, be_u32}, }; use crate::attribute_info::types::StackMapFrame::*; @@ -253,7 +253,10 @@ pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), E 0x11..=0x12 => { let (input, type_parameter_index) = be_u8(input)?; let (input, bound_index) = be_u8(input)?; - target_info = TargetInfo::TypeParameterBound { type_parameter_index, bound_index } + target_info = TargetInfo::TypeParameterBound { + type_parameter_index, + bound_index, + } } 0x13..=0x15 => { // Empty target_info @@ -394,7 +397,7 @@ fn array_value_parser(input: &[u8]) -> Result<(&[u8], ElementArrayValue), Err<&[ Ok((input, ElementArrayValue { num_values, values })) } -fn element_value_parser(input: &[u8]) -> Result<(&[u8], ElementValue), Err<&[u8]>> { +pub fn element_value_parser(input: &[u8]) -> Result<(&[u8], ElementValue), Err<&[u8]>> { let (input, tag) = be_u8(input)?; eprintln!("Element value parsing: tag = {}", tag as char); diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 01f4a74..a51b6f2 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -195,6 +195,8 @@ pub struct RuntimeAnnotation { pub element_value_pairs: Vec, } +type DefaultAnnotation = ElementValue; + #[derive(Clone, Debug)] pub struct ElementValuePair { pub element_name_index: u16, diff --git a/tests/attr_bootstrap_methods.rs b/tests/attr_bootstrap_methods.rs index bed0471..a419e9f 100644 --- a/tests/attr_bootstrap_methods.rs +++ b/tests/attr_bootstrap_methods.rs @@ -30,11 +30,13 @@ fn test_attribute_bootstrap_methods() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "BootstrapMethods" { - if bootstrap_method_const_index != 0 { - panic!("Should not find more than one BootstrapMethods constant"); - } - bootstrap_method_const_index = (const_index + 1) as u16; + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "BootstrapMethods" + { + if bootstrap_method_const_index != 0 { + panic!("Should not find more than one BootstrapMethods constant"); + } + bootstrap_method_const_index = (const_index + 1) as u16; } } assert_ne!(bootstrap_method_const_index, 0); @@ -71,8 +73,12 @@ fn should_have_no_bootstrap_method_attr_if_no_invoke_dynamic() { )) { Ok((_, c)) => { for const_item in c.const_pool.iter() { - if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "BootstrapMethods" { - panic!("Should not have found a BootstrapMethods constant in a class not requiring it") + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "BootstrapMethods" + { + panic!( + "Should not have found a BootstrapMethods constant in a class not requiring it" + ) } } } diff --git a/tests/attr_stack_map_table.rs b/tests/attr_stack_map_table.rs index 30ad912..6fb776d 100644 --- a/tests/attr_stack_map_table.rs +++ b/tests/attr_stack_map_table.rs @@ -18,11 +18,11 @@ fn test_attribute_stack_map_table() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "StackMapTable" { + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "StackMapTable" + { if stack_map_table_index != 0 { - panic!( - "Should not find more than one StackMapTable constant" - ); + panic!("Should not find more than one StackMapTable constant"); } stack_map_table_index = (const_index + 1) as u16; } @@ -49,10 +49,10 @@ fn test_attribute_stack_map_table() { for (idx, code_attr) in code.attributes.iter().enumerate() { println!("\t[{}] = {:?}", idx, code_attr); let AttributeInfo { - ref attribute_name_index, - attribute_length: _, - info: _, - } = *code_attr; + ref attribute_name_index, + attribute_length: _, + info: _, + } = *code_attr; if attribute_name_index == &stack_map_table_index { stack_map_table_attr_index = idx; } diff --git a/tests/classfile.rs b/tests/classfile.rs index 14bb38d..e63b94f 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -29,10 +29,12 @@ fn test_valid_class() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - if let ConstantInfo::Utf8(ref c) = *const_item && c.utf8_string.to_string() == "Code" { - code_const_index = (const_index + 1) as u16; - } + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "Code" + { + code_const_index = (const_index + 1) as u16; } + } println!("Code index = {}", code_const_index); println!("Interfaces:"); diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index a08441b..c90c459 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,11 +5,17 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, ElementValue, InnerClassAccessFlags, TargetInfo + ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, element_value_parser, + enclosing_method_attribute_parser, inner_classes_attribute_parser, + method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, + runtime_invisible_parameter_annotations_attribute_parser, + runtime_visible_annotations_attribute_parser, + runtime_visible_parameter_annotations_attribute_parser, + runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -561,8 +567,17 @@ fn runtime_visible_type_annotations() { assert_eq!(inner.1.type_annotations[0].type_index, 36); assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); - assert_eq!(inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37); - assert_matches!(inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { tag: 's', value: 38 }); + assert_eq!( + inner.1.type_annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + assert_matches!( + inner.1.type_annotations[0].element_value_pairs[0].value, + ElementValue::ConstValueIndex { + tag: 's', + value: 38 + } + ); } #[test] @@ -576,7 +591,9 @@ fn runtime_invisible_type_annotations() { .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleTypeAnnotations")) .collect::>(); assert_eq!(runtime_invisible_type_annotations_attribute.len(), 1); - let f = runtime_invisible_type_annotations_attribute.first().unwrap(); + let f = runtime_invisible_type_annotations_attribute + .first() + .unwrap(); let invisible_annotations = runtime_visible_type_annotations_attribute_parser(&f.info); let inner = &invisible_annotations.unwrap(); @@ -589,8 +606,42 @@ fn runtime_invisible_type_annotations() { assert_eq!(inner.1.type_annotations[0].type_index, 41); assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); - assert_eq!(inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37); - assert_matches!(inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { tag: 's', value: 42 }); + assert_eq!( + inner.1.type_annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + assert_matches!( + inner.1.type_annotations[0].element_value_pairs[0].value, + ElementValue::ConstValueIndex { + tag: 's', + value: 42 + } + ); +} + +#[test] +fn default_annotation_value() { + let class_bytes = + include_bytes!("../java-assets/compiled-classes/Annotations$VisibleAtRuntime.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let default_annotation_attributes = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "AnnotationDefault")) + .collect::>(); + assert_eq!(default_annotation_attributes.len(), 1); + let f = default_annotation_attributes.first().unwrap(); + + let default_annotation = element_value_parser(&f.info); + let inner = &default_annotation.unwrap(); + assert_matches!( + inner.1, + ElementValue::ConstValueIndex { + tag: 's', + value: 10 + } + ); } #[test] From 001474853b0174e8d981d1cd673cdfa879a9a3ed Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 20:48:05 -0600 Subject: [PATCH 19/27] update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7acf373..9bd6b00 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ fn main() { - [x] StackMapTable - [x] Exceptions - [x] BootstrapMethods - - [ ] Critical for Java SE + - [x] Critical for Java SE - [x] InnerClasses - [x] EnclosingMethod - [x] Synthetic @@ -102,7 +102,7 @@ fn main() { - [x] RuntimeInvisibleParameterAnnotations - [x] RuntimeVisibleTypeAnnotations - [x] RuntimeInvisibleTypeAnnotations - - [ ] AnnotationDefault + - [x] AnnotationDefault - [X] MethodParameters - [ ] Useful but not critical - [x] SourceFile From 843542e430df7651a15428c9f65c1e67945099c1 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Fri, 5 Dec 2025 22:22:53 -0600 Subject: [PATCH 20/27] add source debug extension extraction, parsing is left for development when use cases are determined --- src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 8 ++++++++ src/attribute_info/types.rs | 9 +++++++++ tests/code_attribute.rs | 23 +++++++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 8066866..a405fc6 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -19,5 +19,6 @@ pub use self::parser::runtime_visible_annotations_attribute_parser; pub use self::parser::runtime_visible_parameter_annotations_attribute_parser; pub use self::parser::runtime_visible_type_annotations_attribute_parser; pub use self::parser::signature_attribute_parser; +pub use self::parser::source_debug_extension_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index cfe9e5e..e8f51c1 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -461,6 +461,14 @@ fn enum_const_value_parser(input: &[u8]) -> Result<(&[u8], EnumConstValue), Err< )) } +// not even really parsing ... +pub fn source_debug_extension_parser( + input: &[u8], +) -> Result<(&[u8], SourceDebugExtensionAttribute), Err<&[u8]>> { + let debug_extension = Vec::from(input); + Ok((input, SourceDebugExtensionAttribute { debug_extension })) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index a51b6f2..d1f3cb8 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -225,6 +225,15 @@ pub struct EnumConstValue { pub const_name_index: u16, } +#[derive(Clone, Debug)] +pub struct SourceDebugExtensionAttribute { + // Per the spec: + // The debug_extension array holds extended debugging information which has no + // semantic effect on the Java Virtual Machine. The information is represented + // using a modified UTF-8 string with no terminating zero byte. + pub debug_extension: Vec, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index c90c459..ce0d127 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -12,6 +12,7 @@ use classfile_parser::attribute_info::{ runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, + source_debug_extension_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ @@ -644,6 +645,28 @@ fn default_annotation_value() { ); } +// SourceDebugExtension attributes appear to be custom/non-standard. While it would +// be nice to parse, ultimately the spec defines the attribute as a byte array that +// contains "extended debugging information which has no semantic effect on the Java +// Virtual Machine", so I will leave this test to be better developed when example +// use cases are found. +// #[test] +fn source_debug_extension() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let source_debug_extension_attribute = class + .attributes + .iter() + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "SourceDebugExtension")) + .collect::>(); + assert_eq!(source_debug_extension_attribute.len(), 1); + let f = source_debug_extension_attribute.first().unwrap(); + + let default_annotation = source_debug_extension_parser(&f.info); + let inner = &default_annotation.unwrap(); + dbg!(inner); +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); From c900267271aa3da04ed3ddefcef6e023c470c27a Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Sat, 6 Dec 2025 17:28:07 -0600 Subject: [PATCH 21/27] fix clippy issues --- src/attribute_info/parser.rs | 18 +++++++++--------- src/attribute_info/types.rs | 2 +- tests/code_attribute.rs | 17 +++++------------ 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index e8f51c1..6d51134 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -241,18 +241,18 @@ pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), E let mut target_info: TargetInfo = TargetInfo::Empty; match target_type { 0x0 | 0x1 => { - let (input, type_parameter_index) = be_u8(input)?; + let (_input, type_parameter_index) = be_u8(input)?; target_info = TargetInfo::TypeParameter { type_parameter_index, }; } 0x10 => { - let (input, supertype_index) = be_u16(input)?; + let (_input, supertype_index) = be_u16(input)?; target_info = TargetInfo::SuperType { supertype_index }; } 0x11..=0x12 => { let (input, type_parameter_index) = be_u8(input)?; - let (input, bound_index) = be_u8(input)?; + let (_input, bound_index) = be_u8(input)?; target_info = TargetInfo::TypeParameterBound { type_parameter_index, bound_index, @@ -262,18 +262,18 @@ pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), E // Empty target_info } 0x16 => { - let (input, formal_parameter_index) = be_u8(input)?; + let (_input, formal_parameter_index) = be_u8(input)?; target_info = TargetInfo::FormalParameter { formal_parameter_index, }; } 0x17 => { - let (input, throws_type_index) = be_u16(input)?; + let (_input, throws_type_index) = be_u16(input)?; target_info = TargetInfo::Throws { throws_type_index }; } 0x40 | 0x41 => { let (input, table_length) = be_u16(input)?; - let (input, tables) = count( + let (_input, tables) = count( local_variable_table_annotation_parser, table_length as usize, )(input)?; @@ -283,18 +283,18 @@ pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), E }; } 0x42 => { - let (input, exception_table_index) = be_u16(input)?; + let (_input, exception_table_index) = be_u16(input)?; target_info = TargetInfo::Catch { exception_table_index, } } 0x43..=0x46 => { - let (input, offset) = be_u16(input)?; + let (_input, offset) = be_u16(input)?; target_info = TargetInfo::Offset { offset } } 0x47..=0x4B => { let (input, offset) = be_u16(input)?; - let (input, type_argument_index) = be_u8(input)?; + let (_input, type_argument_index) = be_u8(input)?; target_info = TargetInfo::TypeArgument { offset, type_argument_index, diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index d1f3cb8..bbc09e5 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -195,7 +195,7 @@ pub struct RuntimeAnnotation { pub element_value_pairs: Vec, } -type DefaultAnnotation = ElementValue; +pub type DefaultAnnotation = ElementValue; #[derive(Clone, Debug)] pub struct ElementValuePair { diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index ce0d127..6cf3d03 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,18 +5,11 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, element_value_parser, - enclosing_method_attribute_parser, inner_classes_attribute_parser, - method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, - runtime_invisible_parameter_annotations_attribute_parser, - runtime_visible_annotations_attribute_parser, - runtime_visible_parameter_annotations_attribute_parser, - runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, - source_debug_extension_parser, + code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -634,10 +627,10 @@ fn default_annotation_value() { assert_eq!(default_annotation_attributes.len(), 1); let f = default_annotation_attributes.first().unwrap(); - let default_annotation = element_value_parser(&f.info); - let inner = &default_annotation.unwrap(); + let default_annotation= element_value_parser(&f.info); + let inner: DefaultAnnotation = default_annotation.unwrap().1 as DefaultAnnotation; assert_matches!( - inner.1, + inner, ElementValue::ConstValueIndex { tag: 's', value: 10 From 318d85fb9b24ee89524e261bc9093dee9b70219e Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Sat, 6 Dec 2025 18:13:28 -0600 Subject: [PATCH 22/27] add line number table parsing --- src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 18 ++++++++++++++++ src/attribute_info/types.rs | 12 +++++++++++ tests/code_attribute.rs | 42 ++++++++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index a405fc6..49de015 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -11,6 +11,7 @@ pub use self::parser::element_value_parser; pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; pub use self::parser::inner_classes_attribute_parser; +pub use self::parser::line_number_table_attribute_parser; pub use self::parser::method_parameters_attribute_parser; pub use self::parser::runtime_invisible_annotations_attribute_parser; pub use self::parser::runtime_invisible_parameter_annotations_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 6d51134..1294210 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -469,6 +469,24 @@ pub fn source_debug_extension_parser( Ok((input, SourceDebugExtensionAttribute { debug_extension })) } +pub fn line_number_table_attribute_parser(input: &[u8]) -> Result<(&[u8], LineNumberTable), Err<&[u8]>> { + let (input, line_number_table_length) = be_u16(input)?; + let (input, line_number_table) = count(line_number_table_entry_parser, line_number_table_length as usize)(input)?; + Ok(( + input, + LineNumberTable { line_number_table_length, line_number_table }, + )) +} + +pub fn line_number_table_entry_parser(input: &[u8]) -> Result<(&[u8], LineNumberTableEntry), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, line_number) = be_u16(input)?; + Ok(( + input, + LineNumberTableEntry { start_pc, line_number }, + )) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index bbc09e5..6febc66 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -234,6 +234,18 @@ pub struct SourceDebugExtensionAttribute { pub debug_extension: Vec, } +#[derive(Clone, Debug)] +pub struct LineNumberTable { + pub line_number_table_length: u16, + pub line_number_table: Vec, +} + +#[derive(Clone, Debug)] +pub struct LineNumberTableEntry { + pub start_pc: u16, + pub line_number: u16, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 6cf3d03..767766c 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,7 +5,15 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo + code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, + inner_classes_attribute_parser, line_number_table_attribute_parser, + method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, + runtime_invisible_parameter_annotations_attribute_parser, + runtime_visible_annotations_attribute_parser, + runtime_visible_parameter_annotations_attribute_parser, + runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, + source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, + TargetInfo, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ @@ -627,7 +635,7 @@ fn default_annotation_value() { assert_eq!(default_annotation_attributes.len(), 1); let f = default_annotation_attributes.first().unwrap(); - let default_annotation= element_value_parser(&f.info); + let default_annotation = element_value_parser(&f.info); let inner: DefaultAnnotation = default_annotation.unwrap().1 as DefaultAnnotation; assert_matches!( inner, @@ -687,3 +695,33 @@ fn source_file() { assert_eq!(s, "BasicClass.java"); } + +#[test] +fn line_number_table() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Instructions.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let default_annotation_attributes = class + .methods + .iter() + .find(|m| m.access_flags.contains(MethodAccessFlags::STATIC)) + .unwrap(); + + let (_, code_attribute) = + code_attribute_parser(&default_annotation_attributes.attributes[0].info).unwrap(); + + assert_eq!( + code_attribute.attributes.len(), + code_attribute.attributes_count as usize + ); + + let line_number_tables = &code_attribute.attributes.iter() + .filter(|a| lookup_string(&class, a.attribute_name_index).unwrap() == "LineNumberTable") + .map(|a| line_number_table_attribute_parser(&a.info).unwrap().1) + .collect::>(); + + assert_eq!(line_number_tables.len(), 1); + assert_eq!(line_number_tables[0].line_number_table_length, 12); + assert_eq!(line_number_tables[0].line_number_table.len(), 12); + assert_eq!(line_number_tables[0].line_number_table[0].start_pc, 0); + assert_eq!(line_number_tables[0].line_number_table[0].line_number, 3); +} From b6bdd3ff92b37c1de1cfac0eb861f03c526b0d68 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Sat, 6 Dec 2025 23:50:11 -0600 Subject: [PATCH 23/27] add local variable type table parsing --- src/code_attribute/mod.rs | 1 + src/code_attribute/parser.rs | 46 ++++++++++++++++++++++++++-- src/code_attribute/types.rs | 15 ++++++++++ tests/code_attribute.rs | 58 ++++++++++++++++++++++++++++++++++-- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/code_attribute/mod.rs b/src/code_attribute/mod.rs index 2ab312f..cae52dd 100644 --- a/src/code_attribute/mod.rs +++ b/src/code_attribute/mod.rs @@ -6,3 +6,4 @@ pub use self::types::*; pub use self::parser::code_parser; pub use self::parser::instruction_parser; pub use self::parser::local_variable_table_parser; +pub use self::parser::local_variable_type_table_parser; diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index 29d2693..d454ec8 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -1,15 +1,18 @@ use crate::code_attribute::types::Instruction; use nom::{ - Err as BaseErr, IResult, Offset, bytes::complete::{tag, take}, combinator::{complete, fail, map, success}, error::Error, multi::{count, many0}, - number::complete::{be_i8, be_i16, be_i32, be_u8, be_u16, be_u32}, + number::complete::{be_i16, be_i32, be_i8, be_u16, be_u32, be_u8}, sequence::{pair, preceded, tuple}, + Err as BaseErr, IResult, Offset, }; -use super::{LocalVariableTableAttribute, LocalVariableTableItem}; +use super::{ + LocalVariableTableAttribute, LocalVariableTableItem, LocalVariableTypeTableAttribute, + LocalVariableTypeTableItem, +}; type Err = BaseErr>; fn offset<'a>(remaining: &'a [u8], input: &[u8]) -> IResult<&'a [u8], usize> { @@ -331,3 +334,40 @@ pub fn variable_table_item_parser( }, )) } + +pub fn local_variable_type_table_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTypeTableAttribute), Err<&[u8]>> { + let (input, local_variable_type_table_length) = be_u16(input)?; + let (input, local_variable_type_table) = count( + local_variable_type_table_item_parser, + local_variable_type_table_length as usize, + )(input)?; + Ok(( + input, + LocalVariableTypeTableAttribute { + local_variable_type_table_length, + local_variable_type_table, + }, + )) +} + +pub fn local_variable_type_table_item_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTypeTableItem), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, length) = be_u16(input)?; + let (input, name_index) = be_u16(input)?; + let (input, signature_index) = be_u16(input)?; + let (input, index) = be_u16(input)?; + Ok(( + input, + LocalVariableTypeTableItem { + start_pc, + length, + name_index, + signature_index, + index, + }, + )) +} diff --git a/src/code_attribute/types.rs b/src/code_attribute/types.rs index 650ab06..5d336a5 100644 --- a/src/code_attribute/types.rs +++ b/src/code_attribute/types.rs @@ -249,3 +249,18 @@ pub struct LocalVariableTableItem { pub descriptor_index: u16, pub index: u16, } + +#[derive(Clone, Debug)] +pub struct LocalVariableTypeTableAttribute { + pub local_variable_type_table_length: u16, + pub local_variable_type_table: Vec, +} + +#[derive(Clone, Debug)] +pub struct LocalVariableTypeTableItem { + pub start_pc: u16, + pub length: u16, + pub name_index: u16, + pub signature_index: u16, + pub index: u16, +} diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 767766c..0fc9d7a 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -17,7 +17,7 @@ use classfile_parser::attribute_info::{ }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, + code_parser, instruction_parser, local_variable_type_table_parser, Instruction, LocalVariableTableAttribute }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -714,7 +714,9 @@ fn line_number_table() { code_attribute.attributes_count as usize ); - let line_number_tables = &code_attribute.attributes.iter() + let line_number_tables = &code_attribute + .attributes + .iter() .filter(|a| lookup_string(&class, a.attribute_name_index).unwrap() == "LineNumberTable") .map(|a| line_number_table_attribute_parser(&a.info).unwrap().1) .collect::>(); @@ -725,3 +727,55 @@ fn line_number_table() { assert_eq!(line_number_tables[0].line_number_table[0].start_pc, 0); assert_eq!(line_number_tables[0].line_number_table[0].line_number, 3); } + +#[test] +fn local_variable_type_table() { + // The class was not compiled with "javac -g" + let class_bytes = include_bytes!("../java-assets/compiled-classes/LocalVariableTable.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let method_info = &class.methods.iter().last().unwrap(); + + let local_variable_table_type_attribute = method_info + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "Code" => { + code_attribute_parser(&attribute_info.info) + .ok() + } + _ => None, + } + }) + .map(|i| i.1) + .unwrap() + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "LocalVariableTypeTable" => { + local_variable_type_table_parser( + &attribute_info.info, + ) + .ok() + } + _ => None, + } + }) + .map(|a| a.1) + .unwrap(); + + let types: Vec = local_variable_table_type_attribute + .local_variable_type_table + .iter() + .filter_map(|i| lookup_string(&class, i.signature_index)) + .collect(); + + // All used types in method code block of last method + assert_eq!( + types, + vec![ + "Ljava/util/HashMap;" + ] + ); +} From 2cdad24b846c506ffab2fc07dd7ee47883b84cdf Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Sun, 7 Dec 2025 00:19:13 -0600 Subject: [PATCH 24/27] add tests to show how to find a deprecated annotation --- tests/code_attribute.rs | 70 ++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 0fc9d7a..3974e6a 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -17,7 +17,8 @@ use classfile_parser::attribute_info::{ }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, local_variable_type_table_parser, Instruction, LocalVariableTableAttribute + code_parser, instruction_parser, local_variable_type_table_parser, Instruction, + LocalVariableTableAttribute, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; @@ -740,10 +741,7 @@ fn local_variable_type_table() { .iter() .find_map(|attribute_info| { match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { - "Code" => { - code_attribute_parser(&attribute_info.info) - .ok() - } + "Code" => code_attribute_parser(&attribute_info.info).ok(), _ => None, } }) @@ -754,10 +752,7 @@ fn local_variable_type_table() { .find_map(|attribute_info| { match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { "LocalVariableTypeTable" => { - local_variable_type_table_parser( - &attribute_info.info, - ) - .ok() + local_variable_type_table_parser(&attribute_info.info).ok() } _ => None, } @@ -774,8 +769,59 @@ fn local_variable_type_table() { // All used types in method code block of last method assert_eq!( types, - vec![ - "Ljava/util/HashMap;" - ] + vec!["Ljava/util/HashMap;"] ); } + +#[test] +fn deprecated() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/DeprecatedAnnotation.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + let deprecated_class_attribute = &class + .attributes + .iter() + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_class_attribute.len(), 1); + + let deprecated_method_attribute = &class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_method_attribute.len(), 1); + + let deprecated_field_attribute = &class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_field_attribute.len(), 1); +} From e3141f8e4bbfc52af82c765de9663e694dbf5495 Mon Sep 17 00:00:00 2001 From: prockallsyms Date: Sun, 7 Dec 2025 00:21:09 -0600 Subject: [PATCH 25/27] update README and fix formatting --- README.md | 12 ++++++------ src/attribute_info/parser.rs | 23 ++++++++++++++++++----- src/code_attribute/parser.rs | 4 ++-- tests/code_attribute.rs | 14 +++++++------- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9bd6b00..088beaf 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ fn main() { - [x] Methods - [x] Attributes - [x] Basic attribute info block parsing - - [ ] Known typed attributes parsing + - [x] Known typed attributes parsing - [x] Critical for JVM - [x] ConstantValue - [x] Code @@ -104,10 +104,10 @@ fn main() { - [x] RuntimeInvisibleTypeAnnotations - [x] AnnotationDefault - [X] MethodParameters - - [ ] Useful but not critical + - [x] Useful but not critical - [x] SourceFile - - [ ] SourceDebugExtension - - [ ] LineNumberTable + - [~] SourceDebugExtension + - [x] LineNumberTable - [x] LocalVariableTable - - [ ] LocalVariableTypeTable - - [ ] Deprecated + - [x] LocalVariableTypeTable + - [x] Deprecated diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 1294210..7226f3c 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -469,21 +469,34 @@ pub fn source_debug_extension_parser( Ok((input, SourceDebugExtensionAttribute { debug_extension })) } -pub fn line_number_table_attribute_parser(input: &[u8]) -> Result<(&[u8], LineNumberTable), Err<&[u8]>> { +pub fn line_number_table_attribute_parser( + input: &[u8], +) -> Result<(&[u8], LineNumberTable), Err<&[u8]>> { let (input, line_number_table_length) = be_u16(input)?; - let (input, line_number_table) = count(line_number_table_entry_parser, line_number_table_length as usize)(input)?; + let (input, line_number_table) = count( + line_number_table_entry_parser, + line_number_table_length as usize, + )(input)?; Ok(( input, - LineNumberTable { line_number_table_length, line_number_table }, + LineNumberTable { + line_number_table_length, + line_number_table, + }, )) } -pub fn line_number_table_entry_parser(input: &[u8]) -> Result<(&[u8], LineNumberTableEntry), Err<&[u8]>> { +pub fn line_number_table_entry_parser( + input: &[u8], +) -> Result<(&[u8], LineNumberTableEntry), Err<&[u8]>> { let (input, start_pc) = be_u16(input)?; let (input, line_number) = be_u16(input)?; Ok(( input, - LineNumberTableEntry { start_pc, line_number }, + LineNumberTableEntry { + start_pc, + line_number, + }, )) } diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index d454ec8..f706f8d 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -1,12 +1,12 @@ use crate::code_attribute::types::Instruction; use nom::{ + Err as BaseErr, IResult, Offset, bytes::complete::{tag, take}, combinator::{complete, fail, map, success}, error::Error, multi::{count, many0}, - number::complete::{be_i16, be_i32, be_i8, be_u16, be_u32, be_u8}, + number::complete::{be_i8, be_i16, be_i32, be_u8, be_u16, be_u32}, sequence::{pair, preceded, tuple}, - Err as BaseErr, IResult, Offset, }; use super::{ diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 3974e6a..dcad37e 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -5,20 +5,20 @@ extern crate classfile_parser; use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, - inner_classes_attribute_parser, line_number_table_attribute_parser, - method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, + DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, + element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, + line_number_table_attribute_parser, method_parameters_attribute_parser, + runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, - source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, - TargetInfo, + source_debug_extension_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, local_variable_type_table_parser, Instruction, - LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + local_variable_type_table_parser, }; use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; From f4381659d5978ad051e862ee6ee192bcb6b494e1 Mon Sep 17 00:00:00 2001 From: Sam V Date: Mon, 8 Dec 2025 09:53:44 -0600 Subject: [PATCH 26/27] remove nightly test features --- tests/code_attribute.rs | 65 ++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index dcad37e..e73dc45 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -1,26 +1,27 @@ -#![feature(assert_matches)] +//only works for nightly builds at the moment +//#![feature(assert_matches)] extern crate classfile_parser; -use std::assert_matches::assert_matches; +//use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, - element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, - line_number_table_attribute_parser, method_parameters_attribute_parser, - runtime_invisible_annotations_attribute_parser, + code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, + inner_classes_attribute_parser, line_number_table_attribute_parser, + method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, - source_debug_extension_parser, + source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, + TargetInfo, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, - local_variable_type_table_parser, + code_parser, instruction_parser, local_variable_type_table_parser, Instruction, + LocalVariableTableAttribute, }; -use classfile_parser::constant_info::ConstantInfo; +use classfile_parser::constant_info::{ConstantInfo, Utf8Constant}; use classfile_parser::method_info::MethodAccessFlags; #[test] @@ -200,13 +201,24 @@ fn enclosing_method() { match &class.const_pool[(inner_class_attrs.class_index - 1) as usize] { classfile_parser::constant_info::ConstantInfo::Class(class_constant) => { + /* nightly only rn + * use regular asserts + decomposition instead let _expected = String::from("InnerClasses"); assert_matches!( &class.const_pool[(class_constant.name_index - 1) as usize], ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { utf8_string: _expected, }) - ); + ); */ + if let ConstantInfo::Utf8(inner_str) = + &class.const_pool[(class_constant.name_index - 1) as usize] + { + assert_eq!( + inner_str.utf8_string, + binrw::NullWideString::from("InnerClasses") + ); + } + dbg!(&class.const_pool[(class_constant.name_index - 1) as usize]); } _ => panic!("Expected Class constant"), @@ -216,6 +228,7 @@ fn enclosing_method() { classfile_parser::constant_info::ConstantInfo::NameAndType( name_and_type_constant, ) => { + /* let mut _expected = String::from("sayHello"); assert_matches!( &class.const_pool[(name_and_type_constant.name_index - 1) as usize], @@ -223,8 +236,18 @@ fn enclosing_method() { utf8_string: _expected, }) ); + */ + if let ConstantInfo::Utf8(inner_str) = + &class.const_pool[(name_and_type_constant.name_index - 1) as usize] + { + assert_eq!( + inner_str.utf8_string, + binrw::NullWideString::from("sayHello") + ); + } dbg!(&class.const_pool[(name_and_type_constant.name_index - 1) as usize]); + /* _expected = String::from("()V"); assert_matches!( &class.const_pool @@ -233,6 +256,12 @@ fn enclosing_method() { utf8_string: _expected, }) ); + */ + if let ConstantInfo::Utf8(inner_str) = &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize] + { + assert_eq!(inner_str.utf8_string, binrw::NullWideString::from("()V")); + } dbg!( &class.const_pool [(name_and_type_constant.descriptor_index - 1) as usize] @@ -564,7 +593,7 @@ fn runtime_visible_type_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.type_annotations.len(), 1); assert_eq!(inner.1.type_annotations[0].target_type, 19); - assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + //assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); assert_eq!(inner.1.type_annotations[0].type_index, 36); @@ -574,6 +603,7 @@ fn runtime_visible_type_annotations() { inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37 ); + /* assert_matches!( inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { @@ -581,6 +611,7 @@ fn runtime_visible_type_annotations() { value: 38 } ); + */ } #[test] @@ -603,7 +634,7 @@ fn runtime_invisible_type_annotations() { assert_eq!(inner.1.num_annotations, 1); assert_eq!(inner.1.type_annotations.len(), 1); assert_eq!(inner.1.type_annotations[0].target_type, 19); - assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + //assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); assert_eq!(inner.1.type_annotations[0].type_index, 41); @@ -613,6 +644,7 @@ fn runtime_invisible_type_annotations() { inner.1.type_annotations[0].element_value_pairs[0].element_name_index, 37 ); + /* assert_matches!( inner.1.type_annotations[0].element_value_pairs[0].value, ElementValue::ConstValueIndex { @@ -620,6 +652,7 @@ fn runtime_invisible_type_annotations() { value: 42 } ); + */ } #[test] @@ -638,6 +671,7 @@ fn default_annotation_value() { let default_annotation = element_value_parser(&f.info); let inner: DefaultAnnotation = default_annotation.unwrap().1 as DefaultAnnotation; + /* assert_matches!( inner, ElementValue::ConstValueIndex { @@ -645,6 +679,11 @@ fn default_annotation_value() { value: 10 } ); + */ + if let ElementValue::ConstValueIndex { tag, value } = inner { + assert_eq!(tag, 's'); + assert_eq!(value, 10); + } } // SourceDebugExtension attributes appear to be custom/non-standard. While it would From 203045e1cd1e6a6c0949847f9a2ce19b0577e00f Mon Sep 17 00:00:00 2001 From: Sam V Date: Mon, 8 Dec 2025 10:37:01 -0600 Subject: [PATCH 27/27] forgot to fmt --- tests/code_attribute.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index e73dc45..c492465 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -6,20 +6,20 @@ extern crate classfile_parser; //use std::assert_matches::assert_matches; use classfile_parser::attribute_info::{ - code_attribute_parser, element_value_parser, enclosing_method_attribute_parser, - inner_classes_attribute_parser, line_number_table_attribute_parser, - method_parameters_attribute_parser, runtime_invisible_annotations_attribute_parser, + DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, + element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, + line_number_table_attribute_parser, method_parameters_attribute_parser, + runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, - source_debug_extension_parser, DefaultAnnotation, ElementValue, InnerClassAccessFlags, - TargetInfo, + source_debug_extension_parser, }; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, local_variable_type_table_parser, Instruction, - LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + local_variable_type_table_parser, }; use classfile_parser::constant_info::{ConstantInfo, Utf8Constant}; use classfile_parser::method_info::MethodAccessFlags;