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
. Added support for 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
full path (rather than just the basename) of the uploaded file. This is
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. */
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);

View file

@ -504,6 +504,11 @@ 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)
{
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)
{
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);
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 +1036,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);
}
}
}

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_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;

View file

@ -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;
}

View file

@ -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);

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)