use crate::{
emulation::exception::{ExceptionClause, HandlerMatch, InstructionLocation},
metadata::token::Token,
};
#[derive(Clone, Copy, Debug, Default)]
pub struct ExceptionHandler;
#[derive(Clone, Debug)]
pub enum MethodHandlerResult {
Found(HandlerMatch),
ExecuteCleanup {
handlers: Vec<HandlerMatch>,
},
NotFound,
}
impl ExceptionHandler {
#[must_use]
pub fn new() -> Self {
Self
}
pub fn find_handler<F>(
&self,
clauses: &[ExceptionClause],
throw_offset: u32,
exception_type: Token,
method: Token,
is_type_compatible: F,
) -> MethodHandlerResult
where
F: Fn(Token, Token) -> bool,
{
let mut cleanup_handlers = Vec::new();
let mut found_catch = None;
for clause in clauses {
if !clause.is_in_try(throw_offset) {
continue;
}
match clause {
ExceptionClause::Catch { catch_type, .. } => {
if is_type_compatible(exception_type, *catch_type) {
found_catch = Some(HandlerMatch::Catch {
method,
handler_offset: clause.handler_offset(),
});
break;
}
}
ExceptionClause::Filter { filter_offset, .. } => {
found_catch = Some(HandlerMatch::Filter {
method,
filter_offset: *filter_offset,
handler_offset: clause.handler_offset(),
});
break;
}
ExceptionClause::Finally { .. } => {
cleanup_handlers.push(HandlerMatch::Finally {
method,
handler_offset: clause.handler_offset(),
handler_length: clause.handler_length(),
continue_search_after: true,
});
}
ExceptionClause::Fault { .. } => {
cleanup_handlers.push(HandlerMatch::Fault {
method,
handler_offset: clause.handler_offset(),
handler_length: clause.handler_length(),
});
}
}
}
if let Some(catch) = found_catch {
if cleanup_handlers.is_empty() {
return MethodHandlerResult::Found(catch);
}
cleanup_handlers.push(catch);
return MethodHandlerResult::ExecuteCleanup {
handlers: cleanup_handlers,
};
}
if cleanup_handlers.is_empty() {
MethodHandlerResult::NotFound
} else {
MethodHandlerResult::ExecuteCleanup {
handlers: cleanup_handlers,
}
}
}
#[must_use]
pub fn find_finally_for_leave(
&self,
clauses: &[ExceptionClause],
leave_offset: u32,
target_offset: u32,
method: Token,
) -> Vec<HandlerMatch> {
let mut handlers = Vec::new();
for clause in clauses {
if !clause.is_finally() {
continue;
}
let in_try = clause.is_in_try(leave_offset);
let target_in_try = clause.is_in_try(target_offset);
if in_try && !target_in_try {
handlers.push(HandlerMatch::Finally {
method,
handler_offset: clause.handler_offset(),
handler_length: clause.handler_length(),
continue_search_after: false, });
}
}
handlers
}
#[must_use]
pub fn is_in_handler(&self, clauses: &[ExceptionClause], offset: u32) -> bool {
clauses.iter().any(|c| c.is_in_handler(offset))
}
#[must_use]
pub fn get_handler_clause<'a>(
&self,
clauses: &'a [ExceptionClause],
offset: u32,
) -> Option<&'a ExceptionClause> {
clauses.iter().find(|c| c.is_in_handler(offset))
}
#[must_use]
pub fn build_stack_trace(locations: &[InstructionLocation]) -> String {
locations
.iter()
.map(|loc| format!(" at {loc}"))
.collect::<Vec<_>>()
.join("\n")
}
}
#[derive(Clone, Debug)]
pub struct HandlerSearchState {
pub exception_type: Token,
pub current_location: InstructionLocation,
pub frames: Vec<FrameSearchInfo>,
pub current_frame: usize,
pub pending_cleanup: Vec<HandlerMatch>,
pub handler_found: Option<HandlerMatch>,
}
#[derive(Clone, Debug)]
pub struct FrameSearchInfo {
pub method: Token,
pub offset: u32,
pub clauses: Vec<ExceptionClause>,
}
impl HandlerSearchState {
#[must_use]
pub fn new(exception_type: Token, throw_location: InstructionLocation) -> Self {
Self {
exception_type,
current_location: throw_location,
frames: Vec::new(),
current_frame: 0,
pending_cleanup: Vec::new(),
handler_found: None,
}
}
pub fn add_frame(&mut self, method: Token, offset: u32, clauses: Vec<ExceptionClause>) {
self.frames.push(FrameSearchInfo {
method,
offset,
clauses,
});
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.handler_found.is_some()
|| (!self.frames.is_empty() && self.current_frame >= self.frames.len())
}
#[must_use]
pub fn has_pending_cleanup(&self) -> bool {
!self.pending_cleanup.is_empty()
}
pub fn take_next_cleanup(&mut self) -> Option<HandlerMatch> {
if self.pending_cleanup.is_empty() {
None
} else {
Some(self.pending_cleanup.remove(0))
}
}
}
#[cfg(test)]
mod tests {
use crate::{
emulation::exception::{
ExceptionClause, ExceptionHandler, HandlerMatch, HandlerSearchState,
InstructionLocation, MethodHandlerResult,
},
metadata::token::Token,
};
fn create_test_clauses() -> Vec<ExceptionClause> {
vec![
ExceptionClause::Catch {
try_offset: 0x10,
try_length: 0x20,
handler_offset: 0x30,
handler_length: 0x10,
catch_type: Token::new(0x01000010), },
ExceptionClause::Finally {
try_offset: 0x00,
try_length: 0x50,
handler_offset: 0x50,
handler_length: 0x10,
},
ExceptionClause::Catch {
try_offset: 0x00,
try_length: 0x50,
handler_offset: 0x60,
handler_length: 0x10,
catch_type: Token::new(0x0100_0001), },
]
}
fn test_type_checker(exception_type: Token, catch_type: Token) -> bool {
if exception_type == catch_type {
return true;
}
if catch_type.value() == 0x0100_0001 {
return true;
}
false
}
#[test]
fn test_find_matching_catch() {
let handler = ExceptionHandler::new();
let clauses = create_test_clauses();
let method = Token::new(0x06000001);
let result = handler.find_handler(
&clauses,
0x15,
Token::new(0x01000010),
method,
test_type_checker,
);
match result {
MethodHandlerResult::Found(HandlerMatch::Catch { handler_offset, .. }) => {
assert_eq!(handler_offset, 0x30);
}
_ => panic!("Expected to find catch handler"),
}
}
#[test]
fn test_find_base_exception_catch() {
let handler = ExceptionHandler::new();
let clauses = create_test_clauses();
let method = Token::new(0x06000001);
let result = handler.find_handler(
&clauses,
0x05,
Token::new(0x01000099),
method,
test_type_checker,
);
match result {
MethodHandlerResult::ExecuteCleanup { handlers } => {
assert_eq!(handlers.len(), 2);
assert!(matches!(handlers[0], HandlerMatch::Finally { .. }));
assert!(matches!(handlers[1], HandlerMatch::Catch { .. }));
}
_ => panic!("Expected cleanup handlers"),
}
}
#[test]
fn test_find_finally_for_leave() {
let handler = ExceptionHandler::new();
let clauses = create_test_clauses();
let method = Token::new(0x06000001);
let handlers = handler.find_finally_for_leave(&clauses, 0x20, 0x70, method);
assert_eq!(handlers.len(), 1);
match &handlers[0] {
HandlerMatch::Finally { handler_offset, .. } => {
assert_eq!(*handler_offset, 0x50);
}
_ => panic!("Expected finally handler"),
}
}
#[test]
fn test_no_handler_found() {
let handler = ExceptionHandler::new();
let clauses = vec![ExceptionClause::Catch {
try_offset: 0x00,
try_length: 0x10,
handler_offset: 0x10,
handler_length: 0x10,
catch_type: Token::new(0x01000010),
}];
let method = Token::new(0x06000001);
let result = handler.find_handler(
&clauses,
0x50,
Token::new(0x01000010),
method,
test_type_checker,
);
assert!(matches!(result, MethodHandlerResult::NotFound));
}
#[test]
fn test_filter_handler() {
let handler = ExceptionHandler::new();
let clauses = vec![ExceptionClause::Filter {
try_offset: 0x00,
try_length: 0x20,
handler_offset: 0x30,
handler_length: 0x10,
filter_offset: 0x20,
}];
let method = Token::new(0x06000001);
let result = handler.find_handler(
&clauses,
0x10,
Token::new(0x01000010),
method,
test_type_checker,
);
match result {
MethodHandlerResult::Found(HandlerMatch::Filter {
filter_offset,
handler_offset,
..
}) => {
assert_eq!(filter_offset, 0x20);
assert_eq!(handler_offset, 0x30);
}
_ => panic!("Expected filter handler"),
}
}
#[test]
fn test_is_in_handler() {
let handler = ExceptionHandler::new();
let clauses = create_test_clauses();
assert!(!handler.is_in_handler(&clauses, 0x15)); assert!(handler.is_in_handler(&clauses, 0x35)); assert!(handler.is_in_handler(&clauses, 0x55)); }
#[test]
fn test_handler_search_state() {
let exception_type = Token::new(0x01000010);
let location = InstructionLocation::new(Token::new(0x06000001), 0x20);
let mut state = HandlerSearchState::new(exception_type, location);
assert!(!state.is_complete());
assert!(!state.has_pending_cleanup());
state.handler_found = Some(HandlerMatch::Catch {
method: Token::new(0x06000001),
handler_offset: 0x30,
});
assert!(state.is_complete());
}
}