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/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/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); 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 diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a068f9f48ac6..ec060cbbf3e9 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(); } @@ -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(); } @@ -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/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 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..8bd82ff64020 --- /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-- +Cannot use setRawValueWithoutLazyInitialization() on static property Demo::$myStatic +Cannot use skipLazyInitialization() on static property Demo::$myStatic + +Cannot use setRawValueWithoutLazyInitialization() on virtual property Demo::$myVirtual +Cannot use skipLazyInitialization() on virtual property Demo::$myVirtual + +Cannot use setRawValueWithoutLazyInitialization() on dynamic property Demo::$myDynamic +Cannot use skipLazyInitialization() on dynamic property Demo::$myDynamic + +Cannot use setRawValueWithoutLazyInitialization() on internal class ReflectionClass +Cannot use skipLazyInitialization() on internal class ReflectionClass 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 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 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 b2dfdbb217ad..067a604af787 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); @@ -83,8 +83,23 @@ PHPAPI 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); @@ -283,7 +299,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; } @@ -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);