use crate::{
emulation::{exception::types::InstructionLocation, EmValue, HeapRef},
metadata::token::Token,
};
#[derive(Clone, Debug)]
pub struct ExceptionInfo {
pub exception_ref: HeapRef,
pub type_token: Token,
pub throw_location: InstructionLocation,
pub stack_trace: Vec<InstructionLocation>,
}
impl ExceptionInfo {
#[must_use]
pub fn new(
exception_ref: HeapRef,
type_token: Token,
throw_location: InstructionLocation,
) -> Self {
Self {
exception_ref,
type_token,
throw_location,
stack_trace: vec![throw_location],
}
}
pub fn push_stack_frame(&mut self, location: InstructionLocation) {
self.stack_trace.push(location);
}
#[must_use]
pub fn as_value(&self) -> EmValue {
EmValue::ObjectRef(self.exception_ref)
}
}
#[derive(Clone, Debug)]
pub struct PendingFinally {
pub method: Token,
pub handler_offset: u32,
pub leave_target: Option<u32>,
}
#[derive(Clone, Debug, Default)]
pub struct ThreadExceptionState {
current_exception: Option<ExceptionInfo>,
pending_finally: Vec<PendingFinally>,
handling_exception: bool,
rethrow_requested: bool,
leave_target: Option<u32>,
in_filter: bool,
filter_handler_offset: Option<u32>,
filter_result: Option<bool>,
exception_origin_offset: Option<u32>,
current_handler_offset: Option<u32>,
}
impl ThreadExceptionState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_exception(&self) -> bool {
self.current_exception.is_some()
}
#[must_use]
pub fn exception(&self) -> Option<&ExceptionInfo> {
self.current_exception.as_ref()
}
#[must_use]
pub fn get_exception_value(&self) -> Option<EmValue> {
self.current_exception.as_ref().map(ExceptionInfo::as_value)
}
pub fn set_exception(&mut self, exception: ExceptionInfo) {
self.current_exception = Some(exception);
self.handling_exception = true;
}
pub fn take_exception(&mut self) -> Option<ExceptionInfo> {
self.handling_exception = false;
self.current_exception.take()
}
pub fn take_exception_as_value(&mut self) -> Option<EmValue> {
self.handling_exception = false;
self.current_exception.take().map(|info| info.as_value())
}
pub fn clear(&mut self) {
self.current_exception = None;
self.handling_exception = false;
self.rethrow_requested = false;
self.pending_finally.clear();
self.leave_target = None;
self.in_filter = false;
self.filter_handler_offset = None;
self.filter_result = None;
self.exception_origin_offset = None;
self.current_handler_offset = None;
}
#[must_use]
pub fn is_handling(&self) -> bool {
self.handling_exception
}
pub fn push_finally(&mut self, method: Token, handler_offset: u32, leave_target: Option<u32>) {
self.pending_finally.push(PendingFinally {
method,
handler_offset,
leave_target,
});
}
pub fn pop_finally(&mut self) -> Option<PendingFinally> {
self.pending_finally.pop()
}
#[must_use]
pub fn has_pending_finally(&self) -> bool {
!self.pending_finally.is_empty()
}
#[must_use]
pub fn pending_finally_count(&self) -> usize {
self.pending_finally.len()
}
pub fn request_rethrow(&mut self) {
self.rethrow_requested = true;
}
pub fn take_rethrow_request(&mut self) -> bool {
let was_requested = self.rethrow_requested;
self.rethrow_requested = false;
was_requested
}
pub fn enter_catch(&mut self) {
self.current_exception = None;
self.handling_exception = false;
}
pub fn enter_finally(&mut self) {
}
pub fn exit_finally(&mut self, exception_to_restore: Option<ExceptionInfo>) {
if self.rethrow_requested {
self.handling_exception = exception_to_restore.is_some();
self.current_exception = exception_to_restore;
}
self.rethrow_requested = false;
}
#[must_use]
pub fn leave_target(&self) -> Option<u32> {
self.leave_target
}
pub fn set_leave_target(&mut self, target: Option<u32>) {
self.leave_target = target;
}
pub fn take_leave_target(&mut self) -> Option<u32> {
self.leave_target.take()
}
#[must_use]
pub fn in_filter(&self) -> bool {
self.in_filter
}
pub fn enter_filter(&mut self, handler_offset: u32) {
self.in_filter = true;
self.filter_handler_offset = Some(handler_offset);
}
pub fn set_in_filter(&mut self, in_filter: bool) {
self.in_filter = in_filter;
if !in_filter {
self.filter_handler_offset = None;
}
}
#[must_use]
pub fn filter_handler_offset(&self) -> Option<u32> {
self.filter_handler_offset
}
#[must_use]
pub fn filter_result(&self) -> Option<bool> {
self.filter_result
}
pub fn set_filter_result(&mut self, result: Option<bool>) {
self.filter_result = result;
}
#[must_use]
pub fn exception_origin_offset(&self) -> Option<u32> {
self.exception_origin_offset
}
#[must_use]
pub fn current_handler_offset(&self) -> Option<u32> {
self.current_handler_offset
}
pub fn enter_catch_handler(&mut self, origin_offset: u32, handler_offset: u32) {
self.exception_origin_offset = Some(origin_offset);
self.current_handler_offset = Some(handler_offset);
}
pub fn leave_catch_handler(&mut self) {
self.exception_origin_offset = None;
self.current_handler_offset = None;
}
pub fn clear_exception(&mut self) {
self.filter_result = None;
self.exception_origin_offset = None;
self.current_handler_offset = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exception_state_new() {
let state = ThreadExceptionState::new();
assert!(!state.has_exception());
assert!(!state.is_handling());
}
#[test]
fn test_exception_state_set_and_take() {
let mut state = ThreadExceptionState::new();
let heap_ref = HeapRef::new(42);
let throw_loc = InstructionLocation::new(Token::new(0x06000001), 10);
let exception_info = ExceptionInfo::new(heap_ref, Token::new(0x02000001), throw_loc);
state.set_exception(exception_info);
assert!(state.has_exception());
assert!(state.is_handling());
let value = state.get_exception_value();
assert!(value.is_some());
assert!(matches!(value, Some(EmValue::ObjectRef(_))));
let taken = state.take_exception_as_value();
assert!(taken.is_some());
assert!(!state.has_exception());
}
#[test]
fn test_pending_finally() {
let mut state = ThreadExceptionState::new();
let method = Token::new(0x06000001);
assert!(!state.has_pending_finally());
state.push_finally(method, 0x50, Some(0x100));
state.push_finally(method, 0x80, None);
assert!(state.has_pending_finally());
assert_eq!(state.pending_finally_count(), 2);
let f1 = state.pop_finally().unwrap();
assert_eq!(f1.handler_offset, 0x80);
assert!(f1.leave_target.is_none());
let f2 = state.pop_finally().unwrap();
assert_eq!(f2.handler_offset, 0x50);
assert_eq!(f2.leave_target, Some(0x100));
assert!(!state.has_pending_finally());
}
#[test]
fn test_rethrow() {
let mut state = ThreadExceptionState::new();
assert!(!state.take_rethrow_request());
state.request_rethrow();
assert!(state.take_rethrow_request());
assert!(!state.take_rethrow_request()); }
#[test]
fn test_clear() {
let mut state = ThreadExceptionState::new();
let method = Token::new(0x06000001);
let heap_ref = HeapRef::new(42);
let throw_loc = InstructionLocation::new(method, 10);
let exception_info = ExceptionInfo::new(heap_ref, Token::new(0x02000001), throw_loc);
state.set_exception(exception_info);
state.push_finally(method, 0x50, None);
state.request_rethrow();
state.clear();
assert!(!state.has_exception());
assert!(!state.has_pending_finally());
assert!(!state.take_rethrow_request());
}
}