diff --git a/Zend/tests/gh21639.phpt b/Zend/tests/gh21639.phpt new file mode 100644 index 000000000000..cebad61a0dbe --- /dev/null +++ b/Zend/tests/gh21639.phpt @@ -0,0 +1,151 @@ +--TEST-- +GH-21639: Frameless calls keep volatile arguments alive +--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 MinArg { + public function __toString(): string { + global $minArg; + + $minArg = null; + + return "a"; + } +} + +$minArg = new MinArg(); +$minResult = min($minArg, "b"); + +var_dump($minResult instanceof MinArg); +var_dump($minArg); + +class MaxArg { + public function __toString(): string { + global $maxArg; + + $maxArg = null; + + return "a"; + } +} + +$maxArg = new MaxArg(); +$maxResult = max("0", $maxArg); + +var_dump($maxResult instanceof MaxArg); +var_dump($maxArg); +?> +--EXPECT-- +string(5) "C, 42" +NULL +NULL +string(4) "A, B" +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 +bool(true) +NULL diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index f7009c4ffba6..235ee78fecd3 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -75,13 +75,13 @@ function method_exists($object_or_class, string $method): bool {} /** * @param object|string $object_or_class - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function property_exists($object_or_class, string $property): bool {} /** - * @frameless-function {"arity": 1} - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 1, "needs_arg_copy": true} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function class_exists(string $class, bool $autoload = true): bool {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index cf34b1c0012d..fdce250e70a4 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3dbc84896823c9aaa9ac8aeef8841266920c3e50 */ + * Stub hash: f55ea5d885964793884ccae6fd166e92e48abe8d */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0) ZEND_ARG_TYPE_MASK(0, status, MAY_BE_STRING|MAY_BE_LONG, "0") @@ -226,15 +226,15 @@ ZEND_END_ARG_INFO() ZEND_FRAMELESS_FUNCTION(property_exists, 2); static const zend_frameless_function_info frameless_function_infos_property_exists[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(property_exists, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(property_exists, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(class_exists, 1); ZEND_FRAMELESS_FUNCTION(class_exists, 2); static const zend_frameless_function_info frameless_function_infos_class_exists[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(class_exists, 1), 1 }, - { ZEND_FRAMELESS_FUNCTION_NAME(class_exists, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(class_exists, 1), 1 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(class_exists, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5e93c2f864ec..5f1157c448d5 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4668,11 +4668,11 @@ static const zend_frameless_function_info *find_frameless_function_info(zend_ast } while (frameless_function_info->handler) { - if (frameless_function_info->num_args >= args->children + uint32_t num_args = ZEND_FLF_INFO_NUM_ARGS(frameless_function_info); + if (num_args >= args->children && fbc->common.required_num_args <= args->children && (!(fbc->common.fn_flags & ZEND_ACC_VARIADIC) - || frameless_function_info->num_args == args->children)) { - uint32_t num_args = frameless_function_info->num_args; + || num_args == args->children)) { uint32_t offset = find_frameless_function_offset(num_args, frameless_function_info->handler); if (offset == (uint32_t)-1) { continue; @@ -4688,7 +4688,7 @@ static const zend_frameless_function_info *find_frameless_function_info(zend_ast static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *args, zend_function *fbc, const zend_frameless_function_info *frameless_function_info, uint32_t type) { int lineno = CG(zend_lineno); - uint32_t num_args = frameless_function_info->num_args; + uint32_t num_args = ZEND_FLF_INFO_NUM_ARGS(frameless_function_info); uint32_t offset = find_frameless_function_offset(num_args, frameless_function_info->handler); znode arg_zvs[3]; for (uint32_t i = 0; i < num_args; i++) { @@ -4705,7 +4705,7 @@ static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *ar uint8_t opcode = ZEND_FRAMELESS_ICALL_0 + num_args; uint32_t opnum = get_next_op_number(); zend_op *opline = zend_emit_op_tmp(result, opcode, NULL, NULL); - opline->extended_value = offset; + opline->extended_value = offset | ZEND_FLF_INFO_FLAGS(frameless_function_info); opline->lineno = lineno; if (num_args >= 1) { SET_NODE(opline->op1, &arg_zvs[0]); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ea5f55cf6ef7..fef193028726 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1582,15 +1582,20 @@ 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) +static zend_always_inline void zend_frameless_copy_arg(zval *dst, zval *src) { - if (Z_ISUNDEF_P(zv)) { - ZVAL_NULL(ZEND_CALL_VAR_NUM(call, arg)); + if (Z_ISUNDEF_P(src)) { + ZVAL_NULL(dst); } else { - ZVAL_COPY_DEREF(ZEND_CALL_VAR_NUM(call, arg), zv); + ZVAL_COPY_DEREF(dst, src); } } +static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv) +{ + zend_frameless_copy_arg(ZEND_CALL_VAR_NUM(call, arg), zv); +} + ZEND_API void zend_frameless_observed_call(zend_execute_data *execute_data) { const zend_op *opline = EX(opline); diff --git a/Zend/zend_frameless_function.h b/Zend/zend_frameless_function.h index d64ca7ee15e2..1bdeb29ba2a0 100644 --- a/Zend/zend_frameless_function.h +++ b/Zend/zend_frameless_function.h @@ -36,8 +36,14 @@ #define ZEND_FRAMELESS_FUNCTION_NAME(name, arity) zflf_##name##_##arity #define ZEND_OP_IS_FRAMELESS_ICALL(opcode) ((opcode) >= ZEND_FRAMELESS_ICALL_0 && (opcode) <= ZEND_FRAMELESS_ICALL_3) #define ZEND_FLF_NUM_ARGS(opcode) ((opcode) - ZEND_FRAMELESS_ICALL_0) -#define ZEND_FLF_FUNC(opline) (zend_flf_functions[(opline)->extended_value]) -#define ZEND_FLF_HANDLER(opline) (zend_flf_handlers[(opline)->extended_value]) +#define ZEND_FLF_ARG_COPY (1u << 31) +#define ZEND_FLF_NUM_ARGS_MASK (~ZEND_FLF_ARG_COPY) +#define ZEND_FLF_INFO_NUM_ARGS(info) ((info)->num_args & ZEND_FLF_NUM_ARGS_MASK) +#define ZEND_FLF_INFO_FLAGS(info) ((info)->num_args & ZEND_FLF_ARG_COPY) +#define ZEND_FLF_OFFSET(opline) ((opline)->extended_value & ZEND_FLF_NUM_ARGS_MASK) +#define ZEND_FLF_USES_ARG_COPY(opline) ((opline)->extended_value & ZEND_FLF_ARG_COPY) +#define ZEND_FLF_FUNC(opline) (zend_flf_functions[ZEND_FLF_OFFSET(opline)]) +#define ZEND_FLF_HANDLER(opline) (zend_flf_handlers[ZEND_FLF_OFFSET(opline)]) #define ZEND_FRAMELESS_FUNCTION(name, arity) \ void ZEND_FRAMELESS_FUNCTION_NAME(name, arity)(ZEND_FRAMELESS_FUNCTION_PARAMETERS_##arity) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 34906e1bfcca..c2f645978443 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9713,7 +9713,14 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER)) #endif { zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + function(result, &arg1_copy); + zval_ptr_dtor_nogc(&arg1_copy); + } else { + function(result, arg1); + } } FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9741,7 +9748,16 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER)) #endif { zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + function(result, &arg1_copy, &arg2_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + } else { + function(result, arg1, arg2); + } } FREE_OP1(); @@ -9777,7 +9793,18 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER)) #endif { zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy, arg3_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + zend_frameless_copy_arg(&arg3_copy, arg3); + function(result, &arg1_copy, &arg2_copy, &arg3_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + zval_ptr_dtor_nogc(&arg3_copy); + } else { + function(result, arg1, arg2, arg3); + } } FREE_OP1(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d4b2aff1907b..c6fef89352d8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3736,7 +3736,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_HANDLER #endif { zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + function(result, &arg1_copy, &arg2_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + } else { + function(result, arg1, arg2); + } } FREE_OP(opline->op1_type, opline->op1.var); @@ -3770,7 +3779,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVE #endif { zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + function(result, &arg1_copy, &arg2_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + } else { + function(result, arg1, arg2); + } } FREE_OP(opline->op1_type, opline->op1.var); @@ -3806,7 +3824,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_HANDLER #endif { zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy, arg3_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + zend_frameless_copy_arg(&arg3_copy, arg3); + function(result, &arg1_copy, &arg2_copy, &arg3_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + zval_ptr_dtor_nogc(&arg3_copy); + } else { + function(result, arg1, arg2, arg3); + } } FREE_OP(opline->op1_type, opline->op1.var); @@ -3846,7 +3875,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVE #endif { zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline); - function(result, arg1, arg2, arg3); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy, arg2_copy, arg3_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + zend_frameless_copy_arg(&arg2_copy, arg2); + zend_frameless_copy_arg(&arg3_copy, arg3); + function(result, &arg1_copy, &arg2_copy, &arg3_copy); + zval_ptr_dtor_nogc(&arg1_copy); + zval_ptr_dtor_nogc(&arg2_copy); + zval_ptr_dtor_nogc(&arg3_copy); + } else { + function(result, arg1, arg2, arg3); + } } FREE_OP(opline->op1_type, opline->op1.var); @@ -4271,7 +4311,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_ #endif { zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + function(result, &arg1_copy); + zval_ptr_dtor_nogc(&arg1_copy); + } else { + function(result, arg1); + } } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4297,7 +4344,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVE #endif { zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); - function(result, arg1); + if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { + zval arg1_copy; + zend_frameless_copy_arg(&arg1_copy, arg1); + function(result, &arg1_copy); + zval_ptr_dtor_nogc(&arg1_copy); + } else { + function(result, arg1); + } } FREE_OP(opline->op1_type, opline->op1.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); diff --git a/build/gen_stub.php b/build/gen_stub.php index 28b168c72642..f8cd466cf94d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1406,7 +1406,11 @@ public function getFramelessDeclaration(FuncInfo $funcInfo): ?string { $code .= 'static const zend_frameless_function_info ' . $this->getFramelessFunctionInfosName() . "[] = {\n"; foreach ($this->framelessFunctionInfos as $framelessFunctionInfo) { - $code .= "\t{ ZEND_FRAMELESS_FUNCTION_NAME({$this->name->getFunctionName()}, {$framelessFunctionInfo->arity}), {$framelessFunctionInfo->arity} },\n"; + $numArgs = (string) $framelessFunctionInfo->arity; + if ($framelessFunctionInfo->needsArgCopy) { + $numArgs .= " | ZEND_FLF_ARG_COPY"; + } + $code .= "\t{ ZEND_FRAMELESS_FUNCTION_NAME({$this->name->getFunctionName()}, {$framelessFunctionInfo->arity}), $numArgs },\n"; } $code .= "\t{ 0 },\n"; $code .= "};\n"; @@ -4166,6 +4170,7 @@ function parseDocComment(DocComment $comment): array { class FramelessFunctionInfo { public int $arity; + public bool $needsArgCopy = false; } function parseFramelessFunctionInfo(string $json): FramelessFunctionInfo { @@ -4173,6 +4178,7 @@ function parseFramelessFunctionInfo(string $json): FramelessFunctionInfo { $json = json_decode($json, true); $framelessFunctionInfo = new FramelessFunctionInfo(); $framelessFunctionInfo->arity = $json["arity"]; + $framelessFunctionInfo->needsArgCopy = $json["needs_arg_copy"] ?? false; return $framelessFunctionInfo; } diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index da73c98c4355..dd65d9c7bc1b 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2551,15 +2551,24 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op jit_frameless_icall0(jit, opline); goto done; case ZEND_FRAMELESS_ICALL_1: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); jit_frameless_icall1(jit, opline, op1_info); goto done; case ZEND_FRAMELESS_ICALL_2: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); op2_info = OP2_INFO(); jit_frameless_icall2(jit, opline, op1_info, op2_info); goto done; case ZEND_FRAMELESS_ICALL_3: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); op2_info = OP2_INFO(); jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO()); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 320967419055..1a15823d9959 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -6382,15 +6382,24 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par jit_frameless_icall0(jit, opline); goto done; case ZEND_FRAMELESS_ICALL_1: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); jit_frameless_icall1(jit, opline, op1_info); goto done; case ZEND_FRAMELESS_ICALL_2: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); op2_info = OP2_INFO(); jit_frameless_icall2(jit, opline, op1_info, op2_info); goto done; case ZEND_FRAMELESS_ICALL_3: + if (ZEND_FLF_USES_ARG_COPY(opline)) { + break; + } op1_info = OP1_INFO(); op2_info = OP2_INFO(); jit_frameless_icall3(jit, opline, op1_info, op2_info, OP1_DATA_INFO()); diff --git a/ext/pcre/php_pcre.stub.php b/ext/pcre/php_pcre.stub.php index 0cd045b7efae..2e8e61ad95df 100644 --- a/ext/pcre/php_pcre.stub.php +++ b/ext/pcre/php_pcre.stub.php @@ -100,7 +100,7 @@ /** * @param array $matches - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function preg_match(string $pattern, string $subject, &$matches = null, int $flags = 0, int $offset = 0): int|false {} @@ -110,7 +110,7 @@ function preg_match_all(string $pattern, string $subject, &$matches = null, int /** * @param int $count * @return string|array|null - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function preg_replace(string|array $pattern, string|array $replacement, string|array $subject, int $limit = -1, &$count = null): string|array|null {} diff --git a/ext/pcre/php_pcre_arginfo.h b/ext/pcre/php_pcre_arginfo.h index 86eaf0d8d60f..1d9e0958df2e 100644 --- a/ext/pcre/php_pcre_arginfo.h +++ b/ext/pcre/php_pcre_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 63de1d37ab303e1d6af7c96eaeeba09d7f35d116 */ + * Stub hash: a04e5cf44bf936f6e5122b7ca2526b98913df1c7 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_preg_match, 0, 2, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0) @@ -65,13 +65,13 @@ ZEND_END_ARG_INFO() ZEND_FRAMELESS_FUNCTION(preg_match, 2); static const zend_frameless_function_info frameless_function_infos_preg_match[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(preg_match, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(preg_match, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(preg_replace, 3); static const zend_frameless_function_info frameless_function_infos_preg_replace[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(preg_replace, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(preg_replace, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; diff --git a/ext/pcre/tests/gh21639.phpt b/ext/pcre/tests/gh21639.phpt new file mode 100644 index 000000000000..4a896e966d27 --- /dev/null +++ b/ext/pcre/tests/gh21639.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-21639: Frameless preg_replace keeps volatile arguments alive +--EXTENSIONS-- +pcre +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(1) "b" + [1]=> + string(2) "bb" +} +NULL diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 091b5cbac22b..5a8d000d1368 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -1614,13 +1614,13 @@ function key(array|object $array): int|string|null {} /** * @compile-time-eval - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function min(mixed $value, mixed ...$values): mixed {} /** * @compile-time-eval - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function max(mixed $value, mixed ...$values): mixed {} @@ -1630,8 +1630,8 @@ function array_walk_recursive(array|object &$array, callable $callback, mixed $a /** * @compile-time-eval - * @frameless-function {"arity": 2} - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 2, "needs_arg_copy": true} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function in_array(mixed $needle, array $haystack, bool $strict = false): bool {} @@ -2311,7 +2311,7 @@ function strcoll(string $string1, string $string2): int {} /** * @compile-time-eval * @frameless-function {"arity": 1} - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function trim(string $string, string $characters = " \n\r\t\v\0"): string {} @@ -2339,8 +2339,8 @@ function explode(string $separator, string $string, int $limit = PHP_INT_MAX): a /** * @compile-time-eval - * @frameless-function {"arity": 1} - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 1, "needs_arg_copy": true} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function implode(string|array $separator, ?array $array = null): string {} @@ -2368,7 +2368,7 @@ function basename(string $path, string $suffix = ""): string {} /** * @refcount 1 * @frameless-function {"arity": 1} - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function dirname(string $path, int $levels = 1): string {} @@ -2387,8 +2387,8 @@ function stristr(string $haystack, string $needle, bool $before_needle = false): /** * @compile-time-eval * @refcount 1 - * @frameless-function {"arity": 2} - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 2, "needs_arg_copy": true} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function strstr(string $haystack, string $needle, bool $before_needle = false): string|false {} @@ -2397,8 +2397,8 @@ function strchr(string $haystack, string $needle, bool $before_needle = false): /** * @compile-time-eval - * @frameless-function {"arity": 2} - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 2, "needs_arg_copy": true} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function strpos(string $haystack, string $needle, int $offset = 0): int|false {} @@ -2419,13 +2419,13 @@ function strrchr(string $haystack, string $needle, bool $before_needle = false): /** * @compile-time-eval - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function str_contains(string $haystack, string $needle): bool {} /** * @compile-time-eval - * @frameless-function {"arity": 2} + * @frameless-function {"arity": 2, "needs_arg_copy": true} */ function str_starts_with(string $haystack, string $needle): bool {} @@ -2440,8 +2440,8 @@ function chunk_split(string $string, int $length = 76, string $separator = "\r\n /** * @compile-time-eval - * @frameless-function {"arity": 2} - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 2, "needs_arg_copy": true} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function substr(string $string, int $offset, ?int $length = null): string {} @@ -2480,8 +2480,8 @@ function ucwords(string $string, string $separators = " \t\r\n\f\v"): string {} /** * @compile-time-eval - * @frameless-function {"arity": 2} - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 2, "needs_arg_copy": true} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function strtr(string $string, string|array $from, ?string $to = null): string {} @@ -2520,7 +2520,7 @@ function stripslashes(string $string): string {} * @param int $count * @return string|array * @compile-time-eval - * @frameless-function {"arity": 3} + * @frameless-function {"arity": 3, "needs_arg_copy": true} */ function str_replace(array|string $search, array|string $replace, string|array $subject, &$count = null): string|array {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 666957db7120..8c0652186132 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 5eeeaf1f292c72e4553dabc5a64f27781bf57d86 */ + * Stub hash: e9a00a008a50c21542a4daf8e99e2cf7332be0e4 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -2183,21 +2183,21 @@ ZEND_END_ARG_INFO() ZEND_FRAMELESS_FUNCTION(min, 2); static const zend_frameless_function_info frameless_function_infos_min[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(min, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(min, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(max, 2); static const zend_frameless_function_info frameless_function_infos_max[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(max, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(max, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(in_array, 2); ZEND_FRAMELESS_FUNCTION(in_array, 3); static const zend_frameless_function_info frameless_function_infos_in_array[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(in_array, 2), 2 }, - { ZEND_FRAMELESS_FUNCTION_NAME(in_array, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(in_array, 2), 2 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(in_array, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; @@ -2205,15 +2205,15 @@ ZEND_FRAMELESS_FUNCTION(trim, 1); ZEND_FRAMELESS_FUNCTION(trim, 2); static const zend_frameless_function_info frameless_function_infos_trim[] = { { ZEND_FRAMELESS_FUNCTION_NAME(trim, 1), 1 }, - { ZEND_FRAMELESS_FUNCTION_NAME(trim, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(trim, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(implode, 1); ZEND_FRAMELESS_FUNCTION(implode, 2); static const zend_frameless_function_info frameless_function_infos_implode[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(implode, 1), 1 }, - { ZEND_FRAMELESS_FUNCTION_NAME(implode, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(implode, 1), 1 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(implode, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; @@ -2221,57 +2221,57 @@ ZEND_FRAMELESS_FUNCTION(dirname, 1); ZEND_FRAMELESS_FUNCTION(dirname, 2); static const zend_frameless_function_info frameless_function_infos_dirname[] = { { ZEND_FRAMELESS_FUNCTION_NAME(dirname, 1), 1 }, - { ZEND_FRAMELESS_FUNCTION_NAME(dirname, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(dirname, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(strstr, 2); ZEND_FRAMELESS_FUNCTION(strstr, 3); static const zend_frameless_function_info frameless_function_infos_strstr[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(strstr, 2), 2 }, - { ZEND_FRAMELESS_FUNCTION_NAME(strstr, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(strstr, 2), 2 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(strstr, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(strpos, 2); ZEND_FRAMELESS_FUNCTION(strpos, 3); static const zend_frameless_function_info frameless_function_infos_strpos[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(strpos, 2), 2 }, - { ZEND_FRAMELESS_FUNCTION_NAME(strpos, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(strpos, 2), 2 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(strpos, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(str_contains, 2); static const zend_frameless_function_info frameless_function_infos_str_contains[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(str_contains, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(str_contains, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(str_starts_with, 2); static const zend_frameless_function_info frameless_function_infos_str_starts_with[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(str_starts_with, 2), 2 }, + { ZEND_FRAMELESS_FUNCTION_NAME(str_starts_with, 2), 2 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(substr, 2); ZEND_FRAMELESS_FUNCTION(substr, 3); static const zend_frameless_function_info frameless_function_infos_substr[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(substr, 2), 2 }, - { ZEND_FRAMELESS_FUNCTION_NAME(substr, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(substr, 2), 2 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(substr, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(strtr, 2); ZEND_FRAMELESS_FUNCTION(strtr, 3); static const zend_frameless_function_info frameless_function_infos_strtr[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(strtr, 2), 2 }, - { ZEND_FRAMELESS_FUNCTION_NAME(strtr, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(strtr, 2), 2 | ZEND_FLF_ARG_COPY }, + { ZEND_FRAMELESS_FUNCTION_NAME(strtr, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, }; ZEND_FRAMELESS_FUNCTION(str_replace, 3); static const zend_frameless_function_info frameless_function_infos_str_replace[] = { - { ZEND_FRAMELESS_FUNCTION_NAME(str_replace, 3), 3 }, + { ZEND_FRAMELESS_FUNCTION_NAME(str_replace, 3), 3 | ZEND_FLF_ARG_COPY }, { 0 }, };