Skip to content

Commit f36a12e

Browse files
kocsismatearnaud-lb
andcommitted
Allow readonly properties to be reset once during cloning
Co-Authored-By: Arnaud Le Blanc <arnaud.lb@gmail.com>
1 parent 3e6d49e commit f36a12e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3387
-645
lines changed

Zend/Optimizer/compact_literals.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
558558
break;
559559
case ZEND_ASSIGN_OBJ:
560560
case ZEND_ASSIGN_OBJ_REF:
561+
case ZEND_CLONE_INIT_PROP:
561562
case ZEND_FETCH_OBJ_R:
562563
case ZEND_FETCH_OBJ_W:
563564
case ZEND_FETCH_OBJ_RW:

Zend/Optimizer/zend_inference.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2310,7 +2310,8 @@ static zend_always_inline zend_result _zend_update_type_info(
23102310
|| opline->opcode == ZEND_ASSIGN_OBJ_OP
23112311
|| opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
23122312
|| opline->opcode == ZEND_ASSIGN_DIM
2313-
|| opline->opcode == ZEND_ASSIGN_OBJ)
2313+
|| opline->opcode == ZEND_ASSIGN_OBJ
2314+
|| opline->opcode == ZEND_CLONE_INIT_PROP)
23142315
&& !(OP1_DATA_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS)) /*&& 0*/)) {
23152316
tmp = 0;
23162317
if (ssa_op->result_def >= 0 && !(ssa_var_info[ssa_op->result_def].type & MAY_BE_REF)) {
@@ -2326,7 +2327,8 @@ static zend_always_inline zend_result _zend_update_type_info(
23262327
|| opline->opcode == ZEND_ASSIGN_OBJ_OP
23272328
|| opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
23282329
|| opline->opcode == ZEND_ASSIGN_DIM
2329-
|| opline->opcode == ZEND_ASSIGN_OBJ) {
2330+
|| opline->opcode == ZEND_ASSIGN_OBJ
2331+
|| opline->opcode == ZEND_CLONE_INIT_PROP) {
23302332
if ((ssa_op+1)->op1_def >= 0 && !(ssa_var_info[(ssa_op+1)->op1_def].type & MAY_BE_REF)) {
23312333
UPDATE_SSA_TYPE(tmp, (ssa_op+1)->op1_def);
23322334
}
@@ -2756,6 +2758,7 @@ static zend_always_inline zend_result _zend_update_type_info(
27562758
}
27572759
break;
27582760
case ZEND_ASSIGN_OBJ:
2761+
case ZEND_CLONE_INIT_PROP:
27592762
if (opline->op1_type == IS_CV) {
27602763
tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
27612764
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
@@ -3385,6 +3388,7 @@ static zend_always_inline zend_result _zend_update_type_info(
33853388
case ZEND_ASSIGN_OBJ:
33863389
case ZEND_ASSIGN_OBJ_OP:
33873390
case ZEND_ASSIGN_OBJ_REF:
3391+
case ZEND_CLONE_INIT_PROP:
33883392
case ZEND_PRE_INC_OBJ:
33893393
case ZEND_PRE_DEC_OBJ:
33903394
case ZEND_POST_INC_OBJ:
@@ -4809,6 +4813,41 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
48094813
}
48104814
}
48114815
return 1;
4816+
case ZEND_CLONE_INIT_PROP:
4817+
if (t1 & (MAY_BE_ANY-MAY_BE_OBJECT)) {
4818+
return 1;
4819+
}
4820+
if (ssa_op->op1_use) {
4821+
zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
4822+
zend_class_entry *ce = var_info->ce;
4823+
4824+
if (var_info->is_instanceof ||
4825+
!ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
4826+
return 1;
4827+
}
4828+
4829+
if (opline->op2_type != IS_CONST) {
4830+
return 1;
4831+
}
4832+
4833+
zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op2));
4834+
if (ZSTR_LEN(prop_name) > 0 && ZSTR_VAL(prop_name)[0] == '\0') {
4835+
return 1;
4836+
}
4837+
4838+
zend_property_info *prop_info =
4839+
zend_hash_find_ptr(&ce->properties_info, prop_name);
4840+
if (prop_info) {
4841+
if (ZEND_TYPE_IS_SET(prop_info->type)) {
4842+
return 1;
4843+
}
4844+
return !(prop_info->flags & ZEND_ACC_PUBLIC)
4845+
&& prop_info->ce != op_array->scope;
4846+
} else {
4847+
return !(ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
4848+
}
4849+
}
4850+
return 1;
48124851
case ZEND_ROPE_INIT:
48134852
case ZEND_ROPE_ADD:
48144853
case ZEND_ROPE_END:

Zend/Optimizer/zend_optimizer.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ bool zend_optimizer_update_op2_const(zend_op_array *op_array,
533533
break;
534534
case ZEND_ASSIGN_OBJ:
535535
case ZEND_ASSIGN_OBJ_REF:
536+
case ZEND_CLONE_INIT_PROP:
536537
case ZEND_FETCH_OBJ_R:
537538
case ZEND_FETCH_OBJ_W:
538539
case ZEND_FETCH_OBJ_RW:

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)];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list is required when "with" is given
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with;
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected token ";", expecting "{" in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list cannot contain invalid identifiers when using the : syntax
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {1: "value"};
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected token ":" in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Test that the clone property initializer respects visibility
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
private $bar;
9+
}
10+
11+
$foo = new Foo();
12+
13+
try {
14+
$foo = clone $foo with {bar: 1};
15+
} catch (Error $exception) {
16+
echo $exception->getMessage() . "\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Cannot access private property Foo::$bar
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Test that readonly properties cannot be assigned in "cloned with" multiple times
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
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+
}
19+
}
20+
21+
$foo = new Foo(0);
22+
23+
try {
24+
$foo->with();
25+
} catch (Error $exception) {
26+
echo $exception->getMessage() . "\n";
27+
}
28+
29+
?>
30+
--EXPECT--
31+
Cannot modify readonly property Foo::$bar
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Test TypeError during "clone with"
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
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+
}
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";
23+
}
24+
25+
?>
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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Test "clone with" with a declared untyped property of an object throwing an exception in the destructor
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar;
9+
10+
public function __destruct() {
11+
throw new Exception("Error in destructor");
12+
}
13+
}
14+
15+
function returnFoo() {
16+
return new Foo();
17+
}
18+
19+
try {
20+
clone returnFoo() with {bar: new stdClass()};
21+
} catch (Exception $e) {
22+
echo $e->getMessage() . "\n";
23+
}
24+
25+
try {
26+
clone returnFoo() with {bar: new stdClass()}; // The same as above but now using cache slots
27+
} catch (Exception $e) {
28+
echo $e->getMessage() . "\n";
29+
}
30+
31+
?>
32+
--EXPECT--
33+
Error in destructor
34+
Error in destructor
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Test "clone with" with dynamic properties of an object throwing an exception in the destructor
3+
--FILE--
4+
<?php
5+
6+
#[AllowDynamicProperties]
7+
class Foo
8+
{
9+
public function __destruct() {
10+
throw new Exception("Error in destructor");
11+
}
12+
}
13+
14+
function returnFoo() {
15+
return new Foo();
16+
}
17+
18+
try {
19+
clone returnFoo() with {bar: new stdClass()};
20+
} catch (Exception $e) {
21+
echo $e->getMessage() . "\n";
22+
}
23+
24+
try {
25+
clone returnFoo() with {bar: new stdClass()}; // The same as above but now using cache slots
26+
} catch (Exception $e) {
27+
echo $e->getMessage() . "\n";
28+
}
29+
30+
?>
31+
--EXPECT--
32+
Error in destructor
33+
Error in destructor
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
Test "clone with" with invalid property names
3+
--FILE--
4+
<?php
5+
6+
try {
7+
clone new stdClass() with {$undefined => 1};
8+
} catch (TypeError $e) {
9+
echo $e->getMessage() . "\n";
10+
}
11+
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";
16+
}
17+
18+
try {
19+
clone new stdClass() with {null => 1};
20+
} catch (TypeError $e) {
21+
echo $e->getMessage() . "\n";
22+
}
23+
24+
try {
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) {
51+
echo $e->getMessage() . "\n";
52+
}
53+
54+
?>
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)