Skip to content

Commit 8624843

Browse files
committed
Add support for cloning with expressions as property names
1 parent d35d6a5 commit 8624843

22 files changed

+2103
-703
lines changed

Zend/Optimizer/zend_inference.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,6 +2744,7 @@ static zend_always_inline zend_result _zend_update_type_info(
27442744
}
27452745
break;
27462746
case ZEND_ASSIGN_OBJ:
2747+
case ZEND_CLONE_INIT_PROP:
27472748
if (opline->op1_type == IS_CV) {
27482749
tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
27492750
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
@@ -4798,6 +4799,41 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
47984799
}
47994800
}
48004801
return 1;
4802+
case ZEND_CLONE_INIT_PROP:
4803+
if (t1 & (MAY_BE_ANY-MAY_BE_OBJECT)) {
4804+
return 1;
4805+
}
4806+
if (ssa_op->op1_use) {
4807+
zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
4808+
zend_class_entry *ce = var_info->ce;
4809+
4810+
if (var_info->is_instanceof ||
4811+
!ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
4812+
return 1;
4813+
}
4814+
4815+
if (opline->op2_type != IS_CONST) {
4816+
return 1;
4817+
}
4818+
4819+
zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op2));
4820+
if (ZSTR_LEN(prop_name) > 0 && ZSTR_VAL(prop_name)[0] == '\0') {
4821+
return 1;
4822+
}
4823+
4824+
zend_property_info *prop_info =
4825+
zend_hash_find_ptr(&ce->properties_info, prop_name);
4826+
if (prop_info) {
4827+
if (ZEND_TYPE_IS_SET(prop_info->type)) {
4828+
return 1;
4829+
}
4830+
return !(prop_info->flags & ZEND_ACC_PUBLIC)
4831+
&& prop_info->ce != op_array->scope;
4832+
} else {
4833+
return !(ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
4834+
}
4835+
}
4836+
return 1;
48014837
case ZEND_ROPE_INIT:
48024838
case ZEND_ROPE_ADD:
48034839
case ZEND_ROPE_END:

Zend/Optimizer/zend_ssa.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
588588
break;
589589
case ZEND_ASSIGN_DIM:
590590
case ZEND_ASSIGN_OBJ:
591+
case ZEND_CLONE_INIT_PROP:
591592
next = opline + 1;
592593
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
593594
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];

Zend/tests/clone_initializer/clone_initializer_error2.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Test that the property initializer list cannot contain integer keys
2+
Test that the property initializer list cannot contain invalid identifiers when using the : syntax
33
--FILE--
44
<?php
55

@@ -13,4 +13,4 @@ class Foo
1313

1414
?>
1515
--EXPECTF--
16-
Parse error: syntax error, unexpected integer "1" in %s on line %d
16+
Parse error: syntax error, unexpected token ":" in %s on line %d
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
--TEST--
2-
Test that the property initializer list can only contain literals
2+
Test that the clone property initializer respects visibility
33
--FILE--
44
<?php
55

66
class Foo
77
{
8-
public function withProperties()
9-
{
10-
$property = "string";
8+
private $bar;
9+
}
10+
11+
$foo = new Foo();
1112

12-
return clone $this with {$property: "value"};
13-
}
13+
try {
14+
$foo = clone $foo with {bar: 1};
15+
} catch (Error $exception) {
16+
echo $exception->getMessage() . "\n";
1417
}
1518

1619
?>
17-
--EXPECTF--
18-
Parse error: syntax error, unexpected variable "$property" in %s on line %d
20+
--EXPECT--
21+
Cannot access private property Foo::$bar
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
--TEST--
2-
Test that the clone property initializer respects visibility
2+
Test that readonly properties cannot be assigned in "cloned with" multiple times
33
--FILE--
44
<?php
55

66
class Foo
77
{
8-
private $bar;
8+
public function __construct(
9+
public readonly int $bar
10+
) {}
11+
12+
public function with()
13+
{
14+
return clone $this with {
15+
bar: 1,
16+
bar: 2,
17+
};
18+
}
919
}
1020

11-
$foo = new Foo();
21+
$foo = new Foo(0);
1222

1323
try {
14-
$foo = clone $foo with {bar: 1};
24+
$foo->with();
1525
} catch (Error $exception) {
1626
echo $exception->getMessage() . "\n";
1727
}
1828

1929
?>
2030
--EXPECT--
21-
Cannot access private property Foo::$bar
31+
Cannot modify readonly property Foo::$bar
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
--TEST--
2-
Test that the "clone with" initializer list cannot contain the same property twice
2+
Test TypeError during "clone with"
33
--FILE--
44
<?php
55

66
class Foo
77
{
8-
public function __construct(
9-
public readonly int $bar
10-
) {}
8+
public int $bar = 1;
9+
}
10+
11+
$obj1 = new Foo();
12+
13+
try {
14+
clone $obj1 with {bar: []};
15+
} catch (TypeError $e) {
16+
echo $e->getMessage() . "\n";
17+
}
1118

