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 0000000..ff0af9a Binary files /dev/null and b/java-assets/compiled-classes/LocalVariableTable.class differ 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() + ] + ); +}