use crate::{
emulation::{
exception::{
ExceptionClause, ExceptionInfo, HandlerMatch, InstructionLocation, ThreadExceptionState,
},
thread::ThreadCallFrame,
},
metadata::token::Token,
};
#[derive(Clone, Debug)]
pub enum UnwindStepResult {
ExecuteHandler {
handler: HandlerMatch,
is_catch: bool,
},
ContinueUnwind,
UnhandledException,
HandlerEntered {
method: Token,
offset: u32,
},
}
#[derive(Clone, Debug, Default)]
pub struct StackUnwinder {
state: UnwindState,
pending_handlers: Vec<PendingHandler>,
stack_trace: Vec<InstructionLocation>,
}
#[derive(Clone, Debug, Default)]
enum UnwindState {
#[default]
Idle,
Searching {
exception_type: Token,
},
ExecutingCleanup {
exception_type: Token,
target: Option<HandlerTarget>,
},
HandlerFound {
target: HandlerTarget,
},
}
#[derive(Clone, Debug)]
struct PendingHandler {
handler: HandlerMatch,
method: Token,
}
#[derive(Clone, Debug)]
struct HandlerTarget {
method: Token,
offset: u32,
handler_type: HandlerType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum HandlerType {
Catch,
Filter,
}
impl StackUnwinder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_unwinding(&self) -> bool {
!matches!(self.state, UnwindState::Idle)
}
pub fn begin_unwind(&mut self, exception_info: &ExceptionInfo) {
self.state = UnwindState::Searching {
exception_type: exception_info.type_token,
};
self.pending_handlers.clear();
self.stack_trace.clear();
self.stack_trace.push(exception_info.throw_location);
}
pub fn process_frame<F>(
&mut self,
frame: &ThreadCallFrame,
clauses: &[ExceptionClause],
exception_type: Token,
is_type_compatible: F,
) -> UnwindStepResult
where
F: Fn(Token, Token) -> bool,
{
let current_offset = frame.ip();
let method = frame.method();
self.stack_trace
.push(InstructionLocation::new(method, current_offset));
let mut finally_handlers = Vec::new();
let mut fault_handlers = Vec::new();
let mut catch_handler = None;
let mut filter_handler = None;
for clause in clauses {
if !clause.is_in_try(current_offset) {
continue;
}
match clause {
ExceptionClause::Catch { catch_type, .. } => {
if is_type_compatible(exception_type, *catch_type) && catch_handler.is_none() {
catch_handler = Some(HandlerMatch::Catch {
method,
handler_offset: clause.handler_offset(),
});
}
}
ExceptionClause::Filter { filter_offset, .. } => {
if filter_handler.is_none() {
filter_handler = Some(HandlerMatch::Filter {
method,
filter_offset: *filter_offset,
handler_offset: clause.handler_offset(),
});
}
}
ExceptionClause::Finally { .. } => {
finally_handlers.push(HandlerMatch::Finally {
method,
handler_offset: clause.handler_offset(),
handler_length: clause.handler_length(),
continue_search_after: true,
});
}
ExceptionClause::Fault { .. } => {
fault_handlers.push(HandlerMatch::Fault {
method,
handler_offset: clause.handler_offset(),
handler_length: clause.handler_length(),
});
}
}
}
if let Some(filter) = filter_handler {
for h in finally_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
for h in fault_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
return UnwindStepResult::ExecuteHandler {
handler: filter,
is_catch: false, };
}
if let Some(catch) = catch_handler {
for h in finally_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
for h in fault_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
if !self.pending_handlers.is_empty() {
self.state = UnwindState::ExecutingCleanup {
exception_type,
target: Some(HandlerTarget {
method,
offset: match &catch {
HandlerMatch::Catch { handler_offset, .. } => *handler_offset,
_ => unreachable!(),
},
handler_type: HandlerType::Catch,
}),
};
let next = self.pending_handlers.remove(0);
return UnwindStepResult::ExecuteHandler {
handler: next.handler,
is_catch: false,
};
}
return UnwindStepResult::ExecuteHandler {
handler: catch,
is_catch: true,
};
}
for h in finally_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
for h in fault_handlers {
self.pending_handlers
.push(PendingHandler { handler: h, method });
}
if !self.pending_handlers.is_empty() {
self.state = UnwindState::ExecutingCleanup {
exception_type,
target: None,
};
let next = self.pending_handlers.remove(0);
return UnwindStepResult::ExecuteHandler {
handler: next.handler,
is_catch: false,
};
}
UnwindStepResult::ContinueUnwind
}
pub fn cleanup_complete(&mut self) -> UnwindStepResult {
if !self.pending_handlers.is_empty() {
let next = self.pending_handlers.remove(0);
return UnwindStepResult::ExecuteHandler {
handler: next.handler,
is_catch: false,
};
}
if let UnwindState::ExecutingCleanup {
target: Some(target),
..
} = &self.state
{
let result = UnwindStepResult::HandlerEntered {
method: target.method,
offset: target.offset,
};
self.state = UnwindState::Idle;
return result;
}
UnwindStepResult::ContinueUnwind
}
pub fn filter_complete(
&mut self,
accepted: bool,
handler_offset: u32,
method: Token,
) -> UnwindStepResult {
if accepted {
if !self.pending_handlers.is_empty() {
if let UnwindState::Searching { exception_type } = self.state {
self.state = UnwindState::ExecutingCleanup {
exception_type,
target: Some(HandlerTarget {
method,
offset: handler_offset,
handler_type: HandlerType::Filter,
}),
};
}
let next = self.pending_handlers.remove(0);
return UnwindStepResult::ExecuteHandler {
handler: next.handler,
is_catch: false,
};
}
UnwindStepResult::HandlerEntered {
method,
offset: handler_offset,
}
} else {
UnwindStepResult::ContinueUnwind
}
}
pub fn complete_unhandled(&mut self) -> UnwindStepResult {
self.state = UnwindState::Idle;
UnwindStepResult::UnhandledException
}
pub fn reset(&mut self) {
self.state = UnwindState::Idle;
self.pending_handlers.clear();
self.stack_trace.clear();
}
#[must_use]
pub fn stack_trace(&self) -> &[InstructionLocation] {
&self.stack_trace
}
pub fn enter_catch_handler(exception_state: &mut ThreadExceptionState) {
exception_state.enter_catch();
}
pub fn enter_finally_handler(exception_state: &mut ThreadExceptionState) {
exception_state.enter_finally();
}
pub fn exit_finally_handler(
exception_state: &mut ThreadExceptionState,
saved_exception: Option<ExceptionInfo>,
) {
exception_state.exit_finally(saved_exception);
}
}
#[derive(Clone, Debug, Default)]
pub struct UnwindSequenceBuilder {
handlers: Vec<HandlerMatch>,
}
impl UnwindSequenceBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_handler(&mut self, handler: HandlerMatch) {
self.handlers.push(handler);
}
pub fn add_handlers(&mut self, handlers: impl IntoIterator<Item = HandlerMatch>) {
self.handlers.extend(handlers);
}
#[must_use]
pub fn build(self) -> Vec<HandlerMatch> {
self.handlers
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.handlers.len()
}
}
#[cfg(test)]
mod tests {
use crate::{emulation::HeapRef, metadata::typesystem::CilFlavor};
use super::*;
fn create_test_exception() -> ExceptionInfo {
ExceptionInfo::new(
HeapRef::new(1),
Token::new(0x01000010),
InstructionLocation::new(Token::new(0x06000001), 0x20),
)
}
fn create_test_frame(method: Token, offset: u32) -> ThreadCallFrame {
let mut frame = ThreadCallFrame::new(method, None, 0, vec![CilFlavor::I4], vec![], false);
frame.set_ip(offset);
frame
}
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_unwinder_begin() {
let mut unwinder = StackUnwinder::new();
let exception = create_test_exception();
assert!(!unwinder.is_unwinding());
unwinder.begin_unwind(&exception);
assert!(unwinder.is_unwinding());
assert_eq!(unwinder.stack_trace().len(), 1);
}
#[test]
fn test_unwinder_reset() {
let mut unwinder = StackUnwinder::new();
let exception = create_test_exception();
unwinder.begin_unwind(&exception);
assert!(unwinder.is_unwinding());
unwinder.reset();
assert!(!unwinder.is_unwinding());
assert!(unwinder.stack_trace().is_empty());
}
#[test]
fn test_process_frame_with_catch() {
let mut unwinder = StackUnwinder::new();
let exception = create_test_exception();
unwinder.begin_unwind(&exception);
let method = Token::new(0x06000001);
let frame = create_test_frame(method, 0x15);
let clauses = vec![ExceptionClause::Catch {
try_offset: 0x10,
try_length: 0x20,
handler_offset: 0x30,
handler_length: 0x10,
catch_type: Token::new(0x0100_0001), }];
let result =
unwinder.process_frame(&frame, &clauses, exception.type_token, test_type_checker);
match result {
UnwindStepResult::ExecuteHandler { is_catch, .. } => {
assert!(is_catch);
}
_ => panic!("Expected catch handler"),
}
}
#[test]
fn test_process_frame_with_finally() {
let mut unwinder = StackUnwinder::new();
let exception = create_test_exception();
unwinder.begin_unwind(&exception);
let method = Token::new(0x06000001);
let frame = create_test_frame(method, 0x15);
let clauses = vec![ExceptionClause::Finally {
try_offset: 0x10,
try_length: 0x20,
handler_offset: 0x30,
handler_length: 0x10,
}];
let result =
unwinder.process_frame(&frame, &clauses, exception.type_token, test_type_checker);
match result {
UnwindStepResult::ExecuteHandler { handler, is_catch } => {
assert!(!is_catch);
assert!(matches!(handler, HandlerMatch::Finally { .. }));
}
_ => panic!("Expected finally handler"),
}
}
#[test]
fn test_process_frame_continue_unwind() {
let mut unwinder = StackUnwinder::new();
let exception = create_test_exception();
unwinder.begin_unwind(&exception);
let method = Token::new(0x06000001);
let frame = create_test_frame(method, 0x50);
let clauses = vec![ExceptionClause::Catch {
try_offset: 0x10,
try_length: 0x20,
handler_offset: 0x30,
handler_length: 0x10,
catch_type: Token::new(0x01000010),
}];
let result =
unwinder.process_frame(&frame, &clauses, exception.type_token, test_type_checker);
assert!(matches!(result, UnwindStepResult::ContinueUnwind));
}
#[test]
fn test_cleanup_complete() {
let mut unwinder = StackUnwinder::new();
let result = unwinder.cleanup_complete();
assert!(matches!(result, UnwindStepResult::ContinueUnwind));
}
#[test]
fn test_unwind_sequence_builder() {
let mut builder = UnwindSequenceBuilder::new();
assert!(builder.is_empty());
builder.add_handler(HandlerMatch::Finally {
method: Token::new(0x06000001),
handler_offset: 0x30,
handler_length: 0x10,
continue_search_after: true,
});
builder.add_handler(HandlerMatch::Catch {
method: Token::new(0x06000001),
handler_offset: 0x40,
});
assert_eq!(builder.len(), 2);
let sequence = builder.build();
assert_eq!(sequence.len(), 2);
}
}