Skip to content

Commit

Permalink
Merge pull request #101 from cmjava2023/feat/executor-exceptions
Browse files Browse the repository at this point in the history
[executor] Exceptions
  • Loading branch information
liketechnik authored Jan 9, 2024
2 parents 8acee10 + 65e8a2d commit 5254c58
Show file tree
Hide file tree
Showing 17 changed files with 554 additions and 13 deletions.
2 changes: 2 additions & 0 deletions src/class/builtin_classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -13,4 +14,5 @@ pub use crate::class::builtin_classes::{
print_stream::{PrintStream, PrintStreamInstance},
string::{StringClass, StringInstance},
system::SystemClass,
throwable::{ThrowableClass, ThrowableInstance},
};
155 changes: 155 additions & 0 deletions src/class/builtin_classes/throwable.rs
Original file line number Diff line number Diff line change
@@ -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<Rc<Method>>,
}

impl ThrowableClass {
pub fn new() -> Self {
Self {
class_identifier: class_identifier!(java / lang, Throwable),
methods: vec![
Rc::new(Method {
code: MethodCode::Rust(init),
name: "<init>".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: "getMessage".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<dyn ClassInstance> = 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::<ThrowableInstance>() {
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<dyn ClassInstance> = match frame.local_variables.get(0) {
VariableValueOrValue::Reference(s) => s.expect("null pointer"),
_ => panic!("local variables have reference at index 0"),
};
let message: Option<Rc<dyn ClassInstance>> =
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::<ThrowableInstance>() {
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<Method>] {
self.methods.as_slice()
}

fn static_fields(&self) -> &[Rc<Field>] {
&[]
}

fn instance_fields(&self) -> &[FieldDescriptor] {
&[]
}

fn class_identifier(&self) -> &crate::class::ClassIdentifier {
&self.class_identifier
}

fn super_class(&self) -> Option<Rc<dyn Class>> {
None
}

fn interfaces(&self) -> &[Rc<dyn std::any::Any>] {
&[]
}

fn as_any(&self) -> &dyn Any {
self
}

fn new_instance(&self, cls: Rc<dyn Class>) -> Rc<dyn ClassInstance> {
// make sure that self and cls really are equal
let _cls_ref: &Self =
cls.as_ref().as_any().downcast_ref::<Self>().unwrap();

Rc::new(ThrowableInstance {
class: cls.clone(),
message: OnceCell::new(),
})
}
}

pub struct ThrowableInstance {
class: Rc<dyn Class>,
message: OnceCell<Option<Rc<dyn ClassInstance>>>,
}

impl ClassInstance for ThrowableInstance {
fn class(&self) -> Rc<dyn Class> {
self.class.clone()
}

fn as_any(&self) -> &dyn std::any::Any {
self
}

fn instance_fields(&self) -> &[Rc<Field>] {
&[]
}
}
58 changes: 57 additions & 1 deletion src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(&current_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);
},
}
}
}
Expand Down Expand Up @@ -209,6 +263,7 @@ pub enum Update {
Return(ReturnValue),
GoTo(usize, OffsetDirection),
MethodCall { method: Rc<Method>, is_static: bool },
Exception(Rc<dyn ClassInstance>),
}

pub enum ReturnValue {
Expand All @@ -226,6 +281,7 @@ pub enum ReturnValue {
Reference(Option<Rc<dyn ClassInstance>>),
}

#[derive(Debug)]
pub struct Frame {
pub local_variables: LocalVariables,
pub operand_stack: FrameStack,
Expand Down
5 changes: 5 additions & 0 deletions src/executor/frame_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ impl StackValue {
}
}

#[derive(Debug)]
pub struct FrameStack {
values: Vec<StackValue>,
}
Expand Down Expand Up @@ -112,6 +113,10 @@ impl FrameStack {
pub fn pop(&mut self) -> Option<StackValue> {
self.values.pop()
}

pub fn clear(&mut self) {
self.values.clear()
}
}

impl From<FieldValue> for StackValue {
Expand Down
3 changes: 2 additions & 1 deletion src/executor/local_variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
},
};

#[derive(Clone)]
#[derive(Debug, Clone)]
pub enum VariableValue {
// Primitive Types
// Integral Types
Expand Down Expand Up @@ -41,6 +41,7 @@ pub enum VariableValue {
Reference(Option<Rc<dyn ClassInstance>>),
}

#[derive(Debug)]
pub struct LocalVariables {
local_variables: Vec<VariableValue>,
}
Expand Down
7 changes: 7 additions & 0 deletions src/executor/op_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,13 @@ on top of the stack, got: {:?}",
Update::None
},

Self::Athrow => {
let exception: Rc<dyn ClassInstance> =
frame.operand_stack.pop().unwrap().try_into().unwrap();

Update::Exception(exception)
},

Self::Baload => {
let index = frame
.operand_stack
Expand Down
7 changes: 6 additions & 1 deletion src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::class::{
ShortArrayInstance,
},
FileInputStream, InputStream, ObjectClass, PrintStream, StringClass,
StringInstance, SystemClass,
StringInstance, SystemClass, ThrowableClass,
},
ArrayName, Class, ClassIdentifier, ClassName,
};
Expand Down Expand Up @@ -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<ClassIdentifier, Rc<dyn Class>> =
HashMap::new();
Expand All @@ -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(),
Expand Down
Binary file added tests/data/exceptions/nested/Main.class
Binary file not shown.
19 changes: 19 additions & 0 deletions tests/data/exceptions/nested/Main.java
Original file line number Diff line number Diff line change
@@ -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");
}
}

Loading

0 comments on commit 5254c58

Please sign in to comment.