@@ -13,10 +13,10 @@ use crate::ffi::{
13
13
zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS ,
14
14
} ;
15
15
use crate :: types:: { ZendObject , Zval } ;
16
- use crate :: zend:: ExecutorGlobals ;
16
+ use crate :: zend:: { panic_wrapper , try_catch , ExecutorGlobals } ;
17
17
use parking_lot:: { const_rwlock, RwLock } ;
18
18
use std:: ffi:: { c_char, c_void, CString , NulError } ;
19
- use std:: panic:: { catch_unwind , resume_unwind, RefUnwindSafe } ;
19
+ use std:: panic:: { resume_unwind, RefUnwindSafe } ;
20
20
use std:: path:: Path ;
21
21
use std:: ptr:: null_mut;
22
22
@@ -29,6 +29,13 @@ pub enum EmbedError {
29
29
ExecuteScriptError ,
30
30
InvalidEvalString ( NulError ) ,
31
31
InvalidPath ,
32
+ CatchError ,
33
+ }
34
+
35
+ impl EmbedError {
36
+ pub fn is_bailout ( & self ) -> bool {
37
+ matches ! ( self , EmbedError :: CatchError )
38
+ }
32
39
}
33
40
34
41
static RUN_FN_LOCK : RwLock < ( ) > = const_rwlock ( ( ) ) ;
@@ -79,10 +86,12 @@ impl Embed {
79
86
zend_stream_init_filename ( & mut file_handle, path. as_ptr ( ) ) ;
80
87
}
81
88
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 ) ,
86
95
}
87
96
}
88
97
@@ -93,6 +102,12 @@ impl Embed {
93
102
/// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access
94
103
/// variables defined in previous calls
95
104
///
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
+ ///
96
111
/// # Example
97
112
///
98
113
/// ```
@@ -105,41 +120,36 @@ impl Embed {
105
120
/// assert_eq!(foo.unwrap().string().unwrap(), "foo");
106
121
/// });
107
122
/// ```
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
+ {
109
127
// @TODO handle php thread safe
110
128
//
111
129
// This is to prevent multiple threads from running php at the same time
112
130
// At some point we should detect if php is compiled with thread safety and avoid doing that in this case
113
131
let _guard = RUN_FN_LOCK . write ( ) ;
114
132
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
-
127
133
let panic = unsafe {
128
134
ext_php_rs_embed_callback (
129
135
0 ,
130
136
null_mut ( ) ,
131
- wrapper :: < F > ,
137
+ panic_wrapper :: < R , F > ,
132
138
& func as * const F as * const c_void ,
133
139
)
134
140
} ;
135
141
142
+ // This can happen if there is a bailout
136
143
if panic. is_null ( ) {
137
- return ;
144
+ return R :: default ( ) ;
138
145
}
139
146
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
+ }
143
153
}
144
154
}
145
155
@@ -170,21 +180,18 @@ impl Embed {
170
180
171
181
let mut result = Zval :: new ( ) ;
172
182
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 {
175
184
zend_eval_string (
176
185
cstr. as_ptr ( ) as * const c_char ,
177
186
& mut result,
178
187
b"run\0 " . as_ptr ( ) as * const _ ,
179
188
)
180
- } ;
181
-
182
- let exception = ExecutorGlobals :: take_exception ( ) ;
189
+ } ) ;
183
190
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 ( ) ) ) ,
188
195
}
189
196
}
190
197
}
@@ -244,4 +251,23 @@ mod tests {
244
251
panic ! ( "test panic" ) ;
245
252
} ) ;
246
253
}
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
+ }
247
273
}
0 commit comments