@@ -13,10 +13,10 @@ use crate::ffi::{
1313 zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS ,
1414} ;
1515use crate :: types:: { ZendObject , Zval } ;
16- use crate :: zend:: ExecutorGlobals ;
16+ use crate :: zend:: { panic_wrapper , try_catch , ExecutorGlobals } ;
1717use parking_lot:: { const_rwlock, RwLock } ;
1818use std:: ffi:: { c_char, c_void, CString , NulError } ;
19- use std:: panic:: { catch_unwind , resume_unwind, RefUnwindSafe } ;
19+ use std:: panic:: { resume_unwind, RefUnwindSafe } ;
2020use std:: path:: Path ;
2121use std:: ptr:: null_mut;
2222
@@ -29,6 +29,13 @@ pub enum EmbedError {
2929 ExecuteScriptError ,
3030 InvalidEvalString ( NulError ) ,
3131 InvalidPath ,
32+ CatchError ,
33+ }
34+
35+ impl EmbedError {
36+ pub fn is_bailout ( & self ) -> bool {
37+ matches ! ( self , EmbedError :: CatchError )
38+ }
3239}
3340
3441static RUN_FN_LOCK : RwLock < ( ) > = const_rwlock ( ( ) ) ;
@@ -79,10 +86,12 @@ impl Embed {
7986 zend_stream_init_filename ( & mut file_handle, path. as_ptr ( ) ) ;
8087 }
8188
82- if unsafe { php_execute_script ( & mut file_handle) } {
83- Ok ( ( ) )
84- } else {
85- Err ( EmbedError :: ExecuteScriptError )
89+ let exec_result = try_catch ( || unsafe { php_execute_script ( & mut file_handle) } ) ;
90+
91+ match exec_result {
92+ Err ( _) => Err ( EmbedError :: CatchError ) ,
93+ Ok ( true ) => Ok ( ( ) ) ,
94+ Ok ( false ) => Err ( EmbedError :: ExecuteScriptError ) ,
8695 }
8796 }
8897
@@ -93,6 +102,12 @@ impl Embed {
93102 /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access
94103 /// variables defined in previous calls
95104 ///
105+ /// # Returns
106+ ///
107+ /// * R - The result of the function passed to this method
108+ ///
109+ /// R must implement [`Default`] so it can be returned in case of a bailout
110+ ///
96111 /// # Example
97112 ///
98113 /// ```
@@ -105,41 +120,36 @@ impl Embed {
105120 /// assert_eq!(foo.unwrap().string().unwrap(), "foo");
106121 /// });
107122 /// ```
108- pub fn run < F : Fn ( ) + RefUnwindSafe > ( func : F ) {
123+ pub fn run < R , F : FnMut ( ) -> R + RefUnwindSafe > ( func : F ) -> R
124+ where
125+ R : Default ,
126+ {
109127 // @TODO handle php thread safe
110128 //
111129 // This is to prevent multiple threads from running php at the same time
112130 // At some point we should detect if php is compiled with thread safety and avoid doing that in this case
113131 let _guard = RUN_FN_LOCK . write ( ) ;
114132
115- unsafe extern "C" fn wrapper < F : Fn ( ) + RefUnwindSafe > ( ctx : * const c_void ) -> * mut c_void {
116- // we try to catch panic here so we correctly shutdown php if it happens
117- // mandatory when we do assert on test as other test would not run correctly
118- let panic = catch_unwind ( || {
119- ( * ( ctx as * const F ) ) ( ) ;
120- } ) ;
121-
122- let panic_ptr = Box :: into_raw ( Box :: new ( panic) ) ;
123-
124- panic_ptr as * mut c_void
125- }
126-
127133 let panic = unsafe {
128134 ext_php_rs_embed_callback (
129135 0 ,
130136 null_mut ( ) ,
131- wrapper :: < F > ,
137+ panic_wrapper :: < R , F > ,
132138 & func as * const F as * const c_void ,
133139 )
134140 } ;
135141
142+ // This can happen if there is a bailout
136143 if panic. is_null ( ) {
137- return ;
144+ return R :: default ( ) ;
138145 }
139146
140- if let Err ( err) = unsafe { * Box :: from_raw ( panic as * mut std:: thread:: Result < ( ) > ) } {
141- // we resume the panic here so it can be catched correctly by the test framework
142- resume_unwind ( err) ;
147+ match unsafe { * Box :: from_raw ( panic as * mut std:: thread:: Result < R > ) } {
148+ Ok ( r) => r,
149+ Err ( err) => {
150+ // we resume the panic here so it can be catched correctly by the test framework
151+ resume_unwind ( err) ;
152+ }
143153 }
144154 }
145155
@@ -170,21 +180,18 @@ impl Embed {
170180
171181 let mut result = Zval :: new ( ) ;
172182
173- // this eval is very limited as it only allow simple code, it's the same eval used by php -r
174- let exec_result = unsafe {
183+ let exec_result = try_catch ( || unsafe {
175184 zend_eval_string (
176185 cstr. as_ptr ( ) as * const c_char ,
177186 & mut result,
178187 b"run\0 " . as_ptr ( ) as * const _ ,
179188 )
180- } ;
181-
182- let exception = ExecutorGlobals :: take_exception ( ) ;
189+ } ) ;
183190
184- if exec_result != ZEND_RESULT_CODE_SUCCESS {
185- Err ( EmbedError :: ExecuteError ( exception ) )
186- } else {
187- Ok ( result )
191+ match exec_result {
192+ Err ( _ ) => Err ( EmbedError :: CatchError ) ,
193+ Ok ( ZEND_RESULT_CODE_SUCCESS ) => Ok ( result ) ,
194+ Ok ( _ ) => Err ( EmbedError :: ExecuteError ( ExecutorGlobals :: take_exception ( ) ) ) ,
188195 }
189196 }
190197}
@@ -244,4 +251,23 @@ mod tests {
244251 panic ! ( "test panic" ) ;
245252 } ) ;
246253 }
254+
255+ #[ test]
256+ fn test_return ( ) {
257+ let foo = Embed :: run ( || {
258+ return "foo" ;
259+ } ) ;
260+
261+ assert_eq ! ( foo, "foo" ) ;
262+ }
263+
264+ #[ test]
265+ fn test_eval_bailout ( ) {
266+ Embed :: run ( || {
267+ let result = Embed :: eval ( "str_repeat('a', 100_000_000_000_000);" ) ;
268+
269+ assert ! ( result. is_err( ) ) ;
270+ assert ! ( result. unwrap_err( ) . is_bailout( ) ) ;
271+ } ) ;
272+ }
247273}
0 commit comments