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::{UnwindSafe, catch_unwind, resume_unwind};
use std::ptr::null_mut;
#[derive(Debug)]
pub struct CatchError;
pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnOnce() -> R + UnwindSafe>(
ctx: *const c_void,
) -> *const c_void {
let func = unsafe { std::ptr::read(ctx.cast::<F>()) };
let panic = catch_unwind(func);
Box::into_raw(Box::new(panic)).cast::<c_void>()
}
pub fn try_catch<R, F: FnOnce() -> R + UnwindSafe>(func: F) -> Result<R, CatchError> {
do_try_catch(func, false)
}
pub fn try_catch_first<R, F: FnOnce() -> R + UnwindSafe>(func: F) -> Result<R, CatchError> {
do_try_catch(func, true)
}
fn do_try_catch<R, F: FnOnce() -> R + UnwindSafe>(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>,
(&raw const func).cast::<c_void>(),
&raw mut panic_ptr,
)
} else {
ext_php_rs_zend_try_catch(
panic_wrapper::<R, F>,
(&raw const func).cast::<c_void>(),
&raw mut panic_ptr,
)
}
};
std::mem::forget(func);
let panic = panic_ptr.cast::<std::thread::Result<R>>();
if panic.is_null() || has_bailout {
return Err(CatchError);
}
match unsafe { *Box::from_raw(panic.cast::<std::thread::Result<R>>()) } {
Ok(r) => Ok(r),
Err(err) => {
resume_unwind(err);
}
}
}
pub unsafe fn bailout() -> ! {
unsafe { 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)]
#[allow(clippy::assertions_on_constants)]
{
assert!(false);
}
});
assert!(catch.is_err());
});
}
#[test]
fn test_no_catch() {
Embed::run(|| {
let catch = try_catch(|| {
#[allow(clippy::assertions_on_constants)]
{
assert!(true);
}
});
assert!(catch.is_ok());
});
}
#[test]
fn test_bailout() {
Embed::run(|| {
unsafe {
bailout();
}
#[allow(unreachable_code)]
#[allow(clippy::assertions_on_constants)]
{
assert!(false);
}
});
}
#[test]
#[should_panic(expected = "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(|| "foo");
assert!(result.is_ok());
#[allow(clippy::unwrap_used)]
result.unwrap()
});
assert_eq!(foo, "foo");
}
#[test]
fn test_memory_leak() {
use std::panic::AssertUnwindSafe;
Embed::run(|| {
let mut ptr = null_mut();
let _ = try_catch(AssertUnwindSafe(|| {
let mut result = "foo".to_string();
ptr = &raw mut result;
unsafe {
bailout();
}
}));
let result = unsafe { &*ptr as &str };
assert_eq!(result, "foo");
});
}
}