#[allow(dead_code, reason = "This is only used by some of the backends")]
pub unsafe trait ThrowByPointer {
type ExceptionHeader;
fn new_header() -> Self::ExceptionHeader;
unsafe fn throw(ex: *mut Self::ExceptionHeader) -> !;
#[allow(
clippy::missing_errors_doc,
reason = "`Err` value is described immediately"
)]
fn intercept<Func: FnOnce() -> R, R>(func: Func) -> Result<R, *mut Self::ExceptionHeader>;
}
pub unsafe trait ThrowByValue {
type RethrowHandle<E>: RethrowHandle;
unsafe fn throw<E>(cause: E) -> !;
#[allow(
clippy::missing_errors_doc,
reason = "`Err` value is described immediately"
)]
unsafe fn intercept<Func: FnOnce() -> R, R, E>(
func: Func,
) -> Result<R, (E, Self::RethrowHandle<E>)>;
}
pub trait RethrowHandle {
unsafe fn rethrow<F>(self, new_cause: F) -> !;
}
#[cfg(backend = "itanium")]
#[path = "itanium.rs"]
mod imp;
#[cfg(backend = "seh")]
#[path = "seh.rs"]
mod imp;
#[cfg(backend = "panic")]
#[path = "panic.rs"]
mod imp;
#[cfg(backend = "emscripten")]
#[path = "emscripten.rs"]
mod imp;
#[cfg(backend = "unimplemented")]
#[path = "unimplemented.rs"]
mod imp;
pub(crate) use imp::ActiveBackend;
#[cfg(test)]
mod test {
use super::{ActiveBackend, RethrowHandle, ThrowByValue};
use alloc::string::String;
#[test]
fn intercept_ok() {
let result =
unsafe { ActiveBackend::intercept::<_, _, ()>(|| String::from("Hello, world!")) };
assert_eq!(result.unwrap(), "Hello, world!");
}
#[test]
fn intercept_err() {
let result = unsafe {
ActiveBackend::intercept::<_, _, String>(|| {
ActiveBackend::throw(String::from("Hello, world!"));
})
};
let (caught_ex, _) = result.unwrap_err();
assert_eq!(caught_ex, "Hello, world!");
}
#[test]
fn intercept_panic() {
let result = std::panic::catch_unwind(|| unsafe {
ActiveBackend::intercept::<_, _, ()>(|| {
std::panic::resume_unwind(alloc::boxed::Box::new("Hello, world!"))
})
});
assert_eq!(
*result.unwrap_err().downcast_ref::<&'static str>().unwrap(),
"Hello, world!",
);
}
#[test]
fn nested_intercept() {
let result = unsafe {
ActiveBackend::intercept::<_, _, ()>(|| {
ActiveBackend::intercept::<_, _, String>(|| {
ActiveBackend::throw(String::from("Hello, world!"));
})
})
};
let (caught_ex, _) = result.unwrap().unwrap_err();
assert_eq!(caught_ex, "Hello, world!");
}
#[test]
fn rethrow() {
let result = unsafe {
ActiveBackend::intercept::<_, _, String>(|| {
let result = ActiveBackend::intercept::<_, _, String>(|| {
ActiveBackend::throw(String::from("Hello, world!"));
});
let (ex2, handle) = result.unwrap_err();
assert_eq!(ex2, "Hello, world!");
handle.rethrow(ex2);
})
};
let (caught_ex, _) = result.unwrap_err();
assert_eq!(caught_ex, "Hello, world!");
}
#[test]
fn destructors_are_run() {
struct Dropper<'a>(&'a mut bool);
impl Drop for Dropper<'_> {
fn drop(&mut self) {
*self.0 = true;
}
}
let mut destructor_was_run = false;
let result = unsafe {
ActiveBackend::intercept::<_, _, String>(|| {
let _dropper = Dropper(&mut destructor_was_run);
ActiveBackend::throw(String::from("Hello, world!"));
})
};
let (caught_ex, _) = result.unwrap_err();
assert_eq!(caught_ex, "Hello, world!");
assert!(destructor_was_run);
}
#[test]
fn nested_with_drop() {
struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
let result = unsafe {
ActiveBackend::intercept::<_, _, String>(|| {
ActiveBackend::throw(String::from("Awful idea"));
})
};
let (caught_ex2, _) = result.unwrap_err();
assert_eq!(caught_ex2, "Awful idea");
}
}
let result = unsafe {
ActiveBackend::intercept::<_, _, String>(|| {
let _dropper = Dropper;
ActiveBackend::throw(String::from("Hello, world!"));
})
};
let (caught_ex1, _) = result.unwrap_err();
assert_eq!(caught_ex1, "Hello, world!");
}
}