diff --git a/jdescriptor/src/lib.rs b/jdescriptor/src/lib.rs index c54815e5..470bfca4 100644 --- a/jdescriptor/src/lib.rs +++ b/jdescriptor/src/lib.rs @@ -42,7 +42,7 @@ pub fn default_value(type_descriptor: &TypeDescriptor) -> Vec { Double => todo!(), Void => panic!("field can't be a void type"), Array(_, _) => vec![0], - Object(_) => todo!(), + Object(_) => vec![0], } } @@ -52,7 +52,7 @@ pub fn get_length(type_descriptor: &TypeDescriptor) -> usize { Long | Double => 2, Void => panic!("field can't be a void type"), Array(_, _) => 1, - Object(_) => todo!(), + Object(_) => 1, } } diff --git a/src/main.rs b/src/main.rs index 03b20720..b42596cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,8 +46,5 @@ fn main() { } }; - println!( - "\nresult={:?}", - result.map_or_else(|| vec![], |v| v) - ); + println!("\nresult={:?}", result.map_or_else(|| vec![], |v| v)); } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 620896b1..f35cb1b7 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -24,7 +24,7 @@ fn should_do_adding_with_negative_longs() { vec!["tests/test_data/AdderNegativeLong.class"], "tests/test_data/std", ) - .unwrap(); + .unwrap(); let last_frame_value = vm.run("AdderNegativeLong").unwrap(); assert_eq!(-1990000000000000, get_long(last_frame_value)) } @@ -66,7 +66,7 @@ fn should_write_read_instance_fields_with_longs() { ], "tests/test_data/std", ) - .unwrap(); + .unwrap(); let last_frame_value = vm.run("InstanceFieldsUserLong").unwrap(); assert_eq!(4_380_866_642_760, get_long(last_frame_value)) } @@ -102,7 +102,7 @@ fn should_do_extreme_stack_operations_with_longs() { vec!["tests/test_data/ExtremeStackTestLong.class"], "tests/test_data/std", ) - .unwrap(); + .unwrap(); let last_frame_value = vm.run("ExtremeStackTestLong").unwrap(); assert_eq!(454, get_long(last_frame_value)) } @@ -138,7 +138,11 @@ fn should_do_arrays() { #[test] fn should_do_arrays_with_longs() { - let vm = VM::new(vec!["tests/test_data/ArrayLong.class"], "tests/test_data/std").unwrap(); + let vm = VM::new( + vec!["tests/test_data/ArrayLong.class"], + "tests/test_data/std", + ) + .unwrap(); let last_frame_value = vm.run("ArrayLong").unwrap(); assert_eq!(233646220932000, get_long(last_frame_value)) } @@ -154,7 +158,6 @@ fn should_do_class_static_initialization() { assert_eq!(257, get_int(last_frame_value)) } -#[ignore] #[test] fn should_do_class_static_initialization_multiple_classes() { let vm = VM::new( @@ -170,6 +173,51 @@ fn should_do_class_static_initialization_multiple_classes() { assert_eq!(350, get_int(last_frame_value)) } +#[test] +fn should_do_class_static_initialization_within_one_class() { + let vm = VM::new( + vec!["tests/test_data/StaticInitializationWithinOneClass.class"], + "tests/test_data/std", + ) + .unwrap(); + let last_frame_value = vm.run("StaticInitializationWithinOneClass").unwrap(); + assert_eq!(100, get_int(last_frame_value)) +} + +#[test] +fn should_do_class_static_initialization_advanced() { + let vm = VM::new( + vec![ + "tests/test_data/StaticInitializationAdvanced.class", + "tests/test_data/ClassA.class", + "tests/test_data/ClassB.class", + "tests/test_data/ClassC.class", + "tests/test_data/ClassD.class", + "tests/test_data/ClassE.class", + "tests/test_data/Helper.class", + ], + "tests/test_data/std", + ) + .unwrap(); + let last_frame_value = vm.run("StaticInitializationAdvanced").unwrap(); + assert_eq!(826, get_int(last_frame_value)) +} + +#[test] +fn should_do_class_static_initialization_circular() { + let vm = VM::new( + vec![ + "tests/test_data/StaticInitializationCircular.class", + "tests/test_data/ClassACircular.class", + "tests/test_data/ClassBCircular.class", + ], + "tests/test_data/std", + ) + .unwrap(); + let last_frame_value = vm.run("StaticInitializationCircular").unwrap(); + assert_eq!(700, get_int(last_frame_value)) +} + fn get_int(locals: Option>) -> i32 { *locals.unwrap().last().unwrap() } diff --git a/tests/test_data/ClassA.class b/tests/test_data/ClassA.class new file mode 100644 index 00000000..be418d6f Binary files /dev/null and b/tests/test_data/ClassA.class differ diff --git a/tests/test_data/ClassACircular.class b/tests/test_data/ClassACircular.class new file mode 100644 index 00000000..03d534e8 Binary files /dev/null and b/tests/test_data/ClassACircular.class differ diff --git a/tests/test_data/ClassB.class b/tests/test_data/ClassB.class new file mode 100644 index 00000000..7c0b5441 Binary files /dev/null and b/tests/test_data/ClassB.class differ diff --git a/tests/test_data/ClassBCircular.class b/tests/test_data/ClassBCircular.class new file mode 100644 index 00000000..492f3df5 Binary files /dev/null and b/tests/test_data/ClassBCircular.class differ diff --git a/tests/test_data/ClassC.class b/tests/test_data/ClassC.class new file mode 100644 index 00000000..90f9587d Binary files /dev/null and b/tests/test_data/ClassC.class differ diff --git a/tests/test_data/ClassD.class b/tests/test_data/ClassD.class new file mode 100644 index 00000000..9a5c55fa Binary files /dev/null and b/tests/test_data/ClassD.class differ diff --git a/tests/test_data/ClassE.class b/tests/test_data/ClassE.class new file mode 100644 index 00000000..bc3fe574 Binary files /dev/null and b/tests/test_data/ClassE.class differ diff --git a/tests/test_data/Helper.class b/tests/test_data/Helper.class new file mode 100644 index 00000000..756f2d84 Binary files /dev/null and b/tests/test_data/Helper.class differ diff --git a/tests/test_data/StaticInitializationAdvanced.class b/tests/test_data/StaticInitializationAdvanced.class new file mode 100644 index 00000000..64e7f361 Binary files /dev/null and b/tests/test_data/StaticInitializationAdvanced.class differ diff --git a/tests/test_data/StaticInitializationAdvanced.java b/tests/test_data/StaticInitializationAdvanced.java new file mode 100644 index 00000000..a8dbc0d1 --- /dev/null +++ b/tests/test_data/StaticInitializationAdvanced.java @@ -0,0 +1,56 @@ +public class StaticInitializationAdvanced { + public static void main(String[] args) { + int classCWithHelperNonStaticGetter = ClassC.staticField; + + int classDWithHelperStaticGetter = ClassD.staticField; + + ClassC.staticField = 600; + int classCIsModified = ClassC.staticField; + + int classEAsSumOfCAndD = ClassE.staticField; + + int result = classCWithHelperNonStaticGetter + classDWithHelperStaticGetter * classCIsModified / classEAsSumOfCAndD; + } +} + +class ClassC { + static Helper helper; + static int staticField; + + static { + helper = new Helper(500); + staticField = 150 + helper.calculateNonStaticFieldValue(); + } +} + +class ClassD { + static int staticField; + + static { + staticField = Helper.calculateStaticFieldValue(); + } +} + +class ClassE { + static int staticField; + + static { + staticField = ClassC.staticField + ClassD.staticField; + } +} + +class Helper { + private final int value; + + Helper(int value) { + this.value = value; + } + + static int calculateStaticFieldValue() { + return 250; + } + + public int calculateNonStaticFieldValue() { + return value; + } +} diff --git a/tests/test_data/StaticInitializationCircular.class b/tests/test_data/StaticInitializationCircular.class new file mode 100644 index 00000000..801a4aa0 Binary files /dev/null and b/tests/test_data/StaticInitializationCircular.class differ diff --git a/tests/test_data/StaticInitializationCircular.java b/tests/test_data/StaticInitializationCircular.java new file mode 100644 index 00000000..6f301dcd --- /dev/null +++ b/tests/test_data/StaticInitializationCircular.java @@ -0,0 +1,25 @@ + + +public class StaticInitializationCircular { + public static void main(String[] args) { + int classA = ClassACircular.staticField; + int classB = ClassBCircular.staticField; + int result = classA + classB; + } +} + +class ClassACircular { + static int staticField; + + static { + staticField = 100 + ClassBCircular.staticField; + } +} + +class ClassBCircular { + static int staticField; + + static { + staticField = 300 + ClassACircular.staticField; + } +} diff --git a/tests/test_data/StaticInitializationWithinOneClass.class b/tests/test_data/StaticInitializationWithinOneClass.class new file mode 100644 index 00000000..5fb82161 Binary files /dev/null and b/tests/test_data/StaticInitializationWithinOneClass.class differ diff --git a/tests/test_data/StaticInitializationWithinOneClass.java b/tests/test_data/StaticInitializationWithinOneClass.java new file mode 100644 index 00000000..695291fd --- /dev/null +++ b/tests/test_data/StaticInitializationWithinOneClass.java @@ -0,0 +1,15 @@ + + +public class StaticInitializationWithinOneClass { + static int staticField; + + static { + int xxx = 1337; + staticField = xxx; + } + + public static void main(String[] args) { + staticField = 100; + int result = staticField; + } +} diff --git a/vm/src/execution_engine/engine.rs b/vm/src/execution_engine/engine.rs index ae2ea31b..27da625a 100644 --- a/vm/src/execution_engine/engine.rs +++ b/vm/src/execution_engine/engine.rs @@ -9,6 +9,7 @@ use crate::util::{ get_class_name_by_cpool_class_index, get_cpool_integer, get_cpool_long_double, Primitive, }; use jdescriptor::get_length; +use std::collections::HashSet; pub(crate) struct Engine<'a> { method_area: &'a MethodArea, @@ -16,10 +17,14 @@ pub(crate) struct Engine<'a> { } impl<'a> Engine<'a> { - pub(crate) fn execute(&mut self, method: &JavaMethod) -> crate::error::Result>> { + pub(crate) fn execute( + &mut self, + method: &JavaMethod, + ) -> crate::error::Result>> { let mut stack_frames = vec![method.new_stack_frame()]; let mut last_value: Option> = None; let mut current_class_name: String; + let mut static_set: HashSet = HashSet::new(); while !stack_frames.is_empty() { let stack_frame = stack_frames @@ -245,7 +250,10 @@ impl<'a> Engine<'a> { stack_frame.push(value[0]); stack_frame.incr_pc(); - println!("IALOAD -> arrayref={arrayref}, index={index}, value={}", value[0]); + println!( + "IALOAD -> arrayref={arrayref}, index={index}, value={}", + value[0] + ); } LALOAD => { let index = stack_frame.pop(); @@ -267,7 +275,10 @@ impl<'a> Engine<'a> { stack_frame.push(objref[0]); stack_frame.incr_pc(); - println!("AALOAD -> arrayref={arrayref}, index={index}, objref={}", objref[0]); + println!( + "AALOAD -> arrayref={arrayref}, index={index}, objref={}", + objref[0] + ); } ISTORE => { stack_frame.incr_pc(); @@ -285,7 +296,7 @@ impl<'a> Engine<'a> { let low = stack_frame.pop(); stack_frame.set_local(pos, low); - stack_frame.set_local(pos+1, high); + stack_frame.set_local(pos + 1, high); stack_frame.incr_pc(); let value = i32toi64(high, low); @@ -664,11 +675,13 @@ impl<'a> Engine<'a> { } RETURN => { println!("RETURN -> stack_frame.locals={:?}", stack_frame.locals); - last_value = Some(stack_frames - .last() - .ok_or(Error::new_execution("Error getting stack last value"))? - .locals - .clone()); + last_value = Some( + stack_frames + .last() + .ok_or(Error::new_execution("Error getting stack last value"))? + .locals + .clone(), + ); stack_frames.pop(); // Return from method, pop the current frame // add more logic here @@ -688,6 +701,21 @@ impl<'a> Engine<'a> { fieldref_constpool_index, )?; + //calling static block if needed, todo: move me to single place + if !static_set.contains(class_name.as_str()) { + static_set.insert(class_name.clone()); + if let Ok(clinit) = self + .method_area + .get_method_by_name_signature(class_name.as_str(), ":()V") + { + stack_frame.advance_pc(-2); + let next_frame = clinit.new_stack_frame(); + stack_frames.push(next_frame); + println!(" -> {class_name}.:()V"); + continue; + } + } + let field = self .method_area .loaded_classes @@ -699,9 +727,16 @@ impl<'a> Engine<'a> { .unwrap() .borrow(); - field.raw_value().iter().rev().for_each(|x| stack_frame.push(*x)); + field + .raw_value() + .iter() + .rev() + .for_each(|x| stack_frame.push(*x)); - println!("GETSTATIC -> {class_name}.{field_name} is {:?}", field.raw_value()); + println!( + "GETSTATIC -> {class_name}.{field_name} is {:?}", + field.raw_value() + ); stack_frame.incr_pc(); } PUTSTATIC => { @@ -719,6 +754,21 @@ impl<'a> Engine<'a> { fieldref_constpool_index, )?; + //calling static block if needed, todo: move me to single place + if !static_set.contains(class_name.as_str()) { + static_set.insert(class_name.clone()); + if let Ok(clinit) = self + .method_area + .get_method_by_name_signature(class_name.as_str(), ":()V") + { + stack_frame.advance_pc(-2); + let next_frame = clinit.new_stack_frame(); + stack_frames.push(next_frame); + println!(" -> {class_name}.:()V"); + continue; + } + } + let len = { let field = self .method_area @@ -739,8 +789,11 @@ impl<'a> Engine<'a> { value.push(stack_frame.pop()); } - self.method_area - .set_static_field_value(&class_name, &field_name, value.clone())?; + self.method_area.set_static_field_value( + &class_name, + &field_name, + value.clone(), + )?; println!("PUTSTATIC -> {class_name}.{field_name} = {value:?}"); stack_frame.incr_pc(); @@ -886,6 +939,22 @@ impl<'a> Engine<'a> { methodref_constpool_index, )?; + //calling static block if needed, todo: move me to single place + // requirements of JVMS Section 5.4 + if !static_set.contains(class_name.as_str()) { + static_set.insert(class_name.clone()); + if let Ok(clinit) = self + .method_area + .get_method_by_name_signature(class_name.as_str(), ":()V") + { + stack_frame.advance_pc(-2); + let next_frame = clinit.new_stack_frame(); + stack_frames.push(next_frame); + println!(" -> {class_name}.:()V"); + continue; + } + } + let mut next_frame = static_method.new_stack_frame(); let arg_num = static_method.get_signature().arguments_length(); @@ -915,7 +984,12 @@ impl<'a> Engine<'a> { self.method_area .loaded_classes .get(class_to_invoke_new_for.as_str()) - .unwrap(), + .expect( + format!( + "class_to_invoke_new_for not found: {class_to_invoke_new_for}" + ) + .as_str(), + ), )?; let instanceref = self.heap.create_instance(default_field_values_instance); diff --git a/vm/src/heap/heap.rs b/vm/src/heap/heap.rs index 92ccb85f..d611bcab 100644 --- a/vm/src/heap/heap.rs +++ b/vm/src/heap/heap.rs @@ -59,7 +59,11 @@ impl<'a> Heap<'a> { self.next_id } - pub(crate) fn get_array_value(&self, arrayref: i32, index: i32) -> crate::error::Result<&Vec> { + pub(crate) fn get_array_value( + &self, + arrayref: i32, + index: i32, + ) -> crate::error::Result<&Vec> { if let Some(Arr(arr)) = self.data.get(&arrayref) { arr.get_value(index) } else { diff --git a/vm/src/heap/java_instance.rs b/vm/src/heap/java_instance.rs index 5ae7a23a..c56e7050 100644 --- a/vm/src/heap/java_instance.rs +++ b/vm/src/heap/java_instance.rs @@ -19,7 +19,7 @@ pub(crate) struct Array { impl Array { pub fn new(len: i32) -> Self { Self { - data: vec![vec![0,0]; len as usize], //todo: use either 1 or 2 elements vector for corresponding type + data: vec![vec![0, 0]; len as usize], //todo: use either 1 or 2 elements vector for corresponding type } } diff --git a/vm/src/method_area/method_area.rs b/vm/src/method_area/method_area.rs index c8913a0e..2f37c2b8 100644 --- a/vm/src/method_area/method_area.rs +++ b/vm/src/method_area/method_area.rs @@ -40,7 +40,7 @@ impl MethodArea { if let Some(found) = self .loaded_classes .get(class_name) - .unwrap() + .expect(format!("class {class_name} not found").as_str()) .methods .method_by_signature .get(method_name_signature) diff --git a/vm/src/stack/stack_frame.rs b/vm/src/stack/stack_frame.rs index 8880c3c9..001de012 100644 --- a/vm/src/stack/stack_frame.rs +++ b/vm/src/stack/stack_frame.rs @@ -58,7 +58,6 @@ impl<'a> StackFrame<'a> { self.push(high); } - pub fn pop_i64(&mut self) -> i64 { let high = self.pop(); let low = self.pop(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 029a4850..19238108 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -20,15 +20,6 @@ impl VM { let mut engine = Engine::new(&self.class_loader.method_area()); - for (class_name, java_class) in self.class_loader.method_area().loaded_classes.iter() { - if let Some(java_method) = java_class.methods.method_by_signature.get(":()V") { - println!( - "About to initialize java class {class_name} java_method={java_method:?}" - ); - engine.execute(java_method)?; //todo implement multiclass correct initialization order - } - } - engine.execute(main_method) } }