ext_php_rs/zend/
try_catch.rs1use crate::ffi::{
2 ext_php_rs_zend_bailout, ext_php_rs_zend_first_try_catch, ext_php_rs_zend_try_catch,
3};
4use std::ffi::c_void;
5use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe};
6use std::ptr::null_mut;
7
8#[derive(Debug)]
9pub struct CatchError;
10
11pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnMut() -> R + RefUnwindSafe>(
12 ctx: *const c_void,
13) -> *const c_void {
14 let panic = catch_unwind(|| (*(ctx as *mut F))());
17
18 Box::into_raw(Box::new(panic)) as *mut c_void
19}
20
21pub fn try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
33 do_try_catch(func, false)
34}
35
36pub fn try_catch_first<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
51 do_try_catch(func, true)
52}
53
54fn do_try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F, first: bool) -> Result<R, CatchError> {
55 let mut panic_ptr = null_mut();
56 let has_bailout = unsafe {
57 if first {
58 ext_php_rs_zend_first_try_catch(
59 panic_wrapper::<R, F>,
60 &func as *const F as *const c_void,
61 (&mut panic_ptr) as *mut *mut c_void,
62 )
63 } else {
64 ext_php_rs_zend_try_catch(
65 panic_wrapper::<R, F>,
66 &func as *const F as *const c_void,
67 (&mut panic_ptr) as *mut *mut c_void,
68 )
69 }
70 };
71
72 let panic = panic_ptr as *mut std::thread::Result<R>;
73
74 if panic.is_null() || has_bailout {
76 return Err(CatchError);
77 }
78
79 match unsafe { *Box::from_raw(panic as *mut std::thread::Result<R>) } {
80 Ok(r) => Ok(r),
81 Err(err) => {
82 resume_unwind(err);
84 }
85 }
86}
87
88pub unsafe fn bailout() -> ! {
102 ext_php_rs_zend_bailout();
103}
104
105#[cfg(feature = "embed")]
106#[cfg(test)]
107mod tests {
108 use crate::embed::Embed;
109 use crate::zend::{bailout, try_catch};
110 use std::ptr::null_mut;
111
112 #[test]
113 fn test_catch() {
114 Embed::run(|| {
115 let catch = try_catch(|| {
116 unsafe {
117 bailout();
118 }
119
120 #[allow(unreachable_code)]
121 {
122 assert!(false);
123 }
124 });
125
126 assert!(catch.is_err());
127 });
128 }
129
130 #[test]
131 fn test_no_catch() {
132 Embed::run(|| {
133 let catch = try_catch(|| {
134 assert!(true);
135 });
136
137 assert!(catch.is_ok());
138 });
139 }
140
141 #[test]
142 fn test_bailout() {
143 Embed::run(|| {
144 unsafe {
145 bailout();
146 }
147
148 #[allow(unreachable_code)]
149 {
150 assert!(false);
151 }
152 });
153 }
154
155 #[test]
156 #[should_panic]
157 fn test_panic() {
158 Embed::run(|| {
159 let _ = try_catch(|| {
160 panic!("should panic");
161 });
162 });
163 }
164
165 #[test]
166 fn test_return() {
167 let foo = Embed::run(|| {
168 let result = try_catch(|| {
169 return "foo";
170 });
171
172 assert!(result.is_ok());
173
174 result.unwrap()
175 });
176
177 assert_eq!(foo, "foo");
178 }
179
180 #[test]
181 fn test_memory_leak() {
182 let mut ptr = null_mut();
183
184 let _ = try_catch(|| {
185 let mut result = "foo".to_string();
186 ptr = &mut result;
187
188 unsafe {
189 bailout();
190 }
191 });
192
193 let result = unsafe { &*ptr as &str };
195
196 assert_eq!(result, "foo");
197 }
198}