From 2671fc47247b79968782af20ad08f0f065d8af4a Mon Sep 17 00:00:00 2001 From: pratik bhujel Date: Fri, 24 Apr 2026 20:00:04 +0545 Subject: [PATCH] Fix GH-21639: Keep frameless call args stable during reentry --- Zend/tests/gh21639.phpt | 122 ++++++++++++++++ Zend/zend.c | 1 + Zend/zend_execute.c | 243 ++++++++++++++++++++++++++++++- Zend/zend_execute.h | 5 + Zend/zend_execute_API.c | 4 + Zend/zend_globals.h | 2 + Zend/zend_object_handlers.c | 1 + Zend/zend_vm_def.h | 25 ++++ Zend/zend_vm_execute.h | 98 +++++++++++++ ext/opcache/jit/zend_jit.c | 14 +- ext/opcache/jit/zend_jit_ir.c | 36 +++++ ext/opcache/jit/zend_jit_trace.c | 12 +- ext/pcre/tests/gh21639.phpt | 29 ++++ 13 files changed, 585 insertions(+), 7 deletions(-) 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 0000000000000..cbd326ed18219 --- /dev/null +++ b/Zend/tests/gh21639.phpt @@ -0,0 +1,122 @@ +--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); + +class StrContainsHaystack { + public function __toString(): string { + global $strContainsNeedle; + + $strContainsNeedle = null; + + return "abc"; + } +} + +class StrContainsNeedle { + public function __toString(): string { + return "b"; + } +} + +$strContainsHaystack = new StrContainsHaystack(); +$strContainsNeedle = new StrContainsNeedle(); + +var_dump(str_contains($strContainsHaystack, $strContainsNeedle)); +var_dump($strContainsNeedle); +?> +--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 +bool(true) +NULL diff --git a/Zend/zend.c b/Zend/zend.c index f4236053af3d1..09f93600b1364 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -812,6 +812,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ ZVAL_UNDEF(&executor_globals->user_error_handler); ZVAL_UNDEF(&executor_globals->user_exception_handler); 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 85461eaa15693..840524e95d07e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1564,7 +1564,166 @@ static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval * } } -static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv) +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; +} + +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_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 (copies->execute_data != execute_data || copies->opline != opline) { + next = &copies->prev; + continue; + } + + *next = copies->prev; + zend_frameless_free_reentry_copies(copies); + } +} + +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; + zend_frameless_free_reentry_copies(copies); + } +} + +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_call_copy_arg(zend_execute_data *call, uint32_t arg, zval *zv) { if (Z_ISUNDEF_P(zv)) { ZVAL_NULL(ZEND_CALL_VAR_NUM(call, arg)); @@ -1573,6 +1732,81 @@ static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, } } +static zend_always_inline bool zend_frameless_arg_is_object(zval *arg) +{ + ZVAL_DEREF(arg); + return Z_TYPE_P(arg) == IS_OBJECT; +} + +static zend_always_inline bool zend_frameless_call_needs_separated_args( + const zend_op *opline, zval *arg1, zval *arg2, zval *arg3 +) +{ + uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode); + bool has_cv_arg = false; + + if (num_args >= 1) { + has_cv_arg |= opline->op1_type == IS_CV; + } + if (num_args >= 2) { + has_cv_arg |= opline->op2_type == IS_CV; + } + if (num_args >= 3) { + has_cv_arg |= (opline + 1)->op1_type == IS_CV; + } + + if (!has_cv_arg) { + return false; + } + + return (num_args >= 1 && zend_frameless_arg_is_object(arg1)) + || (num_args >= 2 && zend_frameless_arg_is_object(arg2)) + || (num_args >= 3 && zend_frameless_arg_is_object(arg3)); +} + +ZEND_API void zend_frameless_call_with_separated_args(zend_execute_data *execute_data) +{ + const zend_op *opline = EX(opline); + uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode); + zend_function *fbc = ZEND_FLF_FUNC(opline); + zval *result = EX_VAR(opline->result.var); + + zend_execute_data *call = zend_vm_stack_push_call_frame_ex( + zend_vm_calc_used_stack(num_args, fbc), ZEND_CALL_NESTED_FUNCTION, fbc, num_args, NULL); + call->prev_execute_data = execute_data; + + switch (num_args) { + case 3: + frameless_call_copy_arg(call, 2, + zend_get_zval_ptr(opline + 1, (opline + 1)->op1_type, &(opline + 1)->op1, execute_data)); + ZEND_FALLTHROUGH; + case 2: + frameless_call_copy_arg(call, 1, + zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data)); + ZEND_FALLTHROUGH; + case 1: + frameless_call_copy_arg(call, 0, + zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data)); + } + + EG(current_execute_data) = call; + fbc->internal_function.handler(call, result); + EG(current_execute_data) = execute_data; + + if (UNEXPECTED(EG(exception) != NULL)) { + zend_rethrow_exception(execute_data); + } + + zend_vm_stack_free_args(call); + + uint32_t call_info = ZEND_CALL_INFO(call); + if (UNEXPECTED(call_info & ZEND_CALL_ALLOCATED)) { + zend_vm_stack_free_call_frame_ex(call_info, call); + } else { + EG(vm_stack_top) = (zval*) call; + } +} + ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data) { const zend_op *opline = EX(opline); @@ -1584,9 +1818,9 @@ ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data) call->prev_execute_data = execute_data; switch (num_args) { - case 3: frameless_observed_call_copy(call, 2, zend_get_zval_ptr(opline+1, (opline+1)->op1_type, &(opline+1)->op1, execute_data)); ZEND_FALLTHROUGH; - case 2: frameless_observed_call_copy(call, 1, zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data)); ZEND_FALLTHROUGH; - case 1: frameless_observed_call_copy(call, 0, zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data)); + case 3: frameless_call_copy_arg(call, 2, zend_get_zval_ptr(opline+1, (opline+1)->op1_type, &(opline+1)->op1, execute_data)); ZEND_FALLTHROUGH; + case 2: frameless_call_copy_arg(call, 1, zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data)); ZEND_FALLTHROUGH; + case 1: frameless_call_copy_arg(call, 0, zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data)); } EG(current_execute_data) = call; @@ -4311,6 +4545,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 ba48b19bcfe1b..77527726ea171 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -502,6 +502,11 @@ 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_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); +ZEND_API void zend_frameless_call_with_separated_args(zend_execute_data *execute_data); 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 0022eb4a1df8b..b32c7d75432c6 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -173,6 +173,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; @@ -269,6 +270,8 @@ void shutdown_destructors(void) /* {{{ */ /* Free values held by the executor. */ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) { + zend_frameless_cleanup_reentry_copies_force(); + EG(flags) |= EG_FLAGS_IN_RESOURCE_SHUTDOWN; zend_close_rsrc_list(&EG(regular_list)); @@ -1048,6 +1051,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 8257df32e8312..49e0c68ec51b1 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -74,6 +74,7 @@ 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_error_info zend_error_info; +typedef struct _zend_frameless_reentry_copies zend_frameless_reentry_copies; typedef enum { ZEND_MEMOIZE_NONE, @@ -225,6 +226,7 @@ struct _zend_executor_globals { zend_atomic_bool vm_interrupt; zend_atomic_bool timed_out; + zend_frameless_reentry_copies *frameless_reentry_copies; HashTable autoload_current_classnames; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 571aa9e92abc7..d29fc4a824d6f 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2544,6 +2544,7 @@ ZEND_API zend_result zend_std_cast_object_tostring(zend_object *readobj, zval *w const zend_class_entry *ce = readobj->ce; if (ce->__tostring) { zval retval; + 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); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1f7e09d1be358..761d75b7d8873 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9874,10 +9874,16 @@ ZEND_VM_HANDLER(204, ZEND_FRAMELESS_ICALL_0, UNUSED, UNUSED, SPEC(OBSERVER)) zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, NULL, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(result); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -9899,10 +9905,16 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER)) zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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(); } @@ -9927,10 +9939,16 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER)) zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -9963,10 +9981,16 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, arg3))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -10625,6 +10649,7 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); 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 d5860da23b4c6..910572118735e 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3888,10 +3888,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -3922,10 +3928,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -3958,10 +3970,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, arg3))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -3998,10 +4016,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, arg3))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -4028,6 +4052,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); SAVE_OPLINE(); + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { @@ -4424,10 +4449,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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(); } @@ -4450,10 +4481,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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(); } @@ -37522,10 +37559,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, NULL, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(result); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -37542,10 +37585,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICA zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, NULL, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(result); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -56546,10 +56595,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_2_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -56580,10 +56635,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_2_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -56616,10 +56677,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_3_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, arg3))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -56656,10 +56723,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_3_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, arg2, arg3))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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. */ @@ -56686,6 +56759,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); SAVE_OPLINE(); + zend_frameless_cleanup_reentry_copies(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { @@ -57082,10 +57156,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_1_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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(); } @@ -57108,10 +57188,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_1_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, arg1, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { 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(); } @@ -89978,10 +90064,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_0_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, NULL, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(result); } + if (UNEXPECTED(EG(frameless_reentry_copies))) { + zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline); + } ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -89998,10 +90090,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_0_ zend_frameless_observed_call(execute_data); } else #endif + if (UNEXPECTED(zend_frameless_call_needs_separated_args(opline, NULL, NULL, NULL))) { + zend_frameless_call_with_separated_args(execute_data); + } else { zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline); function(result); } + 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.c b/ext/opcache/jit/zend_jit.c index 738f204c50261..5fc4287c04654 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -1426,7 +1426,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op bool recv_emitted = false; /* emitted at least one RECV opcode */ uint8_t smart_branch_opcode; uint32_t target_label, target_label2; - uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info, op1_mem_info; + uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info, op1_data_info, op1_mem_info; zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr; zend_class_entry *ce = NULL; bool ce_is_instanceof; @@ -2693,17 +2693,27 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op goto done; case ZEND_FRAMELESS_ICALL_1: op1_info = OP1_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, 0, 0)) { + break; + } jit_frameless_icall1(jit, opline, op1_info); goto done; case ZEND_FRAMELESS_ICALL_2: op1_info = OP1_INFO(); op2_info = OP2_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, op2_info, 0)) { + break; + } jit_frameless_icall2(jit, opline, op1_info, op2_info); goto done; case ZEND_FRAMELESS_ICALL_3: op1_info = OP1_INFO(); op2_info = OP2_INFO(); - jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO()); + op1_data_info = OP1_DATA_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, op2_info, op1_data_info)) { + break; + } + jit_frameless_icall3(jit, opline, op1_info, op2_info, op1_data_info); goto done; default: break; diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index e802d213aff1b..f4c3aa0359202 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -17800,6 +17800,38 @@ 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 bool zend_jit_frameless_call_may_need_separated_args( + const zend_op *opline, uint32_t op1_info, uint32_t op2_info, uint32_t op3_info) +{ + uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode); + bool has_cv_arg = false; + bool may_have_object_arg = false; + + if (num_args >= 1) { + has_cv_arg |= opline->op1_type == IS_CV; + may_have_object_arg |= (op1_info & (MAY_BE_OBJECT|MAY_BE_REF)) != 0; + } + if (num_args >= 2) { + has_cv_arg |= opline->op2_type == IS_CV; + may_have_object_arg |= (op2_info & (MAY_BE_OBJECT|MAY_BE_REF)) != 0; + } + if (num_args >= 3) { + has_cv_arg |= (opline + 1)->op1_type == IS_CV; + may_have_object_arg |= (op3_info & (MAY_BE_OBJECT|MAY_BE_REF)) != 0; + } + + return has_cv_arg && may_have_object_arg; +} + static void jit_frameless_icall0(zend_jit_ctx *jit, const zend_op *opline) { jit_SET_EX_OPLINE(jit, opline); @@ -17820,6 +17852,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); } @@ -17859,6 +17892,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); } @@ -17913,6 +17947,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 @@ -17994,6 +18029,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; diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 4b3cb663686d0..0807a717cd5a7 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -6470,17 +6470,27 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer, goto done; case ZEND_FRAMELESS_ICALL_1: op1_info = OP1_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, 0, 0)) { + break; + } jit_frameless_icall1(jit, opline, op1_info); goto done; case ZEND_FRAMELESS_ICALL_2: op1_info = OP1_INFO(); op2_info = OP2_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, op2_info, 0)) { + break; + } jit_frameless_icall2(jit, opline, op1_info, op2_info); goto done; case ZEND_FRAMELESS_ICALL_3: op1_info = OP1_INFO(); op2_info = OP2_INFO(); - jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO()); + op1_data_info = OP1_DATA_INFO(); + if (zend_jit_frameless_call_may_need_separated_args(opline, op1_info, op2_info, op1_data_info)) { + break; + } + jit_frameless_icall3(jit, opline, op1_info, op2_info, op1_data_info); goto done; default: break; diff --git a/ext/pcre/tests/gh21639.phpt b/ext/pcre/tests/gh21639.phpt new file mode 100644 index 0000000000000..ac2def4d08f54 --- /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