use crate::ffi::{
ext_php_rs_zend_bailout, ext_php_rs_zend_first_try_catch, ext_php_rs_zend_try_catch,
};
use std::ffi::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe};
use std::ptr::null_mut;
#[derive(Debug)]
pub struct CatchError;
pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnMut() -> R + RefUnwindSafe>(
ctx: *const c_void,
) -> *const c_void {
let panic = catch_unwind(|| (*(ctx as *mut F))());
Box::into_raw(Box::new(panic)) as *mut c_void
}
pub fn try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
do_try_catch(func, false)
}
pub fn try_catch_first<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
do_try_catch(func, true)
}
fn do_try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F, first: bool) -> Result<R, CatchError> {
let mut panic_ptr = null_mut();
let has_bailout = unsafe {
if first {
ext_php_rs_zend_first_try_catch(
panic_wrapper::<R, F>,
&func as *const F as *const c_void,
(&mut panic_ptr) as *mut *mut c_void,
)
} else {
ext_php_rs_zend_try_catch(
panic_wrapper::<R, F>,
&func as *const F as *const c_void,
(&mut panic_ptr) as *mut *mut c_void,
)
}
};
let panic = panic_ptr as *mut std::thread::Result<R>;
if panic.is_null() || has_bailout {
return Err(CatchError);
}
match unsafe { *Box::from_raw(panic as *mut std::thread::Result<R>) } {
Ok(r) => Ok(r),
Err(err) => {
resume_unwind(err);
}
}
}
pub unsafe fn bailout() -> ! {
ext_php_rs_zend_bailout();
}
#[cfg(feature = "embed")]
#[cfg(test)]
mod tests {
use crate::embed::Embed;
use crate::zend::{bailout, try_catch};
use std::ptr::null_mut;
#[test]
fn test_catch() {
Embed::run(|| {
let catch = try_catch(|| {
unsafe {
bailout();
}
#[allow(unreachable_code)]
{
assert!(false);
}
});
assert!(catch.is_err());
});
}
#[test]
fn test_no_catch() {
Embed::run(|| {
let catch = try_catch(|| {
assert!(true);
});
assert!(catch.is_ok());
});
}
#[test]
fn test_bailout() {
Embed::run(|| {
unsafe {
bailout();
}
#[allow(unreachable_code)]
{
assert!(false);
}
});
}
#[test]
#[should_panic]
fn test_panic() {
Embed::run(|| {
let _ = try_catch(|| {
panic!("should panic");
});
});
}
#[test]
fn test_return() {
let foo = Embed::run(|| {
let result = try_catch(|| {
return "foo";
});
assert!(result.is_ok());
result.unwrap()
});
assert_eq!(foo, "foo");
}
#[test]
fn test_memory_leak() {
let mut ptr = null_mut();
let _ = try_catch(|| {
let mut result = "foo".to_string();
ptr = &mut result;
unsafe {
bailout();
}
});
let result = unsafe { &*ptr as &str };
assert_eq!(result, "foo");
}
}