use std::{
mem::{self, ManuallyDrop},
panic,
ptr,
thread::Result,
};
use crate::{
AnyException,
AnyObject,
Object,
ruby,
};
pub fn protected<F, O>(f: F) -> crate::Result<O>
where F: FnOnce() -> O
{
unsafe extern "C" fn wrapper<F, O>(ctx: ruby::VALUE) -> ruby::VALUE
where F: FnOnce() -> O
{
let (f, out) = &mut *(ctx as *mut (Option<F>, &mut Result<O>));
let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked());
ptr::write(*out, panic::catch_unwind(panic::AssertUnwindSafe(f)));
AnyObject::nil().raw()
}
unsafe {
let mut out = ManuallyDrop::new(mem::uninitialized::<Result<O>>());
let mut ctx = (Some(f), &mut *out);
let ctx = &mut ctx as *mut (Option<F>, &mut _) as ruby::VALUE;
let mut err = 1;
ruby::rb_protect(Some(wrapper::<F, O>), ctx, &mut err);
match err {
0 => match ManuallyDrop::into_inner(out) {
Ok(out) => Ok(out),
Err(panic_info) => panic::resume_unwind(panic_info),
},
_ => Err(AnyException::_take_current()),
}
}
}
pub unsafe fn protected_no_panic<F, O>(f: F) -> crate::Result<O>
where F: FnOnce() -> O
{
if crate::util::matches_ruby_size_align::<O>() {
return protected_no_panic_size_opt(f);
}
unsafe extern "C" fn wrapper<F, O>(ctx: ruby::VALUE) -> ruby::VALUE
where F: FnOnce() -> O
{
let (f, out) = &mut *(ctx as *mut (Option<F>, &mut O));
let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked());
ptr::write(*out, f());
AnyObject::nil().raw()
}
let mut out = ManuallyDrop::new(mem::uninitialized::<O>());
let mut ctx = (Some(f), &mut *out);
let ctx = &mut ctx as *mut (Option<F>, &mut O) as ruby::VALUE;
let mut err = 0;
ruby::rb_protect(Some(wrapper::<F, O>), ctx, &mut err);
match err {
0 => Ok(ManuallyDrop::into_inner(out)),
_ => Err(AnyException::_take_current()),
}
}
#[inline]
unsafe fn protected_no_panic_size_opt<F, O>(f: F) -> crate::Result<O>
where F: FnOnce() -> O
{
unsafe extern "C" fn wrapper<F, O>(ctx: ruby::VALUE) -> ruby::VALUE
where F: FnOnce() -> O
{
let f: &mut Option<F> = &mut *(ctx as *mut Option<F>);
let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked());
let value = ManuallyDrop::new(f());
ptr::read(&value as *const ManuallyDrop<O> as *const ruby::VALUE)
}
let mut ctx = Some(f);
let ctx = &mut ctx as *mut Option<F> as ruby::VALUE;
let mut err = 0;
let val = ruby::rb_protect(Some(wrapper::<F, O>), ctx, &mut err);
match err {
0 => Ok(ptr::read(&val as *const ruby::VALUE as *const O)),
_ => Err(AnyException::_take_current()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn panic() {
crate::vm::init().unwrap();
struct DropAndPanic;
impl Drop for DropAndPanic {
fn drop(&mut self) {
panic!("This was never instantiated and shouldn't be dropped");
}
}
let message = "panic happened";
panic::catch_unwind(|| {
protected(|| -> DropAndPanic { panic!("{}", message); }).unwrap();
}).unwrap_err();
}
}