From 7edb9bc76ef26cdec6a22c0c5f29424a0eb27e50 Mon Sep 17 00:00:00 2001 From: emilycares Date: Thu, 17 Apr 2025 17:35:43 +0200 Subject: [PATCH] Implement LocalVariableTable --- README.md | 2 +- java-assets/compile.sh | 1 + .../compiled-classes/LocalVariableTable.class | Bin 0 -> 701 bytes java-assets/src/LocalVariableTable.java | 8 +++ src/code_attribute/mod.rs | 1 + src/code_attribute/parser.rs | 43 ++++++++++++- src/code_attribute/types.rs | 15 +++++ tests/code_attribute.rs | 60 +++++++++++++++++- 8 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 java-assets/compiled-classes/LocalVariableTable.class create mode 100644 java-assets/src/LocalVariableTable.java diff --git a/README.md b/README.md index 8cece0f..3f54dad 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,6 @@ fn main() { - [x] SourceFile - [ ] SourceDebugExtension - [ ] LineNumberTable - - [ ] LocalVariableTable + - [x] LocalVariableTable - [ ] LocalVariableTypeTable - [ ] Deprecated diff --git a/java-assets/compile.sh b/java-assets/compile.sh index 9a94a7f..2ce8fb1 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 -g -d java-assets/compiled-classes/ java-assets/src/LocalVariableTable.java javac -d java-assets/compiled-classes/ java-assets/src/HelloWorld.java printf '\xde\xad\xbe\xef' > java-assets/compiled-classes/malformed.class tail -c+5 java-assets/compiled-classes/HelloWorld.class >> java-assets/compiled-classes/malformed.class diff --git a/java-assets/compiled-classes/LocalVariableTable.class b/java-assets/compiled-classes/LocalVariableTable.class new file mode 100644 index 0000000000000000000000000000000000000000..ff0af9ab970c440b8c7482a6657bc4aaadd5fde8 GIT binary patch literal 701 zcmZWnZA-#X6n>6d&eqD*)ZX6%Ey%Bt6+{pTt3DJ(AFp=P#hNm=pr6&3ih_PXKPo!c zX)WB?x!iM}=Q(ft`hNceaEJ{P31|j%3nNGpChx_gsJOywR?h49vJnuHdyeM>2L!ED zzA}&^WO|@5aNNp?=(bKp$AkgR#273C;}#~69-w+sH7}4&=@X1c;fC`3j!-Do%JyHf zKBAgJh5#j(voM7`XYGVs#7-#&jw-7Td*X_Hkbxp0YquN1y%N47>aM(0hM*s{Z}~{u zjwjE;hr09!fuQAd2~+kkt4c6i(wDVvL?%JVMRml-!LX#)lO#}$oBgl!)EU&UJIvjS zwKbSZ%ykj?j@PUns7fx{q2G|lj_RiPKZR;>Ta6bhDDaBalK`nDvz_K!g-;$eMmAqT z&%N*rTNPH&B3mm0U>0+HBh3GjeQ5jwg%21vuNbwr^f#E<$rog=wSA?gL8)b-v(=H} q#5rc?d9g})Ib!#fQ~bj$V39NA`CH;>g3FHbN3m7*6V}+5!1@o6j+Tr7 literal 0 HcmV?d00001 diff --git a/java-assets/src/LocalVariableTable.java b/java-assets/src/LocalVariableTable.java new file mode 100644 index 0000000..f53225d --- /dev/null +++ b/java-assets/src/LocalVariableTable.java @@ -0,0 +1,8 @@ +import java.util.*; +public class LocalVariableTable { + public void hereIsCode() { + HashMap a = new HashMap<>(); + a.put(1, ""); + int number = 0; + } +} diff --git a/src/code_attribute/mod.rs b/src/code_attribute/mod.rs index bfbf1c3..2ab312f 100644 --- a/src/code_attribute/mod.rs +++ b/src/code_attribute/mod.rs @@ -5,3 +5,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; diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index 323c8bb..3cdafcd 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -2,12 +2,16 @@ use crate::code_attribute::types::Instruction; use nom::{ 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}, sequence::{pair, preceded, tuple}, - IResult, Offset, + Err as BaseErr, IResult, Offset, }; +use super::{LocalVariableTableAttribute, LocalVariableTableItem}; +type Err = BaseErr>; + fn offset<'a>(remaining: &'a [u8], input: &[u8]) -> IResult<&'a [u8], usize> { Ok((remaining, input.offset(remaining))) } @@ -290,3 +294,40 @@ pub fn instruction_parser(input: &[u8], address: usize) -> IResult<&[u8], Instru }; Ok((input, instruction)) } + +pub fn local_variable_table_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTableAttribute), Err<&[u8]>> { + let (input, local_variable_table_length) = be_u16(input)?; + let (input, items) = count( + variable_table_item_parser, + local_variable_table_length as usize, + )(input)?; + Ok(( + input, + LocalVariableTableAttribute { + local_variable_table_length, + items, + }, + )) +} + +pub fn variable_table_item_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTableItem), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, length) = be_u16(input)?; + 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, + }, + )) +} diff --git a/src/code_attribute/types.rs b/src/code_attribute/types.rs index 7946538..650ab06 100644 --- a/src/code_attribute/types.rs +++ b/src/code_attribute/types.rs @@ -234,3 +234,18 @@ pub enum Instruction { offsets: Vec, }, } + +#[derive(Clone, Debug)] +pub struct LocalVariableTableAttribute { + pub local_variable_table_length: u16, + pub items: Vec, +} + +#[derive(Clone, Debug)] +pub struct LocalVariableTableItem { + pub start_pc: u16, + pub length: u16, + pub name_index: u16, + pub descriptor_index: u16, + pub index: u16, +} diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 0e72d6c..c92a28e 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -2,7 +2,9 @@ extern crate classfile_parser; use classfile_parser::attribute_info::{code_attribute_parser, method_parameters_attribute_parser}; use classfile_parser::class_parser; -use classfile_parser::code_attribute::{code_parser, instruction_parser, Instruction}; +use classfile_parser::code_attribute::{ + code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, +}; use classfile_parser::method_info::MethodAccessFlags; #[test] @@ -115,3 +117,59 @@ fn method_parameters() { Some("b".to_string()) ); } + +#[test] +fn local_variable_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 code_attribute = method_info + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "Code" => { + classfile_parser::attribute_info::code_attribute_parser(&attribute_info.info) + .ok() + } + _ => None, + } + }) + .map(|i| i.1) + .unwrap(); + + let local_variable_table_attribute: LocalVariableTableAttribute = code_attribute + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "LocalVariableTable" => { + classfile_parser::code_attribute::local_variable_table_parser( + &attribute_info.info, + ) + .ok() + } + _ => None, + } + }) + .map(|a| a.1) + .unwrap(); + + let types: Vec = local_variable_table_attribute + .items + .iter() + .filter_map(|i| lookup_string(&class, i.descriptor_index)) + .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() + ] + ); +}