@@ -2,19 +2,22 @@ use crate::ffi::{
22 ext_php_rs_zend_bailout, ext_php_rs_zend_first_try_catch, ext_php_rs_zend_try_catch,
33} ;
44use std:: ffi:: c_void;
5- use std:: panic:: { RefUnwindSafe , catch_unwind, resume_unwind} ;
5+ use std:: panic:: { UnwindSafe , catch_unwind, resume_unwind} ;
66use std:: ptr:: null_mut;
77
88/// Error returned when a bailout occurs
99#[ derive( Debug ) ]
1010pub struct CatchError ;
1111
12- pub ( crate ) unsafe extern "C" fn panic_wrapper < R , F : FnMut ( ) -> R + RefUnwindSafe > (
12+ pub ( crate ) unsafe extern "C" fn panic_wrapper < R , F : FnOnce ( ) -> R + UnwindSafe > (
1313 ctx : * const c_void ,
1414) -> * const c_void {
1515 // we try to catch panic here so we correctly shutdown php if it happens
1616 // mandatory when we do assert on test as other test would not run correctly
17- let panic = catch_unwind ( || unsafe { ( * ( ctx as * mut F ) ) ( ) } ) ;
17+ // SAFETY: We read the closure from the pointer and consume it. This is safe because
18+ // the closure is only called once.
19+ let func = unsafe { std:: ptr:: read ( ctx. cast :: < F > ( ) ) } ;
20+ let panic = catch_unwind ( func) ;
1821
1922 Box :: into_raw ( Box :: new ( panic) ) . cast :: < c_void > ( )
2023}
@@ -33,7 +36,7 @@ pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnMut() -> R + RefUnwindSafe
3336/// # Errors
3437///
3538/// * [`CatchError`] - A bailout occurred during the execution
36- pub fn try_catch < R , F : FnMut ( ) -> R + RefUnwindSafe > ( func : F ) -> Result < R , CatchError > {
39+ pub fn try_catch < R , F : FnOnce ( ) -> R + UnwindSafe > ( func : F ) -> Result < R , CatchError > {
3740 do_try_catch ( func, false )
3841}
3942
@@ -54,11 +57,11 @@ pub fn try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, Catch
5457/// # Errors
5558///
5659/// * [`CatchError`] - A bailout occurred during the execution
57- pub fn try_catch_first < R , F : FnMut ( ) -> R + RefUnwindSafe > ( func : F ) -> Result < R , CatchError > {
60+ pub fn try_catch_first < R , F : FnOnce ( ) -> R + UnwindSafe > ( func : F ) -> Result < R , CatchError > {
5861 do_try_catch ( func, true )
5962}
6063
61- fn do_try_catch < R , F : FnMut ( ) -> R + RefUnwindSafe > ( func : F , first : bool ) -> Result < R , CatchError > {
64+ fn do_try_catch < R , F : FnOnce ( ) -> R + UnwindSafe > ( func : F , first : bool ) -> Result < R , CatchError > {
6265 let mut panic_ptr = null_mut ( ) ;
6366 let has_bailout = unsafe {
6467 if first {
@@ -76,6 +79,9 @@ fn do_try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F, first: bool) -> Res
7679 }
7780 } ;
7881
82+ // Prevent the closure from being dropped here since it was consumed in panic_wrapper
83+ std:: mem:: forget ( func) ;
84+
7985 let panic = panic_ptr. cast :: < std:: thread:: Result < R > > ( ) ;
8086
8187 // can be null if there is a bailout
@@ -190,17 +196,19 @@ mod tests {
190196
191197 #[ test]
192198 fn test_memory_leak ( ) {
199+ use std:: panic:: AssertUnwindSafe ;
200+
193201 Embed :: run ( || {
194202 let mut ptr = null_mut ( ) ;
195203
196- let _ = try_catch ( || {
204+ let _ = try_catch ( AssertUnwindSafe ( || {
197205 let mut result = "foo" . to_string ( ) ;
198206 ptr = & raw mut result;
199207
200208 unsafe {
201209 bailout ( ) ;
202210 }
203- } ) ;
211+ } ) ) ;
204212
205213 // Check that the string is never released
206214 let result = unsafe { & * ptr as & str } ;
0 commit comments