Add limited support for new in initializers

Add support for new expressions inside parameter default values,
static variable initializers, global constant initializers and
attribute arguments.

RFC: https://wiki.php.net/rfc/new_in_initializers

Closes GH-7153.
This commit is contained in:
Nikita Popov 2021-06-15 15:10:48 +02:00
parent cce31657d6
commit 52d3d0d8d7
21 changed files with 629 additions and 22 deletions

View file

@ -193,6 +193,10 @@ PHP 8.1 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/noreturn_type RFC: https://wiki.php.net/rfc/noreturn_type
. Added support for fibers. . Added support for fibers.
RFC: https://wiki.php.net/rfc/fibers RFC: https://wiki.php.net/rfc/fibers
. It is now possible to use "new ClassName()" expressions as parameter
default values, static variable and global constant initializers, as well
as attribute arguments.
RFC: https://wiki.php.net/rfc/new_in_initializers
. File uploads now provide an additional full_path key, which contains the . File uploads now provide an additional full_path key, which contains the
full path (rather than just the basename) of the uploaded file. This is full path (rather than just the basename) of the uploaded file. This is
intended for use in conjunction with "upload webkitdirectory". intended for use in conjunction with "upload webkitdirectory".

View file

@ -0,0 +1,62 @@
--TEST--
new in constant expressions
--FILE--
<?php
try {
eval('static $a = new DoesNotExist;');
} catch (Error $e) {
echo $e->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

View file

@ -0,0 +1,35 @@
--TEST--
Places where new is allowed
--FILE--
<?php
#[SomeAttribute(new stdClass)]
class Test {
public function __construct(
public $prop = new stdClass,
) {
var_dump($prop);
}
}
function test($param = new stdClass) {
static $var = new stdClass;
var_dump($param, $var);
}
const TEST = new stdClass;
new Test;
test();
var_dump(TEST);
?>
--EXPECT--
object(stdClass)#3 (0) {
}
object(stdClass)#2 (0) {
}
object(stdClass)#3 (0) {
}
object(stdClass)#1 (0) {
}

View file

@ -0,0 +1,10 @@
--TEST--
New with anonymous class is not supported in constant expressions
--FILE--
<?php
static $x = new class {};
?>
--EXPECTF--
Fatal error: Cannot use anonymous class in constant expression in %s on line %d

View file

@ -0,0 +1,44 @@
--TEST--
Check that const exprs are pre-evaluated in new arguments
--FILE--
<?php
class C {
public function __construct(public $x) {}
}
function test(
$a = new C(__CLASS__),
$b = new C(__FUNCTION__),
$c = new C(x: __FILE__),
) {
var_dump($a, $b, $c);
}
test();
// Check that nested new works as well.
function test2($p = new C(new C(__FUNCTION__))) {
var_dump($p);
}
test2();
?>
--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"
}
}

View file

@ -0,0 +1,10 @@
--TEST--
Argument unpacking in new arguments in const expr (not yet supported)
--FILE--
<?php
static $x = new stdClass(...[0]);
?>
--EXPECTF--
Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d

View file

@ -0,0 +1,10 @@
--TEST--
Dynamic class name in new is not supported
--FILE--
<?php
static $x = new (FOO);
?>
--EXPECTF--
Fatal error: Cannot use dynamic class name in constant expression in %s on line %d

View file

@ -0,0 +1,10 @@
--TEST--
Invalid operation in new arg in const expr
--FILE--
<?php
static $x = new stdClass($foo);
?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s on line %d

View file

