use crate::{
emulation::{
engine::{
cctors::CctorTracker, context::EmulationContext, error::synthetic_exception,
interpreter::Interpreter, EmulationError,
},
exception::{
ExceptionClause, ExceptionInfo, HandlerMatch, InstructionLocation, ThreadExceptionState,
},
memory::AddressSpace,
thread::EmulationThread,
tokens, EmValue,
},
metadata::token::Token,
Result,
};
use log::{debug, trace};
pub fn create_clr_exception(
address_space: &AddressSpace,
error: &EmulationError,
) -> Result<EmValue> {
let type_token = error.to_exception_token();
let heap_ref = address_space.alloc_object(type_token)?;
Ok(EmValue::ObjectRef(heap_ref))
}
pub fn create_exception_from_type(
address_space: &AddressSpace,
exception_type: Token,
) -> Result<EmValue> {
let heap_ref = address_space.alloc_object(exception_type)?;
Ok(EmValue::ObjectRef(heap_ref))
}
pub fn wrap_in_target_invocation_exception(
address_space: &AddressSpace,
inner_exception: &EmValue,
) -> Result<(EmValue, Token)> {
let tie_type = synthetic_exception::TARGET_INVOCATION;
let tie_ref = address_space.alloc_object(tie_type)?;
address_space.set_field(
tie_ref,
tokens::exception_fields::INNER_EXCEPTION,
inner_exception.clone(),
)?;
Ok((EmValue::ObjectRef(tie_ref), tie_type))
}
pub fn route_clr_exception(
address_space: &AddressSpace,
context: &EmulationContext,
interpreter: &mut Interpreter,
thread: &mut EmulationThread,
current_method: Token,
exception_type: Token,
exception: EmValue,
) -> Result<bool> {
let current_offset = interpreter.ip().offset();
let throw_location = InstructionLocation::new(current_method, current_offset);
debug!(
"route_clr_exception: method=0x{:08X} offset=0x{:04X} exc_type=0x{:08X} depth={}",
current_method.value(),
current_offset,
exception_type.value(),
thread.call_depth()
);
if let Some(heap_ref) = exception.as_object_ref() {
let exception_info = ExceptionInfo::new(heap_ref, exception_type, throw_location);
thread.exception_state_mut().set_exception(exception_info);
}
if let Some(handler_match) = find_exception_handler(
context,
current_method,
current_offset,
Some(exception_type),
thread.exception_state_mut(),
None,
)? {
debug!(
" Found handler in current method 0x{:08X}",
current_method.value()
);
thread.stack_mut().clear();
let target_offset = apply_handler_match(
&handler_match,
exception,
current_offset,
current_method,
thread,
)?;
interpreter.set_offset(target_offset);
return Ok(true);
}
debug!(
" No handler in 0x{:08X}, unwinding...",
current_method.value()
);
let mut effective_exception_type = exception_type;
loop {
if let Some(pending) = thread.exception_state_mut().pop_finally() {
interpreter.set_method(pending.method);
interpreter.set_offset(pending.handler_offset);
thread
.exception_state_mut()
.set_leave_target(pending.leave_target);
return Ok(true);
}
let (frame_return_offset, frame_is_reflection, frame_method) = thread
.current_frame()
.map_or((0, false, Token::new(0)), |f| {
(f.return_offset(), f.is_reflection_invoke(), f.method())
});
if let Some(exc_info) = thread.exception_state_mut().exception_mut() {
exc_info.push_stack_frame(InstructionLocation::new(frame_method, frame_return_offset));
}
if frame_is_reflection {
let inner = thread
.exception_state_mut()
.take_exception_as_value()
.unwrap_or_else(|| {
trace!("No pending exception for extraction in reflection frame");
exception.clone()
});
if let Ok((tie_val, tie_type)) =
wrap_in_target_invocation_exception(address_space, &inner)
{
effective_exception_type = tie_type;
if let Some(tie_ref) = tie_val.as_object_ref() {
let throw_loc = InstructionLocation::new(frame_method, frame_return_offset);
thread
.exception_state_mut()
.set_exception(ExceptionInfo::new(tie_ref, tie_type, throw_loc));
}
}
debug!(
" Wrapping exception in TargetInvocationException (reflection invoke boundary)"
);
}
thread.pop_frame();
if thread.call_depth() == 0 {
return Ok(false);
}
let caller_frame = thread
.current_frame()
.ok_or(EmulationError::InternalError {
description: "call stack empty during exception unwinding".to_string(),
})?;
let caller_method = caller_frame.method();
if let Some(handler_match) = find_exception_handler(
context,
caller_method,
frame_return_offset,
Some(effective_exception_type),
thread.exception_state_mut(),
None,
)? {
let exc = thread
.exception_state_mut()
.take_exception_as_value()
.unwrap_or_else(|| {
trace!("No pending exception for extraction");
EmValue::Null
});
thread.stack_mut().clear();
let target_offset = apply_handler_match(
&handler_match,
exc,
frame_return_offset,
caller_method,
thread,
)?;
interpreter.set_method(caller_method);
interpreter.set_offset(target_offset);
return Ok(true);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn find_exception_handler(
context: &EmulationContext,
method_token: Token,
current_offset: u32,
exception_type: Option<Token>,
exception_state: &mut ThreadExceptionState,
skip_handler_offset: Option<u32>,
) -> Result<Option<HandlerMatch>> {
let clauses = if let Some(handlers) = context.get_synthetic_exception_handlers(method_token) {
ExceptionClause::from_metadata_handlers(&handlers)
} else {
let method = context.get_method(method_token)?;
let Some(body) = method.body.get() else {
return Ok(None);
};
ExceptionClause::from_metadata_handlers(&body.exception_handlers)
};
for clause in &clauses {
if !clause.is_in_try(current_offset) {
continue;
}
if let Some(skip_offset) = skip_handler_offset {
if clause.handler_offset() <= skip_offset {
continue;
}
}
match clause {
ExceptionClause::Catch { catch_type, .. } => {
let is_compatible = match exception_type {
Some(exc_token) => context.is_type_compatible(exc_token, *catch_type),
None => true,
};
if is_compatible {
return Ok(Some(HandlerMatch::Catch {
method: method_token,
handler_offset: clause.handler_offset(),
}));
}
}
ExceptionClause::Filter { filter_offset, .. } => {
let handler_offset = clause.handler_offset();
exception_state.enter_filter(handler_offset);
return Ok(Some(HandlerMatch::Filter {
method: method_token,
filter_offset: *filter_offset,
handler_offset,
}));
}
ExceptionClause::Finally { handler_length, .. } => {
exception_state.push_finally(method_token, clause.handler_offset(), None);
_ = handler_length;
}
ExceptionClause::Fault { handler_length, .. } => {
exception_state.push_finally(method_token, clause.handler_offset(), None);
_ = handler_length;
}
}
}
Ok(None)
}
pub fn schedule_finally_blocks(
context: &EmulationContext,
method_token: Token,
current_offset: u32,
leave_target: u32,
exception_state: &mut ThreadExceptionState,
) -> Result<()> {
let method = context.get_method(method_token)?;
let Some(body) = method.body.get() else {
return Ok(());
};
let clauses = ExceptionClause::from_metadata_handlers(&body.exception_handlers);
let mut finally_blocks: Vec<(u32, u32)> = Vec::new();
for clause in &clauses {
if !clause.is_finally() {
continue;
}
let inside_now = clause.is_in_try(current_offset);
let inside_target = clause.is_in_try(leave_target);
if inside_now && !inside_target {
finally_blocks.push((clause.handler_offset(), clause.try_offset()));
}
}
finally_blocks.sort_by_key(|f| std::cmp::Reverse(f.1));
for (i, (handler_offset, _)) in finally_blocks.iter().enumerate() {
let target = if i == finally_blocks.len() - 1 {
Some(leave_target)
} else {
None
};
exception_state.push_finally(method_token, *handler_offset, target);
}
Ok(())
}
pub fn resolve_exception_type(exception: &EmValue, thread: &EmulationThread) -> Option<Token> {
match exception {
EmValue::ObjectRef(href) => thread.heap().get_type_token(*href).ok(),
EmValue::ValueType { type_token, .. } | EmValue::TypedRef { type_token, .. } => {
Some(*type_token)
}
_ => None,
}
}
pub fn apply_handler_match(
handler_match: &HandlerMatch,
exception: EmValue,
origin_offset: u32,
method: Token,
thread: &mut EmulationThread,
) -> Result<u32> {
match handler_match {
HandlerMatch::Catch { handler_offset, .. } => {
thread.push(exception)?;
thread.exception_state_mut().enter_catch_handler(
method,
origin_offset,
*handler_offset,
);
Ok(*handler_offset)
}
HandlerMatch::Filter { filter_offset, .. } => {
thread.push(exception)?;
Ok(*filter_offset)
}
HandlerMatch::Finally { handler_offset, .. }
| HandlerMatch::Fault { handler_offset, .. } => Ok(*handler_offset),
}
}
pub fn wrap_in_type_initialization_exception(
address_space: &AddressSpace,
inner_exception: &EmValue,
type_name: &str,
) -> Result<(EmValue, Token)> {
let tie_type = synthetic_exception::TYPE_INITIALIZATION;
let tie_ref = address_space.alloc_object(tie_type)?;
address_space.set_field(
tie_ref,
tokens::exception_fields::MESSAGE,
EmValue::ObjectRef(address_space.heap().alloc_string(type_name)?),
)?;
address_space.set_field(
tie_ref,
tokens::exception_fields::INNER_EXCEPTION,
inner_exception.clone(),
)?;
Ok((EmValue::ObjectRef(tie_ref), tie_type))
}
pub fn track_cctor_failure_if_needed(
cctor_tracker: &CctorTracker,
thread: &EmulationThread,
exception: &EmValue,
context: &EmulationContext,
) -> Result<()> {
let Some(frame) = thread.current_frame() else {
return Ok(());
};
if !frame.is_cctor() {
return Ok(());
}
let cctor_token = frame.method();
let Some(type_token) = context
.assembly()
.resolver()
.declaring_type(cctor_token)
.map(|t| t.token)
else {
return Ok(());
};
let exception_ref = match exception {
EmValue::ObjectRef(href) => *href,
other => {
let desc = format!("TypeInitializationException: {other}");
thread.heap().alloc_string(&desc)?
}
};
cctor_tracker.mark_type_failed(type_token, exception_ref)?;
Ok(())
}