12-
public function with(int $bar, stdClass $baz)
13-
{
14-
return clone $this with {
15-
bar: 1,
16-
bar: 2,
17-
};
18-
}
19+
try {
20+
clone $obj1 with {bar: []}; // The same as above but now using cache slots
21+
} catch (TypeError $e) {
22+
echo $e->getMessage() . "\n";
1923
}
2024

2125
?>
22-
--EXPECTF--
23-
Fatal error: Initializer list cannot contain the same property twice in %s on line %d
26+
--EXPECT--
27+
Cannot assign array to property Foo::$bar of type int
28+
Cannot assign array to property Foo::$bar of type int
Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
--TEST--
2-
Test TypeError during "clone with"
2+
Test "clone with" with a declared untyped property of an object throwing an exception in the destructor
33
--FILE--
44
<?php
55

66
class Foo
77
{
8-
public int $bar = 1;
8+
public $bar;
9+
10+
public function __destruct() {
11+
throw new Exception("Error in destructor");
12+
}
913
}
1014

11-
$obj1 = new Foo();
15+
function returnFoo() {
16+
return new Foo();
17+
}
1218

1319
try {
14-
clone $obj1 with {bar: []};
15-
} catch (TypeError $e) {
20+
clone returnFoo() with {bar: new stdClass()};
21+
} catch (Exception $e) {
1622
echo $e->getMessage() . "\n";
1723
}
1824

1925
try {
20-
clone $obj1 with {bar: []}; // The same as above but now using cache slots
21-
} catch (TypeError $e) {
26+
clone returnFoo() with {bar: new stdClass()}; // The same as above but now using cache slots
27+
} catch (Exception $e) {
2228
echo $e->getMessage() . "\n";
2329
}
2430

2531
?>
2632
--EXPECT--
27-
Cannot assign array to property Foo::$bar of type int
28-
Cannot assign array to property Foo::$bar of type int
33+
Error in destructor
34+
Error in destructor

Zend/tests/clone_initializer/clone_initializer_error7.phpt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
--TEST--
2-
Test "clone with" with a declared untyped property of an object throwing an exception in the destructor
2+
Test "clone with" with dynamic properties of an object throwing an exception in the destructor
33
--FILE--
44
<?php
55

6+
#[AllowDynamicProperties]
67
class Foo
78
{
8-
public $bar;
9-
109
public function __destruct() {
1110
throw new Exception("Error in destructor");
1211
}
Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,66 @@
11
--TEST--
2-
Test "clone with" with dynamic properties of an object throwing an exception in the destructor
2+
Test "clone with" with invalid property names
33
--FILE--
44
<?php
55

6-
#[AllowDynamicProperties]
7-
class Foo
8-
{
9-
public function __destruct() {
10-
throw new Exception("Error in destructor");
11-
}
6+
try {
7+
clone new stdClass() with {$undefined => 1};
8+
} catch (TypeError $e) {
9+
echo $e->getMessage() . "\n";
1210
}
1311

14-
function returnFoo() {
15-
return new Foo();
12+
try {
13+
clone new stdClass() with {$undefined => 1}; // The same as above but now using cache slots
14+
} catch (TypeError $e) {
15+
echo $e->getMessage() . "\n";
1616
}
1717

1818
try {
19-
clone returnFoo() with {bar: new stdClass()};
20-
} catch (Exception $e) {
19+
clone new stdClass() with {null => 1};
20+
} catch (TypeError $e) {
2121
echo $e->getMessage() . "\n";
2222
}
2323

2424
try {
25-
clone returnFoo() with {bar: new stdClass()}; // The same as above but now using cache slots
26-
} catch (Exception $e) {
25+
clone new stdClass() with {null => 1}; // The same as above but now using cache slots
26+
} catch (TypeError $e) {
27+
echo $e->getMessage() . "\n";
28+
}
29+
30+
try {
31+
clone new stdClass() with {[] => 1};
32+
} catch (TypeError $e) {
33+
echo $e->getMessage() . "\n";
34+
}
35+
36+
try {
37+
clone new stdClass() with {[] => 1}; // The same as above but now using cache slots
38+
} catch (TypeError $e) {
39+
echo $e->getMessage() . "\n";
40+
}
41+
42+
try {
43+
clone new stdClass() with {1 => 1};
44+
} catch (TypeError $e) {
45+
echo $e->getMessage() . "\n";
46+
}
47+
48+
try {
49+
clone new stdClass() with {1 => 1}; // The same as above but now using cache slots
50+
} catch (TypeError $e) {
2751
echo $e->getMessage() . "\n";
2852
}
2953

3054
?>
31-
--EXPECT--
32-
Error in destructor
33-
Error in destructor
55+
--EXPECTF--
56+
Warning: Undefined variable $undefined in %s on line %d
57+
Property name must be of type string, null given
58+
59+
Warning: Undefined variable $undefined in %s on line %d
60+
Property name must be of type string, null given
61+
Property name must be of type string, null given
62+
Property name must be of type string, null given
63+
Property name must be of type string, array given
64+
Property name must be of type string, array given
65+
Property name must be of type string, int given
66+
Property name must be of type string, int given

0 commit comments

Comments
 (0)