@ -0,0 +1,51 @@
--TEST--
Named params in new in const expr
--FILE--
<?php
class Vec {
public function __construct(public float $x, public float $y, public float $z) {}
}
static $a = new Vec(x: 0.0, y: 1.0, z: 2.0);
var_dump($a);
static $b = new Vec(z: 0.0, y: 1.0, x: 2.0);
var_dump($b);
static $c = new Vec(0.0, z: 1.0, y: 2.0);
var_dump($c);
try {
eval('static $d = new Vec(x: 0.0, x: 1.0);');
} catch (Error $e) {
echo $e->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

View file

@ -0,0 +1,18 @@
--TEST--
New not allowed in class constant
--FILE--
<?php
// New in class constants (and static properties) brings up evaluation order questions: When
// should the (potentially side-effecting) new expression be evaluated? Evaluating it when the
// class is declared would break references to classes that are declared later in the same
// file. On the other hand, the current lazy evaluation of initializers is somewhat ill-defined
// when we start considering side-effecting expressions.
class Test {
const X = new stdClass;
}
?>
--EXPECTF--
Fatal error: New expressions are not supported in this context in %s on line %d

View file

@ -0,0 +1,23 @@
--TEST--
New not allowed in property
--FILE--
<?php
// New in (non-static) properties is a particularly tricky case. The initializer needs to be
// evaluated on each object construction. Currently, the places where this can happen is
// during object creation, or as part of the constructor. Doing this during object creation
// can issues for use-cases such as serialization or generally anything that is effectively
// based on newInstanceWithoutConstructor(). Handling this via the constructor is more
// promising, but requires injecting code in the constructor, which may require adding a
// constructor which is not explicitly declared, which may also require a child class to
// call a constructor that has not been explicitly declared, otherwise properties may be
// left uninitialized. A third option is another mechanism between object creation and
// constructor invocation. Overall, the best way to address this is not clear right now.
class Test {
public $prop = new stdClass;
}
?>
--EXPECTF--
Fatal error: New expressions are not supported in this context in %s on line %d

View file

@ -0,0 +1,10 @@
--TEST--
Positional argument after named argument in new arguments
--FILE--
<?php
static $x = new stdClass(x: 0, 1);
?>
--EXPECTF--
Fatal error: Cannot use positional argument after named argument in %s on line %d

View file

@ -0,0 +1,39 @@
--TEST--
new self / new parent in constant expression
--FILE--
<?php
class A {
public static function invalid($x = new parent) {
}
}
class B extends A {
public static function method($x = new self, $y = new parent) {
var_dump($x, $y);
}
}
function invalid($x = new self) {}
B::method();
try {
invalid();
} catch (Error $e) {
echo $e->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

View file

@ -0,0 +1,10 @@
--TEST--
Static in new is not supported
--FILE--
<?php
static $x = new static;
?>
--EXPECTF--
Fatal error: "static" is not allowed in compile-time constants in %s on line %d

View file

@ -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. */ /* Disable constant substitution, to make getDefaultValueConstant() work. */
CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION; CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION;
zend_file_context_begin(&original_file_context); 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(ast_arena) = original_ast_arena;
CG(compiler_options) = original_compiler_options; CG(compiler_options) = original_compiler_options;
zend_file_context_end(&original_file_context); zend_file_context_end(&original_file_context);

View file

@ -504,6 +504,11 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) {
return FAILURE; return FAILURE;
} }
zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope)
{
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) ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
{ {
zval op1, op2; zval op1, op2;
@ -797,6 +802,88 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
ZVAL_COPY_OR_DUP(result, zv); ZVAL_COPY_OR_DUP(result, zv);
break; 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: default:
zend_throw_error(NULL, "Unsupported constant expression"); zend_throw_error(NULL, "Unsupported constant expression");
ret = FAILURE; ret = FAILURE;
@ -949,17 +1036,17 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast)
efree(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)) { if (zend_ast_is_list(ast)) {
zend_ast_list *list = zend_ast_get_list(ast); zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i; uint32_t i;
for (i = 0; i < list->children; ++i) { for (i = 0; i < list->children; ++i) {
fn(&list->child[i]); fn(&list->child[i], context);
} }
} else { } else {
uint32_t i, children = zend_ast_get_num_children(ast); uint32_t i, children = zend_ast_get_num_children(ast);
for (i = 0; i < children; ++i) { for (i = 0; i < children; ++i) {
fn(&ast->child[i]); fn(&ast->child[i], context);
} }
} }
} }

