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 1/9] [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 2/9] 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 3/9] `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 4/9] 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 5/9] 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 6/9] `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 7/9] 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 8/9] 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); From c5cdea5ada55eec8a12bc2b9e966e88691fde714 Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Tue, 28 Jul 2026 05:16:34 +0700 Subject: [PATCH 9/9] docs/source/miscellaneous/writing-tests.rst: remove misplaced "is" (#22904) --- docs/source/miscellaneous/writing-tests.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index 7365267648a9..8e17674ae481 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -39,7 +39,7 @@ What do you write phpt tests on? If you want more guidance than that you can always ask the PHP Quality Assurance Team on their mailing list (php-qa@lists.php.net) where they would like you to direct your attentions. -How is a phpt test is used? +How is a phpt test used? When a test is called by the ``run-tests.php`` script it takes various parts of the phpt file to name and create a .php file. That .php file is then executed. The output of the .php file is then