From eb19e0ad3f01b2ac6b5f0394cc4af76d233e52da Mon Sep 17 00:00:00 2001 From: Igor Rudenko Date: Mon, 2 Sep 2024 11:29:05 +0300 Subject: [PATCH] Add basic support for instance fields --- tests/integration_test.rs | 10 ++-- tests/test_data/InstanceFields.class | Bin 0 -> 839 bytes tests/test_data/InstanceFields.java | 43 +++++++++++++++++ tests/test_data/SubNew.class | Bin 371 -> 0 bytes tests/test_data/SubNew.java | 18 ------- vm/src/execution_engine/engine.rs | 69 ++++++++++++++++++++++++--- vm/src/heap/heap.rs | 27 +++++++++++ vm/src/heap/java_instance.rs | 34 +++++++++++-- vm/src/method_area/method_area.rs | 56 +++++++++++++++++++++- vm/src/util.rs | 19 ++++++++ 10 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 tests/test_data/InstanceFields.class create mode 100644 tests/test_data/InstanceFields.java delete mode 100644 tests/test_data/SubNew.class delete mode 100644 tests/test_data/SubNew.java diff --git a/tests/integration_test.rs b/tests/integration_test.rs index d4a86e47..9cdec8fa 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -15,10 +15,14 @@ fn should_do_subtraction() { } #[test] -fn should_do_subtraction_with_instance() { - let vm = VM::new("tests/test_data/SubNew.class", "tests/test_data/std").unwrap(); +fn should_write_read_instance_fields() { + let vm = VM::new( + "tests/test_data/InstanceFields.class", + "tests/test_data/std", + ) + .unwrap(); let last_frame_value = vm.run().unwrap(); - assert_eq!(-989, last_frame_value.unwrap()) + assert_eq!(11022, last_frame_value.unwrap()) } #[test] diff --git a/tests/test_data/InstanceFields.class b/tests/test_data/InstanceFields.class new file mode 100644 index 0000000000000000000000000000000000000000..c49df36f89a625432baba0a4544b28dd9b891964 GIT binary patch literal 839 zcmZuv+iDY06kR7Xxu53JHZkoa)zn0jLM{5>i?vV%VW76qlz{k>NoI@_CR0de{3>4r zV?n_W=$G^>#C0Zxq#+Np_SyTawQpx`|NZ_8;2l~S#GoaiOT>{7D2%Lc){$rV!=sD- z$Qc9ziDTDygA)O*)$S&d63ChUG_d@E^TBmIdzwKKnu4r^#0H7eurCmAnP$7I;(3We z6t`?!`I{13kv|T-H2so98IJ_g6K5KFK}V^Dscv>8c9(8WZCgdF61Ao1$Iuhd-(TB~ zK>p12o%3+qcP3X>-(y4{TdprqZhbym%+?7eu0MR!?h44E6I?Fpa!7MI$db!$R4dcD z4kv1Qm4EBuGG3@6sI$u$7pxIE8c#7bNMzYMZ3^5F62v`vby`6iKe&PT!GpkKqIw)K zl}>sZPa>M4e_`S{?W;oexTxK|{e$GE8>G(N94K;rV~`r*iWE literal 0 HcmV?d00001 diff --git a/tests/test_data/InstanceFields.java b/tests/test_data/InstanceFields.java new file mode 100644 index 00000000..1d7303a8 --- /dev/null +++ b/tests/test_data/InstanceFields.java @@ -0,0 +1,43 @@ + +public class InstanceFields { + + private int resultSub; + private int resultAdd; + private int resultMul; + public InstanceFields() { + } + + public static void main(String[] args) { + int first = 11; + int second = 1000; + InstanceFields instance = new InstanceFields(); + instance.sub(first, second); + instance.add(first, second); + instance.mul(first, second); + int result = instance.resultSub + instance.resultAdd + instance.resultMul; + } + + public void sub(int first, int second) { + resultSub = first - second; + } + + public void add(int first, int second) { + resultAdd = first + second; + } + + public void mul(int first, int second) { + resultMul = first * second; + } + + public int getResultSub() { + return resultSub; + } + + public int getResultAdd() { + return resultAdd; + } + + public int getResultMul() { + return resultMul; + } +} diff --git a/tests/test_data/SubNew.class b/tests/test_data/SubNew.class deleted file mode 100644 index 244d9059e7bdc3ea1bf5beebccca950cfa983ddf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371 zcmYL_&q~8U5XQgBpQibvrYcovF1;m!_U6SZq96oHJVXlODQTCorA;AC^rgIbv7q1s z_#{4sIBN@KVZPnjZ|66&-@hMU0B&&TK!a|<@L-}r@Tb{x)|+L;q<5cB#i$}Q`m&JK z6+sW9p@k-A(mGGXivtU~3a$qaJ8W3iIl&C$IEo3z?P4qldjnaBq@L%Ze8}<{XN-9! z3qm`58f&9U$HTl&xCC#=U6?Bui)yu&#+wiVj{z9#!^Mmt>9#Sssy csew1u0`-H6(+S3}tZTMv4eTfEvuZf_1Ax&r=Kufz diff --git a/tests/test_data/SubNew.java b/tests/test_data/SubNew.java deleted file mode 100644 index 558e1647..00000000 --- a/tests/test_data/SubNew.java +++ /dev/null @@ -1,18 +0,0 @@ - -public class SubNew { - - public SubNew() { - } - - public static void main(String[] args) { - int first = 11; - int second = 1000; - SubNew subNew = new SubNew(); - int result = subNew.sub(first, second); - } - - private int sub(int first, int second) { - return first - second; - } - -} diff --git a/vm/src/execution_engine/engine.rs b/vm/src/execution_engine/engine.rs index 3f1058fe..b26728c6 100644 --- a/vm/src/execution_engine/engine.rs +++ b/vm/src/execution_engine/engine.rs @@ -205,12 +205,13 @@ impl<'a> Engine<'a> { println!("DUP -> {val}"); } IADD => { - println!("IADD"); let b = stack_frame.pop(); let a = stack_frame.pop(); - stack_frame.push(a + b); + let result = a + b; + stack_frame.push(result); stack_frame.incr_pc(); + println!("IADD -> {a} + {b} = {result}"); } ISUB => { let b = stack_frame.pop(); @@ -381,6 +382,62 @@ impl<'a> Engine<'a> { stack_frames.pop(); // Return from method, pop the current frame // add more logic here } + GETFIELD => { + stack_frame.incr_pc(); + let high = stack_frame.get_bytecode_byte() as u16; + + stack_frame.incr_pc(); + let low = stack_frame.get_bytecode_byte() as u16; + let fieldref_constpool_index = (high << 8) | low; + + let java_class = self + .method_area + .loaded_classes + .get(main_class_name) + .unwrap(); + + let objectref = stack_frame.pop(); + let field_name = self.method_area.get_fieldname_by_fieldref_cpool_index( + java_class, + fieldref_constpool_index, + )?; + + let value = self + .heap + .get_object_field_value(objectref, field_name.as_str())?; + + stack_frame.push(value); + + stack_frame.incr_pc(); + println!("GETFIELD -> fieldref_constpool_index={fieldref_constpool_index}, objectref={objectref}, value={value}"); + } + PUTFIELD => { + stack_frame.incr_pc(); + let high = stack_frame.get_bytecode_byte() as u16; + + stack_frame.incr_pc(); + let low = stack_frame.get_bytecode_byte() as u16; + let fieldref_constpool_index = (high << 8) | low; + + let java_class = self + .method_area + .loaded_classes + .get(main_class_name) + .unwrap(); + + let field_name = self.method_area.get_fieldname_by_fieldref_cpool_index( + java_class, + fieldref_constpool_index, + )?; + let value = stack_frame.pop(); + let objectref = stack_frame.pop(); + + self.heap + .set_object_field_value(objectref, field_name.as_str(), value)?; + + stack_frame.incr_pc(); + println!("PUTFIELD -> fieldref_constpool_index={fieldref_constpool_index}, objectref={objectref}, value={value}"); + } INVOKEVIRTUAL => { println!( "INVOKEVIRTUAL -> locals={:?}, operand_stack={:?}", @@ -514,12 +571,12 @@ impl<'a> Engine<'a> { .loaded_classes .get(class_to_invoke_new_for.as_str()) .unwrap(), - ); - let reference = self.heap.create_instance(default_field_values_instance); + )?; - stack_frame.push(reference); + let instanceref = self.heap.create_instance(default_field_values_instance); + stack_frame.push(instanceref); - println!("NEW -> class={class_constpool_index}, reference={reference}"); + println!("NEW -> class={class_constpool_index}, reference={instanceref}"); stack_frame.incr_pc(); } _ => unreachable!("{}", format! {"xxx = {}", stack_frame.get_bytecode_byte()}), diff --git a/vm/src/heap/heap.rs b/vm/src/heap/heap.rs index f64d7161..f95a67ec 100644 --- a/vm/src/heap/heap.rs +++ b/vm/src/heap/heap.rs @@ -1,3 +1,4 @@ +use crate::error::Error; use crate::heap::java_instance::JavaInstance; use std::collections::HashMap; @@ -22,4 +23,30 @@ impl<'a> Heap<'a> { self.next_id } + + pub fn set_object_field_value( + &mut self, + objectref: i32, + fieldname: &str, + value: i32, + ) -> crate::error::Result<()> { + if let Some(instance) = self.data.get_mut(&objectref) { + instance.set_field_value(fieldname, value)?; + Ok(()) + } else { + Err(Error::new_execution("error setting field value")) + } + } + + pub fn get_object_field_value( + &mut self, + objectref: i32, + fieldname: &str, + ) -> crate::error::Result { + if let Some(java_instance) = self.data.get(&objectref) { + java_instance.get_field_value(fieldname) + } else { + Err(Error::new_execution("error getting field value")) + } + } } diff --git a/vm/src/heap/java_instance.rs b/vm/src/heap/java_instance.rs index d070721f..b87507b9 100644 --- a/vm/src/heap/java_instance.rs +++ b/vm/src/heap/java_instance.rs @@ -1,17 +1,35 @@ +use crate::error::Error; use crate::method_area::java_class::JavaClass; +use crate::util::get_fields; use std::collections::HashMap; #[derive(Debug)] pub(crate) struct JavaInstance<'a> { + #[allow(dead_code)] class_ref: &'a JavaClass, fields: HashMap, } impl<'a> JavaInstance<'a> { - pub fn new(class_ref: &'a JavaClass) -> Self { - let mut fields = HashMap::new(); + pub fn new(class_ref: &'a JavaClass) -> crate::error::Result { + Ok(Self { + class_ref, + fields: get_fields(&class_ref.class_file)?, + }) + } + + pub fn set_field_value(&mut self, fieldname: &str, value: i32) -> crate::error::Result<()> { + self.fields + .get_mut(fieldname) + .and_then(|v| Some(v.set_value(value))) + .ok_or(Error::new_execution("error setting instance field value")) + } - Self { class_ref, fields } + pub fn get_field_value(&self, fieldname: &str) -> crate::error::Result { + self.fields + .get(fieldname) + .and_then(|v| Some(v.value)) + .ok_or(Error::new_execution("error getting instance field value")) } } @@ -19,3 +37,13 @@ impl<'a> JavaInstance<'a> { pub(crate) struct Field { value: i32, // todo: support other types } + +impl Field { + pub fn new() -> Self { + Self { value: 0 } + } + + pub fn set_value(&mut self, value: i32) { + self.value = value; + } +} diff --git a/vm/src/method_area/method_area.rs b/vm/src/method_area/method_area.rs index eb1facf3..ec86e123 100644 --- a/vm/src/method_area/method_area.rs +++ b/vm/src/method_area/method_area.rs @@ -2,7 +2,7 @@ use crate::error::Error; use crate::method_area::java_class::JavaClass; use crate::method_area::java_method::JavaMethod; use crate::util::{get_class_name_by_cpool_class_index, get_cpool_string}; -use jclass::constant_pool::ConstantPool::{Methodref, NameAndType}; +use jclass::constant_pool::ConstantPool::{Fieldref, Methodref, NameAndType}; use std::collections::HashMap; #[derive(Debug)] @@ -100,4 +100,58 @@ impl MethodArea { let full_signature = format!("{}:{}", method_name.unwrap(), method_signature.unwrap()); self.get_method_by_name_signature(class_name.unwrap().as_str(), full_signature.as_str()) } + + pub(crate) fn get_fieldname_by_fieldref_cpool_index( + &self, + java_class: &JavaClass, + fieldref_cpool_index: u16, + ) -> crate::error::Result { + let cpool = java_class.class_file.constant_pool(); + + // Retrieve Fieldref from the constant pool + let fieldref = cpool + .get(fieldref_cpool_index as usize) + .and_then(|entry| match entry { + Fieldref { + class_index, + name_and_type_index, + } => Some((*class_index as usize, *name_and_type_index as usize)), + _ => None, + }) + .ok_or_else(|| { + Error::new_constant_pool( + format!( + "Invalid Fieldref at index {} in class {:?}", + fieldref_cpool_index, java_class + ) + .as_str(), + ) + })?; + + // Retrieve field name from the constant pool + let field_name = if let NameAndType { + name_index, + descriptor_index: _, + } = cpool.get(fieldref.1).ok_or_else(|| { + Error::new_constant_pool( + format!( + "Invalid NameAndType reference at index {} in class {:?}", + fieldref_cpool_index, java_class + ) + .as_str(), + ) + })? { + get_cpool_string(&java_class.class_file, *name_index as usize) + } else { + return Err(Error::new_constant_pool( + format!( + "Expected NameAndType at index {} in class {:?}", + fieldref_cpool_index, java_class + ) + .as_str(), + )); + }; + + Ok(field_name.unwrap()) + } } diff --git a/vm/src/util.rs b/vm/src/util.rs index e6239bac..63db4e72 100644 --- a/vm/src/util.rs +++ b/vm/src/util.rs @@ -1,5 +1,8 @@ +use crate::error::Error; +use crate::heap::java_instance::Field; use jclass::class_file::ClassFile; use jclass::constant_pool::ConstantPool::{Class, Utf8}; +use std::collections::HashMap; pub(crate) fn get_class_name_by_cpool_class_index( class_index: usize, @@ -23,3 +26,19 @@ pub(crate) fn get_cpool_string(class_file: &ClassFile, index: usize) -> Option None, }) } + +pub(crate) fn get_fields(class_file: &ClassFile) -> crate::error::Result> { + let fields = class_file.fields(); + let mut field_by_name: HashMap = HashMap::new(); + + for field in fields.iter() { + let field_name = get_cpool_string(class_file, field.name_index() as usize) + .ok_or(Error::new_constant_pool("Error getting field name"))?; + let _field_signature = get_cpool_string(class_file, field.descriptor_index() as usize) + .ok_or(Error::new_constant_pool("Error getting field signature"))?; + + field_by_name.insert(field_name, Field::new()); + } + + Ok(field_by_name) +}