From fda18e6c875f8fe39befcfc6e22fcf64874ea1a5 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 15 Jun 2021 15:10:48 +0200 Subject: [PATCH 1/2] New in initializers (reduced version) --- Zend/tests/constexpr/new.phpt | 62 +++++++++ Zend/tests/constexpr/new_allowed.phpt | 35 ++++++ Zend/tests/constexpr/new_anon_class.phpt | 10 ++ Zend/tests/constexpr/new_arg_eval.phpt | 44 +++++++ Zend/tests/constexpr/new_arg_unpack.phpt | 10 ++ .../constexpr/new_dynamic_class_name.phpt | 10 ++ .../new_invalid_operation_in_arg.phpt | 10 ++ Zend/tests/constexpr/new_named_params.phpt | 51 ++++++++ .../new_not_allowed_class_constant.phpt | 18 +++ .../constexpr/new_not_allowed_property.phpt | 23 ++++ .../constexpr/new_positional_after_named.phpt | 10 ++ Zend/tests/constexpr/new_static.phpt | 10 ++ Zend/zend_API.c | 2 +- Zend/zend_ast.c | 96 +++++++++++++- Zend/zend_ast.h | 4 +- Zend/zend_compile.c | 118 +++++++++++++++--- Zend/zend_compile.h | 2 +- ext/reflection/tests/new_in_attributes.phpt | 69 ++++++++++ ext/reflection/tests/new_in_constexpr.phpt | 27 ++++ 19 files changed, 589 insertions(+), 22 deletions(-) create mode 100644 Zend/tests/constexpr/new.phpt create mode 100644 Zend/tests/constexpr/new_allowed.phpt create mode 100644 Zend/tests/constexpr/new_anon_class.phpt create mode 100644 Zend/tests/constexpr/new_arg_eval.phpt create mode 100644 Zend/tests/constexpr/new_arg_unpack.phpt create mode 100644 Zend/tests/constexpr/new_dynamic_class_name.phpt create mode 100644 Zend/tests/constexpr/new_invalid_operation_in_arg.phpt create mode 100644 Zend/tests/constexpr/new_named_params.phpt create mode 100644 Zend/tests/constexpr/new_not_allowed_class_constant.phpt create mode 100644 Zend/tests/constexpr/new_not_allowed_property.phpt create mode 100644 Zend/tests/constexpr/new_positional_after_named.phpt create mode 100644 Zend/tests/constexpr/new_static.phpt create mode 100644 ext/reflection/tests/new_in_attributes.phpt create mode 100644 ext/reflection/tests/new_in_constexpr.phpt diff --git a/Zend/tests/constexpr/new.phpt b/Zend/tests/constexpr/new.phpt new file mode 100644 index 0000000000000..79bbc41f69b5b --- /dev/null +++ b/Zend/tests/constexpr/new.phpt @@ -0,0 +1,62 @@ +--TEST-- +new in constant expressions +--FILE-- +getMessage(), "\n"; +} + +static $b = new stdClass; +var_dump($b); + +try { + eval('static $c = new stdClass([] + 0);'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +class Test { + public function __construct(public $a, public $b) {} +} + +try { + eval('static $d = new Test(new stdClass, [] + 0);'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +static $e = new Test(new stdClass, 42); +var_dump($e); + +class Test2 { + public function __construct() { + echo "Side-effect\n"; + throw new Exception("Failed to construct"); + } +} + +try { + eval('static $f = new Test2();'); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Class "DoesNotExist" not found +object(stdClass)#2 (0) { +} +Unsupported operand types: array + int +Unsupported operand types: array + int +object(Test)#4 (2) { + ["a"]=> + object(stdClass)#1 (0) { + } + ["b"]=> + int(42) +} +Side-effect +Failed to construct diff --git a/Zend/tests/constexpr/new_allowed.phpt b/Zend/tests/constexpr/new_allowed.phpt new file mode 100644 index 0000000000000..c278341a4ae03 --- /dev/null +++ b/Zend/tests/constexpr/new_allowed.phpt @@ -0,0 +1,35 @@ +--TEST-- +Places where new is allowed +--FILE-- + +--EXPECT-- +object(stdClass)#3 (0) { +} +object(stdClass)#2 (0) { +} +object(stdClass)#3 (0) { +} +object(stdClass)#1 (0) { +} diff --git a/Zend/tests/constexpr/new_anon_class.phpt b/Zend/tests/constexpr/new_anon_class.phpt new file mode 100644 index 0000000000000..6f2b433136d40 --- /dev/null +++ b/Zend/tests/constexpr/new_anon_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +New with anonymous class is not supported in constant expressions +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use anonymous class in constant expression in %s on line %d diff --git a/Zend/tests/constexpr/new_arg_eval.phpt b/Zend/tests/constexpr/new_arg_eval.phpt new file mode 100644 index 0000000000000..d4d1f92899522 --- /dev/null +++ b/Zend/tests/constexpr/new_arg_eval.phpt @@ -0,0 +1,44 @@ +--TEST-- +Check that const exprs are pre-evaluated in new arguments +--FILE-- + +--EXPECTF-- +object(C)#1 (1) { + ["x"]=> + string(0) "" +} +object(C)#2 (1) { + ["x"]=> + string(4) "test" +} +object(C)#3 (1) { + ["x"]=> + string(%d) "%snew_arg_eval.php" +} +object(C)#3 (1) { + ["x"]=> + object(C)#2 (1) { + ["x"]=> + string(5) "test2" + } +} diff --git a/Zend/tests/constexpr/new_arg_unpack.phpt b/Zend/tests/constexpr/new_arg_unpack.phpt new file mode 100644 index 0000000000000..303116054ede9 --- /dev/null +++ b/Zend/tests/constexpr/new_arg_unpack.phpt @@ -0,0 +1,10 @@ +--TEST-- +Argument unpacking in new arguments in const expr (not yet supported) +--FILE-- + +--EXPECTF-- +Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d diff --git a/Zend/tests/constexpr/new_dynamic_class_name.phpt b/Zend/tests/constexpr/new_dynamic_class_name.phpt new file mode 100644 index 0000000000000..645e3b7240b3b --- /dev/null +++ b/Zend/tests/constexpr/new_dynamic_class_name.phpt @@ -0,0 +1,10 @@ +--TEST-- +Dynamic class name in new is not supported +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic class name in constant expression in %s on line %d diff --git a/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt b/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt new file mode 100644 index 0000000000000..0537d21874e53 --- /dev/null +++ b/Zend/tests/constexpr/new_invalid_operation_in_arg.phpt @@ -0,0 +1,10 @@ +--TEST-- +Invalid operation in new arg in const expr +--FILE-- + +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/constexpr/new_named_params.phpt b/Zend/tests/constexpr/new_named_params.phpt new file mode 100644 index 0000000000000..6b57088f60fe6 --- /dev/null +++ b/Zend/tests/constexpr/new_named_params.phpt @@ -0,0 +1,51 @@ +--TEST-- +Named params in new in const expr +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +object(Vec)#1 (3) { + ["x"]=> + float(0) + ["y"]=> + float(1) + ["z"]=> + float(2) +} +object(Vec)#2 (3) { + ["x"]=> + float(2) + ["y"]=> + float(1) + ["z"]=> + float(0) +} +object(Vec)#3 (3) { + ["x"]=> + float(0) + ["y"]=> + float(2) + ["z"]=> + float(1) +} +Named parameter $x overwrites previous argument diff --git a/Zend/tests/constexpr/new_not_allowed_class_constant.phpt b/Zend/tests/constexpr/new_not_allowed_class_constant.phpt new file mode 100644 index 0000000000000..1d8a39b7be647 --- /dev/null +++ b/Zend/tests/constexpr/new_not_allowed_class_constant.phpt @@ -0,0 +1,18 @@ +--TEST-- +New not allowed in class constant +--FILE-- + +--EXPECTF-- +Fatal error: New expressions are not supported in this context in %s on line %d diff --git a/Zend/tests/constexpr/new_not_allowed_property.phpt b/Zend/tests/constexpr/new_not_allowed_property.phpt new file mode 100644 index 0000000000000..108ee857a164a --- /dev/null +++ b/Zend/tests/constexpr/new_not_allowed_property.phpt @@ -0,0 +1,23 @@ +--TEST-- +New not allowed in property +--FILE-- + +--EXPECTF-- +Fatal error: New expressions are not supported in this context in %s on line %d diff --git a/Zend/tests/constexpr/new_positional_after_named.phpt b/Zend/tests/constexpr/new_positional_after_named.phpt new file mode 100644 index 0000000000000..351899d03ec37 --- /dev/null +++ b/Zend/tests/constexpr/new_positional_after_named.phpt @@ -0,0 +1,10 @@ +--TEST-- +Positional argument after named argument in new arguments +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use positional argument after named argument in %s on line %d diff --git a/Zend/tests/constexpr/new_static.phpt b/Zend/tests/constexpr/new_static.phpt new file mode 100644 index 0000000000000..a626b2982475d --- /dev/null +++ b/Zend/tests/constexpr/new_static.phpt @@ -0,0 +1,10 @@ +--TEST-- +Static in new is not supported +--FILE-- + +--EXPECTF-- +Fatal error: "static" is not allowed in compile-time constants in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 57e0a406e458d..cb81ab79628b8 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4803,7 +4803,7 @@ static zend_result get_default_via_ast(zval *default_value_zval, const char *def /* Disable constant substitution, to make getDefaultValueConstant() work. */ CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION; zend_file_context_begin(&original_file_context); - zend_const_expr_to_zval(default_value_zval, const_expr_ast_ptr); + zend_const_expr_to_zval(default_value_zval, const_expr_ast_ptr, /* allow_dynamic */ true); CG(ast_arena) = original_ast_arena; CG(compiler_options) = original_compiler_options; zend_file_context_end(&original_file_context); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index ba46b7feb6cba..bc3e810938a18 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -504,6 +504,14 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) { return FAILURE; } +zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope) +{ + zend_string *class_name = zend_ast_get_str(ast); + switch (ast->attr) { + } + return zend_fetch_class_by_name(class_name, NULL, ZEND_FETCH_CLASS_EXCEPTION); +} + ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope) { zval op1, op2; @@ -797,6 +805,88 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast ZVAL_COPY_OR_DUP(result, zv); break; } + case ZEND_AST_NEW: + { + zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope); + if (!ce) { + return FAILURE; + } + + if (object_init_ex(result, ce) != SUCCESS) { + return FAILURE; + } + + zend_ast_list *args_ast = zend_ast_get_list(ast->child[1]); + if (args_ast->attr) { + /* Has named arguments. */ + HashTable *args = zend_new_array(args_ast->children); + for (uint32_t i = 0; i < args_ast->children; i++) { + zend_ast *arg_ast = args_ast->child[i]; + zend_string *name = NULL; + zval arg; + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + name = zend_ast_get_str(arg_ast->child[0]); + arg_ast = arg_ast->child[1]; + } + if (zend_ast_evaluate(&arg, arg_ast, scope) == FAILURE) { + zend_array_destroy(args); + zval_ptr_dtor(result); + return FAILURE; + } + if (name) { + if (!zend_hash_add(args, name, &arg)) { + zend_throw_error(NULL, + "Named parameter $%s overwrites previous argument", + ZSTR_VAL(name)); + zend_array_destroy(args); + zval_ptr_dtor(result); + return FAILURE; + } + } else { + zend_hash_next_index_insert(args, &arg); + } + } + + zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result)); + if (ctor) { + zend_call_known_function( + ctor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args); + } + + zend_array_destroy(args); + } else { + ALLOCA_FLAG(use_heap) + zval *args = do_alloca(sizeof(zval) * args_ast->children, use_heap); + for (uint32_t i = 0; i < args_ast->children; i++) { + if (zend_ast_evaluate(&args[i], args_ast->child[i], scope) == FAILURE) { + for (uint32_t j = 0; j < i; j++) { + zval_ptr_dtor(&args[j]); + } + free_alloca(args, use_heap); + zval_ptr_dtor(result); + return FAILURE; + } + } + + zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result)); + if (ctor) { + zend_call_known_instance_method( + ctor, Z_OBJ_P(result), NULL, args_ast->children, args); + } + + for (uint32_t i = 0; i < args_ast->children; i++) { + zval_ptr_dtor(&args[i]); + } + free_alloca(args, use_heap); + } + + if (EG(exception)) { + zend_object_store_ctor_failed(Z_OBJ_P(result)); + zval_ptr_dtor(result); + return FAILURE; + } + return SUCCESS; + } default: zend_throw_error(NULL, "Unsupported constant expression"); ret = FAILURE; @@ -949,17 +1039,17 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast) efree(ast); } -ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn) { +ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context) { if (zend_ast_is_list(ast)) { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; for (i = 0; i < list->children; ++i) { - fn(&list->child[i]); + fn(&list->child[i], context); } } else { uint32_t i, children = zend_ast_get_num_children(ast); for (i = 0; i < children; ++i) { - fn(&ast->child[i]); + fn(&ast->child[i], context); } } } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 1bf8f263fa45e..0b66d909b6835 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -303,8 +303,8 @@ ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast); ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast); -typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr); -ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn); +typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context); +ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context); static zend_always_inline bool zend_ast_is_special(zend_ast *ast) { return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 100ac6563cb13..d783df0d12d0d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4738,7 +4738,7 @@ void zend_compile_static_var(zend_ast *ast) /* {{{ */ zval value_zv; if (*value_ast_ptr) { - zend_const_expr_to_zval(&value_zv, value_ast_ptr); + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ true); } else { ZVAL_NULL(&value_zv); } @@ -6069,7 +6069,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */ if (zend_string_equals_literal_ci(name, "ticks")) { zval value_zv; - zend_const_expr_to_zval(&value_zv, value_ast_ptr); + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); FC(declarables).ticks = zval_get_long(&value_zv); zval_ptr_dtor_nogc(&value_zv); } else if (zend_string_equals_literal_ci(name, "encoding")) { @@ -6091,7 +6091,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */ "use block mode"); } - zend_const_expr_to_zval(&value_zv, value_ast_ptr); + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) { zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must have 0 or 1 as its value"); @@ -6477,7 +6477,8 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3 "Cannot use positional argument after named argument"); } - zend_const_expr_to_zval(&attr->args[j].value, arg_ast_ptr); + zend_const_expr_to_zval( + &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); } } } @@ -6607,7 +6608,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION; opcode = ZEND_RECV_INIT; default_node.op_type = IS_CONST; - zend_const_expr_to_zval(&default_node.u.constant, default_ast_ptr); + zend_const_expr_to_zval( + &default_node.u.constant, default_ast_ptr, /* allow_dynamic */ true); CG(compiler_options) = cops; if (last_required_param != (uint32_t) -1 && i < last_required_param) { @@ -7285,7 +7287,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z } if (*value_ast_ptr) { - zend_const_expr_to_zval(&value_zv, value_ast_ptr); + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv) && !zend_is_valid_default_value(type, &value_zv)) { @@ -7374,7 +7376,7 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr ); } - zend_const_expr_to_zval(&value_zv, value_ast_ptr); + zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment); if (attr_ast) { @@ -7833,7 +7835,7 @@ static void zend_compile_enum_case(zend_ast *ast) zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_zval_ast); zval value_zv; - zend_const_expr_to_zval(&value_zv, &const_enum_init_ast); + zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false); /* Doc comment has been appended as second last element in ZEND_AST_ENUM ast - attributes are conventionally last */ zend_ast *doc_comment_ast = ast->child[2]; @@ -8015,7 +8017,7 @@ void zend_compile_const_decl(zend_ast *ast) /* {{{ */ zval *value_zv = &value_node.u.constant; value_node.op_type = IS_CONST; - zend_const_expr_to_zval(value_zv, value_ast_ptr); + zend_const_expr_to_zval(value_zv, value_ast_ptr, /* allow_dynamic */ true); if (zend_get_special_const(ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name))) { zend_error_noreturn(E_COMPILE_ERROR, @@ -9600,7 +9602,9 @@ bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_CONST || kind == ZEND_AST_CLASS_CONST || kind == ZEND_AST_CLASS_NAME || kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE - || kind == ZEND_AST_CONST_ENUM_INIT; + || kind == ZEND_AST_CONST_ENUM_INIT + || kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST + || kind == ZEND_AST_NAMED_ARG; } /* }}} */ @@ -9704,8 +9708,61 @@ void zend_compile_const_expr_magic_const(zend_ast **ast_ptr) /* {{{ */ } /* }}} */ -void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */ +static void zend_compile_const_expr_new(zend_ast **ast_ptr) { + zend_ast *class_ast = (*ast_ptr)->child[0]; + if (class_ast->kind == ZEND_AST_CLASS) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use anonymous class in constant expression"); + } + if (class_ast->kind != ZEND_AST_ZVAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use dynamic class name in constant expression"); + } + + zend_string *class_name = zend_resolve_class_name_ast(class_ast); + int fetch_type = zend_get_class_fetch_type(class_name); + if (ZEND_FETCH_CLASS_STATIC == fetch_type) { + zend_error_noreturn(E_COMPILE_ERROR, + "\"static\" is not allowed in compile-time constants"); + } + + zval *class_ast_zv = zend_ast_get_zval(class_ast); + zval_ptr_dtor_nogc(class_ast_zv); + ZVAL_STR(class_ast_zv, class_name); + class_ast->attr = fetch_type; +} + +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; + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *arg = list->child[i]; + if (arg->kind == ZEND_AST_UNPACK) { + zend_error_noreturn(E_COMPILE_ERROR, + "Argument unpacking in constant expressions is not supported"); + } + 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_named_args) { + list->attr = 1; + } +} + +typedef struct { + /* Whether the value of this expression may differ on each evaluation. */ + bool allow_dynamic; +} const_expr_context; + +void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */ +{ + const_expr_context *ctx = (const_expr_context *) context; zend_ast *ast = *ast_ptr; if (ast == NULL || ast->kind == ZEND_AST_ZVAL) { return; @@ -9728,17 +9785,29 @@ void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */ case ZEND_AST_MAGIC_CONST: zend_compile_const_expr_magic_const(ast_ptr); break; - default: - zend_ast_apply(ast, zend_compile_const_expr); + case ZEND_AST_NEW: + if (!ctx->allow_dynamic) { + zend_error_noreturn(E_COMPILE_ERROR, + "New expressions are not supported in this context"); + } + zend_compile_const_expr_new(ast_ptr); + break; + case ZEND_AST_ARG_LIST: + zend_compile_const_expr_args(ast_ptr); break; } + + zend_ast_apply(ast, zend_compile_const_expr, context); } /* }}} */ -void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr) /* {{{ */ +void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr, bool allow_dynamic) /* {{{ */ { + const_expr_context context; + context.allow_dynamic = allow_dynamic; + zend_eval_const_expr(ast_ptr); - zend_compile_const_expr(ast_ptr); + zend_compile_const_expr(ast_ptr, &context); if ((*ast_ptr)->kind != ZEND_AST_ZVAL) { /* Replace with compiled AST zval representation. */ zval ast_zv; @@ -10376,6 +10445,25 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ } break; } + // TODO: We should probably use zend_ast_apply to recursively walk nodes without + // special handling. It is required that all nodes that are part of a const expr + // are visited. Probably we should be distinguishing evaluation of const expr and + // normal exprs here. + case ZEND_AST_ARG_LIST: + { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_eval_const_expr(&list->child[i]); + } + return; + } + case ZEND_AST_NEW: + zend_eval_const_expr(&ast->child[0]); + zend_eval_const_expr(&ast->child[1]); + return; + case ZEND_AST_NAMED_ARG: + zend_eval_const_expr(&ast->child[1]); + return; default: return; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 76405a3689dc9..c78d2be54ae50 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -130,7 +130,7 @@ void zend_compile_stmt(zend_ast *ast); void zend_compile_expr(znode *node, zend_ast *ast); zend_op *zend_compile_var(znode *node, zend_ast *ast, uint32_t type, bool by_ref); void zend_eval_const_expr(zend_ast **ast_ptr); -void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr); +void zend_const_expr_to_zval(zval *result, zend_ast **ast_ptr, bool allow_dynamic); typedef int (*user_opcode_handler_t) (zend_execute_data *execute_data); diff --git a/ext/reflection/tests/new_in_attributes.phpt b/ext/reflection/tests/new_in_attributes.phpt new file mode 100644 index 0000000000000..4c6d4476f3dee --- /dev/null +++ b/ext/reflection/tests/new_in_attributes.phpt @@ -0,0 +1,69 @@ +--TEST-- +new in attribute arguments +--FILE-- +getAttributes()[0]; +$args1 = $ra->getArguments(); +$obj1 = $ra->newInstance(); +var_dump($args1, $obj1); + +// Check that we get fresh instances each time: +$args2 = $ra->getArguments(); +$obj2 = $ra->newInstance(); +var_dump($args1[1] !== $args2[1]); +var_dump($obj1->y !== $obj2->y); + +// Check that named args work: +#[MyAttribute(y: new stdClass, x: null)] +class Test2 { +} + +$rc = new ReflectionClass(Test2::class); +$ra = $rc->getAttributes()[0]; +$args = $ra->getArguments(); +$obj = $ra->newInstance(); +var_dump($args, $obj); + +?> +--EXPECT-- +array(2) { + [0]=> + NULL + [1]=> + object(stdClass)#3 (0) { + } +} +object(MyAttribute)#4 (2) { + ["x"]=> + NULL + ["y"]=> + object(stdClass)#5 (0) { + } +} +bool(true) +bool(true) +array(2) { + ["y"]=> + object(stdClass)#2 (0) { + } + ["x"]=> + NULL +} +object(MyAttribute)#10 (2) { + ["x"]=> + NULL + ["y"]=> + object(stdClass)#11 (0) { + } +} diff --git a/ext/reflection/tests/new_in_constexpr.phpt b/ext/reflection/tests/new_in_constexpr.phpt new file mode 100644 index 0000000000000..c9b77c0eed216 --- /dev/null +++ b/ext/reflection/tests/new_in_constexpr.phpt @@ -0,0 +1,27 @@ +--TEST-- +Handling of new in constant expressions in reflection +--FILE-- +getStaticVariables(); +var_dump($s['x'] === test1()); + +function test2($x = new stdClass) { + return $x; +} + +$rf = new ReflectionFunction('test2'); +$rp = $rf->getParameters()[0]; +// Parameter default should *not* be the same. +var_dump($rp->getDefaultValue() !== test2()); + +?> +--EXPECT-- +bool(true) +bool(true) From 9d3ff893e135d6b49c042eb9177bef701c0f8586 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 13 Jul 2021 12:22:33 +0200 Subject: [PATCH 2/2] Support new self / new parent --- Zend/tests/constexpr/new_self_parent.phpt | 39 +++++++++++++++++++++++ Zend/zend_ast.c | 5 +-- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/constexpr/new_self_parent.phpt diff --git a/Zend/tests/constexpr/new_self_parent.phpt b/Zend/tests/constexpr/new_self_parent.phpt new file mode 100644 index 0000000000000..b134f77a498e5 --- /dev/null +++ b/Zend/tests/constexpr/new_self_parent.phpt @@ -0,0 +1,39 @@ +--TEST-- +new self / new parent in constant expression +--FILE-- +getMessage(), "\n"; +} + +try { + B::invalid(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +object(B)#1 (0) { +} +object(A)#2 (0) { +} +Cannot access "self" when no class scope is active +Cannot access "parent" when current class scope has no parent diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index bc3e810938a18..6978eb196f6b2 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -506,10 +506,7 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) { zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope) { - zend_string *class_name = zend_ast_get_str(ast); - switch (ast->attr) { - } - return zend_fetch_class_by_name(class_name, NULL, ZEND_FETCH_CLASS_EXCEPTION); + return zend_fetch_class(zend_ast_get_str(ast), ast->attr | ZEND_FETCH_CLASS_EXCEPTION); } ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)