From 9ae220fb0f40a810fef2a29a44a29706d0bba848 Mon Sep 17 00:00:00 2001 From: pratik bhujel Date: Tue, 21 Apr 2026 17:39:51 +0545 Subject: [PATCH 1/3] Fix GH-21639: Protect frameless args during __toString reentry --- Zend/tests/gh21639.phpt | 98 ++++++++++++++++++++++++ Zend/zend.c | 1 + Zend/zend_execute.c | 148 ++++++++++++++++++++++++++++++++++++ Zend/zend_execute.h | 4 + Zend/zend_execute_API.c | 4 + Zend/zend_globals.h | 2 + Zend/zend_object_handlers.c | 4 + Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 1 + ext/pcre/tests/gh21639.phpt | 29 +++++++ 10 files changed, 292 insertions(+) create mode 100644 Zend/tests/gh21639.phpt create mode 100644 ext/pcre/tests/gh21639.phpt diff --git a/Zend/tests/gh21639.phpt b/Zend/tests/gh21639.phpt new file mode 100644 index 000000000000..7eb28fe395be --- /dev/null +++ b/Zend/tests/gh21639.phpt @@ -0,0 +1,98 @@ +--TEST-- +GH-21639: Frameless calls keep volatile CV arguments alive during __toString() +--FILE-- + new StrtrReplacement()]; + +var_dump(strtr("a", $strtrReplacements)); +var_dump($strtrReplacements); + +class StrReplaceSubject { + public function __toString(): string { + global $strReplaceSubject; + + $strReplaceSubject = null; + + return "a"; + } +} + +$strReplaceSubject = [new StrReplaceSubject(), "aa"]; + +var_dump(str_replace("a", "b", $strReplaceSubject)); +var_dump($strReplaceSubject); +?> +--EXPECT-- +string(5) "C, 42" +NULL +NULL +string(3) "D42" +NULL +bool(true) +NULL +string(1) "b" +NULL +array(2) { + [0]=> + string(1) "b" + [1]=> + string(2) "bb" +} +NULL diff --git a/Zend/zend.c b/Zend/zend.c index b4a084b1f95c..145e85effdff 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -810,6 +810,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ ZVAL_UNDEF(&executor_globals->user_exception_handler); executor_globals->in_autoload = NULL; executor_globals->current_execute_data = NULL; + executor_globals->frameless_reentry_copies = NULL; executor_globals->current_module = NULL; executor_globals->exit_status = 0; #if XPFPA_HAVE_CW diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ea5f55cf6ef7..0e42b78eeb4b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1582,6 +1582,153 @@ static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval * } } +struct _zend_frameless_reentry_copies { + struct _zend_frameless_reentry_copies *prev; + zend_execute_data *execute_data; + const zend_op *opline; + uint8_t copied_args; + zval args[3]; +}; + +static zend_always_inline bool zend_frameless_arg_needs_reentry_copy(zval *zv) +{ + ZVAL_DEREF(zv); + return Z_TYPE_P(zv) == IS_ARRAY || Z_TYPE_P(zv) == IS_STRING; +} + +static void zend_frameless_reentry_copy_arg(zend_frameless_reentry_copies *copies, uint32_t arg, zval *zv) +{ + if (!zend_frameless_arg_needs_reentry_copy(zv)) { + return; + } + + ZVAL_COPY_DEREF(&copies->args[arg], zv); + copies->copied_args |= (1u << arg); +} + +static bool zend_frameless_reentry_has_copies(zend_execute_data *execute_data, const zend_op *opline) +{ + for (zend_frameless_reentry_copies *copies = EG(frameless_reentry_copies); + copies; + copies = copies->prev) { + if (copies->execute_data == execute_data && copies->opline == opline) { + return true; + } + } + + return false; +} + +ZEND_API bool zend_frameless_protect_args_for_reentry(void) +{ + zend_execute_data *execute_data = EG(current_execute_data); + if (!execute_data) { + return false; + } + + if (!EX(func) || !ZEND_USER_CODE(EX(func)->type)) { + return false; + } + + const zend_op *opline = EX(opline); + if (!opline || !ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) { + return false; + } + + if (zend_frameless_reentry_has_copies(execute_data, opline)) { + return true; + } + + uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode); + if (num_args == 0) { + return false; + } + + zend_frameless_reentry_copies *copies = emalloc(sizeof(zend_frameless_reentry_copies)); + copies->execute_data = execute_data; + copies->opline = opline; + copies->copied_args = 0; + + if (opline->op1_type == IS_CV) { + zend_frameless_reentry_copy_arg(copies, 0, + zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data)); + } + if (num_args >= 2 && opline->op2_type == IS_CV) { + zend_frameless_reentry_copy_arg(copies, 1, + zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data)); + } + if (num_args >= 3 && (opline + 1)->op1_type == IS_CV) { + zend_frameless_reentry_copy_arg(copies, 2, + zend_get_zval_ptr(opline + 1, (opline + 1)->op1_type, &(opline + 1)->op1, execute_data)); + } + + if (copies->copied_args == 0) { + efree(copies); + return false; + } + + copies->prev = EG(frameless_reentry_copies); + EG(frameless_reentry_copies) = copies; + + return true; +} + +ZEND_API void zend_frameless_schedule_reentry_cleanup(void) +{ + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); +} + +static bool zend_frameless_reentry_copies_in_use(zend_frameless_reentry_copies *copies) +{ + for (zend_execute_data *execute_data = EG(current_execute_data); + execute_data; + execute_data = execute_data->prev_execute_data) { + if (execute_data == copies->execute_data && execute_data->opline == copies->opline) { + return true; + } + } + + return false; +} + +static void zend_frameless_cleanup_reentry_copies_ex(bool force) +{ + zend_frameless_reentry_copies **next = &EG(frameless_reentry_copies); + + while (*next) { + zend_frameless_reentry_copies *copies = *next; + + if (!force && zend_frameless_reentry_copies_in_use(copies)) { + next = &copies->prev; + continue; + } + + *next = copies->prev; + + for (uint32_t i = 0; i < 3; i++) { + if (copies->copied_args & (1u << i)) { + zval_ptr_dtor(&copies->args[i]); + } + } + + efree(copies); + } + + if (EG(frameless_reentry_copies)) { + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + } +} + +ZEND_API void zend_frameless_cleanup_reentry_copies(void) +{ + zend_frameless_cleanup_reentry_copies_ex(false); +} + +ZEND_API void zend_frameless_cleanup_reentry_copies_force(void) +{ + zend_frameless_cleanup_reentry_copies_ex(true); +} + static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv) { if (Z_ISUNDEF_P(zv)) { @@ -4081,6 +4228,7 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 1dd50ab8a69e..1ddd821e0c1a 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -428,6 +428,10 @@ ZEND_API zend_result zend_set_user_opcode_handler(uint8_t opcode, user_opcode_ha ZEND_API user_opcode_handler_t zend_get_user_opcode_handler(uint8_t opcode); ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode_op *node, const zend_execute_data *execute_data); +ZEND_API bool zend_frameless_protect_args_for_reentry(void); +ZEND_API void zend_frameless_schedule_reentry_cleanup(void); +ZEND_API void zend_frameless_cleanup_reentry_copies(void); +ZEND_API void zend_frameless_cleanup_reentry_copies_force(void); ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table); ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *execute_data); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index c81a8cfe5ff7..bbbf4b2528d6 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -176,6 +176,7 @@ void init_executor(void) /* {{{ */ EG(full_tables_cleanup) = 0; ZEND_ATOMIC_BOOL_INIT(&EG(vm_interrupt), false); ZEND_ATOMIC_BOOL_INIT(&EG(timed_out), false); + EG(frameless_reentry_copies) = NULL; EG(exception) = NULL; EG(prev_exception) = NULL; @@ -275,6 +276,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) zend_string *key; zval *zv; + zend_frameless_cleanup_reentry_copies_force(); + EG(flags) |= EG_FLAGS_IN_RESOURCE_SHUTDOWN; zend_close_rsrc_list(&EG(regular_list)); @@ -1039,6 +1042,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_ /* This flag is regularly checked while running user functions, but not internal * So see whether interrupt flag was set while the function was running... */ if (zend_atomic_bool_exchange_ex(&EG(vm_interrupt), false)) { + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 62a97d753634..17412c95fc73 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -74,6 +74,7 @@ typedef struct _zend_vm_stack *zend_vm_stack; typedef struct _zend_ini_entry zend_ini_entry; typedef struct _zend_fiber_context zend_fiber_context; typedef struct _zend_fiber zend_fiber; +typedef struct _zend_frameless_reentry_copies zend_frameless_reentry_copies; typedef enum { ZEND_MEMOIZE_NONE, @@ -215,6 +216,7 @@ struct _zend_executor_globals { zend_atomic_bool vm_interrupt; zend_atomic_bool timed_out; + zend_frameless_reentry_copies *frameless_reentry_copies; HashTable *in_autoload; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 50f563b2c605..129a24a9e174 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2558,9 +2558,13 @@ ZEND_API zend_result zend_std_cast_object_tostring(zend_object *readobj, zval *w zend_class_entry *ce = readobj->ce; if (ce->__tostring) { zval retval; + bool frameless_reentry = zend_frameless_protect_args_for_reentry(); GC_ADDREF(readobj); zend_call_known_instance_method_with_0_params(ce->__tostring, readobj, &retval); zend_object_release(readobj); + if (frameless_reentry) { + zend_frameless_schedule_reentry_cleanup(); + } if (EXPECTED(Z_TYPE(retval) == IS_STRING)) { ZVAL_COPY_VALUE(writeobj, &retval); return SUCCESS; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 34906e1bfcca..b07f16947bb4 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10410,6 +10410,7 @@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA); ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); + zend_frameless_cleanup_reentry_copies(); SAVE_OPLINE(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d4b2aff1907b..bde0e72f24ea 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3873,6 +3873,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_FORWARD_SPEC_H static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); + zend_frameless_cleanup_reentry_copies(); SAVE_OPLINE(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); diff --git a/ext/pcre/tests/gh21639.phpt b/ext/pcre/tests/gh21639.phpt new file mode 100644 index 000000000000..ac2def4d08f5 --- /dev/null +++ b/ext/pcre/tests/gh21639.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-21639: Frameless preg_replace keeps volatile CV arguments alive during __toString() +--EXTENSIONS-- +pcre +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(1) "b" + [1]=> + string(2) "bb" +} +NULL From 9ce623cf1931738fc9ef0baa1019aee482e092fd Mon Sep 17 00:00:00 2001 From: pratik bhujel Date: Tue, 21 Apr 2026 21:43:06 +0545 Subject: [PATCH 2/3] Fix frameless reentry cleanup ordering --- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b07f16947bb4..e5c6675e4a5a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10410,8 +10410,8 @@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA); ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); - zend_frameless_cleanup_reentry_copies(); SAVE_OPLINE(); + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index bde0e72f24ea..940a33aa01fd 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3873,8 +3873,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMP_FORWARD_SPEC_H static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); - zend_frameless_cleanup_reentry_copies(); SAVE_OPLINE(); + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { From 28d83f6d0f8752560c4cf7f6ed11dbd6bf56a5f8 Mon Sep 17 00:00:00 2001 From: pratik bhujel Date: Tue, 21 Apr 2026 22:22:20 +0545 Subject: [PATCH 3/3] Clean up frameless reentry copies after handler return --- Zend/zend_execute.c | 44 ++++++++++++++++++++++------------- Zend/zend_execute.h | 2 +- Zend/zend_object_handlers.c | 5 +--- Zend/zend_vm_def.h | 12 ++++++++++ Zend/zend_vm_execute.h | 24 +++++++++++++++++++ ext/opcache/jit/zend_jit_ir.c | 13 +++++++++++ 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0e42b78eeb4b..fe71662ace1b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1673,11 +1673,6 @@ ZEND_API bool zend_frameless_protect_args_for_reentry(void) return true; } -ZEND_API void zend_frameless_schedule_reentry_cleanup(void) -{ - zend_atomic_bool_store_ex(&EG(vm_interrupt), true); -} - static bool zend_frameless_reentry_copies_in_use(zend_frameless_reentry_copies *copies) { for (zend_execute_data *execute_data = EG(current_execute_data); @@ -1691,31 +1686,48 @@ static bool zend_frameless_reentry_copies_in_use(zend_frameless_reentry_copies * return false; } -static void zend_frameless_cleanup_reentry_copies_ex(bool force) +static void zend_frameless_free_reentry_copies(zend_frameless_reentry_copies *copies) +{ + for (uint32_t i = 0; i < 3; i++) { + if (copies->copied_args & (1u << i)) { + zval_ptr_dtor(&copies->args[i]); + } + } + + efree(copies); +} + +ZEND_API void zend_frameless_cleanup_reentry_copies_for_handler(zend_execute_data *execute_data, const zend_op *opline) { zend_frameless_reentry_copies **next = &EG(frameless_reentry_copies); while (*next) { zend_frameless_reentry_copies *copies = *next; - if (!force && zend_frameless_reentry_copies_in_use(copies)) { + if (copies->execute_data != execute_data || copies->opline != opline) { next = &copies->prev; continue; } *next = copies->prev; + zend_frameless_free_reentry_copies(copies); + } +} - for (uint32_t i = 0; i < 3; i++) { - if (copies->copied_args & (1u << i)) { - zval_ptr_dtor(&copies->args[i]); - } - } +static void zend_frameless_cleanup_reentry_copies_ex(bool force) +{ + zend_frameless_reentry_copies **next = &EG(frameless_reentry_copies); - efree(copies); - } + while (*next) { + zend_frameless_reentry_copies *copies = *next; - if (EG(frameless_reentry_copies)) { - zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + if (!force && zend_frameless_reentry_copies_in_use(copies)) { + next = &copies->prev; + continue; + } + + *next = copies->prev; + zend_frameless_free_reentry_copies(copies); } } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 1ddd821e0c1a..e1184d1ee110 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -429,7 +429,7 @@ ZEND_API user_opcode_handler_t zend_get_user_opcode_handler(uint8_t opcode); ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode_op *node, const zend_execute_data *execute_data); ZEND_API bool zend_frameless_protect_args_for_reentry(void); -ZEND_API void zend_frameless_schedule_reentry_cleanup(void); +ZEND_API void zend_frameless_cleanup_reentry_copies_for_handler(zend_execute_data *execute_data, const zend_op *opline); ZEND_API void zend_frameless_cleanup_reentry_copies(void); ZEND_API void zend_frameless_cleanup_reentry_copies_force(void); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 129a24a9e174..8107fcbb4030 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2558,13 +2558,10 @@ ZEND_API zend_result zend_std_cast_object_tostring(zend_object *readobj, zval *w zend_class_entry *ce = readobj->ce; if (ce->__tostring) { zval retval; - bool frameless_reentry = zend_frameless_protect_args_for_reentry(); + zend_frameless_protect_args_for_reentry(); GC_ADDREF(readobj); zend_call_known_instance_method_with_0_params(ce->__tostring, readobj, &retval); zend_object_release(readobj); - if (frameless_reentry) { - zend_frameless_schedule_reentry_cleanup(); - } if (EXPECTED(Z_TYPE(retval) == IS_STRING)) { ZVAL_COPY_VALUE(writeobj, &retval); return SUCCESS; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index e5c6675e4a5a..830a943b80bf 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9690,6 +9690,9 @@ ZEND_VM_HANDLER(204, ZEND_FRAMELESS_ICALL_0, UNUSED, UNUSED, SPEC(OBSERVER)) zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(EX_VAR(opline->result.var)); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -9715,6 +9718,9 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER)) zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); function(result, arg1); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -9743,6 +9749,9 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER)) zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP1(); /* Set OP1 to UNDEF in case FREE_OP2() throws. */ @@ -9779,6 +9788,9 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2, arg3); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP1(); /* Set to UNDEF in case FREE_OP2() throws. */ diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 940a33aa01fd..3f85bede8476 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3738,6 +3738,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_HANDLER zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); /* Set OP1 to UNDEF in case FREE_OP(opline->op2_type, opline->op2.var) throws. */ @@ -3772,6 +3775,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVE zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); /* Set OP1 to UNDEF in case FREE_OP(opline->op2_type, opline->op2.var) throws. */ @@ -3808,6 +3814,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_HANDLER zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2, arg3); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); /* Set to UNDEF in case FREE_OP(opline->op2_type, opline->op2.var) throws. */ @@ -3848,6 +3857,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVE zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); function(result, arg1, arg2, arg3); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); /* Set to UNDEF in case FREE_OP(opline->op2_type, opline->op2.var) throws. */ @@ -4274,6 +4286,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_ zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); function(result, arg1); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -4300,6 +4315,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVE zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); function(result, arg1); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -38384,6 +38402,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_0_SPEC_UNUSED_ zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(EX_VAR(opline->result.var)); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -38404,6 +38425,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_0_SPEC_OBSERVE zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(EX_VAR(opline->result.var)); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 4251d6b891c9..669148ca05d6 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -17513,6 +17513,15 @@ static ir_ref jit_frameless_observer(zend_jit_ctx *jit, const zend_op *opline) { return skip; } +static void jit_frameless_cleanup_reentry_copies(zend_jit_ctx *jit, const zend_op *opline) +{ + ir_ref if_copies = ir_IF(ir_LOAD_A(jit_EG(frameless_reentry_copies))); + ir_IF_TRUE_cold(if_copies); + ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)zend_frameless_cleanup_reentry_copies_for_handler), + jit_FP(jit), ir_CONST_ADDR((size_t)opline)); + ir_MERGE_WITH_EMPTY_FALSE(if_copies); +} + static void jit_frameless_icall0(zend_jit_ctx *jit, const zend_op *opline) { jit_SET_EX_OPLINE(jit, opline); @@ -17533,6 +17542,7 @@ static void jit_frameless_icall0(zend_jit_ctx *jit, const zend_op *opline) ir_MERGE_WITH(skip_observer); } + jit_frameless_cleanup_reentry_copies(jit, opline); zend_jit_check_exception(jit); } @@ -17572,6 +17582,7 @@ static void jit_frameless_icall1(zend_jit_ctx *jit, const zend_op *opline, uint3 ir_MERGE_WITH(skip_observer); } + jit_frameless_cleanup_reentry_copies(jit, opline); jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL); zend_jit_check_exception(jit); } @@ -17626,6 +17637,7 @@ static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint3 ir_MERGE_WITH(skip_observer); } + jit_frameless_cleanup_reentry_copies(jit, opline); jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL); /* Set OP1 to UNDEF in case FREE_OP2() throws. */ if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0 @@ -17707,6 +17719,7 @@ static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint3 ir_MERGE_WITH(skip_observer); } + jit_frameless_cleanup_reentry_copies(jit, opline); jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL); /* Set OP1 to UNDEF in case FREE_OP2() throws. */ bool op1_undef = false;