Skip to content

Commit 840d53a

Browse files
Allow writing to readonly properties during cloning
1 parent ae95644 commit 840d53a

File tree

3 files changed

+95
-6
lines changed

3 files changed

+95
-6
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
clone can write to readonly properties
3+
--FILE--
4+
<?php
5+
6+
class Counter
7+
{
8+
private static int $counter = 0;
9+
10+
public readonly int $count;
11+
private readonly int $foo;
12+
13+
public function __construct()
14+
{
15+
$this->count = ++self::$counter;
16+
$this->foo = 0;
17+
}
18+
19+
public function count(?int $count = null): static
20+
{
21+
$new = clone $this;
22+
$new->count = $count ?? ++self::$counter;
23+
24+
return $new;
25+
}
26+
27+
public function __clone()
28+
{
29+
if (is_a(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? '', self::class, true)) {
30+
unset($this->count);
31+
} else {
32+
$this->count = ++self::$counter;
33+
}
34+
$this->foo = 1;
35+
}
36+
}
37+
38+
$a = new Counter();
39+
var_dump($a);
40+
41+
var_dump(clone $a);
42+
43+
$b = $a->count();
44+
var_dump($b);
45+
46+
$c = $a->count(123);
47+
var_dump($c);
48+
49+
?>
50+
--EXPECT--
51+
object(Counter)#1 (1) {
52+
["count"]=>
53+
int(1)
54+
}
55+
object(Counter)#2 (1) {
56+
["count"]=>
57+
int(2)
58+
}
59+
object(Counter)#2 (1) {
60+
["count"]=>
61+
int(3)
62+
}
63+
object(Counter)#3 (1) {
64+
["count"]=>
65+
int(123)
66+
}

Zend/zend_object_handlers.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
811811
Z_TRY_ADDREF_P(value);
812812

813813
if (UNEXPECTED(prop_info)) {
814-
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
814+
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && Z_PROP_FLAG_P(variable_ptr) != IS_PROP_UNINIT)) {
815815
Z_TRY_DELREF_P(value);
816816
zend_readonly_property_modification_error(prop_info);
817817
variable_ptr = &EG(error_zval);
@@ -1126,7 +1126,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
11261126
zval *slot = OBJ_PROP(zobj, property_offset);
11271127

11281128
if (Z_TYPE_P(slot) != IS_UNDEF) {
1129-
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY))) {
1129+
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY) && Z_PROP_FLAG_P(slot) != IS_PROP_UNINIT)) {
11301130
zend_readonly_property_unset_error(prop_info->ce, name);
11311131
return;
11321132
}

Zend/zend_objects.c

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,21 +192,30 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce)
192192

193193
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object)
194194
{
195+
zval *src, *dst, *end, *slot;
196+
zend_property_info *prop_info;
197+
195198
if (old_object->ce->default_properties_count) {
196-
zval *src = old_object->properties_table;
197-
zval *dst = new_object->properties_table;
198-
zval *end = src + old_object->ce->default_properties_count;
199+
src = old_object->properties_table;
200+
dst = new_object->properties_table;
201+
end = src + old_object->ce->default_properties_count;
199202

200203
do {
201204
i_zval_ptr_dtor(dst);
202205
ZVAL_COPY_VALUE_PROP(dst, src);
203206
zval_add_ref(dst);
204207
if (UNEXPECTED(Z_ISREF_P(dst)) &&
205208
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(dst)))) {
206-
zend_property_info *prop_info = zend_get_property_info_for_slot(new_object, dst);
209+
prop_info = zend_get_property_info_for_slot(new_object, dst);
207210
if (ZEND_TYPE_IS_SET(prop_info->type)) {
208211
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(dst), prop_info);
209212
}
213+
} else if (UNEXPECTED(old_object->ce->clone)) {
214+
prop_info = zend_get_property_info_for_slot(new_object, dst);
215+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
216+
slot = OBJ_PROP(new_object, prop_info->offset);
217+
Z_PROP_FLAG_P(slot) = IS_PROP_UNINIT;
218+
}
210219
}
211220
src++;
212221
dst++;
@@ -256,6 +265,20 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
256265
if (old_object->ce->clone) {
257266
GC_ADDREF(new_object);
258267
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
268+
if (new_object->ce->default_properties_count) {
269+
dst = new_object->properties_table;
270+
end = dst + new_object->ce->default_properties_count;
271+
do {
272+
prop_info = zend_get_property_info_for_slot(new_object, dst);
273+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
274+
slot = OBJ_PROP(new_object, prop_info->offset);
275+
if (Z_TYPE_P(slot) != IS_UNDEF && Z_PROP_FLAG_P(slot) == IS_PROP_UNINIT) {
276+
Z_PROP_FLAG_P(slot) = 0;
277+
}
278+
}
279+
dst++;
280+
} while (dst != end);
281+
}
259282
OBJ_RELEASE(new_object);
260283
}
261284
}

0 commit comments

Comments
 (0)