From dd9311a2b93f8d95b2326b89e4b38b47c4cf74e7 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Tue, 23 Jul 2024 15:58:04 -0600 Subject: [PATCH 1/3] feat!: use instruction enum instead of bytes for code --- Cargo.toml | 2 +- examples/hello_world/src/main.rs | 17 +- .../src/attributes/attribute.rs | 26 +- .../src/attributes/instruction_utils.rs | 474 ++++++++++++++++++ ristretto_classfile/src/attributes/mod.rs | 1 + 5 files changed, 491 insertions(+), 29 deletions(-) create mode 100644 ristretto_classfile/src/attributes/instruction_utils.rs diff --git a/Cargo.toml b/Cargo.toml index f5d73701..089593dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ dependent-version = "upgrade" tag-name = "v{{version}}" [profile.release] -#codegen-units = 1 +codegen-units = 1 lto = true opt-level = "z" panic = "abort" diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index 041e82ca..b8605eeb 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -6,7 +6,6 @@ use ristretto_classfile::{ ClassAccessFlags, ClassFile, Constant, ConstantPool, Error, MethodAccessFlags, Result, Version, }; use std::fs; -use std::io::Cursor; /// Creates a simple "Hello, World!" class file equivalent to the following Java code: /// @@ -60,11 +59,11 @@ fn main() -> Result<()> { name_index: code_index, max_stack: 1, max_locals: 1, - code: instructions_as_bytes(&vec![ + code: vec![ Instruction::Aload_0, Instruction::Invokespecial(object_init), Instruction::Return, - ])?, + ], exceptions: Vec::new(), attributes: Vec::new(), }); @@ -80,12 +79,12 @@ fn main() -> Result<()> { name_index: code_index, max_stack: 2, max_locals: 1, - code: instructions_as_bytes(&vec![ + code: vec![ Instruction::Getstatic(println_field), Instruction::Ldc(u8::try_from(hello_world_string)?), Instruction::Invokevirtual(println_method), Instruction::Return, - ])?, + ], exceptions: Vec::new(), attributes: Vec::new(), }); @@ -121,14 +120,6 @@ fn main() -> Result<()> { Ok(()) } -fn instructions_as_bytes(instructions: &Vec) -> Result> { - let mut bytes = Cursor::new(Vec::new()); - for instruction in instructions { - instruction.to_bytes(&mut bytes)?; - } - Ok(bytes.into_inner()) -} - #[cfg(test)] mod test { use super::*; diff --git a/ristretto_classfile/src/attributes/attribute.rs b/ristretto_classfile/src/attributes/attribute.rs index 43149c85..d385135f 100644 --- a/ristretto_classfile/src/attributes/attribute.rs +++ b/ristretto_classfile/src/attributes/attribute.rs @@ -1,5 +1,6 @@ use crate::attributes::bootstrap_method::BootstrapMethod; use crate::attributes::inner_class::InnerClass; +use crate::attributes::instruction_utils; use crate::attributes::line_number::LineNumber; use crate::attributes::parameter_annotation::ParameterAnnotation; use crate::attributes::{ @@ -42,7 +43,7 @@ pub enum Attribute { name_index: u16, max_stack: u16, max_locals: u16, - code: Vec, + code: Vec, exceptions: Vec, attributes: Vec, }, @@ -286,14 +287,7 @@ impl Attribute { let code_length = bytes.read_u32::()?; let mut code = vec![0; code_length as usize]; bytes.read_exact(&mut code)?; - - let code_length = u64::try_from(code.len())?; - let mut code_cursor = Cursor::new(code.clone()); - let mut instructions = Vec::new(); - while code_cursor.position() < code_length { - let instruction = Instruction::from_bytes(&mut code_cursor)?; - instructions.push(instruction); - } + let instructions = instruction_utils::from_bytes(&mut Cursor::new(code))?; let exception_length = bytes.read_u16::()?; let mut exceptions = Vec::with_capacity(exception_length as usize); @@ -311,7 +305,7 @@ impl Attribute { name_index, max_stack, max_locals, - code, + code: instructions, exceptions, attributes, } @@ -678,9 +672,10 @@ impl Attribute { bytes.write_u16::(*max_stack)?; bytes.write_u16::(*max_locals)?; - let code_length = u32::try_from(code.len())?; + let code_bytes = instruction_utils::to_bytes(code)?; + let code_length = u32::try_from(code_bytes.len())?; bytes.write_u32::(code_length)?; - bytes.extend_from_slice(code.as_slice()); + bytes.extend_from_slice(code_bytes.as_slice()); let exceptions_length = u16::try_from(exceptions.len())?; bytes.write_u16::(exceptions_length)?; @@ -1022,8 +1017,9 @@ impl fmt::Display for Attribute { writeln!(f, "Code:")?; writeln!(f, " max_stack = {max_stack}, max_locals = {max_locals}")?; - let code_length = u64::try_from(code.len()).map_err(|_| fmt::Error)?; - let mut cursor = Cursor::new(code.clone()); + let code_bytes = instruction_utils::to_bytes(code).map_err(|_| fmt::Error)?; + let code_length = u64::try_from(code_bytes.len()).map_err(|_| fmt::Error)?; + let mut cursor = Cursor::new(code_bytes.clone()); while cursor.position() < code_length { let index = cursor.position(); let instruction = @@ -1147,7 +1143,7 @@ mod test { name_index: 1, max_stack: 2, max_locals: 3, - code: vec![4], + code: vec![Instruction::Iconst_1], exceptions: vec![exception], attributes: vec![constant.clone()], }; diff --git a/ristretto_classfile/src/attributes/instruction_utils.rs b/ristretto_classfile/src/attributes/instruction_utils.rs new file mode 100644 index 00000000..e5c96f7a --- /dev/null +++ b/ristretto_classfile/src/attributes/instruction_utils.rs @@ -0,0 +1,474 @@ +use crate::attributes::Instruction; +use crate::Result; +use std::collections::HashMap; +use std::io::Cursor; + +/// Converts a vector of instructions to a vector of bytes. Using the instruction enum is a more +/// idiomatic way to represent the instructions, but the JVM uses a byte representation. This +/// function converts the instruction enums to a byte representation and adjusts offsets where +/// necessary. +#[allow(clippy::too_many_lines)] +pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { + let mut bytes = Cursor::new(Vec::new()); + let mut instruction_to_byte_map = HashMap::new(); + for (index, instruction) in instructions.iter().enumerate() { + let byte_position = u16::try_from(bytes.position())?; + let instruction_position = u16::try_from(index)?; + instruction_to_byte_map.insert(instruction_position, byte_position); + instruction.to_bytes(&mut bytes)?; + } + + let mut bytes = Cursor::new(Vec::new()); + let mut instructions = instructions.to_owned(); + for (index, instruction) in instructions.iter_mut().enumerate() { + match instruction { + Instruction::Ifeq(ref mut offset) + | Instruction::Ifne(ref mut offset) + | Instruction::Iflt(ref mut offset) + | Instruction::Ifge(ref mut offset) + | Instruction::Ifgt(ref mut offset) + | Instruction::Ifle(ref mut offset) + | Instruction::If_icmpeq(ref mut offset) + | Instruction::If_icmpne(ref mut offset) + | Instruction::If_icmplt(ref mut offset) + | Instruction::If_icmpge(ref mut offset) + | Instruction::If_icmpgt(ref mut offset) + | Instruction::If_icmple(ref mut offset) + | Instruction::If_acmpeq(ref mut offset) + | Instruction::If_acmpne(ref mut offset) + | Instruction::Goto(ref mut offset) + | Instruction::Jsr(ref mut offset) + | Instruction::Ifnull(ref mut offset) + | Instruction::Ifnonnull(ref mut offset) + | Instruction::Goto_w(ref mut offset) + | Instruction::Jsr_w(ref mut offset) => { + *offset = *instruction_to_byte_map + .get(offset) + .expect("instruction byte"); + } + Instruction::Tableswitch { + ref mut default, + ref mut offsets, + .. + } => { + let position = u32::try_from(index)?; + let default_offset = position + u32::try_from(*default)?; + let byte_default = *instruction_to_byte_map + .get(&u16::try_from(default_offset)?) + .expect("instruction byte") + - u16::try_from(index)?; + *default = i32::from(byte_default); + + for offset in offsets.iter_mut() { + let instruction_offset = position + u32::try_from(*offset)?; + let byte_offset = instruction_to_byte_map + .get(&u16::try_from(instruction_offset)?) + .expect("instruction byte") + - u16::try_from(index)?; + *offset = i32::from(byte_offset); + } + } + Instruction::Lookupswitch { + ref mut default, + ref mut pairs, + } => { + let position = u32::try_from(index)?; + let default_offset = position + u32::try_from(*default)?; + let byte_default = instruction_to_byte_map + .get(&u16::try_from(default_offset)?) + .expect("instruction byte") + - u16::try_from(index)?; + *default = i32::from(byte_default); + + for (_match, offset) in pairs.iter_mut() { + let instruction_offset = position + u32::try_from(*offset)?; + let byte_offset = instruction_to_byte_map + .get(&u16::try_from(instruction_offset)?) + .expect("instruction byte") + - u16::try_from(index)?; + *offset = i32::from(byte_offset); + } + } + _ => {} + } + + instruction.to_bytes(&mut bytes)?; + } + Ok(bytes.into_inner()) +} + +/// Converts a vector of bytes to a vector of instructions. Using the instruction enum is a more +/// idiomatic way to represent the instructions, but the JVM uses a byte representation. This +/// function converts bytes to instruction enums and adjusts offsets where necessary. +#[allow(clippy::too_many_lines)] +pub(crate) fn from_bytes(bytes: &mut Cursor>) -> Result> { + let mut instructions = Vec::new(); + let mut byte_to_instruction_map = HashMap::new(); + let mut instruction_to_byte_map = HashMap::new(); + while bytes.position() < bytes.get_ref().len() as u64 { + let byte_position = u16::try_from(bytes.position())?; + let instruction_position = u16::try_from(instructions.len())?; + byte_to_instruction_map.insert(byte_position, instruction_position); + instruction_to_byte_map.insert(instruction_position, byte_position); + let instruction = Instruction::from_bytes(bytes)?; + instructions.push(instruction); + } + + for (index, instruction) in instructions.iter_mut().enumerate() { + match instruction { + Instruction::Ifeq(ref mut offset) + | Instruction::Ifne(ref mut offset) + | Instruction::Iflt(ref mut offset) + | Instruction::Ifge(ref mut offset) + | Instruction::Ifgt(ref mut offset) + | Instruction::Ifle(ref mut offset) + | Instruction::If_icmpeq(ref mut offset) + | Instruction::If_icmpne(ref mut offset) + | Instruction::If_icmplt(ref mut offset) + | Instruction::If_icmpge(ref mut offset) + | Instruction::If_icmpgt(ref mut offset) + | Instruction::If_icmple(ref mut offset) + | Instruction::If_acmpeq(ref mut offset) + | Instruction::If_acmpne(ref mut offset) + | Instruction::Goto(ref mut offset) + | Instruction::Jsr(ref mut offset) + | Instruction::Ifnull(ref mut offset) + | Instruction::Ifnonnull(ref mut offset) + | Instruction::Goto_w(ref mut offset) + | Instruction::Jsr_w(ref mut offset) => { + *offset = *byte_to_instruction_map + .get(offset) + .expect("byte instruction"); + } + Instruction::Tableswitch { + ref mut default, + ref mut offsets, + .. + } => { + let position = instruction_to_byte_map + .get(&u16::try_from(index)?) + .expect("instruction byte"); + let position = u32::from(*position); + let default_offset = position + u32::try_from(*default)?; + let instruction_default = byte_to_instruction_map + .get(&u16::try_from(default_offset)?) + .expect("byte instruction") + - u16::try_from(index)?; + *default = i32::from(instruction_default); + + for offset in offsets.iter_mut() { + let byte_offset = position + u32::try_from(*offset)?; + let instruction_offset = byte_to_instruction_map + .get(&u16::try_from(byte_offset)?) + .expect("byte instruction") + - u16::try_from(index)?; + *offset = i32::from(instruction_offset); + } + } + Instruction::Lookupswitch { + ref mut default, + ref mut pairs, + } => { + let position = instruction_to_byte_map + .get(&u16::try_from(index)?) + .expect("instruction byte"); + let position = u32::from(*position); + let default_offset = position + u32::try_from(*default)?; + let instruction_default = byte_to_instruction_map + .get(&u16::try_from(default_offset)?) + .expect("byte instruction") + - u16::try_from(index)?; + *default = i32::from(instruction_default); + + for (_match, offset) in pairs.iter_mut() { + let byte_offset = position + u32::try_from(*offset)?; + let instruction_offset = byte_to_instruction_map + .get(&u16::try_from(byte_offset)?) + .expect("byte instruction") + - u16::try_from(index)?; + *offset = i32::from(instruction_offset); + } + } + _ => {} + } + } + Ok(instructions) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_bytes() -> Result<()> { + let instructions = vec![ + Instruction::Iconst_0, + Instruction::Istore_0, + Instruction::Iload_0, + Instruction::Iconst_1, + Instruction::Iadd, + Instruction::Ireturn, + ]; + let bytes = to_bytes(&instructions)?; + let mut cursor = Cursor::new(bytes); + let result = from_bytes(&mut cursor)?; + + assert_eq!(instructions, result); + + Ok(()) + } + + #[test] + fn test_from_bytes() -> Result<()> { + let bytes = vec![ + 3, // Iconst_0 + 59, // Istore_0 + 26, // Iload_0 + 4, // Iconst_1 + 96, // Iadd + 172, // Ireturn + ]; + let mut cursor = Cursor::new(bytes); + let result = from_bytes(&mut cursor)?; + let instructions = vec![ + Instruction::Iconst_0, + Instruction::Istore_0, + Instruction::Iload_0, + Instruction::Iconst_1, + Instruction::Iadd, + Instruction::Ireturn, + ]; + + assert_eq!(instructions, result); + + Ok(()) + } + + fn test_instruction(instruction: Instruction) -> Result<()> { + let expected_bytes = [Instruction::Nop.code(), instruction.code(), 255, 255]; + let instructions = [Instruction::Nop, instruction]; + + let bytes = to_bytes(instructions.as_slice())?; + assert_eq!(expected_bytes, bytes.as_slice()); + + let instructions_from_bytes = from_bytes(&mut Cursor::new(bytes))?; + assert_eq!(instructions, instructions_from_bytes.as_slice()); + Ok(()) + } + + #[test] + fn test_ifeq() -> Result<()> { + test_instruction(Instruction::Ifeq(0)) + } + + #[test] + fn test_ifne() -> Result<()> { + test_instruction(Instruction::Ifne(0)) + } + + #[test] + fn test_iflt() -> Result<()> { + test_instruction(Instruction::Iflt(0)) + } + + #[test] + fn test_ifge() -> Result<()> { + test_instruction(Instruction::Ifge(0)) + } + + #[test] + fn test_ifgt() -> Result<()> { + test_instruction(Instruction::Ifgt(0)) + } + + #[test] + fn test_ifle() -> Result<()> { + test_instruction(Instruction::Ifle(0)) + } + + #[test] + fn test_if_icmpeq() -> Result<()> { + test_instruction(Instruction::If_icmpeq(0)) + } + + #[test] + fn test_if_icmpne() -> Result<()> { + test_instruction(Instruction::If_icmpne(0)) + } + + #[test] + fn test_if_icmplt() -> Result<()> { + test_instruction(Instruction::If_icmplt(0)) + } + + #[test] + fn test_if_icmpge() -> Result<()> { + test_instruction(Instruction::If_icmpge(0)) + } + + #[test] + fn test_if_icmpgt() -> Result<()> { + test_instruction(Instruction::If_icmpgt(0)) + } + + #[test] + fn test_if_icmple() -> Result<()> { + test_instruction(Instruction::If_icmple(0)) + } + + #[test] + fn test_if_acmpeq() -> Result<()> { + test_instruction(Instruction::If_acmpeq(0)) + } + + #[test] + fn test_if_acmpne() -> Result<()> { + test_instruction(Instruction::If_acmpne(0)) + } + + #[test] + fn test_goto() -> Result<()> { + test_instruction(Instruction::Goto(0)) + } + + #[test] + fn test_jsr() -> Result<()> { + test_instruction(Instruction::Jsr(0)) + } + + #[test] + fn test_ifnull() -> Result<()> { + test_instruction(Instruction::Ifnull(0)) + } + + #[test] + fn test_ifnonnull() -> Result<()> { + test_instruction(Instruction::Ifnonnull(0)) + } + + #[test] + fn test_goto_w() -> Result<()> { + let instruction = Instruction::Goto_w(1); + let expected_bytes = [instruction.code(), 0, 0, 0, 5, Instruction::Nop.code()]; + let instructions = [instruction, Instruction::Nop]; + + let bytes = to_bytes(instructions.as_slice())?; + assert_eq!(expected_bytes, bytes.as_slice()); + + let instructions_from_bytes = from_bytes(&mut Cursor::new(bytes))?; + assert_eq!(instructions, instructions_from_bytes.as_slice()); + Ok(()) + } + + #[test] + fn test_jsr_w() -> Result<()> { + let instruction = Instruction::Jsr_w(1); + let expected_bytes = [instruction.code(), 0, 0, 0, 5, Instruction::Nop.code()]; + let instructions = [instruction, Instruction::Nop]; + + let bytes = to_bytes(instructions.as_slice())?; + assert_eq!(expected_bytes, bytes.as_slice()); + + let instructions_from_bytes = from_bytes(&mut Cursor::new(bytes))?; + assert_eq!(instructions, instructions_from_bytes.as_slice()); + Ok(()) + } + + #[test] + fn test_tableswitch() -> Result<()> { + let instruction = Instruction::Tableswitch { + default: 3, + low: 3, + high: 4, + offsets: vec![1, 2], + }; + let expected_bytes = [ + instruction.code(), + 0, + 0, + 0, + 0, + 0, + 0, + 26, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 24, + 0, + 0, + 0, + 25, + Instruction::Nop.code(), + Instruction::Nop.code(), + Instruction::Nop.code(), + ]; + let instructions = [ + instruction, + Instruction::Nop, + Instruction::Nop, + Instruction::Nop, + ]; + + let bytes = to_bytes(instructions.as_slice())?; + assert_eq!(expected_bytes, bytes.as_slice()); + + let instructions_from_bytes = from_bytes(&mut Cursor::new(bytes))?; + assert_eq!(instructions, instructions_from_bytes.as_slice()); + Ok(()) + } + + #[test] + fn test_lookupswitch() -> Result<()> { + let instruction = Instruction::Lookupswitch { + default: 3, + pairs: vec![(1, 2)], + }; + let expected_bytes = [ + instruction.code(), + 0, + 0, + 0, + 0, + 0, + 0, + 22, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 21, + Instruction::Nop.code(), + Instruction::Nop.code(), + Instruction::Nop.code(), + ]; + let instructions = [ + instruction, + Instruction::Nop, + Instruction::Nop, + Instruction::Nop, + ]; + + let bytes = to_bytes(instructions.as_slice())?; + assert_eq!(expected_bytes, bytes.as_slice()); + + let instructions_from_bytes = from_bytes(&mut Cursor::new(bytes))?; + assert_eq!(instructions, instructions_from_bytes.as_slice()); + Ok(()) + } +} diff --git a/ristretto_classfile/src/attributes/mod.rs b/ristretto_classfile/src/attributes/mod.rs index 58dd8a50..502cc633 100644 --- a/ristretto_classfile/src/attributes/mod.rs +++ b/ristretto_classfile/src/attributes/mod.rs @@ -9,6 +9,7 @@ mod exports; mod exports_flags; mod inner_class; mod instruction; +mod instruction_utils; mod line_number; mod local_variable_table; mod local_variable_target; From 3092349e73e9e30ab72a5dc016870cae3db201dd Mon Sep 17 00:00:00 2001 From: brianheineman Date: Tue, 23 Jul 2024 19:56:12 -0600 Subject: [PATCH 2/3] fix: correct instruction to byte conversion --- .../src/attributes/instruction.rs | 4 +- .../src/attributes/instruction_utils.rs | 54 ++++++++++--------- ristretto_classfile/tests/class_tests.rs | 6 +++ 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ristretto_classfile/src/attributes/instruction.rs b/ristretto_classfile/src/attributes/instruction.rs index 43882ec0..1fce1a26 100644 --- a/ristretto_classfile/src/attributes/instruction.rs +++ b/ristretto_classfile/src/attributes/instruction.rs @@ -1137,10 +1137,8 @@ impl fmt::Display for Instruction { write!(f, " }}") } Instruction::Lookupswitch { pairs, default } => { - let first_pair = pairs.first().unwrap_or(&(0, 0)); - let (low, _) = first_pair; let width = 12; - writeln!(f, "lookupswitch {{ // {low}")?; + writeln!(f, "lookupswitch {{ // {}", pairs.len())?; for pair in pairs { let (value, offset) = pair; writeln!(f, " {value:>width$}: {offset}")?; diff --git a/ristretto_classfile/src/attributes/instruction_utils.rs b/ristretto_classfile/src/attributes/instruction_utils.rs index e5c96f7a..7d7d1eab 100644 --- a/ristretto_classfile/src/attributes/instruction_utils.rs +++ b/ristretto_classfile/src/attributes/instruction_utils.rs @@ -52,20 +52,23 @@ pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { .. } => { let position = u32::try_from(index)?; + let position_byte = i32::from( + *instruction_to_byte_map + .get(&u16::try_from(position)?) + .expect("instruction byte"), + ); let default_offset = position + u32::try_from(*default)?; - let byte_default = *instruction_to_byte_map + let default_byte = *instruction_to_byte_map .get(&u16::try_from(default_offset)?) - .expect("instruction byte") - - u16::try_from(index)?; - *default = i32::from(byte_default); + .expect("instruction byte"); + *default = i32::from(default_byte) - position_byte; for offset in offsets.iter_mut() { let instruction_offset = position + u32::try_from(*offset)?; - let byte_offset = instruction_to_byte_map + let offset_byte = instruction_to_byte_map .get(&u16::try_from(instruction_offset)?) - .expect("instruction byte") - - u16::try_from(index)?; - *offset = i32::from(byte_offset); + .expect("instruction byte"); + *offset = i32::from(*offset_byte) - position_byte; } } Instruction::Lookupswitch { @@ -73,20 +76,23 @@ pub(crate) fn to_bytes(instructions: &[Instruction]) -> Result> { ref mut pairs, } => { let position = u32::try_from(index)?; + let position_byte = i32::from( + *instruction_to_byte_map + .get(&u16::try_from(position)?) + .expect("instruction byte"), + ); let default_offset = position + u32::try_from(*default)?; - let byte_default = instruction_to_byte_map + let default_byte = *instruction_to_byte_map .get(&u16::try_from(default_offset)?) - .expect("instruction byte") - - u16::try_from(index)?; - *default = i32::from(byte_default); + .expect("instruction byte"); + *default = i32::from(default_byte) - position_byte; for (_match, offset) in pairs.iter_mut() { let instruction_offset = position + u32::try_from(*offset)?; - let byte_offset = instruction_to_byte_map + let offset_byte = instruction_to_byte_map .get(&u16::try_from(instruction_offset)?) - .expect("instruction byte") - - u16::try_from(index)?; - *offset = i32::from(byte_offset); + .expect("instruction byte"); + *offset = i32::from(*offset_byte) - position_byte; } } _ => {} @@ -220,16 +226,6 @@ mod tests { #[test] fn test_from_bytes() -> Result<()> { - let bytes = vec![ - 3, // Iconst_0 - 59, // Istore_0 - 26, // Iload_0 - 4, // Iconst_1 - 96, // Iadd - 172, // Ireturn - ]; - let mut cursor = Cursor::new(bytes); - let result = from_bytes(&mut cursor)?; let instructions = vec![ Instruction::Iconst_0, Instruction::Istore_0, @@ -238,6 +234,12 @@ mod tests { Instruction::Iadd, Instruction::Ireturn, ]; + let bytes = instructions + .iter() + .map(Instruction::code) + .collect::>(); + let mut cursor = Cursor::new(bytes); + let result = from_bytes(&mut cursor)?; assert_eq!(instructions, result); diff --git a/ristretto_classfile/tests/class_tests.rs b/ristretto_classfile/tests/class_tests.rs index 6225459e..233e351c 100644 --- a/ristretto_classfile/tests/class_tests.rs +++ b/ristretto_classfile/tests/class_tests.rs @@ -6,6 +6,7 @@ pub fn test_class(class_bytes: &[u8]) -> Result<()> { let class_file = ClassFile::from_bytes(&mut original_bytes)?; let mut serde_bytes = Vec::new(); class_file.to_bytes(&mut serde_bytes)?; + let _ = class_file.to_string(); assert_eq!(class_bytes, serde_bytes); Ok(()) } @@ -20,6 +21,11 @@ pub fn test_constants() -> Result<()> { test_class(include_bytes!("../classes/Constants.class")) } +#[test] +pub fn test_expressions() -> Result<()> { + test_class(include_bytes!("../classes/Expressions.class")) +} + #[test] pub fn test_minimum() -> Result<()> { test_class(include_bytes!("../classes/Minimum.class")) From 74c38c1b445c2c73b25dfbd90b66ab72ed8cda78 Mon Sep 17 00:00:00 2001 From: brianheineman Date: Tue, 23 Jul 2024 20:49:20 -0600 Subject: [PATCH 3/3] fix: correct tableswitch and lookupswitch string format --- .../src/attributes/attribute.rs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ristretto_classfile/src/attributes/attribute.rs b/ristretto_classfile/src/attributes/attribute.rs index a5845fef..dde90c9e 100644 --- a/ristretto_classfile/src/attributes/attribute.rs +++ b/ristretto_classfile/src/attributes/attribute.rs @@ -1022,8 +1022,32 @@ impl fmt::Display for Attribute { let mut cursor = Cursor::new(code_bytes.clone()); while cursor.position() < code_length { let index = cursor.position(); - let instruction = + let mut instruction = Instruction::from_bytes(&mut cursor).map_err(|_| fmt::Error)?; + match instruction { + Instruction::Tableswitch { + ref mut default, + ref mut offsets, + .. + } => { + let position = i32::try_from(index).map_err(|_| fmt::Error)?; + *default += position; + for offset in offsets { + *offset += position; + } + } + Instruction::Lookupswitch { + ref mut default, + ref mut pairs, + } => { + let position = i32::try_from(index).map_err(|_| fmt::Error)?; + *default += position; + for (_match, offset) in pairs { + *offset += position; + } + } + _ => {} + } let value = instruction.to_string(); let (name, value) = value.split_once(' ').unwrap_or((value.as_str(), "")); let value = format!("{name:<13} {value}");