use std::ffi::c_void;
unsafe extern "C-unwind" {
fn mb_seh_guard(
body: extern "C-unwind" fn(*mut c_void),
ctx: *mut c_void,
code: *mut u32,
) -> i32;
}
struct Slot<F, T> {
body: Option<F>,
out: Option<T>,
}
extern "C-unwind" fn run<F: FnOnce() -> T, T>(ctx: *mut c_void) {
let slot = unsafe { &mut *ctx.cast::<Slot<F, T>>() };
let body = slot
.body
.take()
.expect("the frame calls the body once, after `guard` filled the slot");
slot.out = Some(body());
}
pub(super) fn guard<T, F: FnOnce() -> T>(body: F) -> Result<T, u32> {
let mut slot = Slot {
body: Some(body),
out: None,
};
let mut code = 0u32;
let caught =
unsafe { mb_seh_guard(run::<F, T>, (&raw mut slot).cast::<c_void>(), &raw mut code) };
if caught == 0 {
Ok(slot
.out
.expect("a returning frame always stored the body's value"))
} else {
Err(code)
}
}
#[cfg(test)]
mod tests {
use super::guard;
#[test]
fn a_fault_inside_the_guard_is_caught() {
let caught = guard(|| unsafe { std::ptr::read_volatile(std::ptr::null::<u8>()) });
assert!(caught.is_err(), "the shim must report the fault");
assert_eq!(guard(|| 42), Ok(42));
}
#[cfg(all(target_env = "msvc", target_pointer_width = "64"))]
#[test]
fn a_foreign_cpp_exception_becomes_a_fault() {
#[repr(C)]
struct ThrowInfo {
attributes: u32,
unwind: u32,
forward_compat: u32,
catchable_types: u32,
}
#[repr(C)]
struct CatchableTypes {
count: i32,
first: u32,
}
#[repr(C)]
struct CatchableType {
properties: u32,
descriptor: u32,
}
#[repr(C)]
struct TypeDescriptor {
vtable: usize,
spare: usize,
name: [u8; 11],
}
unsafe extern "system" {
fn RaiseException(code: u32, flags: u32, count: u32, arguments: *const usize);
}
let descriptor = TypeDescriptor {
vtable: 0,
spare: 0,
name: *b"foreign_ex\0",
};
let mut catchable = CatchableType {
properties: 0,
descriptor: 0,
};
let mut types = CatchableTypes { count: 1, first: 0 };
let mut info = ThrowInfo {
attributes: 0,
unwind: 0,
forward_compat: 0,
catchable_types: 0,
};
let thrown = 0x2A_u64;
let addresses = [
std::ptr::from_ref(&descriptor).addr(),
std::ptr::from_ref(&catchable).addr(),
std::ptr::from_ref(&types).addr(),
std::ptr::from_ref(&info).addr(),
];
let base = addresses.into_iter().min().unwrap();
let offset = |address: usize| u32::try_from(address - base).unwrap();
catchable.descriptor = offset(std::ptr::from_ref(&descriptor).addr());
types.first = offset(std::ptr::from_ref(&catchable).addr());
info.catchable_types = offset(std::ptr::from_ref(&types).addr());
let arguments = [
0x1993_0520usize, std::ptr::from_ref(&thrown).addr(),
std::ptr::from_ref(&info).addr(),
base,
];
let caught = guard(|| unsafe { RaiseException(0xE06D_7363, 0, 4, arguments.as_ptr()) });
assert_eq!(caught.unwrap_err(), 0xE06D_7363);
}
#[cfg(not(target_env = "msvc"))]
#[test]
fn a_foreign_cpp_exception_becomes_a_fault() {
unsafe extern "system" {
fn RaiseException(code: u32, flags: u32, count: u32, arguments: *const usize);
}
let object: [u64; 1] = [u64::from_be_bytes(*b"GNUCC++\0")];
let arguments = [object.as_ptr().addr()];
let caught = guard(|| unsafe { RaiseException(0x2047_4343, 0, 1, arguments.as_ptr()) });
assert_eq!(caught.unwrap_err(), 0x2047_4343);
}
#[test]
fn a_rust_panic_passes_through_the_frame() {
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
guard(|| panic!("raised inside the frame"))
}));
assert!(
caught.is_err(),
"the panic must reach Rust's handler, not become a fault"
);
}
}