@@ -1028,6 +1028,24 @@ extern "C" fn interrupt_function_wrapper(execute_data: *mut zend::zend_execute_d
10281028 }
10291029}
10301030
1031+ /// Returns true if the func tied to the execute_data is a trampoline.
1032+ /// # Safety
1033+ /// This is only safe to execute _before_ executing the trampoline, because the trampoline may
1034+ /// free the `execute_data.func` _without_ setting it to NULL:
1035+ /// https://heap.space/xref/PHP-8.2/Zend/zend_closures.c?r=af2110e6#60-63
1036+ /// So no code can inspect the func after the call has been made, which is why you would call this function: find out before you
1037+ /// call the function if indeed you need to skip certain code after it has been executed.
1038+ unsafe fn execute_data_func_is_trampoline ( execute_data : * const zend:: zend_execute_data ) -> bool {
1039+ if execute_data. is_null ( ) {
1040+ return false ;
1041+ }
1042+
1043+ if ( * execute_data) . func . is_null ( ) {
1044+ return false ;
1045+ }
1046+ return ( ( * ( * execute_data) . func ) . common . fn_flags & zend:: ZEND_ACC_CALL_VIA_TRAMPOLINE ) != 0 ;
1047+ }
1048+
10311049/// Overrides the engine's zend_execute_internal hook in order to process pending VM interrupts
10321050/// while the internal function is still on top of the call stack. The VM does not process the
10331051/// interrupt until the call returns so that it could theoretically jump to a different opcode,
@@ -1041,12 +1059,23 @@ extern "C" fn execute_internal(
10411059 execute_data : * mut zend:: zend_execute_data ,
10421060 return_value : * mut zend:: zval ,
10431061) {
1044- // Safety: PREV_EXECUTE_INTERNAL was written during minit, doesn't change during runtime.
1045- unsafe {
1046- let prev_execute_internal = * PREV_EXECUTE_INTERNAL . as_mut_ptr ( ) ;
1047- prev_execute_internal ( execute_data, return_value) ;
1048- }
1049- interrupt_function ( execute_data) ;
1062+ // SAFETY: called before executing the trampoline.
1063+ let leaf_frame = if unsafe { execute_data_func_is_trampoline ( execute_data) } {
1064+ // SAFETY: if is_trampoline is set, then there must be a valid execute_data.
1065+ unsafe { * execute_data } . prev_execute_data
1066+ } else {
1067+ execute_data
1068+ } ;
1069+
1070+ // SAFETY: PREV_EXECUTE_INTERNAL was written during minit, doesn't change during runtime.
1071+ let prev_execute_internal = unsafe { * PREV_EXECUTE_INTERNAL . as_mut_ptr ( ) } ;
1072+
1073+ // SAFETY: calling prev_execute without modification will be safe.
1074+ unsafe { prev_execute_internal ( execute_data, return_value) } ;
1075+
1076+ // See safety section of `execute_data_func_is_trampoline` docs for why the leaf frame is used
1077+ // instead of the execute_data ptr.
1078+ interrupt_function ( leaf_frame) ;
10501079}
10511080
10521081#[ cfg( test) ]
0 commit comments