View file

@ -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_destroy(zend_ast *ast);
ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast); ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast);
typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr); 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); 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) { static zend_always_inline bool zend_ast_is_special(zend_ast *ast) {
return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1; return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1;

View file

@ -4738,7 +4738,7 @@ void zend_compile_static_var(zend_ast *ast) /* {{{ */
zval value_zv; zval value_zv;
if (*value_ast_ptr) { 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 { } else {
ZVAL_NULL(&value_zv); ZVAL_NULL(&value_zv);
} }
@ -6069,7 +6069,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */
if (zend_string_equals_literal_ci(name, "ticks")) { if (zend_string_equals_literal_ci(name, "ticks")) {
zval value_zv; 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); FC(declarables).ticks = zval_get_long(&value_zv);
zval_ptr_dtor_nogc(&value_zv); zval_ptr_dtor_nogc(&value_zv);
} else if (zend_string_equals_literal_ci(name, "encoding")) { } else if (zend_string_equals_literal_ci(name, "encoding")) {
@ -6091,7 +6091,7 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */
"use block mode"); "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)) { 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"); 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"); "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; CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION | ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION;
opcode = ZEND_RECV_INIT; opcode = ZEND_RECV_INIT;
default_node.op_type = IS_CONST; 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; CG(compiler_options) = cops;
if (last_required_param != (uint32_t) -1 && i < last_required_param) { 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) { 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) if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv)
&& !zend_is_valid_default_value(type, &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); c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment);
if (attr_ast) { 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); 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; 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 */ /* 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]; 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; zval *value_zv = &value_node.u.constant;
value_node.op_type = IS_CONST; 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))) { if (zend_get_special_const(ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name))) {
zend_error_noreturn(E_COMPILE_ERROR, 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_CONST || kind == ZEND_AST_CLASS_CONST
|| kind == ZEND_AST_CLASS_NAME || kind == ZEND_AST_CLASS_NAME
|| kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE || 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; zend_ast *ast = *ast_ptr;
if (ast == NULL || ast->kind == ZEND_AST_ZVAL) { if (ast == NULL || ast->kind == ZEND_AST_ZVAL) {
return; return;
@ -9728,17 +9785,29 @@ void zend_compile_const_expr(zend_ast **ast_ptr) /* {{{ */
case ZEND_AST_MAGIC_CONST: case ZEND_AST_MAGIC_CONST:
zend_compile_const_expr_magic_const(ast_ptr); zend_compile_const_expr_magic_const(ast_ptr);
break; break;
default: case ZEND_AST_NEW:
zend_ast_apply(ast, zend_compile_const_expr); 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; 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_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) { if ((*ast_ptr)->kind != ZEND_AST_ZVAL) {
/* Replace with compiled AST zval representation. */ /* Replace with compiled AST zval representation. */
zval ast_zv; zval ast_zv;
@ -10376,6 +10445,25 @@ void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
} }
break; 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: default:
return; return;
} }

View file

@ -130,7 +130,7 @@ void zend_compile_stmt(zend_ast *ast);
void zend_compile_expr(znode *node, 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); 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_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); typedef int (*user_opcode_handler_t) (zend_execute_data *execute_data);

View file

@ -0,0 +1,69 @@
--TEST--
new in attribute arguments
--FILE--
<?php
#[Attribute]
class MyAttribute {
public function __construct(public $x, public $y) {}
}
#[MyAttribute(null, new stdClass)]
class Test1 {
}
$rc = new ReflectionClass(Test1::class);
$ra = $rc->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) {
}
}

View file

@ -0,0 +1,27 @@
--TEST--
Handling of new in constant expressions in reflection
--FILE--
<?php
function test1() {
static $x = new stdClass;
return $x;
}
$rf = new ReflectionFunction('test1');
$s = $rf->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)