From a8c5fb743d557fa0dbed6683f40d0d68b56270a4 Mon Sep 17 00:00:00 2001 From: emilycares Date: Sat, 31 Jan 2026 15:17:04 +0100 Subject: [PATCH 1/3] Implement --- README.md | 1 + src/attribute_info/mod.rs | 1 + src/attribute_info/parser.rs | 104 +++++++++++++++++++++++++++++++++++ src/attribute_info/types.rs | 36 ++++++++++++ src/constant_info/parser.rs | 12 ++++ src/constant_info/types.rs | 14 +++++ 6 files changed, 168 insertions(+) diff --git a/README.md b/README.md index 088beaf..ce791b6 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ fn main() { - [x] Methods - [x] Attributes - [x] Basic attribute info block parsing + - [x] Module - [x] Known typed attributes parsing - [x] Critical for JVM - [x] ConstantValue diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 49de015..4862564 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -13,6 +13,7 @@ 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::module_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_invisible_type_annotations_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 7226f3c..2c3bf70 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -697,3 +697,107 @@ pub fn sourcefile_attribute_parser( let (input, sourcefile_index) = be_u16(input)?; Ok((input, SourceFileAttribute { sourcefile_index })) } + +pub fn module_attribute_parser(input: &[u8]) -> Result<(&[u8], ModuleAttribute), Err<&[u8]>> { + let (input, module_name_index) = be_u16(input)?; + let (input, module_flags) = be_u16(input)?; + let (input, module_version_index) = be_u16(input)?; + + let (input, requires_count) = be_u16(input)?; + let (input, requires) = + count(module_requires_attribute_parser, requires_count as usize)(input)?; + + let (input, exports_count) = be_u16(input)?; + let (input, exports) = count(module_exports_attribute_parser, exports_count as usize)(input)?; + + let (input, opens_count) = be_u16(input)?; + let (input, opens) = count(module_opens_attribute_parser, opens_count as usize)(input)?; + + let (input, uses_count) = be_u16(input)?; + let (input, uses) = count(be_u16, uses_count as usize)(input)?; + + let (input, provides_count) = be_u16(input)?; + let (input, provides) = count(module_provides_attribute_parser, provides_count as usize)(input)?; + + Ok(( + input, + ModuleAttribute { + module_name_index, + module_flags, + module_version_index, + requires, + exports, + opens, + uses, + provides, + }, + )) +} +pub fn module_requires_attribute_parser( + input: &[u8], +) -> Result<(&[u8], ModuleRequiresAttribute), Err<&[u8]>> { + let (input, requires_index) = be_u16(input)?; + let (input, requires_flags) = be_u16(input)?; + let (input, requires_version_index) = be_u16(input)?; + + Ok(( + input, + ModuleRequiresAttribute { + requires_index, + requires_flags, + requires_version_index, + }, + )) +} + +pub fn module_exports_attribute_parser( + input: &[u8], +) -> Result<(&[u8], ModuleExportsAttribute), Err<&[u8]>> { + let (input, exports_index) = be_u16(input)?; + let (input, exports_flags) = be_u16(input)?; + let (input, exports_to_count) = be_u16(input)?; + + let (input, exports_to_index) = count(be_u16, exports_to_count as usize)(input)?; + + Ok(( + input, + ModuleExportsAttribute { + exports_index, + exports_flags, + exports_to_index, + }, + )) +} + +pub fn module_opens_attribute_parser( + input: &[u8], +) -> Result<(&[u8], ModuleOpensAttribute), Err<&[u8]>> { + let (input, opens_index) = be_u16(input)?; + let (input, opens_flags) = be_u16(input)?; + let (input, opens_to_count) = be_u16(input)?; + let (input, opens_to_index) = count(be_u16, opens_to_count as usize)(input)?; + + Ok(( + input, + ModuleOpensAttribute { + opens_index, + opens_flags, + opens_to_index, + }, + )) +} +pub fn module_provides_attribute_parser( + input: &[u8], +) -> Result<(&[u8], ModuleProvidesAttribute), Err<&[u8]>> { + let (input, provides_index) = be_u16(input)?; + let (input, provides_with_count) = be_u16(input)?; + let (input, provides_with_index) = count(be_u16, provides_with_count as usize)(input)?; + + Ok(( + input, + ModuleProvidesAttribute { + provides_index, + provides_with_index, + }, + )) +} diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 6febc66..df5cf02 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -343,3 +343,39 @@ pub struct SourceFileAttribute { /// The constant_pool entry at that index must be a CONSTANT_Utf8_info structure representing a string. pub sourcefile_index: u16, } + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ModuleAttribute { + pub module_name_index: u16, + pub module_flags: u16, + pub module_version_index: u16, + pub requires: Vec, + pub exports: Vec, + pub opens: Vec, + pub uses: Vec, + pub provides: Vec, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ModuleRequiresAttribute { + pub requires_index: u16, + pub requires_flags: u16, + pub requires_version_index: u16, +} +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ModuleExportsAttribute { + pub exports_index: u16, + pub exports_flags: u16, + pub exports_to_index: Vec, +} +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ModuleOpensAttribute { + pub opens_index: u16, + pub opens_flags: u16, + pub opens_to_index: Vec, +} +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ModuleProvidesAttribute { + pub provides_index: u16, + pub provides_with_index: Vec, +} diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index 7cd196b..957ec38 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -131,6 +131,16 @@ fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult<'_> { )) } +fn const_module(input: &[u8]) -> ConstantInfoResult<'_> { + let (input, name_index) = be_u16(input)?; + Ok((input, ConstantInfo::Module(ModuleConstant { name_index }))) +} + +fn const_package(input: &[u8]) -> ConstantInfoResult<'_> { + let (input, name_index) = be_u16(input)?; + Ok((input, ConstantInfo::Package(PackageConstant { name_index }))) +} + type ConstantInfoResult<'a> = Result<(&'a [u8], ConstantInfo), Err>>; type ConstantInfoVecResult<'a> = Result<(&'a [u8], Vec), Err>>; @@ -150,6 +160,8 @@ fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult<'_> { 15 => const_method_handle(input), 16 => const_method_type(input), 18 => const_invoke_dynamic(input), + 19 => const_module(input), + 20 => const_package(input), _ => Result::Err(Err::Error(error_position!(input, ErrorKind::Alt))), } } diff --git a/src/constant_info/types.rs b/src/constant_info/types.rs index f66612b..081f8a0 100644 --- a/src/constant_info/types.rs +++ b/src/constant_info/types.rs @@ -17,6 +17,8 @@ pub enum ConstantInfo { MethodHandle(MethodHandleConstant), MethodType(MethodTypeConstant), InvokeDynamic(InvokeDynamicConstant), + Module(ModuleConstant), + Package(PackageConstant), Unusable, } @@ -110,3 +112,15 @@ pub struct InvokeDynamicConstant { pub bootstrap_method_attr_index: u16, pub name_and_type_index: u16, } + +#[derive(Clone, Debug)] +#[binrw] +pub struct ModuleConstant { + pub name_index: u16, +} + +#[derive(Clone, Debug)] +#[binrw] +pub struct PackageConstant { + pub name_index: u16, +} From 58c76407dfe3b8382308e42c3cd55b186e68e6e7 Mon Sep 17 00:00:00 2001 From: emilycares Date: Tue, 3 Feb 2026 17:53:39 +0100 Subject: [PATCH 2/3] Add test --- java-assets/compile.sh | 1 + .../compiled-classes/com/some/Thing.class | Bin 0 -> 193 bytes .../compiled-classes/module-info.class | Bin 0 -> 177 bytes java-assets/src/com/some/Thing.java | 3 + java-assets/src/module-info.java | 3 + src/attribute_info/parser.rs | 5 +- tests/code_attribute.rs | 77 ++++++++++++++++-- 7 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 java-assets/compiled-classes/com/some/Thing.class create mode 100644 java-assets/compiled-classes/module-info.class create mode 100644 java-assets/src/com/some/Thing.java create mode 100644 java-assets/src/module-info.java diff --git a/java-assets/compile.sh b/java-assets/compile.sh index cea4c97..56e20a5 100755 --- a/java-assets/compile.sh +++ b/java-assets/compile.sh @@ -27,6 +27,7 @@ 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 -d java-assets/compiled-classes/ java-assets/src/module-info.java java-assets/src/com/some/Thing.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/com/some/Thing.class b/java-assets/compiled-classes/com/some/Thing.class new file mode 100644 index 0000000000000000000000000000000000000000..9bdeec93122504cc3b6ff26e262119b806c5c341 GIT binary patch literal 193 zcmW-aI}XAy5JboMLI|G=P|yJdz5t>@P$4LY=$&8%M~N+kgnLmTQE&hbh1hskdaKzP zX}-Vr2fz-)2oBs3UJM@r!=w-o!AqfYeo70OH4MR8Y1M2P-1*`XqQ@}GOvUe}l03O7 zoilh_bCnF^gVOTYR;jELk(Ts{&Zez1xmQ-$^r~yX3?UVF!)U3R*e5l$8t|7du&1>n NVrK&`aX{BW{0CZEBX|G+ literal 0 HcmV?d00001 diff --git a/java-assets/compiled-classes/module-info.class b/java-assets/compiled-classes/module-info.class new file mode 100644 index 0000000000000000000000000000000000000000..af6662fffa382b4d3a7260e2f07b514402a836db GIT binary patch literal 177 zcmX^0Z`VEs1_oCKUUmj1Mh5QO{FKt1RNc(Hw0uSeuHgLAqU2P!%$!t41_2}~y{yEt zL`DWSU$AOn26jdU&fH2phzel_P9PVgLN6(?7^suY$W+fj&q#!UhmnCJIX_pwI6pVF ofq{V$XfXsZF)#xeObjdxtUy|ZfekDSRNcV9zy(yt4d#Oh0Cxc)$N&HU literal 0 HcmV?d00001 diff --git a/java-assets/src/com/some/Thing.java b/java-assets/src/com/some/Thing.java new file mode 100644 index 0000000..26dee72 --- /dev/null +++ b/java-assets/src/com/some/Thing.java @@ -0,0 +1,3 @@ +package com.some; + +public class Thing {} diff --git a/java-assets/src/module-info.java b/java-assets/src/module-info.java new file mode 100644 index 0000000..bb0aef9 --- /dev/null +++ b/java-assets/src/module-info.java @@ -0,0 +1,3 @@ +module my.module { + exports com.some; +} diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 2c3bf70..142218f 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -717,7 +717,8 @@ pub fn module_attribute_parser(input: &[u8]) -> Result<(&[u8], ModuleAttribute), let (input, uses) = count(be_u16, uses_count as usize)(input)?; let (input, provides_count) = be_u16(input)?; - let (input, provides) = count(module_provides_attribute_parser, provides_count as usize)(input)?; + let (input, provides) = + count(module_provides_attribute_parser, provides_count as usize)(input)?; Ok(( input, @@ -733,6 +734,7 @@ pub fn module_attribute_parser(input: &[u8]) -> Result<(&[u8], ModuleAttribute), }, )) } + pub fn module_requires_attribute_parser( input: &[u8], ) -> Result<(&[u8], ModuleRequiresAttribute), Err<&[u8]>> { @@ -786,6 +788,7 @@ pub fn module_opens_attribute_parser( }, )) } + pub fn module_provides_attribute_parser( input: &[u8], ) -> Result<(&[u8], ModuleProvidesAttribute), Err<&[u8]>> { diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index c492465..ccb059e 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -6,10 +6,11 @@ extern crate classfile_parser; //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, + DefaultAnnotation, ElementValue, InnerClassAccessFlags, ModuleAttribute, + ModuleExportsAttribute, ModuleRequiresAttribute, 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, + module_attribute_parser, runtime_invisible_annotations_attribute_parser, runtime_invisible_parameter_annotations_attribute_parser, runtime_visible_annotations_attribute_parser, runtime_visible_parameter_annotations_attribute_parser, @@ -21,7 +22,7 @@ use classfile_parser::code_attribute::{ Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, local_variable_type_table_parser, }; -use classfile_parser::constant_info::{ConstantInfo, Utf8Constant}; +use classfile_parser::constant_info::ConstantInfo; use classfile_parser::method_info::MethodAccessFlags; #[test] @@ -102,9 +103,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.to_string()) - } + ConstantInfo::Utf8(utf8) => Some(utf8.utf8_string.to_string()), + ConstantInfo::Module(m) => lookup_string(c, m.name_index), + ConstantInfo::Package(p) => lookup_string(c, p.name_index), _ => None, } } @@ -864,3 +865,65 @@ fn deprecated() { assert_eq!(deprecated_field_attribute.len(), 1); } + +#[test] +fn module_info() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/module-info.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + let module = class + .attributes + .iter() + .find(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Module" + ) + }) + .unwrap(); + + let (rest, module) = module_attribute_parser(&module.info).unwrap(); + assert_eq!(rest.len(), 0); + + let exptected = ModuleAttribute { + module_name_index: 6, + module_flags: 0, + module_version_index: 0, + requires: vec![ModuleRequiresAttribute { + requires_index: 8, + requires_flags: 32768, + requires_version_index: 10, + }], + exports: vec![ModuleExportsAttribute { + exports_index: 11, + exports_flags: 0, + exports_to_index: vec![], + }], + opens: vec![], + uses: vec![], + provides: vec![], + }; + + assert_eq!(module, exptected); + + assert_eq!( + lookup_string(&class, exptected.module_name_index) + .unwrap() + .as_str(), + "my.module" + ); + assert_eq!( + lookup_string(&class, exptected.requires.first().unwrap().requires_index) + .unwrap() + .as_str(), + "java.base" + ); + assert_eq!( + lookup_string(&class, exptected.exports.first().unwrap().exports_index) + .unwrap() + .as_str(), + "com/some" + ); +} From de4f8196fd13cde56d48dc1a4a05016a554c5c60 Mon Sep 17 00:00:00 2001 From: emilycares Date: Tue, 3 Feb 2026 18:08:36 +0100 Subject: [PATCH 3/3] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce791b6..d9aac7b 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ fn main() { - [x] Methods - [x] Attributes - [x] Basic attribute info block parsing - - [x] Module - [x] Known typed attributes parsing - [x] Critical for JVM - [x] ConstantValue @@ -112,3 +111,4 @@ fn main() { - [x] LocalVariableTable - [x] LocalVariableTypeTable - [x] Deprecated + - [x] Module