Skip to content

Commit 75b0d56

Browse files
Allow writing to readonly properties during cloning
1 parent ae95644 commit 75b0d56

File tree

3 files changed

+92
-6
lines changed

3 files changed

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

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)