From 46aa76d16ae3790d31dcd193426b960000a6874e Mon Sep 17 00:00:00 2001 From: Florian Warzecha Date: Sat, 6 Jan 2024 19:09:34 +0100 Subject: [PATCH 1/3] feat(executor): builtin class Throwable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonas Patrick Völlmecke --- src/class/builtin_classes.rs | 2 + src/class/builtin_classes/throwable.rs | 155 +++++++++++++++++++++++++ src/executor.rs | 1 + src/executor/frame_stack.rs | 1 + src/executor/local_variables.rs | 3 +- src/heap.rs | 7 +- tests/exceptions/simple.rs | 3 +- 7 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/class/builtin_classes/throwable.rs diff --git a/src/class/builtin_classes.rs b/src/class/builtin_classes.rs index 768a2b5..bed88f2 100644 --- a/src/class/builtin_classes.rs +++ b/src/class/builtin_classes.rs @@ -5,6 +5,7 @@ pub mod object; pub mod print_stream; pub mod string; pub mod system; +pub mod throwable; pub use crate::class::builtin_classes::{ file_input_stream::{FileInputStream, FileInputStreamInstance}, @@ -13,4 +14,5 @@ pub use crate::class::builtin_classes::{ print_stream::{PrintStream, PrintStreamInstance}, string::{StringClass, StringInstance}, system::SystemClass, + throwable::{ThrowableClass, ThrowableInstance}, }; diff --git a/src/class/builtin_classes/throwable.rs b/src/class/builtin_classes/throwable.rs new file mode 100644 index 0000000..6d34b98 --- /dev/null +++ b/src/class/builtin_classes/throwable.rs @@ -0,0 +1,155 @@ +use std::{any::Any, cell::OnceCell, rc::Rc}; + +use crate::{ + class::{ + class_identifier, ArgumentKind, Class, ClassIdentifier, ClassInstance, + Field, FieldDescriptor, FieldValue, Method, MethodCode, + RustMethodReturn, SimpleArgumentKind, + }, + executor::{local_variables::VariableValueOrValue, Frame}, +}; + +pub struct ThrowableClass { + class_identifier: ClassIdentifier, + methods: Vec>, +} + +impl ThrowableClass { + pub fn new() -> Self { + Self { + class_identifier: class_identifier!(java / lang, Throwable), + methods: vec![ + Rc::new(Method { + code: MethodCode::Rust(init), + name: "".to_owned(), + parameters: vec![ArgumentKind::Simple( + SimpleArgumentKind::Class( + "java/lang/String".to_string(), + ), + )], + return_type: None, + is_static: false, + }), + Rc::new(Method { + code: MethodCode::Rust(get_message), + name: "get_message".to_owned(), + parameters: vec![], + return_type: Some(ArgumentKind::Simple( + SimpleArgumentKind::Class( + "java/lang/String".to_string(), + ), + )), + is_static: false, + }), + ], + } + } +} + +impl Default for ThrowableClass { + fn default() -> Self { + Self::new() + } +} + +fn get_message(frame: &mut Frame) -> RustMethodReturn { + let instance: Rc = match frame.local_variables.get(0) { + VariableValueOrValue::Reference(s) => s.expect("null pointer"), + _ => panic!("local variables have reference at index 0"), + }; + let instance = match instance.as_any().downcast_ref::() { + Some(i) => i, + None => panic!("got {:?} expected Throwable", instance), + }; + + let message = instance + .message + .get() + .expect("message has been initialized"); + + RustMethodReturn::Value(FieldValue::Reference(message.clone())) +} + +fn init(frame: &mut Frame) -> RustMethodReturn { + let instance: Rc = match frame.local_variables.get(0) { + VariableValueOrValue::Reference(s) => s.expect("null pointer"), + _ => panic!("local variables have reference at index 0"), + }; + let message: Option> = + match frame.local_variables.get(1) { + VariableValueOrValue::Reference(s) => s, + _ => panic!("local variables have message at index 1"), + }; + + let instance = match instance.as_any().downcast_ref::() { + Some(i) => i, + None => panic!("got {:?} expected Throwable", instance), + }; + + instance + .message + .set(message) + .expect("message has not been set"); + + RustMethodReturn::Void +} + +impl Class for ThrowableClass { + fn methods(&self) -> &[Rc] { + self.methods.as_slice() + } + + fn static_fields(&self) -> &[Rc] { + &[] + } + + fn instance_fields(&self) -> &[FieldDescriptor] { + &[] + } + + fn class_identifier(&self) -> &crate::class::ClassIdentifier { + &self.class_identifier + } + + fn super_class(&self) -> Option> { + None + } + + fn interfaces(&self) -> &[Rc] { + &[] + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn new_instance(&self, cls: Rc) -> Rc { + // make sure that self and cls really are equal + let _cls_ref: &Self = + cls.as_ref().as_any().downcast_ref::().unwrap(); + + Rc::new(ThrowableInstance { + class: cls.clone(), + message: OnceCell::new(), + }) + } +} + +pub struct ThrowableInstance { + class: Rc, + message: OnceCell>>, +} + +impl ClassInstance for ThrowableInstance { + fn class(&self) -> Rc { + self.class.clone() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn instance_fields(&self) -> &[Rc] { + &[] + } +} diff --git a/src/executor.rs b/src/executor.rs index f527ae1..a774c41 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -226,6 +226,7 @@ pub enum ReturnValue { Reference(Option>), } +#[derive(Debug)] pub struct Frame { pub local_variables: LocalVariables, pub operand_stack: FrameStack, diff --git a/src/executor/frame_stack.rs b/src/executor/frame_stack.rs index 8705e26..5dc2e62 100644 --- a/src/executor/frame_stack.rs +++ b/src/executor/frame_stack.rs @@ -85,6 +85,7 @@ impl StackValue { } } +#[derive(Debug)] pub struct FrameStack { values: Vec, } diff --git a/src/executor/local_variables.rs b/src/executor/local_variables.rs index b5b8be9..b9fbf6a 100644 --- a/src/executor/local_variables.rs +++ b/src/executor/local_variables.rs @@ -8,7 +8,7 @@ use crate::{ }, }; -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum VariableValue { // Primitive Types // Integral Types @@ -41,6 +41,7 @@ pub enum VariableValue { Reference(Option>), } +#[derive(Debug)] pub struct LocalVariables { local_variables: Vec, } diff --git a/src/heap.rs b/src/heap.rs index bc01b82..83f0313 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -10,7 +10,7 @@ use crate::class::{ ShortArrayInstance, }, FileInputStream, InputStream, ObjectClass, PrintStream, StringClass, - StringInstance, SystemClass, + StringInstance, SystemClass, ThrowableClass, }, ArrayName, Class, ClassIdentifier, ClassName, }; @@ -46,6 +46,7 @@ impl Heap { let system_class = Rc::new(SystemClass::new(&print_stream_class, &input_stream_class)); let object_class = Rc::new(ObjectClass::default()); + let throwable_class = Rc::new(ThrowableClass::default()); let mut classes: HashMap> = HashMap::new(); @@ -67,6 +68,10 @@ impl Heap { ); classes.insert(system_class.class_identifier().clone(), system_class); classes.insert(object_class.class_identifier().clone(), object_class); + classes.insert( + throwable_class.class_identifier().clone(), + throwable_class, + ); classes.insert( boolean_array_class.class_identifier().clone(), boolean_array_class.clone(), diff --git a/tests/exceptions/simple.rs b/tests/exceptions/simple.rs index cd2c936..cd6a9aa 100644 --- a/tests/exceptions/simple.rs +++ b/tests/exceptions/simple.rs @@ -10,8 +10,7 @@ fn try_catch_finally_throwable() -> Result<(), Box> { // so that failure() can be simply removed when all features // are implemented cmd.assert().failure().stderr(predicate::str::contains( - "\ -called `Option::unwrap()` on a `None` value", + "Missing OpCode implementation for: Athrow", )); // cmd.assert() // .success() From aaca1d67f6d94791265d0d1210a794293cb30f7b Mon Sep 17 00:00:00 2001 From: Florian Warzecha Date: Sun, 7 Jan 2024 15:48:19 +0100 Subject: [PATCH 2/3] feat(executor): opcode athrow; exception handling Co-authored-by: Julius Bierbaum --- src/class/builtin_classes/throwable.rs | 2 +- src/executor.rs | 57 ++++++++- src/executor/frame_stack.rs | 4 + src/executor/op_code.rs | 7 ++ tests/data/exceptions/nested/Main.class | Bin 0 -> 915 bytes tests/data/exceptions/nested/Main.java | 19 +++ tests/data/exceptions/nested/Main.javap | 150 ++++++++++++++++++++++++ tests/exceptions.rs | 1 + tests/exceptions/nested.rs | 14 +++ tests/exceptions/simple.rs | 12 +- 10 files changed, 255 insertions(+), 11 deletions(-) create mode 100644 tests/data/exceptions/nested/Main.class create mode 100644 tests/data/exceptions/nested/Main.java create mode 100644 tests/data/exceptions/nested/Main.javap create mode 100644 tests/exceptions/nested.rs diff --git a/src/class/builtin_classes/throwable.rs b/src/class/builtin_classes/throwable.rs index 6d34b98..72795dd 100644 --- a/src/class/builtin_classes/throwable.rs +++ b/src/class/builtin_classes/throwable.rs @@ -32,7 +32,7 @@ impl ThrowableClass { }), Rc::new(Method { code: MethodCode::Rust(get_message), - name: "get_message".to_owned(), + name: "getMessage".to_owned(), parameters: vec![], return_type: Some(ArgumentKind::Simple( SimpleArgumentKind::Class( diff --git a/src/executor.rs b/src/executor.rs index a774c41..d326838 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -36,7 +36,7 @@ pub fn run(code: &Code, heap: &mut Heap) { let mut current_pc: ProgramCounter = ProgramCounter::new(code.byte_code.clone()); - loop { + 'executor_loop: loop { match current_pc.current().0.execute(&mut current_frame, heap) { Update::None => current_pc.next(1).unwrap(), Update::MethodCall { method, is_static } => match &method.code { @@ -158,6 +158,60 @@ pub fn run(code: &Code, heap: &mut Heap) { current_pc.previous(offset).unwrap() }, }, + Update::Exception(e) => { + // allow the while-loop to handle the current method + // without special casing the first iteration + frame_stack.push(ExecutorFrame { + frame: current_frame, + pc: current_pc, + }); + // search the call stack for an exception handler + // matching the current exception + while let Some(ExecutorFrame { frame, pc }) = frame_stack.pop() + { + current_frame = frame; + current_pc = pc; + // check all exception handler of the current method + // expectation: the order is 'correct', i.e. + // the first matching handler is the one that's supposed + // to handle the current exception + // (i.e. this code does NOT search the most specific + // matching handler) + for exception in code.exception_table.iter() { + // is the exception handler active in the region + // that is currently executed? + if exception.active.contains(¤t_pc.current().1) { + // does the exception handler handle the + // class of the thrown exception? + // TODO: inheritance + // (i.e. is e.class().class_identifier() + // could also be a subclass of identifer) + let catch_type_match = match &exception.catch_type { + // exception handler handles all exceptions + None => true, + Some(identifier) => { + e.class().class_identifier() == identifier + }, + }; + if catch_type_match { + current_frame.operand_stack.clear(); + current_frame + .operand_stack + .push(StackValue::Reference(Some( + e.clone(), + ))) + .unwrap(); + current_pc + .set(exception.handler_position) + .unwrap(); + continue 'executor_loop; + } + } + } + } + // no handler has been found: terminate + panic!("Uncaught exception: {:?}", e); + }, } } } @@ -209,6 +263,7 @@ pub enum Update { Return(ReturnValue), GoTo(usize, OffsetDirection), MethodCall { method: Rc, is_static: bool }, + Exception(Rc), } pub enum ReturnValue { diff --git a/src/executor/frame_stack.rs b/src/executor/frame_stack.rs index 5dc2e62..ad1c57b 100644 --- a/src/executor/frame_stack.rs +++ b/src/executor/frame_stack.rs @@ -113,6 +113,10 @@ impl FrameStack { pub fn pop(&mut self) -> Option { self.values.pop() } + + pub fn clear(&mut self) { + self.values.clear() + } } impl From for StackValue { diff --git a/src/executor/op_code.rs b/src/executor/op_code.rs index 82eb5fc..c540e5c 100644 --- a/src/executor/op_code.rs +++ b/src/executor/op_code.rs @@ -431,6 +431,13 @@ on top of the stack, got: {:?}", Update::None }, + Self::Athrow => { + let exception: Rc = + frame.operand_stack.pop().unwrap().try_into().unwrap(); + + Update::Exception(exception) + }, + Self::Baload => { let index = frame .operand_stack diff --git a/tests/data/exceptions/nested/Main.class b/tests/data/exceptions/nested/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..96f4b07aee8e78661d4bc4302bef24f765b4c759 GIT binary patch literal 915 zcmZuwO>YuW6g`ij3{0m3(t_XGT0fvgrc$eoHExWqpf#as(ij(yaWYJW88Qr3ev#|C zXpKg@_eUAe1GF@DG4I@a-~BlE@c8xn+fM+G@x*|MMFX=~%HR%`)3|Hk9`5USkVeTs z3gt9b6t3#1=y)iQdhQ2){6au0mG=aaFT?k)K(_7&?)ISDa-&_@>M)Y8hmP#*$;em! zB$$lbeqW$a4D&IPwE;OAQn3TbR1q zZAamuYOiW#%ev5bD`$2AG=+u9}aji5yKDdHxwzRRIcn&j4_$>VFA6Tk~&nlP?}P4 zN?^ltm&rq|RA#I~+#o*XT8eLxtg>WVWR_ZO6IXDRc#YpI xv~3;N-|;ipf^TD#4E~GMB7pH7sr`KB1jZ>n{)RlqV&rg>c9JpW-=dws?LP#mu#f-% literal 0 HcmV?d00001 diff --git a/tests/data/exceptions/nested/Main.java b/tests/data/exceptions/nested/Main.java new file mode 100644 index 0000000..ecc098e --- /dev/null +++ b/tests/data/exceptions/nested/Main.java @@ -0,0 +1,19 @@ +package org.cmjava2023; + +public class Main { + public static void main(String[] args) { + try { + oops(); + } catch (Throwable e) { + System.out.println("caught e:"); + System.out.println(e.getMessage()); + } finally { + System.out.println("anyway"); + } + } + + public static void oops() throws Throwable { + throw new Throwable("Oops"); + } +} + diff --git a/tests/data/exceptions/nested/Main.javap b/tests/data/exceptions/nested/Main.javap new file mode 100644 index 0000000..7987038 --- /dev/null +++ b/tests/data/exceptions/nested/Main.javap @@ -0,0 +1,150 @@ +Classfile /home/florian/Documents/fh/WS23_24/Compilerbau/vm/main/tests/data/exceptions/nested/Main.class + Last modified 07.01.2024; size 915 bytes + MD5 checksum e54df0c1aabcea77892df2bfa04f260d + Compiled from "Main.java" +public class org.cmjava2023.Main + minor version: 0 + major version: 52 + flags: ACC_PUBLIC, ACC_SUPER +Constant pool: + #1 = Methodref #12.#32 // java/lang/Object."":()V + #2 = Methodref #11.#33 // org/cmjava2023/Main.oops:()V + #3 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream; + #4 = String #36 // anyway + #5 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V + #6 = Class #39 // java/lang/Throwable + #7 = String #40 // caught e: + #8 = Methodref #6.#41 // java/lang/Throwable.getMessage:()Ljava/lang/String; + #9 = String #42 // Oops + #10 = Methodref #6.#43 // java/lang/Throwable."":(Ljava/lang/String;)V + #11 = Class #44 // org/cmjava2023/Main + #12 = Class #45 // java/lang/Object + #13 = Utf8 + #14 = Utf8 ()V + #15 = Utf8 Code + #16 = Utf8 LineNumberTable + #17 = Utf8 LocalVariableTable + #18 = Utf8 this + #19 = Utf8 Lorg/cmjava2023/Main; + #20 = Utf8 main + #21 = Utf8 ([Ljava/lang/String;)V + #22 = Utf8 e + #23 = Utf8 Ljava/lang/Throwable; + #24 = Utf8 args + #25 = Utf8 [Ljava/lang/String; + #26 = Utf8 StackMapTable + #27 = Class #39 // java/lang/Throwable + #28 = Utf8 oops + #29 = Utf8 Exceptions + #30 = Utf8 SourceFile + #31 = Utf8 Main.java + #32 = NameAndType #13:#14 // "":()V + #33 = NameAndType #28:#14 // oops:()V + #34 = Class #46 // java/lang/System + #35 = NameAndType #47:#48 // out:Ljava/io/PrintStream; + #36 = Utf8 anyway + #37 = Class #49 // java/io/PrintStream + #38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V + #39 = Utf8 java/lang/Throwable + #40 = Utf8 caught e: + #41 = NameAndType #52:#53 // getMessage:()Ljava/lang/String; + #42 = Utf8 Oops + #43 = NameAndType #13:#51 // "":(Ljava/lang/String;)V + #44 = Utf8 org/cmjava2023/Main + #45 = Utf8 java/lang/Object + #46 = Utf8 java/lang/System + #47 = Utf8 out + #48 = Utf8 Ljava/io/PrintStream; + #49 = Utf8 java/io/PrintStream + #50 = Utf8 println + #51 = Utf8 (Ljava/lang/String;)V + #52 = Utf8 getMessage + #53 = Utf8 ()Ljava/lang/String; +{ + public org.cmjava2023.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 3: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lorg/cmjava2023/Main; + + public static void main(java.lang.String[]); + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=3, args_size=1 + 0: invokestatic #2 // Method oops:()V + 3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 6: ldc #4 // String anyway + 8: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 11: goto 55 + 14: astore_1 + 15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 18: ldc #7 // String caught e: + 20: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 23: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 26: aload_1 + 27: invokevirtual #8 // Method java/lang/Throwable.getMessage:()Ljava/lang/String; + 30: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 33: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 36: ldc #4 // String anyway + 38: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 41: goto 55 + 44: astore_2 + 45: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; + 48: ldc #4 // String anyway + 50: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 53: aload_2 + 54: athrow + 55: return + Exception table: + from to target type + 0 3 14 Class java/lang/Throwable + 0 3 44 any + 14 33 44 any + LineNumberTable: + line 6: 0 + line 11: 3 + line 12: 11 + line 7: 14 + line 8: 15 + line 9: 23 + line 11: 33 + line 12: 41 + line 11: 44 + line 12: 53 + line 13: 55 + LocalVariableTable: + Start Length Slot Name Signature + 15 18 1 e Ljava/lang/Throwable; + 0 56 0 args [Ljava/lang/String; + StackMapTable: number_of_entries = 3 + frame_type = 78 /* same_locals_1_stack_item */ + stack = [ class java/lang/Throwable ] + frame_type = 93 /* same_locals_1_stack_item */ + stack = [ class java/lang/Throwable ] + frame_type = 10 /* same */ + + public static void oops() throws java.lang.Throwable; + descriptor: ()V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + 0: new #6 // class java/lang/Throwable + 3: dup + 4: ldc #9 // String Oops + 6: invokespecial #10 // Method java/lang/Throwable."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 16: 0 + Exceptions: + throws java.lang.Throwable +} +SourceFile: "Main.java" diff --git a/tests/exceptions.rs b/tests/exceptions.rs index ce547c7..9f9c72a 100644 --- a/tests/exceptions.rs +++ b/tests/exceptions.rs @@ -1,4 +1,5 @@ // make testfiles in subdir visible mod exceptions { + mod nested; mod simple; } diff --git a/tests/exceptions/nested.rs b/tests/exceptions/nested.rs new file mode 100644 index 0000000..20afb8f --- /dev/null +++ b/tests/exceptions/nested.rs @@ -0,0 +1,14 @@ +use assert_cmd::Command; +use predicates::prelude::predicate; + +#[test] +fn try_catch_finally_throwable() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("cmjava")?; + + cmd.arg("tests/data/exceptions/nested/Main.class"); + cmd.assert() + .success() + .stdout(predicate::str::contains("caught e:\nOops\nanyway\n")); + + Ok(()) +} diff --git a/tests/exceptions/simple.rs b/tests/exceptions/simple.rs index cd6a9aa..668dd3c 100644 --- a/tests/exceptions/simple.rs +++ b/tests/exceptions/simple.rs @@ -6,15 +6,9 @@ fn try_catch_finally_throwable() -> Result<(), Box> { let mut cmd = Command::cargo_bin("cmjava")?; cmd.arg("tests/data/exceptions/simple/Main.class"); - // contains both an assert.failure() and an assert.success(), - // so that failure() can be simply removed when all features - // are implemented - cmd.assert().failure().stderr(predicate::str::contains( - "Missing OpCode implementation for: Athrow", - )); - // cmd.assert() - // .success() - // .stdout(predicate::str::contains("caught e:\nOops\nanyway\n")); + cmd.assert() + .success() + .stdout(predicate::str::contains("caught e:\nOops\nanyway\n")); Ok(()) } From 65e8a2dd4550851564b84595a10df7fe32203de2 Mon Sep 17 00:00:00 2001 From: Florian Warzecha Date: Sun, 7 Jan 2024 15:55:16 +0100 Subject: [PATCH 3/3] test(exceptions): uncaught exceptions Co-authored-by: Julius Bierbaum --- tests/data/exceptions/uncaught/Main.class | Bin 0 -> 663 bytes tests/data/exceptions/uncaught/Main.java | 12 +++ tests/data/exceptions/uncaught/Main.javap | 99 ++++++++++++++++++++++ tests/exceptions.rs | 1 + tests/exceptions/uncaught.rs | 21 +++++ 5 files changed, 133 insertions(+) create mode 100644 tests/data/exceptions/uncaught/Main.class create mode 100644 tests/data/exceptions/uncaught/Main.java create mode 100644 tests/data/exceptions/uncaught/Main.javap create mode 100644 tests/exceptions/uncaught.rs diff --git a/tests/data/exceptions/uncaught/Main.class b/tests/data/exceptions/uncaught/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..50ee827698eef67505d098f51d621c535c66c669 GIT binary patch literal 663 zcmZuu%TC)s6g`ueIC0FQfl$h`h0+F)a0T6gx2{R(h#A0##opO)rS9 zDALj#m$|JOeRf^PYx*yjMh^b{5+EFjEgb^ zoc)_1Y^O@coO0^An*`y3GTW65+0pZzYqObcRtP|btn2uE&pIm%KjR%l% z?^^b3thrdnDo2Ni5ttYNo;0q)Af@cua9J-}>%C4IMm(eQA z3|}Bm52)TF_q~$8gZ1eD#z>{`2lmEeHdZDV*k(Y-XbrT(T_y=M0IxEhGC)~f9NL)1 PTly9;%)T@9b9nb3bpVG% literal 0 HcmV?d00001 diff --git a/tests/data/exceptions/uncaught/Main.java b/tests/data/exceptions/uncaught/Main.java new file mode 100644 index 0000000..c785127 --- /dev/null +++ b/tests/data/exceptions/uncaught/Main.java @@ -0,0 +1,12 @@ +package org.cmjava2023; + +public class Main { + public static void main(String[] args) throws Throwable { + try { + throw new Throwable("Oops"); + } finally { + System.out.println("anyway"); + } + } +} + diff --git a/tests/data/exceptions/uncaught/Main.javap b/tests/data/exceptions/uncaught/Main.javap new file mode 100644 index 0000000..b016863 --- /dev/null +++ b/tests/data/exceptions/uncaught/Main.javap @@ -0,0 +1,99 @@ +Classfile /home/florian/Documents/fh/WS23_24/Compilerbau/vm/main/tests/data/exceptions/uncaught/Main.class + Last modified 07.01.2024; size 663 bytes + MD5 checksum cfde89719e32996de2858e25ba208c0c + Compiled from "Main.java" +public class org.cmjava2023.Main + minor version: 0 + major version: 52 + flags: ACC_PUBLIC, ACC_SUPER +Constant pool: + #1 = Methodref #9.#26 // java/lang/Object."":()V + #2 = Class #27 // java/lang/Throwable + #3 = String #28 // Oops + #4 = Methodref #2.#29 // java/lang/Throwable."":(Ljava/lang/String;)V + #5 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream; + #6 = String #32 // anyway + #7 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V + #8 = Class #35 // org/cmjava2023/Main + #9 = Class #36 // java/lang/Object + #10 = Utf8 + #11 = Utf8 ()V + #12 = Utf8 Code + #13 = Utf8 LineNumberTable + #14 = Utf8 LocalVariableTable + #15 = Utf8 this + #16 = Utf8 Lorg/cmjava2023/Main; + #17 = Utf8 main + #18 = Utf8 ([Ljava/lang/String;)V + #19 = Utf8 args + #20 = Utf8 [Ljava/lang/String; + #21 = Utf8 StackMapTable + #22 = Class #27 // java/lang/Throwable + #23 = Utf8 Exceptions + #24 = Utf8 SourceFile + #25 = Utf8 Main.java + #26 = NameAndType #10:#11 // "":()V + #27 = Utf8 java/lang/Throwable + #28 = Utf8 Oops + #29 = NameAndType #10:#37 // "":(Ljava/lang/String;)V + #30 = Class #38 // java/lang/System + #31 = NameAndType #39:#40 // out:Ljava/io/PrintStream; + #32 = Utf8 anyway + #33 = Class #41 // java/io/PrintStream + #34 = NameAndType #42:#37 // println:(Ljava/lang/String;)V + #35 = Utf8 org/cmjava2023/Main + #36 = Utf8 java/lang/Object + #37 = Utf8 (Ljava/lang/String;)V + #38 = Utf8 java/lang/System + #39 = Utf8 out + #40 = Utf8 Ljava/io/PrintStream; + #41 = Utf8 java/io/PrintStream + #42 = Utf8 println +{ + public org.cmjava2023.Main(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 3: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lorg/cmjava2023/Main; + + public static void main(java.lang.String[]) throws java.lang.Throwable; + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=2, args_size=1 + 0: new #2 // class java/lang/Throwable + 3: dup + 4: ldc #3 // String Oops + 6: invokespecial #4 // Method java/lang/Throwable."":(Ljava/lang/String;)V + 9: athrow + 10: astore_1 + 11: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; + 14: ldc #6 // String anyway + 16: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 19: aload_1 + 20: athrow + Exception table: + from to target type + 0 11 10 any + LineNumberTable: + line 6: 0 + line 8: 10 + line 9: 19 + LocalVariableTable: + Start Length Slot Name Signature + 0 21 0 args [Ljava/lang/String; + StackMapTable: number_of_entries = 1 + frame_type = 74 /* same_locals_1_stack_item */ + stack = [ class java/lang/Throwable ] + Exceptions: + throws java.lang.Throwable +} +SourceFile: "Main.java" diff --git a/tests/exceptions.rs b/tests/exceptions.rs index 9f9c72a..e4a7d7f 100644 --- a/tests/exceptions.rs +++ b/tests/exceptions.rs @@ -2,4 +2,5 @@ mod exceptions { mod nested; mod simple; + mod uncaught; } diff --git a/tests/exceptions/uncaught.rs b/tests/exceptions/uncaught.rs new file mode 100644 index 0000000..069ec41 --- /dev/null +++ b/tests/exceptions/uncaught.rs @@ -0,0 +1,21 @@ +use assert_cmd::Command; +use predicates::prelude::predicate; + +#[test] +fn try_catch_finally_throwable() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("cmjava")?; + + let stderr = predicate::str::is_match( + "thread '[a-zA-Z0-9]*' panicked at .*: +Uncaught exception: instance of Class 'java/lang/Throwable'", + ) + .unwrap(); + + cmd.arg("tests/data/exceptions/uncaught/Main.class"); + cmd.assert() + .failure() + .stderr(stderr) + .stdout(predicate::str::contains("anyway")); + + Ok(()) +}