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::{UnwindSafe, catch_unwind, resume_unwind};
6use std::ptr::null_mut;
7
8#[derive(Debug)]
10pub struct CatchError;
11
12pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnOnce() -> R + UnwindSafe>(
13 ctx: *const c_void,
14) -> *const c_void {
15 let func = unsafe { std::ptr::read(ctx.cast::<F>()) };
20 let panic = catch_unwind(func);
21
22 Box::into_raw(Box::new(panic)).cast::<c_void>()
23}
24
25pub fn try_catch<R, F: FnOnce() -> R + UnwindSafe>(func: F) -> Result<R, CatchError> {
40 do_try_catch(func, false)
41}
42
43pub fn try_catch_first<R, F: FnOnce() -> R + UnwindSafe>(func: F) -> Result<R, CatchError> {
61 do_try_catch(func, true)
62}
63
64fn do_try_catch<R, F: FnOnce() -> R + UnwindSafe>(func: F, first: bool) -> Result<R, CatchError> {
65 let mut panic_ptr = null_mut();
66 let has_bailout = unsafe {
67 if first {
68 ext_php_rs_zend_first_try_catch(
69 panic_wrapper::<R, F>,
70 (&raw const func).cast::<c_void>(),
71 &raw mut panic_ptr,
72 )
73 } else {
74 ext_php_rs_zend_try_catch(
75 panic_wrapper::<R, F>,
76 (&raw const func).cast::<c_void>(),
77 &raw mut panic_ptr,
78 )
79 }
80 };
81
82 std::mem::forget(func);
84
85 let panic = panic_ptr.cast::<std::thread::Result<R>>();
86
87 if panic.is_null() || has_bailout {
89 return Err(CatchError);
90 }
91
92 match unsafe { *Box::from_raw(panic.cast::<std::thread::Result<R>>()) } {
93 Ok(r) => Ok(r),
94 Err(err) => {
95 resume_unwind(err);
97 }
98 }
99}
100
101pub unsafe fn bailout() -> ! {
115 unsafe { ext_php_rs_zend_bailout() };
116}
117
118#[cfg(feature = "embed")]
119#[cfg(test)]
120mod tests {
121 use crate::embed::Embed;
122 use crate::zend::{bailout, try_catch};
123 use std::ptr::null_mut;
124
125 #[test]
126 fn test_catch() {
127 Embed::run(|| {
128 let catch = try_catch(|| {
129 unsafe {
130 bailout();
131 }
132
133 #[allow(unreachable_code)]
134 #[allow(clippy::assertions_on_constants)]
135 {
136 assert!(false);
137 }
138 });
139
140 assert!(catch.is_err());
141 });
142 }
143
144 #[test]
145 fn test_no_catch() {
146 Embed::run(|| {
147 let catch = try_catch(|| {
148 #[allow(clippy::assertions_on_constants)]
149 {
150 assert!(true);
151 }
152 });
153
154 assert!(catch.is_ok());
155 });
156 }
157
158 #[test]
159 fn test_bailout() {
160 Embed::run(|| {
161 unsafe {
162 bailout();
163 }
164
165 #[allow(unreachable_code)]
166 #[allow(clippy::assertions_on_constants)]
167 {
168 assert!(false);
169 }
170 });
171 }
172
173 #[test]
174 #[should_panic(expected = "should panic")]
175 fn test_panic() {
176 Embed::run(|| {
177 let _ = try_catch(|| {
178 panic!("should panic");
179 });
180 });
181 }
182
183 #[test]
184 fn test_return() {
185 let foo = Embed::run(|| {
186 let result = try_catch(|| "foo");
187
188 assert!(result.is_ok());
189
190 #[allow(clippy::unwrap_used)]
191 result.unwrap()
192 });
193
194 assert_eq!(foo, "foo");
195 }
196
197 #[test]
198 fn test_memory_leak() {
199 use std::panic::AssertUnwindSafe;
200
201 Embed::run(|| {
202 let mut ptr = null_mut();
203
204 let _ = try_catch(AssertUnwindSafe(|| {
205 let mut result = "foo".to_string();
206 ptr = &raw mut result;
207
208 unsafe {
209 bailout();
210 }
211 }));
212
213 let result = unsafe { &*ptr as &str };
215
216 assert_eq!(result, "foo");
217 });
218 }
219}