From 3163ac7038c0be38f63c7e4274b336faf6d7fbad Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 27 Jul 2026 22:36:09 +0800 Subject: [PATCH 01/12] ext/curl: fix mem leak when duphandle fails in curl_clone_obj (#22899) Move init_curl_handle() so that memory is not leaked if curl_easy_duphandle() fails within curl_clone_obj(). init_curl_handle() allocates state that curl_free_obj() later frees, but curl_free_obj() does nothing if ch->cp is not set. In curl_clone_obj(), defer that allocation until a valid copied handle exists so failed duplication leaves nothing to clean up. Closes #22899 --- ext/curl/interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 4e4da5503bbb..9eb05a7cfbb4 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -441,7 +441,6 @@ static zend_object *curl_clone_obj(zend_object *object) { clone_object = curl_create_object(curl_ce); clone_ch = curl_from_obj(clone_object); - init_curl_handle(clone_ch); ch = curl_from_obj(object); cp = curl_easy_duphandle(ch->cp); @@ -450,6 +449,7 @@ static zend_object *curl_clone_obj(zend_object *object) { return &clone_ch->std; } + init_curl_handle(clone_ch); clone_ch->cp = cp; _php_setup_easy_copy_handlers(clone_ch, ch); From 2d359a40b748308403100caec4891c09b9262aa7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:59:25 +0200 Subject: [PATCH 02/12] [PFA 3/n] Support PFA in const exprs (#22785) * Use a pointer to the declaring opline->lineno as unique addr, instead of the declaring opline itself zend_partial_create() takes a 'zend_op* declaring_opline' parameter for the purpose of building a cache key: the opline address is unique to this PFA as long as it's in SHM. However the declaring opline is not available when evaluating a PFA AST. Replace the parameter by 'uint32_t* declaring_lineno'. Callers can pass '&zend_op->lineno' or '&ast->lineno': Both are unique to the related PFA. * zend_partial_create(): Remove the dependency on the declaring op_array The declaring op_array is used for multiple purposes: * Generating a PFA name * Finding the filename * Finding whether op_array is cached, and therefore whether the PFA should be cached too However it's not available when evaluating a PFA AST. Remove the declaring_op_array argument, and pass the relevant information directly: * Generate the PFA name at compile time, pass it as argument to zend_partial_create() * Pass the filename and a cacheable flag as argument to zend_partial_create() * Support for PFA in constant expressions --- Zend/Optimizer/compact_literals.c | 3 +- .../partial_application/constexpr_001.phpt | 54 +++ .../partial_application/constexpr_002.phpt | 22 ++ .../partial_application/constexpr_003.phpt | 25 ++ .../partial_application/constexpr_004.phpt | 28 ++ .../partial_application/constexpr_005.phpt | 13 + .../partial_application/constexpr_006.phpt | 13 + .../partial_application/constexpr_007.phpt | 13 + .../partial_application/constexpr_008.phpt | 13 + .../partial_application/constexpr_010.phpt | 20 + .../partial_application/constexpr_011.phpt | 43 +++ .../partial_application/constexpr_012.phpt | 38 ++ .../partial_application/constexpr_014.phpt | 45 +++ .../partial_application/constexpr_015.inc | 20 + .../partial_application/constexpr_015.phpt | 33 ++ .../pipe_optimization_004.phpt | 2 +- .../pipe_optimization_007.phpt | 2 +- .../pipe_optimization_008.phpt | 2 +- .../pipe_optimization_013.phpt | 2 +- .../rfc_examples_const_expr.phpt | 2 - Zend/zend_ast.c | 143 ++++++- Zend/zend_ast.h | 2 + Zend/zend_compile.c | 118 ++++-- Zend/zend_compile.h | 1 - Zend/zend_partial.c | 84 ++--- Zend/zend_partial.h | 16 +- Zend/zend_vm_def.h | 9 +- Zend/zend_vm_execute.h | 348 +++++++++--------- Zend/zend_vm_handlers.h | 4 +- Zend/zend_vm_opcodes.c | 2 +- ext/opcache/ZendAccelerator.c | 32 +- ext/opcache/ZendAccelerator.h | 11 +- .../array_map_foreach_optimization_008.phpt | 2 +- ext/opcache/zend_file_cache.c | 24 +- ext/opcache/zend_persist.c | 14 + ext/opcache/zend_persist_calc.c | 6 + 36 files changed, 901 insertions(+), 308 deletions(-) create mode 100644 Zend/tests/partial_application/constexpr_001.phpt create mode 100644 Zend/tests/partial_application/constexpr_002.phpt create mode 100644 Zend/tests/partial_application/constexpr_003.phpt create mode 100644 Zend/tests/partial_application/constexpr_004.phpt create mode 100644 Zend/tests/partial_application/constexpr_005.phpt create mode 100644 Zend/tests/partial_application/constexpr_006.phpt create mode 100644 Zend/tests/partial_application/constexpr_007.phpt create mode 100644 Zend/tests/partial_application/constexpr_008.phpt create mode 100644 Zend/tests/partial_application/constexpr_010.phpt create mode 100644 Zend/tests/partial_application/constexpr_011.phpt create mode 100644 Zend/tests/partial_application/constexpr_012.phpt create mode 100644 Zend/tests/partial_application/constexpr_014.phpt create mode 100644 Zend/tests/partial_application/constexpr_015.inc create mode 100644 Zend/tests/partial_application/constexpr_015.phpt diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index a4ecb19c85ef..d603fe159e20 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -26,6 +26,7 @@ #include "zend_execute.h" #include "zend_vm.h" #include "zend_extensions.h" +#include "zend_partial.h" #define DEBUG_COMPACT_LITERALS 0 @@ -747,7 +748,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx } break; case ZEND_CALLABLE_CONVERT_PARTIAL: - opline->op1.num = cache_size; + opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS); cache_size += 2 * sizeof(void *); break; } diff --git a/Zend/tests/partial_application/constexpr_001.phpt b/Zend/tests/partial_application/constexpr_001.phpt new file mode 100644 index 000000000000..8ab5fc881af5 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_001.phpt @@ -0,0 +1,54 @@ +--TEST-- +PFA in constexpr 001 +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} diff --git a/Zend/tests/partial_application/constexpr_002.phpt b/Zend/tests/partial_application/constexpr_002.phpt new file mode 100644 index 000000000000..05f48b5eb67d --- /dev/null +++ b/Zend/tests/partial_application/constexpr_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +PFA in constexpr: non-constexpr arg +--FILE-- +getMessage(), " on line ", $e->getLine(); +} + +?> +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_003.phpt b/Zend/tests/partial_application/constexpr_003.phpt new file mode 100644 index 000000000000..4e7b7a17d0b0 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_003.phpt @@ -0,0 +1,25 @@ +--TEST-- +PFA in constexpr: named args +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + string(5) "hello" + [1]=> + int(3) + [2]=> + float(1.5) +} diff --git a/Zend/tests/partial_application/constexpr_004.phpt b/Zend/tests/partial_application/constexpr_004.phpt new file mode 100644 index 000000000000..b5fee160a03e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_004.phpt @@ -0,0 +1,28 @@ +--TEST-- +PFA in constexpr: extra named args +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + array(3) { + [0]=> + int(1) + ["foo"]=> + string(3) "bar" + ["bar"]=> + string(3) "baz" + } +} diff --git a/Zend/tests/partial_application/constexpr_005.phpt b/Zend/tests/partial_application/constexpr_005.phpt new file mode 100644 index 000000000000..48c830a0b830 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_005.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder must be after positional params +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_006.phpt b/Zend/tests/partial_application/constexpr_006.phpt new file mode 100644 index 000000000000..873c0b155fe1 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_006.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder must be after named params +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_007.phpt b/Zend/tests/partial_application/constexpr_007.phpt new file mode 100644 index 000000000000..e932812ddf70 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_007.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder may only appear once +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder may only appear once in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_008.phpt b/Zend/tests/partial_application/constexpr_008.phpt new file mode 100644 index 000000000000..c8f5b36ccb63 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_008.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder not allowed to be named +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "..." in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_010.phpt b/Zend/tests/partial_application/constexpr_010.phpt new file mode 100644 index 000000000000..e4e05cc30a8e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_010.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA in constexpr: error during partial creation +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +ArgumentCountError: Partial application of g() expects exactly 2 arguments, 1 given diff --git a/Zend/tests/partial_application/constexpr_011.phpt b/Zend/tests/partial_application/constexpr_011.phpt new file mode 100644 index 000000000000..a6480f214797 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_011.phpt @@ -0,0 +1,43 @@ +--TEST-- +PFA in constexpr: error during arg list evaluation +--FILE-- +getMessage(), "\n"; +} + +try { + h(); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +try { + j(); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Named parameter $a overwrites previous argument +Error: Call to undefined function invalid() +Error: Undefined constant "MISSING_CONST" diff --git a/Zend/tests/partial_application/constexpr_012.phpt b/Zend/tests/partial_application/constexpr_012.phpt new file mode 100644 index 000000000000..3c941ed6e13e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_012.phpt @@ -0,0 +1,38 @@ +--TEST-- +PFA in constexpr: nested const expr +--FILE-- + +--EXPECTF-- +array(3) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + object(Closure)#%d (2) { + ["function"]=> + string(1) "g" + ["parameter"]=> + array(3) { + ["$a"]=> + string(10) "" + ["$b"]=> + string(10) "" + ["$c"]=> + string(10) "" + } + } + [2]=> + int(1) +} diff --git a/Zend/tests/partial_application/constexpr_014.phpt b/Zend/tests/partial_application/constexpr_014.phpt new file mode 100644 index 000000000000..c75fdb60716b --- /dev/null +++ b/Zend/tests/partial_application/constexpr_014.phpt @@ -0,0 +1,45 @@ +--TEST-- +PFA in constexpr: sites +--FILE-- +getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method attr\n"; +var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method default value\n"; +var_dump((new C()->f())('hello')); + +?> +--EXPECT-- +# Class const +int(5) +# Const +int(5) +# Class attr +int(5) +# Method attr +int(5) +# Method default value +int(5) diff --git a/Zend/tests/partial_application/constexpr_015.inc b/Zend/tests/partial_application/constexpr_015.inc new file mode 100644 index 000000000000..bd9dd1674f89 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_015.inc @@ -0,0 +1,20 @@ + diff --git a/Zend/tests/partial_application/constexpr_015.phpt b/Zend/tests/partial_application/constexpr_015.phpt new file mode 100644 index 000000000000..2449adb9e4b7 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_015.phpt @@ -0,0 +1,33 @@ +--TEST-- +PFA in constexpr: preloading +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/constexpr_015.inc +--SKIPIF-- + +--FILE-- +getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method attr\n"; +var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method default value\n"; +var_dump((new C()->f())('hello')); + +?> +--EXPECT-- +# Class const +int(5) +# Class attr +int(5) +# Method attr +int(5) +# Method default value +int(5) diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index addee498d810..194f08a9c143 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -37,7 +37,7 @@ $_main: 0005 INIT_FCALL_BY_NAME 2 string("foo") 0006 SEND_PLACEHOLDER 1 0007 SEND_PLACEHOLDER 2 -0008 T1 = CALLABLE_CONVERT_PARTIAL %d +0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0009 INIT_DYNAMIC_CALL 1 T1 0010 SEND_VAL_EX int(2) 1 0011 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt index 09c3c765d237..be8d773c44ce 100644 --- a/Zend/tests/partial_application/pipe_optimization_007.phpt +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -37,7 +37,7 @@ $_main: 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_PLACEHOLDER string("a") 0007 SEND_PLACEHOLDER string("b") -0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...) +0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") array(...) 0009 INIT_DYNAMIC_CALL 1 T1 0010 SEND_VAL_EX int(2) 1 0011 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt index 070074632c77..96f8d88815ca 100644 --- a/Zend/tests/partial_application/pipe_optimization_008.phpt +++ b/Zend/tests/partial_application/pipe_optimization_008.phpt @@ -36,7 +36,7 @@ $_main: 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_VAL_EX int(1) string("a") -0007 T1 = CALLABLE_CONVERT_PARTIAL %d +0007 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0008 INIT_DYNAMIC_CALL 1 T1 0009 SEND_VAL_EX int(2) 1 0010 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt index 7d1a48b2f2ed..6c2be6728e73 100644 --- a/Zend/tests/partial_application/pipe_optimization_013.phpt +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -32,7 +32,7 @@ $_main: 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_VAL_EX int(2) string("b") -0007 T0 = CALLABLE_CONVERT_PARTIAL 3 +0007 T0 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0008 INIT_DYNAMIC_CALL 1 T0 0009 SEND_VAL_EX int(1) 1 0010 DO_FCALL diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt index e2fc57440256..23350f6116cf 100644 --- a/Zend/tests/partial_application/rfc_examples_const_expr.phpt +++ b/Zend/tests/partial_application/rfc_examples_const_expr.phpt @@ -1,7 +1,5 @@ --TEST-- PFA RFC examples: "Constant expressions" section ---XFAIL-- -PFA in constant expressions not implemented yet --FILE-- kind = ZEND_AST_CALLABLE_CONVERT; ast->attr = 0; ast->lineno = CG(zend_lineno); + ast->filename = NULL; + ast->name = NULL; ast->args = args; ZEND_MAP_PTR_INIT(ast->fptr, NULL); @@ -671,6 +674,82 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex( return r; } +static zend_execute_data *zend_ast_evaluate_arg_list( + zend_function *func, + zend_ast_list *args_ast, + zend_class_entry *scope, + void *object_or_called_scope, + bool *short_circuited_ptr, + zend_ast_evaluate_ctx *ctx, + zend_array **named_positions_ptr, + bool *uses_variadic_placeholder +) { + zend_execute_data *frame = zend_vm_stack_push_call_frame_ex( + zend_vm_calc_used_stack(args_ast->children, func), + 0, func, 0, object_or_called_scope); + + for (uint32_t i = 0; i < args_ast->children; i++) { + zend_ast *arg_ast = args_ast->child[i]; + uint32_t arg_num = i + 1; + zval *arg; + zend_string *arg_name = NULL; + + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + void *cache_slot[2] = {0}; + arg_name = zend_ast_get_str(arg_ast->child[0]); + arg = zend_handle_named_arg(&frame, arg_name, &arg_num, (void**)&cache_slot); + if (!arg) { + goto fail; + } + if (named_positions_ptr) { + if (!*named_positions_ptr) { + *named_positions_ptr = zend_new_array(0); + } + zval tmp; + ZVAL_LONG(&tmp, zend_hash_num_elements(*named_positions_ptr)); + zend_hash_add(*named_positions_ptr, arg_name, &tmp); + } + arg_ast = arg_ast->child[1]; + } else { + arg = ZEND_CALL_VAR_NUM(frame, ZEND_CALL_NUM_ARGS(frame)); + } + + if (arg_ast->kind == ZEND_AST_PLACEHOLDER_ARG) { + if (arg_ast->attr == ZEND_PLACEHOLDER_VARIADIC) { + if (uses_variadic_placeholder) { + *uses_variadic_placeholder = true; + } + continue; + } else { + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + } + } else { + if (zend_ast_evaluate_ex(arg, arg_ast, scope, short_circuited_ptr, ctx) == FAILURE) { + ZVAL_UNDEF(arg); + goto fail; + } + } + if (!arg_name) { + ZEND_CALL_NUM_ARGS(frame)++; + } + } + + return frame; + +fail: + for (uint32_t i = 0, num_args = ZEND_CALL_NUM_ARGS(frame); i < num_args; i++) { + zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, i)); + } + if (ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_destroy(frame->extra_named_params); + } + zend_vm_stack_free_call_frame(frame); + if (named_positions_ptr && *named_positions_ptr) { + zend_array_destroy(*named_positions_ptr); + } + return NULL; +} + static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( zval *result, zend_ast *ast, @@ -1155,10 +1234,6 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( zend_ast_list *args = zend_ast_get_list(fcc_ast->args); ZEND_ASSERT(args->children > 0); - if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - /* TODO: PFAs */ - zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); - } switch (ast->kind) { case ZEND_AST_CALL: { @@ -1248,9 +1323,46 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( default: ZEND_UNREACHABLE(); } - zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL); + bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC; - return SUCCESS; + if (is_fcc) { + zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL); + return SUCCESS; + } + + zend_array *named_positions = NULL; + bool uses_variadic_placeholder = false; + zend_execute_data *frame = zend_ast_evaluate_arg_list( + fptr, args, scope, called_scope, short_circuited_ptr, ctx, + &named_positions, &uses_variadic_placeholder); + if (!frame) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + + void *cache_slot[2] = {0}; + zend_array *extra_named_params = ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS + ? frame->extra_named_params + : NULL; + uint32_t flags = (fcc_ast->attr & ZEND_PARTIAL_CACHEABLE_IN_SHM); + if (uses_variadic_placeholder) { + flags |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; + } + zend_partial_create(result, &frame->This, fptr, + ZEND_CALL_NUM_ARGS(frame), ZEND_CALL_ARG(frame, 1), + extra_named_params, named_positions, + fcc_ast->filename, &ast->lineno, + (void**)cache_slot, fcc_ast->name, flags); + + if (named_positions) { + zend_array_release(named_positions); + } + if (extra_named_params) { + zend_array_release(extra_named_params); + } + zend_vm_stack_free_call_frame(frame); + + return EG(exception) ? FAILURE : SUCCESS; } case ZEND_AST_OP_ARRAY: { @@ -1425,6 +1537,16 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf) new->kind = old->kind; new->attr = old->attr; new->lineno = old->lineno; + if (old->filename) { + new->filename = zend_string_copy(old->filename); + } else { + new->filename = NULL; + } + if (old->name) { + new->name = zend_string_copy(old->name); + } else { + new->name = NULL; + } ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr)); buf = (void*)((char*)buf + sizeof(zend_ast_fcc)); new->args = buf; @@ -1525,6 +1647,12 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast) } else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) { zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast; + if (fcc_ast->filename) { + zend_string_release_ex(fcc_ast->filename, 0); + } + if (fcc_ast->name) { + zend_string_release_ex(fcc_ast->name, 0); + } ast = fcc_ast->args; goto tail_call; } @@ -1543,6 +1671,9 @@ ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *contex for (i = 0; i < list->children; ++i) { fn(&list->child[i], context); } + } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { + zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast; + fn(&fcc_ast->args, context); } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ ZEND_UNREACHABLE(); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 26f54102a143..5d646f428c1b 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -233,6 +233,8 @@ typedef struct _zend_ast_fcc { zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */ zend_ast_attr attr; /* Additional attribute, use depending on node type */ uint32_t lineno; /* Line number */ + zend_string *filename; + zend_string *name; zend_ast *args; ZEND_MAP_PTR_DEF(zend_function *, fptr); } zend_ast_fcc; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2466710f9cee..420a7cc08658 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -40,6 +40,7 @@ #include "zend_call_stack.h" #include "zend_frameless_function.h" #include "zend_property_hooks.h" +#include "zend_partial.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -4088,7 +4089,50 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, const zend_function *f } /* }}} */ -static void zend_compile_call_partial(znode *result, uint32_t arg_count, +static zend_string *zend_compile_partial_name(const zend_op_array *declaring_op_array, + const uint32_t declaring_lineno) +{ + /* We attempt to generate a name that hints at where the PFA was created, + * similarly to Closures (GH-13550). + * We do not attempt to make the name unique. */ + + zend_string *filename = declaring_op_array->filename; + uint32_t start_lineno = declaring_lineno; + + zend_string *class = zend_empty_string; + zend_string *separator = zend_empty_string; + zend_string *function = filename; + const char *parens = ""; + + if (declaring_op_array->function_name) { + function = declaring_op_array->function_name; + if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) { + /* If the parent function is a closure, don't redundantly + * add the classname and parentheses. */ + } else { + parens = "()"; + + if (declaring_op_array->scope && declaring_op_array->scope->name) { + class = declaring_op_array->scope->name; + separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM); + } + } + } + + zend_string *name = zend_strpprintf_unchecked( + 0, + "{closure:pfa:%S%S%S%s:%" PRIu32 "}", + class, + separator, + function, + parens, + start_lineno + ); + + return name; +} + +static void zend_compile_call_partial(znode *result, zend_ast_fcc *fcc_ast, uint32_t arg_count, bool may_have_extra_named_args, bool uses_variadic_placeholder, zval *named_positions, uint32_t opnum_init, const zend_function *fbc) { @@ -4105,15 +4149,16 @@ static void zend_compile_call_partial(znode *result, uint32_t arg_count, zend_op *opline = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT_PARTIAL, NULL, NULL); - opline->op1.num = zend_alloc_cache_slots(2); + opline->extended_value = zend_alloc_cache_slots(2); - if (may_have_extra_named_args) { - opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS; - } if (uses_variadic_placeholder) { - opline->extended_value |= ZEND_FCALL_USES_VARIADIC_PLACEHOLDER; + opline->extended_value |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; } + zend_string *name = zend_compile_partial_name(CG(active_op_array), opline->lineno); + opline->op1.constant = zend_add_literal_string(&name); + opline->op1_type = IS_CONST; + if (!Z_ISUNDEF_P(named_positions)) { opline->op2.constant = zend_add_literal(named_positions); opline->op2_type = IS_CONST; @@ -4156,7 +4201,8 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze return true; } - args_ast = ((zend_ast_fcc*)args_ast)->args; + zend_ast_fcc *fcc_ast = (zend_ast_fcc*)args_ast; + args_ast = fcc_ast->args; bool may_have_extra_named_args; bool uses_variadic_placeholder; @@ -4168,7 +4214,7 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze &may_have_extra_named_args, true, &uses_variadic_placeholder, &named_positions); - zend_compile_call_partial(result, arg_count, + zend_compile_call_partial(result, fcc_ast, arg_count, may_have_extra_named_args, uses_variadic_placeholder, &named_positions, opnum_init, fbc); @@ -11856,7 +11902,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_NAMED_ARG || kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP || kind == ZEND_AST_CLOSURE - || kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT; + || kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT + || kind == ZEND_AST_PLACEHOLDER_ARG; } /* }}} */ @@ -12021,24 +12068,20 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr) static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) { - zend_ast **args_ast; - switch ((*ast_ptr)->kind) { - case ZEND_AST_CALL: - args_ast = &(*ast_ptr)->child[1]; - break; - case ZEND_AST_STATIC_CALL: - args_ast = &(*ast_ptr)->child[2]; - break; - default: ZEND_UNREACHABLE(); - } - if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) { + zend_ast *args_ast = zend_ast_call_get_args(*ast_ptr); + + if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } - zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)*args_ast)->args); - if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - // TODO: PFAs - zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); + zend_ast_fcc *fcc = (zend_ast_fcc*)args_ast; + + zend_ast_list *args = zend_ast_get_list(fcc->args); + bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC; + + if (!is_fcc) { + fcc->filename = zend_string_copy(CG(active_op_array)->filename); + fcc->name = zend_compile_partial_name(CG(active_op_array), fcc->lineno); } switch ((*ast_ptr)->kind) { @@ -12080,6 +12123,7 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr) { zend_ast_list *list = zend_ast_get_list(*ast_ptr); bool uses_named_args = false; + bool uses_variadic_placeholder = false; for (uint32_t i = 0; i < list->children; i++) { const zend_ast *arg = list->child[i]; if (arg->kind == ZEND_AST_UNPACK) { @@ -12088,11 +12132,31 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr) } if (arg->kind == ZEND_AST_NAMED_ARG) { uses_named_args = true; - } else if (uses_named_args) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use positional argument after named argument"); + if (uses_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last"); + } + } else { + bool is_variadic_placeholder = arg->kind == ZEND_AST_PLACEHOLDER_ARG + && arg->attr == ZEND_PLACEHOLDER_VARIADIC; + + if (uses_named_args && !is_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after named argument"); + } + + if (uses_variadic_placeholder) { + if (is_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder may only appear once"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last"); + } + } + + if (is_variadic_placeholder) { + uses_variadic_placeholder = true; + } } } + if (uses_named_args) { list->attr = 1; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 69d7aeb2f373..3d4e6f3c3f9f 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1121,7 +1121,6 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_THROW_IS_EXPR 1u #define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS (1<<0) -#define ZEND_FCALL_USES_VARIADIC_PLACEHOLDER (1<<1) /* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */ #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index e7a3e1c92b58..a78db3f51ef1 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -51,6 +51,7 @@ #include "zend_closures.h" #include "zend_attributes.h" #include "zend_exceptions.h" +#include "zend_partial.h" #include "ext/opcache/ZendAccelerator.h" static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) { @@ -502,49 +503,6 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function, return attributes_ast; } -static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline) -{ - /* We attempt to generate a name that hints at where the PFA was created, - * similarly to Closures (GH-13550). - * We do not attempt to make the name unique. */ - - zend_string *filename = declaring_op_array->filename; - uint32_t start_lineno = declaring_opline->lineno; - - zend_string *class = zend_empty_string; - zend_string *separator = zend_empty_string; - zend_string *function = filename; - const char *parens = ""; - - if (declaring_op_array->function_name) { - function = declaring_op_array->function_name; - if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) { - /* If the parent function is a closure, don't redundantly - * add the classname and parentheses. */ - } else { - parens = "()"; - - if (declaring_op_array->scope && declaring_op_array->scope->name) { - class = declaring_op_array->scope->name; - separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM); - } - } - } - - zend_string *name = zend_strpprintf_unchecked( - 0, - "{closure:pfa:%S%S%S%s:%" PRIu32 "}", - class, - separator, - function, - parens, - start_lineno - ); - - return name; -} - /* Generate the AST for calling the actual function */ static zend_ast *zp_compile_forwarding_call( zval *this_ptr, zend_function *function, @@ -701,9 +659,9 @@ static uint32_t zp_compute_num_required(const zend_function *function, static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { zend_op_array *op_array = NULL; @@ -713,6 +671,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, return NULL; } + bool uses_variadic_placeholder = flags & ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; + if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) { ZEND_ASSERT(EG(exception)); return NULL; @@ -1012,10 +972,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, } #endif - zend_string *pfa_name = zp_pfa_name(declaring_op_array, declaring_opline); - - op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array, - declaring_opline, function, pfa_name); + op_array = zend_accel_compile_pfa(closure_ast, declaring_filename, + declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); zend_ast_destroy(closure_ast); @@ -1040,9 +998,9 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION ? cache_slot[0] == function @@ -1051,13 +1009,13 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct return cache_slot[1]; } - const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, - declaring_opline, function); + const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function, + flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); if (UNEXPECTED(!op_array)) { op_array = zp_compile(this_ptr, function, argc, argv, - extra_named_params, named_positions, declaring_op_array, declaring_opline, - cache_slot, uses_variadic_placeholder); + extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr, + cache_slot, pfa_name, flags); } if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) { @@ -1135,14 +1093,16 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval * void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { + + ZEND_ASSERT(pfa_name); const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, extra_named_params, named_positions, - declaring_op_array, declaring_opline, - cache_slot, uses_variadic_placeholder); + declaring_filename, declaring_lineno_ptr, + cache_slot, pfa_name, flags); if (UNEXPECTED(!op_array)) { ZEND_ASSERT(EG(exception)); diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index 3c47305ff543..386823261e2b 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,12 +21,22 @@ BEGIN_EXTERN_C() +#define ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER (1<<0) +/* Whether the PFA is cacheable in SHM. This flag is set only when the op_array that declares the PFA is itself in SHM, + * for two reasons: Avoids SHM churn, and ensures that the PFA cache key is unique. */ +#define ZEND_PARTIAL_CACHEABLE_IN_SHM (1<<1) +#define ZEND_PARTIAL_FLAGS (ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER|ZEND_PARTIAL_CACHEABLE_IN_SHM) + +/* Create a partial application of 'function' + * 'declaring_lineno_ptr' should be a pointer the zend_op.lineno or + * zend_ast.lineno that declares the PFA. The address is used to build a cache + * key. */ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder); + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags); void zend_partial_op_array_dtor(zval *pDest); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index cd74145799b3..3466bc68d694 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9866,14 +9866,15 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT) ZEND_VM_NEXT_OPCODE(); } -ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NUM) +ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM) { USE_OPLINE SAVE_OPLINE(); zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = GET_OP2_ZVAL_PTR(); + zend_string *pfa_name = Z_STR_P(GET_OP1_ZVAL_PTR()); zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -9881,8 +9882,8 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NU (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8d2e0670ee3b..c0a23e49d02b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4273,45 +4273,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_R ZEND_VM_NEXT_OPCODE(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = RT_CONSTANT(opline, opline->op2); - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -4456,45 +4417,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_RECV_VARIADIC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = NULL; - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -8741,6 +8663,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ ZEND_VM_SMART_BRANCH(0, 1); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ADD_SPEC_CONST_TMPVARCV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -12067,6 +12029,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS } /* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = NULL; + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57089,45 +57091,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_I ZEND_VM_NEXT_OPCODE(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = RT_CONSTANT(opline, opline->op2); - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57272,45 +57235,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_VARIADIC_SPEC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = NULL; - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -61557,6 +61481,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_IN_AR ZEND_VM_SMART_BRANCH(0, 1); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ADD_SPEC_CONST_TMPVARCV_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -64781,6 +64745,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC } /* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = NULL; + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -109528,10 +109532,10 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_LABEL, (void*)&&ZEND_TYPE_ASSERT_SPEC_CONST_LABEL, - (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, - (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -110942,11 +110946,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_INIT_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_INIT_SPEC_CONST) HYBRID_BREAK(); - HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST): - VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); - VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) - HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP): VM_TRACE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP) ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -110962,11 +110961,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_VARIADIC_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_VARIADIC_SPEC_UNUSED) HYBRID_BREAK(); - HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED): - VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); - VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) - HYBRID_BREAK(); HYBRID_CASE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED): VM_TRACE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED) ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -111566,6 +111560,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_IN_ARRAY_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_IN_ARRAY_SPEC_CONST_CONST) HYBRID_BREAK(); + HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) + HYBRID_BREAK(); HYBRID_CASE(ZEND_ADD_SPEC_CONST_TMPVARCV): VM_TRACE(ZEND_ADD_SPEC_CONST_TMPVARCV) ZEND_ADD_SPEC_CONST_TMPVARCV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -111926,6 +111925,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) + HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED): VM_TRACE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -118496,10 +118500,10 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER, ZEND_NULL_HANDLER, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_HANDLER, ZEND_NULL_HANDLER, @@ -121984,10 +121988,10 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_TAILCALL_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_TAILCALL_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_TAILCALL_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 7ffe2c220a02..503a6d25634b 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1087,8 +1087,8 @@ _(2556, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ _(2557, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST) \ _(2558, ZEND_TYPE_ASSERT_SPEC_CONST) \ - _(2559, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) \ - _(2562, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) \ + _(2559, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) \ + _(2562, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) \ _(2564, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) \ _(2567, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) \ _(2569, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 9846e36037b9..f9b30edb5e9b 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -451,7 +451,7 @@ static uint32_t zend_vm_opcodes_flags[214] = { 0x01001103, 0x00000303, 0x01000003, - 0x010003a0, + 0x01000303, 0x00001301, }; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 6f7cc8a6552a..8685fb564150 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2024,7 +2024,7 @@ static char *zend_accel_uintptr_hex(char *dest, uintptr_t n) * a scheme, except file:// and phar://. */ #define PFA_KEY_PREFIX "pfa://" -static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, +static zend_string *zend_accel_pfa_key(const uint32_t *declaring_lineno_ptr, const zend_function *called_function) { const size_t max_key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2); @@ -2032,7 +2032,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, char *dest = ZSTR_VAL(key); dest = zend_mempcpy(dest, PFA_KEY_PREFIX, strlen(PFA_KEY_PREFIX)); - dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline); + dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_lineno_ptr); *dest++ = ':'; const void *ptr; @@ -2054,17 +2054,17 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, return key; } -const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, const zend_function *called_function) +const zend_op_array *zend_accel_pfa_cache_get( + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm) { - zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function); zend_op_array *op_array = NULL; - /* A PFA is SHM-cacheable if the declaring_op_array and called_function are + /* A PFA is SHM-cacheable if the declaring op_array and called_function are * cached. */ if (ZCG(accelerator_enabled) && !file_cache_only - && !declaring_op_array->refcount + && cacheable_in_shm /* declaring op_array is cached */ && (called_function->type != ZEND_USER_FUNCTION || !called_function->op_array.refcount)) { zend_persistent_script *persistent_script = zend_accel_hash_find(&ZCSG(hash), key); if (persistent_script) { @@ -2084,11 +2084,13 @@ const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_ } zend_op_array *zend_accel_compile_pfa(zend_ast *ast, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, - zend_string *pfa_func_name) + zend_string *pfa_func_name, bool cacheable_in_shm) { + ZEND_ASSERT(zend_accel_in_shm((void*)declaring_lineno_ptr) || !cacheable_in_shm); + zend_begin_record_errors(); zend_op_array *op_array; @@ -2106,7 +2108,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES; #endif - op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_op_array->filename); + op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_filename); CG(compiler_options) = orig_compiler_options; } zend_catch { @@ -2119,21 +2121,21 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, ZEND_ASSERT(op_array->num_dynamic_func_defs == 1); zend_string_release(op_array->dynamic_func_defs[0]->function_name); - op_array->dynamic_func_defs[0]->function_name = pfa_func_name; + op_array->dynamic_func_defs[0]->function_name = zend_string_copy(pfa_func_name); - zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function); /* Cache op_array only if the declaring op_array and the called function * are cached */ if (!ZCG(accelerator_enabled) || file_cache_only - || declaring_op_array->refcount + || !cacheable_in_shm /* declaring op_array is not in SHM */ || (called_function->type == ZEND_USER_FUNCTION && called_function->op_array.refcount) || (ZCSG(restart_in_progress) && accel_restart_is_active()) || (!ZCG(counted) && accel_activate_add() == FAILURE)) { zend_op_array *script_op_array = op_array; zend_op_array *op_array = script_op_array->dynamic_func_defs[0]; - GC_ADDREF(op_array->function_name); + GC_TRY_ADDREF(op_array->function_name); (*op_array->refcount)++; destroy_op_array(script_op_array); efree(script_op_array); diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index ef7eea433c09..b76e5d1d4be7 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -334,15 +334,14 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str); uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); -const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, - const zend_function *called_function); +const zend_op_array *zend_accel_pfa_cache_get( + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm); zend_op_array *zend_accel_compile_pfa(zend_ast *ast, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, - zend_string *pfa_func_name); + zend_string *pfa_func_name, bool cacheable_in_shm); END_EXTERN_C() diff --git a/ext/opcache/tests/array_map_foreach_optimization_008.phpt b/ext/opcache/tests/array_map_foreach_optimization_008.phpt index 8a406ef11ca6..36c80b460e88 100644 --- a/ext/opcache/tests/array_map_foreach_optimization_008.phpt +++ b/ext/opcache/tests/array_map_foreach_optimization_008.phpt @@ -33,7 +33,7 @@ $_main: 0005 INIT_FCALL 2 %d string("array_map") 0006 INIT_FCALL 0 %d string("plusn") 0007 SEND_VAL int(2) string("n") -0008 T2 = CALLABLE_CONVERT_PARTIAL 2 +0008 T2 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0009 SEND_VAL T2 1 0010 SEND_VAR CV0($array) 2 0011 T2 = DO_ICALL diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 265d5de41476..0b6cabe42015 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -22,6 +22,7 @@ #include "zend_attributes.h" #include "zend_system_id.h" #include "zend_enum.h" +#include "zend_partial.h" #include "php.h" #ifdef ZEND_WIN32 @@ -382,6 +383,10 @@ static void zend_file_cache_serialize_ast(zend_ast *ast, } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; ZEND_MAP_PTR_INIT(fcc->fptr, NULL); + /* Will be reset if needed during unserialize */ + fcc->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + SERIALIZE_STR(fcc->filename); + SERIALIZE_STR(fcc->name); if (!IS_SERIALIZED(fcc->args)) { SERIALIZE_PTR(fcc->args); tmp = fcc->args; @@ -636,6 +641,12 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra break; } #endif + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + /* Will be reset if needed during unserialize */ + opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + zend_serialize_opcode_handler(opline); opline++; } @@ -1308,9 +1319,13 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast, zend_ast_get_op_array(ast)->op_array = Z_PTR(z); } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; - if (!script->corrupted) { + if (script->corrupted) { + fcc->attr &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM; + } else { ZEND_MAP_PTR_NEW(fcc->fptr); } + UNSERIALIZE_STR(fcc->filename); + UNSERIALIZE_STR(fcc->name); if (!IS_UNSERIALIZED(fcc->args)) { UNSERIALIZE_PTR(fcc->args); zend_file_cache_unserialize_ast(fcc->args, script, buf); @@ -1541,6 +1556,13 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr zval *literal = RT_CONSTANT(opline, opline->op1); UNSERIALIZE_ATTRIBUTES(Z_PTR_P(literal)); } + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + if (script->corrupted) { + opline->extended_value &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + } + zend_deserialize_opcode_handler(opline); opline++; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index d3e719dbed70..134551781ba2 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -27,6 +27,7 @@ #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "zend_partial.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -197,6 +198,13 @@ static zend_ast *zend_persist_ast(zend_ast *ast) zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc)); if (!ZCG(current_persistent_script)->corrupted) { ZEND_MAP_PTR_NEW(copy->fptr); + copy->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + if (copy->filename) { + zend_accel_store_interned_string(copy->filename); + } + if (copy->name) { + zend_accel_store_interned_string(copy->name); } copy->args = zend_persist_ast(copy->args); node = (zend_ast *) copy; @@ -624,6 +632,12 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc attributes = zend_persist_attributes(attributes); ZVAL_PTR(literal, attributes); } + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + if (!ZCG(current_persistent_script)->corrupted) { + opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + } } efree(op_array->opcodes); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 9ff37079193b..71398024e7c6 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -92,6 +92,12 @@ static void zend_persist_ast_calc(zend_ast *ast) } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast; ADD_SIZE(sizeof(zend_ast_fcc)); + if (fcc_ast->filename) { + ADD_INTERNED_STRING(fcc_ast->filename); + } + if (fcc_ast->name) { + ADD_INTERNED_STRING(fcc_ast->name); + } zend_persist_ast_calc(fcc_ast->args); } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ From 1b091dcf37544e3f871db57e97e224371d85ec8d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:01:34 +0200 Subject: [PATCH 03/12] Forbid \C in UTF-8 patterns (#21139) Fixes GH-21134 --- NEWS | 4 ++++ ext/pcre/php_pcre.c | 11 +++++++++-- ext/pcre/tests/gh21134.phpt | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 ext/pcre/tests/gh21134.phpt diff --git a/NEWS b/NEWS index 0aa44c4d5006..1dc666e361b5 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,10 @@ PHP NEWS . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative offset in a non-UTF-8 encoding). (Eyüp Can Akman) +- PCRE: + . Fixed bug GH-21134 (Crash with \C + UTF-8). Using \C in UTF-8 patterns is + now forbidden. (Arnaud) + - Sockets: . Fixed various memory related issues in ext/sockets. (David Carlier) diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 43cee3b6b57e..6a24691a170e 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -734,6 +734,8 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo #ifdef PCRE2_UCP coptions |= PCRE2_UCP; #endif + /* The \C escape sequence is unsafe in PCRE2_UTF mode */ + coptions |= PCRE2_NEVER_BACKSLASH_C; break; case 'J': coptions |= PCRE2_DUPNAMES; break; @@ -787,8 +789,13 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo if (key != regex) { zend_string_release_ex(key, 0); } - pcre2_get_error_message(errnumber, error, sizeof(error)); - php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %zu", error, erroffset); + const char *err_msg = (const char*) error; + if (errnumber == PCRE2_ERROR_BACKSLASH_C_CALLER_DISABLED) { + err_msg = "using \\C is incompatible with the 'u' modifier"; + } else { + pcre2_get_error_message(errnumber, error, sizeof(error)); + } + php_error_docref(NULL,E_WARNING, "Compilation failed: %s at offset %zu", err_msg, erroffset); pcre_handle_exec_error(PCRE2_ERROR_INTERNAL); efree(pattern); return NULL; diff --git a/ext/pcre/tests/gh21134.phpt b/ext/pcre/tests/gh21134.phpt new file mode 100644 index 000000000000..ddcf19a6e7a2 --- /dev/null +++ b/ext/pcre/tests/gh21134.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-21134: ASan negative-size-param in preg_match_all() with \C + UTF-8 multibyte input +--CREDITS-- +vi3tL0u1s +--FILE-- + +--EXPECTF-- +Warning: preg_match_all(): Compilation failed: using \C is incompatible with the 'u' modifier at offset 6 in %s on line %d +bool(false) +NULL From 1fd0c64beba81ba552ad2969c24068d432d859c6 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 28 Jul 2026 00:03:52 +0800 Subject: [PATCH 04/12] ext/standard: Fix `setlocale()` NUL byte truncation (#22843) This reject NUL byte in the setlocale() function in the standard extension. --- NEWS | 6 +++ UPGRADING | 6 +++ ext/standard/string.c | 27 +++++++++- .../tests/strings/gh18823_strict.phpt | 2 +- .../tests/strings/setlocale_null_byte.phpt | 53 +++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 ext/standard/tests/strings/setlocale_null_byte.phpt diff --git a/NEWS b/NEWS index cc2eed87ccfb..7ec4eb4b809e 100644 --- a/NEWS +++ b/NEWS @@ -65,6 +65,12 @@ PHP NEWS TCP_USER_TIMEOUT, and SO_LINGER options. (Weilin Du) . Fixed various memory related issues in ext/sockets. (David Carlier) +- Standard: + . Fixed setlocale() to reject locale names containing NUL bytes instead of + silently truncating them, and to reject arrays passed after the $locales + argument or additional arguments passed after an array $locales argument. + (Weilin Du) + - Streams: . Added a new IO copy API used by php_stream_copy_to_stream_ex() that leverages platform primitives (sendfile, splice, copy_file_range, diff --git a/UPGRADING b/UPGRADING index 34817c10974f..d30a5663274f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -201,6 +201,12 @@ PHP 8.6 UPGRADE NOTES bytes. . parse_str() now raises a ValueError when the $string argument contains NUL bytes. + . setlocale() now raises a ValueError when a locale name contains NUL bytes, + instead of silently truncating it. + Arrays are now accepted only for the $locales argument. Passing an array as + a later variadic locale argument now throws a TypeError. Passing any + additional locale arguments when $locales is an array now throws an + ArgumentCountError. . linkinfo() now raises a ValueError when the $path argument is empty. . pathinfo() now raises a ValueError when an invalid $flag argument value is passed. diff --git a/ext/standard/string.c b/ext/standard/string.c index 823c32a8cf1e..d8a7479d2a98 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4909,6 +4909,11 @@ static zend_string *try_setlocale_zval(zend_long cat, zval *loc_zv) { if (UNEXPECTED(loc_str == NULL)) { return NULL; } + if (zend_str_has_nul_byte(loc_str)) { + zend_argument_value_error(2, "must not contain any null bytes"); + zend_tmp_string_release(tmp_loc_str); + return NULL; + } zend_string *result = try_setlocale_str(cat, loc_str); zend_tmp_string_release(tmp_loc_str); return result; @@ -4930,8 +4935,26 @@ PHP_FUNCTION(setlocale) zend_string **strings = do_alloca(sizeof(zend_string *) * num_args, use_heap); for (uint32_t i = 0; i < num_args; i++) { - if (UNEXPECTED(Z_TYPE(args[i]) != IS_ARRAY && !zend_parse_arg_str(&args[i], &strings[i], true, i + 2))) { - zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_ARRAY_OR_STRING_OR_NULL, &args[i]); + if (Z_TYPE(args[i]) == IS_ARRAY) { + if (UNEXPECTED(i != 0)) { + zend_wrong_parameter_type_error(i + 2, Z_EXPECTED_STRING_OR_NULL, &args[i]); + goto out; + } + if (UNEXPECTED(num_args > 1)) { + zend_argument_count_error( + "setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, %d given", + ZEND_NUM_ARGS()); + goto out; + } + break; + } + if (UNEXPECTED(!zend_parse_arg_path_str(&args[i], &strings[i], true, i + 2))) { + zend_wrong_parameter_type_error( + i + 2, + Z_TYPE(args[i]) == IS_STRING + ? Z_EXPECTED_PATH + : (i == 0 ? Z_EXPECTED_ARRAY_OR_STRING_OR_NULL : Z_EXPECTED_STRING_OR_NULL), + &args[i]); goto out; } } diff --git a/ext/standard/tests/strings/gh18823_strict.phpt b/ext/standard/tests/strings/gh18823_strict.phpt index 3735eab00670..39ae11ef61bc 100644 --- a/ext/standard/tests/strings/gh18823_strict.phpt +++ b/ext/standard/tests/strings/gh18823_strict.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- setlocale(): Argument #2 ($locales) must be of type array|string|null, int given -setlocale(): Argument #3 must be of type array|string|null, int given +setlocale(): Argument #3 must be of type ?string, int given diff --git a/ext/standard/tests/strings/setlocale_null_byte.phpt b/ext/standard/tests/strings/setlocale_null_byte.phpt new file mode 100644 index 000000000000..85663b98a71a --- /dev/null +++ b/ext/standard/tests/strings/setlocale_null_byte.phpt @@ -0,0 +1,53 @@ +--TEST-- +setlocale() rejects locale names with null bytes +--FILE-- +getMessage(), \PHP_EOL; +} + +try { + var_dump(setlocale(LC_ALL, ["locale\0name", "C"])); +} catch (ValueError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(@setlocale(LC_ALL, [str_repeat("x", 255), "C\0locale"])); +} catch (ValueError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(setlocale(LC_ALL, "zz_ZZ.nope", ["C\0locale"])); +} catch (TypeError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(setlocale(LC_ALL, "zz_ZZ.nope", new NullByteStringable())); +} catch (ValueError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} + +try { + var_dump(setlocale(LC_ALL, [], new NullByteStringable())); +} catch (ArgumentCountError $e) { + echo $e::class, ": ", $e->getMessage(), \PHP_EOL; +} +?> +--EXPECT-- +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +ValueError: setlocale(): Argument #2 ($locales) must not contain any null bytes +TypeError: setlocale(): Argument #3 must be of type ?string, array given +ValueError: setlocale(): Argument #3 must not contain any null bytes +ArgumentCountError: setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given From edc169e7705d5e4411865e9be92b50e82be78f4e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:00:03 +0200 Subject: [PATCH 05/12] [PFA 4/n] Optimize constant pre-bound arguments (#22829) Pre-bound arguments are bound to the generated closure's lexical vars: ``` function f($a, $b) {} $f = f(1, ?); // Generates: $tmp = 1; $f = function ($b) use ($tmp) { return f($tmp, $b); }; ``` Detect which pre-bound arguments are constant and burn them into the generated closure instead: ``` function f($a, $b) {} $f = f(1, ?); // Generates: $f = function ($b) { return f(1, $b); }; ``` --- Zend/Optimizer/dfa_pass.c | 28 +++ .../partial_application/const_arg_opt.phpt | 162 ++++++++++++++++++ .../partial_application/references_004.phpt | 10 +- .../variation_debug_001.phpt | 9 +- .../variation_debug_002.phpt | 16 +- .../variation_variadics_001.phpt | 12 +- .../variation_variadics_002.phpt | 10 +- Zend/zend_ast.c | 2 +- Zend/zend_compile.c | 2 + Zend/zend_partial.c | 59 ++++--- Zend/zend_partial.h | 2 +- Zend/zend_vm_def.h | 9 +- Zend/zend_vm_execute.h | 36 +++- 13 files changed, 311 insertions(+), 46 deletions(-) create mode 100644 Zend/tests/partial_application/const_arg_opt.phpt diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 77dc322fbdec..dcb4e8bc8412 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -469,6 +469,34 @@ static uint32_t zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) } } } + + if (call_info->caller_call_opline && call_info->caller_call_opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + /* Build a bitset of constant pre-bound PFA args: These are args whose value is alway the same for all + * instances of a PFA. */ + uint32_t const_args = 0; + for (uint32_t i = 0, l = MIN(sizeof(const_args)*CHAR_BIT, call_info->num_args); i < l; i++) { + zend_op *send_opline = call_info->arg_info[i].opline; + if (send_opline->op1_type == IS_CONST) { + zval *value = CT_CONSTANT_EX(op_array, send_opline->op1.constant); + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { + /* Const exprs can evaluate to non-const zvals (e.g. objects), and are not idempotent */ + continue; + } + const_args |= (UINT32_C(1) << i); + } + } + + /* Pass the bitset to the ZEND_CALLABLE_CONVERT_PARTIAL opline. */ + zend_op *call_opline = call_info->caller_call_opline; + if (call_opline->op2_type == IS_UNUSED) { + call_opline->op2.num = const_args; + } else { + ZEND_ASSERT(call_opline->op2_type == IS_CONST); + zval *zv = CT_CONSTANT_EX(op_array, call_opline->op2.constant); + Z_EXTRA_P(zv) = const_args; + } + } + call_info = call_info->next_callee; } while (call_info); } diff --git a/Zend/tests/partial_application/const_arg_opt.phpt b/Zend/tests/partial_application/const_arg_opt.phpt new file mode 100644 index 000000000000..d63456a96a5d --- /dev/null +++ b/Zend/tests/partial_application/const_arg_opt.phpt @@ -0,0 +1,162 @@ +--TEST-- +Constant argument optimization +--DESCRIPTION-- +Pre-bound arguments that are constant can be burned into the generated +op_array instead of being passed via the Closure's lexical vars. +--ENV-- +A=1 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--FILE-- +getClosureUsedVariables(); + if ($vars === []) { + echo "no lexical vars\n"; + } else { + $varNames = array_keys($vars); + echo 'lexical vars: ', implode(', ', $varNames), "\n"; + } +} + +echo "# Non-constant pre-bound argument:\n"; +$f = f(getenv('A'), ?); +print_lexical_vars($f); +$f(2); + +echo "# Constant pre-bound argument:\n"; +$f = f(2, ?); +print_lexical_vars($f); +$f(2); + +echo "# Constant pre-bound argument (inverted):\n"; +$f = f(?, 2); +print_lexical_vars($f); +$f(1); + +echo "# Inlined pre-bound argument:\n"; +$f = f(g(), ?); +print_lexical_vars($f); +$f(2); + +echo "# Constexpr pre-bound argument:\n"; +const B = 4; +$f = f(B, ?); +print_lexical_vars($f); +$f(2); + +echo "# Mixed arguments:\n"; +$f = h(5, getenv('A'), ?); +print_lexical_vars($f); +$f(2); + +echo "# Constant array pre-bound argument:\n"; +$f = f(['foo' => 'bar'], ?); +print_lexical_vars($f); +$f(2); + +echo "# Named arguments:\n"; +$f = h(1, c: ?, b: ?); +print_lexical_vars($f); +$f(2, 3); + +echo "# Many arguments (optimization can not be applied for all args) :\n"; +$f = i(?, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33); +print_lexical_vars($f); +$f(33); + +?> +--EXPECT-- +# Non-constant pre-bound argument: +lexical vars: a +array(2) { + [0]=> + string(1) "1" + [1]=> + int(2) +} +# Constant pre-bound argument: +no lexical vars +array(2) { + [0]=> + int(2) + [1]=> + int(2) +} +# Constant pre-bound argument (inverted): +no lexical vars +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +# Inlined pre-bound argument: +no lexical vars +array(2) { + [0]=> + int(3) + [1]=> + int(2) +} +# Constexpr pre-bound argument: +lexical vars: a +array(2) { + [0]=> + int(4) + [1]=> + int(2) +} +# Mixed arguments: +lexical vars: b +array(3) { + [0]=> + int(5) + [1]=> + string(1) "1" + [2]=> + int(2) +} +# Constant array pre-bound argument: +no lexical vars +array(2) { + [0]=> + array(1) { + ["foo"]=> + string(3) "bar" + } + [1]=> + int(2) +} +# Named arguments: +no lexical vars +array(3) { + [0]=> + int(1) + [1]=> + int(3) + [2]=> + int(2) +} +# Many arguments (optimization can not be applied for all args) : +lexical vars: a33 +int(33) diff --git a/Zend/tests/partial_application/references_004.phpt b/Zend/tests/partial_application/references_004.phpt index e5163c710da2..8d56d7dceaf4 100644 --- a/Zend/tests/partial_application/references_004.phpt +++ b/Zend/tests/partial_application/references_004.phpt @@ -1,5 +1,10 @@ --TEST-- PFA receives variadic param by ref if the actual function does +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 --FILE-- static function {closure:%s:%d} ] { @@ %sreferences_004.php 13 - 13 - - Bound Variables [2] { - Variable #0 [ $a ] - Variable #1 [ $args2 ] + - Bound Variables [1] { + Variable #0 [ $args2 ] } - Parameters [2] { diff --git a/Zend/tests/partial_application/variation_debug_001.phpt b/Zend/tests/partial_application/variation_debug_001.phpt index 04b63f3c4010..cf215329f489 100644 --- a/Zend/tests/partial_application/variation_debug_001.phpt +++ b/Zend/tests/partial_application/variation_debug_001.phpt @@ -1,12 +1,19 @@ --TEST-- PFA variation: var_dump(), user function +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 +--ENV-- +A=20 --FILE-- --EXPECTF-- object(Closure)#%d (5) { diff --git a/Zend/tests/partial_application/variation_debug_002.phpt b/Zend/tests/partial_application/variation_debug_002.phpt index a7c4c2d76e49..a4df317bbae9 100644 --- a/Zend/tests/partial_application/variation_debug_002.phpt +++ b/Zend/tests/partial_application/variation_debug_002.phpt @@ -1,8 +1,20 @@ --TEST-- PFA variation: var_dump(), internal function +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 +--ENV-- +A=1 --FILE-- --EXPECTF-- object(Closure)#%d (5) { @@ -11,7 +23,7 @@ object(Closure)#%d (5) { ["file"]=> string(%d) "%svariation_debug_002.php" ["line"]=> - int(2) + int(7) ["static"]=> array(3) { ["array"]=> diff --git a/Zend/tests/partial_application/variation_variadics_001.phpt b/Zend/tests/partial_application/variation_variadics_001.phpt index 850f0eda149d..43b4ff23e29f 100644 --- a/Zend/tests/partial_application/variation_variadics_001.phpt +++ b/Zend/tests/partial_application/variation_variadics_001.phpt @@ -1,5 +1,12 @@ --TEST-- PFA variation: variadics, user function +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 +--ENV-- +A=1 --FILE-- static function {closure:%s:%d} ] { @@ %s 6 - 6 - - Bound Variables [2] { - Variable #0 [ $a ] - Variable #1 [ $b2 ] - } - - Parameters [1] { Parameter #0 [ ...$b ] } diff --git a/Zend/tests/partial_application/variation_variadics_002.phpt b/Zend/tests/partial_application/variation_variadics_002.phpt index 21d8169fc42c..33330723f31d 100644 --- a/Zend/tests/partial_application/variation_variadics_002.phpt +++ b/Zend/tests/partial_application/variation_variadics_002.phpt @@ -1,5 +1,10 @@ --TEST-- PFA variation: variadics, internal function +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 --FILE-- static function {closure:%s:%d} ] { @@ %svariation_variadics_002.php 2 - 2 - - Bound Variables [2] { - Variable #0 [ $format ] - Variable #1 [ $values2 ] - } - - Parameters [1] { Parameter #0 [ mixed ...$values ] } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8e735c6aba74..a17cb3d91b91 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1352,7 +1352,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( ZEND_CALL_NUM_ARGS(frame), ZEND_CALL_ARG(frame, 1), extra_named_params, named_positions, fcc_ast->filename, &ast->lineno, - (void**)cache_slot, fcc_ast->name, flags); + (void**)cache_slot, fcc_ast->name, flags, 0); if (named_positions) { zend_array_release(named_positions); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 420a7cc08658..9c80cccb7cb9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4162,6 +4162,8 @@ static void zend_compile_call_partial(znode *result, zend_ast_fcc *fcc_ast, uint if (!Z_ISUNDEF_P(named_positions)) { opline->op2.constant = zend_add_literal(named_positions); opline->op2_type = IS_CONST; + } else { + opline->op2.num = 0; } } diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index a78db3f51ef1..ec60383a2bc8 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -66,6 +66,14 @@ static zend_always_inline bool zp_is_non_static_closure(const zend_function *fun return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == ZEND_ACC_CLOSURE); } +/* Whether argument at offset 'offset' is const. Such arguments can be burned into the generated op_array */ +static inline bool zp_is_const_arg(uint32_t const_args, uint32_t offset) { + if (offset < sizeof(const_args) * CHAR_BIT) { + return const_args & (UINT32_C(1) << offset); + } + return false; +} + static zend_never_inline ZEND_COLD void zp_args_underflow( const zend_function *function, uint32_t args, uint32_t expected) { @@ -179,7 +187,7 @@ static zend_string *zp_get_func_param_name(const zend_function *function, uint32 * including params and used vars. */ static zp_names *zp_assign_names(uint32_t argc, zval *argv, zend_function *function, bool variadic_partial, - zend_array *extra_named_params) + zend_array *extra_named_params, uint32_t const_args) { zp_names *names = zend_arena_calloc(&CG(ast_arena), 1, zend_safe_address_guarded(argc, sizeof(*names->params), offsetof(zp_names, params))); @@ -226,7 +234,7 @@ static zp_names *zp_assign_names(uint32_t argc, zval *argv, /* Assign names for pre-bound params (lexical vars). * There may be clashes, we ensure to generate unique names. */ for (uint32_t offset = 0; offset < argc; offset++) { - if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset])) { + if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset]) || zp_is_const_arg(const_args, offset)) { continue; } uint32_t n = 2; @@ -510,7 +518,7 @@ static zend_ast *zp_compile_forwarding_call( zp_names *var_names, bool uses_variadic_placeholder, uint32_t num_args, zend_class_entry *called_scope, zend_type return_type, bool forward_superfluous_args, - zend_ast *stmts_ast) + zend_ast *stmts_ast, uint32_t const_args) { bool is_assert = zend_string_equals(function->common.function_name, ZSTR_KNOWN(ZEND_STR_ASSERT)); @@ -551,6 +559,9 @@ static zend_ast *zp_compile_forwarding_call( default_value_ast = zend_ast_create_zval(&default_value); } args_ast = zend_ast_list_add(args_ast, default_value_ast); + } else if (zp_is_const_arg(const_args, offset)) { + ZEND_ASSERT(Z_TYPE(argv[offset]) < IS_OBJECT); + args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&argv[offset])); } else { args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR, zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset])))); @@ -661,7 +672,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, const zend_array *named_positions, zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - zend_string *pfa_name, uint32_t flags) { + zend_string *pfa_name, uint32_t flags, uint32_t const_args) { zend_op_array *op_array = NULL; @@ -771,7 +782,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, /* Assign variable names */ zp_names *var_names = zp_assign_names(argc, argv, function, - uses_variadic_placeholder, extra_named_params); + uses_variadic_placeholder, extra_named_params, const_args); /* Generate AST */ @@ -825,15 +836,17 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, default_value_ast, attributes_ast, NULL, NULL); } else if (!Z_ISUNDEF(argv[offset])) { - // TODO: If the pre-bound parameter is a literal, it can be a - // literal in the function body instead of a lexical var. - zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( - zend_string_copy(var_names->params[offset])); - if (zp_arg_must_be_sent_by_ref(function, offset+1)) { - lexical_var_ast->attr = ZEND_BIND_REF; + if (zp_is_const_arg(const_args, offset)) { + /* Will be burned into the op_array */ + } else { + zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( + zend_string_copy(var_names->params[offset])); + if (zp_arg_must_be_sent_by_ref(function, offset+1)) { + lexical_var_ast->attr = ZEND_BIND_REF; + } + lexical_vars_ast = zend_ast_list_add( + lexical_vars_ast, lexical_var_ast); } - lexical_vars_ast = zend_ast_list_add( - lexical_vars_ast, lexical_var_ast); } } @@ -894,7 +907,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, var_names, uses_variadic_placeholder, num_params, - called_scope, return_type, false, no_forwarding_ast); + called_scope, return_type, false, no_forwarding_ast, const_args); if (!no_forwarding_ast) { ZEND_ASSERT(EG(exception)); @@ -904,7 +917,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, forwarding_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, var_names, uses_variadic_placeholder, num_params, - called_scope, return_type, true, forwarding_ast); + called_scope, return_type, true, forwarding_ast, const_args); if (!forwarding_ast) { ZEND_ASSERT(EG(exception)); @@ -931,7 +944,7 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, stmts_ast = zp_compile_forwarding_call(this_ptr, function, argc, argv, extra_named_params, var_names, uses_variadic_placeholder, num_params, - called_scope, return_type, false, stmts_ast); + called_scope, return_type, false, stmts_ast, const_args); if (!stmts_ast) { ZEND_ASSERT(EG(exception)); @@ -1000,7 +1013,7 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct const zend_array *named_positions, zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - zend_string *pfa_name, uint32_t flags) { + zend_string *pfa_name, uint32_t flags, uint32_t const_args) { if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION ? cache_slot[0] == function @@ -1015,7 +1028,7 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct if (UNEXPECTED(!op_array)) { op_array = zp_compile(this_ptr, function, argc, argv, extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr, - cache_slot, pfa_name, flags); + cache_slot, pfa_name, flags, const_args); } if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) { @@ -1037,7 +1050,7 @@ static void zp_free_unbound_args(uint32_t start, uint32_t argc, zval *argv) /* Bind pre-bound arguments as lexical vars */ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *argv, - zend_array *extra_named_params) { + zend_array *extra_named_params, uint32_t const_args) { zend_arg_info *arg_infos = function->common.arg_info; uint32_t bind_offset = 0; @@ -1052,7 +1065,7 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval * for (uint32_t offset = 0; offset < argc; offset++) { zval *var = &argv[offset]; - if (Z_IS_PLACEHOLDER_P(var) || Z_ISUNDEF_P(var)) { + if (Z_IS_PLACEHOLDER_P(var) || Z_ISUNDEF_P(var) || zp_is_const_arg(const_args, offset)) { continue; } zend_arg_info *arg_info; @@ -1095,14 +1108,14 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, const zend_array *named_positions, zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - zend_string *pfa_name, uint32_t flags) { + zend_string *pfa_name, uint32_t flags, uint32_t const_args) { ZEND_ASSERT(pfa_name); const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr, - cache_slot, pfa_name, flags); + cache_slot, pfa_name, flags, const_args); if (UNEXPECTED(!op_array)) { ZEND_ASSERT(EG(exception)); @@ -1130,7 +1143,7 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, function->common.scope, called_scope, &object, (function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); - zp_bind(result, function, argc, argv, extra_named_params); + zp_bind(result, function, argc, argv, extra_named_params, const_args); } void zend_partial_op_array_dtor(zval *pDest) diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index 386823261e2b..d3fcdae6afc8 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -36,7 +36,7 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, const zend_array *named_positions, zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - zend_string *pfa_name, uint32_t flags); + zend_string *pfa_name, uint32_t flags, uint32_t const_args); void zend_partial_op_array_dtor(zval *pDest); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3466bc68d694..d14230514b34 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9875,6 +9875,13 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM) void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = GET_OP2_ZVAL_PTR(); zend_string *pfa_name = Z_STR_P(GET_OP1_ZVAL_PTR()); + uint32_t const_args; + + if (OP2_TYPE == IS_UNUSED) { + const_args = opline->op2.num; + } else { + const_args = Z_EXTRA_P(named_positions); + } zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -9883,7 +9890,7 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM) call->extra_named_params : NULL, OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, EX(func)->op_array.filename, &opline->lineno, cache_slot, - pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS, const_args); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index c0a23e49d02b..53bcdccd9719 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8672,6 +8672,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = RT_CONSTANT(opline, opline->op2); zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + uint32_t const_args; + + if (IS_CONST == IS_UNUSED) { + const_args = opline->op2.num; + } else { + const_args = Z_EXTRA_P(named_positions); + } zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -8680,7 +8687,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV call->extra_named_params : NULL, IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, EX(func)->op_array.filename, &opline->lineno, cache_slot, - pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS, const_args); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); @@ -12038,6 +12045,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = NULL; zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + uint32_t const_args; + + if (IS_UNUSED == IS_UNUSED) { + const_args = opline->op2.num; + } else { + const_args = Z_EXTRA_P(named_positions); + } zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -12046,7 +12060,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV call->extra_named_params : NULL, IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, EX(func)->op_array.filename, &opline->lineno, cache_slot, - pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS, const_args); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); @@ -61490,6 +61504,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = RT_CONSTANT(opline, opline->op2); zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + uint32_t const_args; + + if (IS_CONST == IS_UNUSED) { + const_args = opline->op2.num; + } else { + const_args = Z_EXTRA_P(named_positions); + } zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -61498,7 +61519,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P call->extra_named_params : NULL, IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, EX(func)->op_array.filename, &opline->lineno, cache_slot, - pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS, const_args); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); @@ -64754,6 +64775,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = NULL; zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + uint32_t const_args; + + if (IS_UNUSED == IS_UNUSED) { + const_args = opline->op2.num; + } else { + const_args = Z_EXTRA_P(named_positions); + } zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -64762,7 +64790,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P call->extra_named_params : NULL, IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, EX(func)->op_array.filename, &opline->lineno, cache_slot, - pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS, const_args); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); From bd8a3ee16fa88565be7dd99a457ff372a8bec52b Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 27 Jul 2026 18:00:11 +0100 Subject: [PATCH 06/12] streams: mark php_stream_error_create_array() as static It's not exported in a header and only used in the file it is defined --- main/streams/stream_errors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index b2dfdbb217ad..9ee6127df777 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -69,7 +69,7 @@ static void php_stream_error_create_object(zval *zv, php_stream_error_entry *ent } /* Create array of StreamError objects from error chain */ -PHPAPI void php_stream_error_create_array(zval *zv, php_stream_error_entry *first) +static void php_stream_error_create_array(zval *zv, php_stream_error_entry *first) { array_init(zv); From a1cd11ad0d022c139cf1ed968514f91b53b03478 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 09:58:22 -0700 Subject: [PATCH 07/12] `ReflectionMethod::getPrototype()`: add parentheses to error message --- ext/reflection/php_reflection.c | 2 +- ext/reflection/tests/bug74949.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a068f9f48ac6..bba42bdfcc1a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3728,7 +3728,7 @@ ZEND_METHOD(ReflectionMethod, getPrototype) if (!mptr->common.prototype) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Method %s::%s does not have a prototype", ZSTR_VAL(intern->ce->name), ZSTR_VAL(mptr->common.function_name)); + "Method %s::%s() does not have a prototype", ZSTR_VAL(intern->ce->name), ZSTR_VAL(mptr->common.function_name)); RETURN_THROWS(); } diff --git a/ext/reflection/tests/bug74949.phpt b/ext/reflection/tests/bug74949.phpt index 20e0fc00e101..858e766c0144 100644 --- a/ext/reflection/tests/bug74949.phpt +++ b/ext/reflection/tests/bug74949.phpt @@ -21,4 +21,4 @@ try { Method [ public method __invoke ] { } -Method Closure::__invoke does not have a prototype +Method Closure::__invoke() does not have a prototype From e0693e9c3dffe80f3d3ad72eac820ff8df5b5c69 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:11:11 -0700 Subject: [PATCH 08/12] Reflection: add regression tests for lazy initialization errors Add tests for the four error cases in `reflection_property_check_lazy_compatible()`, as triggered by both `ReflectionProperty::setRawValueWithoutLazyInitialization()` and `ReflectionProperty::skipLazyInitialization()`. While some of these errors are covered by existing tests, having all of the errors in one place will make it easier to see the changes when the error messages are improved. --- ...onProperty_lazy_initialization_errors.phpt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt diff --git a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt new file mode 100644 index 000000000000..bb7bb56c0fa9 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test ReflectionProperty::setRawValueWithoutLazyInitialization() and skipLazyInitialization() errors +--FILE-- + true; + } +} + +function test(object $obj, string $propertyName) { + $r = new ReflectionProperty($obj, $propertyName); + try { + $r->setRawValueWithoutLazyInitialization($obj, true); + } catch (ReflectionException $e) { + echo $e->getMessage() . "\n"; + } + try { + $r->skipLazyInitialization($obj); + } catch (ReflectionException $e) { + echo $e->getMessage() . "\n\n"; + } +} + +$obj = new Demo(); +test($obj, 'myStatic'); +test($obj, 'myVirtual'); + +$obj->myDynamic = true; +test($obj, 'myDynamic'); + +$obj = new ReflectionClass(Demo::class); +test($obj, 'name'); + +?> +--EXPECT-- +Can not use setRawValueWithoutLazyInitialization on static property Demo::$myStatic +Can not use skipLazyInitialization on static property Demo::$myStatic + +Can not use setRawValueWithoutLazyInitialization on virtual property Demo::$myVirtual +Can not use skipLazyInitialization on virtual property Demo::$myVirtual + +Can not use setRawValueWithoutLazyInitialization on dynamic property Demo::$myDynamic +Can not use skipLazyInitialization on dynamic property Demo::$myDynamic + +Can not use setRawValueWithoutLazyInitialization on internal class ReflectionClass +Can not use skipLazyInitialization on internal class ReflectionClass From acf7ba883f86e9c7abb51e416ac67ba58f17331f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:14:34 -0700 Subject: [PATCH 09/12] Reflection: improve error messages for lazy initialization errors Improve error messages from `reflection_property_check_lazy_compatible()`, as triggered by both `ReflectionProperty::setRawValueWithoutLazyInitialization()` and `ReflectionProperty::skipLazyInitialization()`. Say "cannot" instead of "can not", and include parentheses after the method name. --- ...ithoutLazyInitialization_no_dynamic_prop.phpt | 4 ++-- .../lazy_objects/skipLazyInitialization.phpt | 16 ++++++++-------- .../skipLazyInitialization_no_dynamic_prop.phpt | 4 ++-- ext/reflection/php_reflection.c | 8 ++++---- ...ctionProperty_lazy_initialization_errors.phpt | 16 ++++++++-------- ext/reflection/tests/property_hooks/gh17713.phpt | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt index 9151e58f3fc6..3c9686ba04c4 100644 --- a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt @@ -38,6 +38,6 @@ test('Proxy', $obj); ?> --EXPECT-- # Ghost -ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn # Proxy -ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn diff --git a/Zend/tests/lazy_objects/skipLazyInitialization.phpt b/Zend/tests/lazy_objects/skipLazyInitialization.phpt index 4fc47b13db67..8a95e684a5a2 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization.phpt @@ -198,10 +198,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property A::$static +ReflectionException: Cannot use skipLazyInitialization() on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static ## Property [ public $noDefault = NULL ] @@ -238,10 +238,10 @@ getValue(): string(5) "value" ## Property [ public virtual $virtual { get; set; } ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual ## Property [ $dynamicProp ] @@ -295,10 +295,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property A::$static +ReflectionException: Cannot use skipLazyInitialization() on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static ## Property [ public $noDefault = NULL ] @@ -335,10 +335,10 @@ getValue(): string(5) "value" ## Property [ public virtual $virtual { get; set; } ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual ## Property [ $dynamicProp ] diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt index 74e12cb3629f..dbbe88c52a2c 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt @@ -38,6 +38,6 @@ test('Proxy', $obj); ?> --EXPECT-- # Ghost -ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn # Proxy -ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bba42bdfcc1a..3e1e41893e72 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6039,7 +6039,7 @@ static zend_result reflection_property_check_lazy_compatible( { if (!prop) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on dynamic property %s::$%s", + "Cannot use %s() on dynamic property %s::$%s", method, ZSTR_VAL(scope->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6047,7 +6047,7 @@ static zend_result reflection_property_check_lazy_compatible( if (prop->flags & ZEND_ACC_STATIC) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on static property %s::$%s", + "Cannot use %s() on static property %s::$%s", method, ZSTR_VAL(prop->ce->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6055,7 +6055,7 @@ static zend_result reflection_property_check_lazy_compatible( if (prop->flags & ZEND_ACC_VIRTUAL) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on virtual property %s::$%s", + "Cannot use %s() on virtual property %s::$%s", method, ZSTR_VAL(prop->ce->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6065,7 +6065,7 @@ static zend_result reflection_property_check_lazy_compatible( && !zend_class_can_be_lazy(object->ce) ) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on internal class %s", + "Cannot use %s() on internal class %s", method, ZSTR_VAL(object->ce->name)); return FAILURE; } diff --git a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt index bb7bb56c0fa9..8bd82ff64020 100644 --- a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt +++ b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt @@ -38,14 +38,14 @@ test($obj, 'name'); ?> --EXPECT-- -Can not use setRawValueWithoutLazyInitialization on static property Demo::$myStatic -Can not use skipLazyInitialization on static property Demo::$myStatic +Cannot use setRawValueWithoutLazyInitialization() on static property Demo::$myStatic +Cannot use skipLazyInitialization() on static property Demo::$myStatic -Can not use setRawValueWithoutLazyInitialization on virtual property Demo::$myVirtual -Can not use skipLazyInitialization on virtual property Demo::$myVirtual +Cannot use setRawValueWithoutLazyInitialization() on virtual property Demo::$myVirtual +Cannot use skipLazyInitialization() on virtual property Demo::$myVirtual -Can not use setRawValueWithoutLazyInitialization on dynamic property Demo::$myDynamic -Can not use skipLazyInitialization on dynamic property Demo::$myDynamic +Cannot use setRawValueWithoutLazyInitialization() on dynamic property Demo::$myDynamic +Cannot use skipLazyInitialization() on dynamic property Demo::$myDynamic -Can not use setRawValueWithoutLazyInitialization on internal class ReflectionClass -Can not use skipLazyInitialization on internal class ReflectionClass +Cannot use setRawValueWithoutLazyInitialization() on internal class ReflectionClass +Cannot use skipLazyInitialization() on internal class ReflectionClass diff --git a/ext/reflection/tests/property_hooks/gh17713.phpt b/ext/reflection/tests/property_hooks/gh17713.phpt index c6d4d241bc50..edd0e1204658 100644 --- a/ext/reflection/tests/property_hooks/gh17713.phpt +++ b/ext/reflection/tests/property_hooks/gh17713.phpt @@ -157,7 +157,7 @@ int(43) # Accessing Base->virtualProp from scope Base Must not write to virtual property Base::$virtualProp Must not read from virtual property Base::$virtualProp -Can not use setRawValueWithoutLazyInitialization on virtual property Base::$virtualProp +Cannot use setRawValueWithoutLazyInitialization() on virtual property Base::$virtualProp Must not read from virtual property Base::$virtualProp # Accessing Test->dynamicProp from scope Base int(42) @@ -165,5 +165,5 @@ int(43) # Accessing Test->changedProp from scope Base May not use setRawValue on static properties May not use getRawValue on static properties -Can not use setRawValueWithoutLazyInitialization on static property Test::$changedProp +Cannot use setRawValueWithoutLazyInitialization() on static property Test::$changedProp May not use getRawValue on static properties From a5e34c6030acc6f482a8d4a0d12544a94438117a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:18:03 -0700 Subject: [PATCH 10/12] `ReflectionClass::setStaticPropertyValue()`: update missing property error Align with other Reflection exceptions for missing class properties (and methods, constants, etc.) by changing the message to "Property %s::$%s does not exist". --- ext/reflection/php_reflection.c | 2 +- .../tests/ReflectionClass_setStaticPropertyValue_001.phpt | 4 ++-- .../tests/ReflectionClass_setStaticPropertyValue_002.phpt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 3e1e41893e72..ec060cbbf3e9 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4195,7 +4195,7 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue) if (!variable_ptr) { zend_clear_exception(); zend_throw_exception_ex(reflection_exception_ptr, 0, - "Class %s does not have a property named %s", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + "Property %s::$%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name)); RETURN_THROWS(); } diff --git a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt index 2c855a043674..512fb5d6d916 100644 --- a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt +++ b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt @@ -73,5 +73,5 @@ Array ) Set non-existent values from A with no default value: -Class A does not have a property named protectedDoesNotExist -Class A does not have a property named privateDoesNotExist +Property A::$protectedDoesNotExist does not exist +Property A::$privateDoesNotExist does not exist diff --git a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt index 82de2ce0c2a8..2822471a3971 100644 --- a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt +++ b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt @@ -49,6 +49,6 @@ ReflectionClass::setStaticPropertyValue() expects exactly 2 arguments, 0 given ReflectionClass::setStaticPropertyValue() expects exactly 2 arguments, 1 given Deprecated: ReflectionClass::setStaticPropertyValue(): Passing null to parameter #1 ($name) of type string is deprecated in %s on line %d -Class C does not have a property named -Class C does not have a property named 1.5 +Property C::$ does not exist +Property C::$1.5 does not exist ReflectionClass::setStaticPropertyValue(): Argument #1 ($name) must be of type string, array given From c852fce5a780bc72ea4259a45b804ee323080a37 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 27 Jul 2026 19:32:02 +0100 Subject: [PATCH 11/12] streams: use PRIu32 format specifier rather than u The value is a uint32_t and this may depend on the platform how it is represented --- main/streams/stream_errors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 9ee6127df777..5b52bad1e1ad 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -283,7 +283,7 @@ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void) if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) { php_error_docref(NULL, E_WARNING, - "Stream error operation depth exceeded (%u), possible infinite recursion", + "Stream error operation depth exceeded (%"PRIu32"), possible infinite recursion", state->operation_depth); return NULL; } From 2bddba4db77b7264c0b4b48771ced67a3e758a5a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 27 Jul 2026 20:20:11 +0100 Subject: [PATCH 12/12] streams: use C enums instead of define for error_{store_}mode (#22901) Also make private as these are not used outside of the file. --- main/streams/php_stream_errors.h | 12 ------------ main/streams/stream_errors.c | 30 +++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 67112c8932b7..878350f972ac 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -21,18 +21,6 @@ BEGIN_EXTERN_C() -/* Error mode context options (internal C constants) */ -#define PHP_STREAM_ERROR_MODE_ERROR 0 -#define PHP_STREAM_ERROR_MODE_EXCEPTION 1 -#define PHP_STREAM_ERROR_MODE_SILENT 2 - -/* Error store context options (internal C constants) */ -#define PHP_STREAM_ERROR_STORE_AUTO 0 -#define PHP_STREAM_ERROR_STORE_NONE 1 -#define PHP_STREAM_ERROR_STORE_NON_TERM 2 -#define PHP_STREAM_ERROR_STORE_TERMINAL 3 -#define PHP_STREAM_ERROR_STORE_ALL 4 - /* Maximum operation nesting depth */ #define PHP_STREAM_ERROR_MAX_DEPTH 1000 /* Operations pool size to prevent extra allocations */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 5b52bad1e1ad..067a604af787 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -83,8 +83,23 @@ static void php_stream_error_create_array(zval *zv, php_stream_error_entry *firs } /* Context option helpers */ - -static int php_stream_auto_decide_error_store_mode(int error_mode) +/* Error mode context options (internal C constants) */ +C23_ENUM(php_stream_error_mode, uint8_t) { + PHP_STREAM_ERROR_MODE_ERROR = 0, + PHP_STREAM_ERROR_MODE_EXCEPTION = 1, + PHP_STREAM_ERROR_MODE_SILENT = 2 +}; + +/* Error store context options (internal C constants) */ +C23_ENUM(php_stream_error_store, uint8_t) { + PHP_STREAM_ERROR_STORE_AUTO = 0, + PHP_STREAM_ERROR_STORE_NONE = 1, + PHP_STREAM_ERROR_STORE_NON_TERM = 2, + PHP_STREAM_ERROR_STORE_TERMINAL = 3, + PHP_STREAM_ERROR_STORE_ALL = 4 +}; + +static php_stream_error_store php_stream_auto_decide_error_store_mode(php_stream_error_mode error_mode) { switch (error_mode) { case PHP_STREAM_ERROR_MODE_ERROR: @@ -98,7 +113,7 @@ static int php_stream_auto_decide_error_store_mode(int error_mode) } } -static int php_stream_get_error_mode(php_stream_context *context) +static php_stream_error_mode php_stream_get_error_mode(php_stream_context *context) { if (!context) { return PHP_STREAM_ERROR_MODE_ERROR; @@ -127,7 +142,8 @@ static int php_stream_get_error_mode(php_stream_context *context) return PHP_STREAM_ERROR_MODE_ERROR; } -static int php_stream_get_error_store_mode(php_stream_context *context, int error_mode) +static php_stream_error_store php_stream_get_error_store_mode( + php_stream_context *context, php_stream_error_mode error_mode) { if (!context) { return php_stream_auto_decide_error_store_mode(error_mode); @@ -386,7 +402,7 @@ static void php_stream_throw_exception_with_errors(php_stream_error_operation *o } static void php_stream_report_errors(php_stream_context *context, php_stream_error_operation *op, - int error_mode, bool is_terminating) + php_stream_error_mode error_mode, bool is_terminating) { switch (error_mode) { case PHP_STREAM_ERROR_MODE_ERROR: { @@ -439,8 +455,8 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) context = FG(default_context); } - int error_mode = php_stream_get_error_mode(context); - int store_mode = php_stream_get_error_store_mode(context, error_mode); + php_stream_error_mode error_mode = php_stream_get_error_mode(context); + php_stream_error_store store_mode = php_stream_get_error_store_mode(context, error_mode); bool is_terminating = php_stream_has_terminating_error(op);