Skip to content

Commit 4db8ae5

Browse files
committed
Do not use additional zend_object member
1 parent dd46516 commit 4db8ae5

9 files changed

+136
-42
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Readonly property cannot be reset after cloning when there is no custom clone handler
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function __construct(
8+
public readonly int $bar,
9+
public readonly int $baz
10+
) {}
11+
12+
public function wrongCloneOld()
13+
{
14+
$instance = clone $this;
15+
$this->bar++;
16+
}
17+
18+
public function wrongCloneNew()
19+
{
20+
$instance = clone $this;
21+
$instance->baz++;
22+
}
23+
}
24+
25+
$foo = new Foo(1, 1);
26+
27+
try {
28+
$foo->wrongCloneOld();
29+
} catch (Error $exception) {
30+
echo $exception->getMessage() . "\n";
31+
}
32+
33+
try {
34+
$foo->wrongCloneNew();
35+
} catch (Error $exception) {
36+
echo $exception->getMessage() . "\n";
37+
}
38+
39+
?>
40+
--EXPECT--
41+
Cannot modify readonly property Foo::$bar
42+
Cannot modify readonly property Foo::$baz
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Readonly property cannot be reset after cloning when there is a custom clone handler
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function __construct(
8+
public readonly int $bar,
9+
public readonly int $baz
10+
) {}
11+
12+
public function __clone() {}
13+
14+
public function wrongCloneOld()
15+
{
16+
$instance = clone $this;
17+
$this->bar++;
18+
}
19+
20+
public function wrongCloneNew()
21+
{
22+
$instance = clone $this;
23+
$instance->baz++;
24+
}
25+
}
26+
27+
$foo = new Foo(1, 1);
28+
29+
try {
30+
$foo->wrongCloneOld();
31+
} catch (Error $exception) {
32+
echo $exception->getMessage() . "\n";
33+
}
34+
35+
try {
36+
$foo->wrongCloneNew();
37+
} catch (Error $exception) {
38+
echo $exception->getMessage() . "\n";
39+
}
40+
41+
?>
42+
--EXPECT--
43+
Cannot modify readonly property Foo::$bar
44+
Cannot modify readonly property Foo::$baz

Zend/tests/readonly_props/readonly_clone_success.phpt

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,35 @@ Readonly property can be reset once during cloning
55

66
class Foo {
77
public function __construct(
8-
public readonly int $bar,
9-
public readonly int $baz
8+
public readonly int $bar
109
) {}
1110

1211
public function __clone()
1312
{
1413
$this->bar++;
1514
}
16-
17-
public function wrongClone()
18-
{
19-
$instance = clone $this;
20-
$instance->baz++;
21-
}
2215
}
2316

24-
$foo = new Foo(1, 1);
25-
$foo2 = clone $foo;
26-
var_dump($foo2);
17+
$foo = new Foo(1);
2718

28-
try {
29-
$foo->wrongClone();
30-
} catch (Error $exception) {
31-
echo $exception->getMessage() . "\n";
32-
}
19+
var_dump(clone $foo);
3320

34-
$foo3 = clone $foo2;
21+
$foo2 = clone $foo;
3522
var_dump($foo2);
3623

24+
var_dump(clone $foo2);
25+
3726
?>
38-
--EXPECT--
39-
object(Foo)#2 (2) {
27+
--EXPECTF--
28+
object(Foo)#%d (%d) {
4029
["bar"]=>
4130
int(2)
42-
["baz"]=>
43-
int(0)
4431
}
45-
Cannot modify readonly property Foo::$baz
46-
object(Foo)#2 (2) {
32+
object(Foo)#%d (%d) {
4733
["bar"]=>
4834
int(2)
49-
["baz"]=>
50-
int(0)
35+
}
36+
object(Foo)#%d (%d) {
37+
["bar"]=>
38+
int(3)
5139
}

Zend/zend_API.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,6 @@ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zen
16931693

16941694
if (class_type->create_object == NULL) {
16951695
zend_object *obj = zend_objects_new(class_type);
1696-
obj->in_clone = 0;
16971696

16981697
ZVAL_OBJ(arg, obj);
16991698
if (properties) {

Zend/zend_enum.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case
4444

4545
ZVAL_STR_COPY(OBJ_PROP_NUM(zobj, 0), case_name);
4646
if (backing_value_zv != NULL) {
47-
ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
47+
zval *prop = OBJ_PROP_NUM(zobj, 1);
48+
49+
ZVAL_COPY(prop, backing_value_zv);
50+
Z_PROP_FLAG_P(prop) = 0;
4851
}
4952

5053
return zobj;
@@ -180,7 +183,7 @@ void zend_enum_add_interfaces(zend_class_entry *ce)
180183

181184
if (ce->enum_backing_type != IS_UNDEF) {
182185
ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name);
183-
ce->interface_names[num_interfaces_before + 1].lc_name = zend_string_init("backedenum", sizeof("backedenum") - 1, 0);
186+
ce->interface_names[num_interfaces_before + 1].lc_name = zend_string_init("backedenum", sizeof("backedenum") - 1, 0);
184187
}
185188

186189
ce->default_object_handlers = &enum_handlers;

Zend/zend_object_handlers.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
616616
* to make sure no actual modification is possible. */
617617
ZVAL_COPY(rv, retval);
618618
retval = rv;
619+
} else if (Z_PROP_FLAG_P(retval) & IS_PROP_REINITABLE) {
620+
Z_PROP_FLAG_P(retval) &= ~IS_PROP_REINITABLE;
619621
} else {
620622
zend_readonly_property_modification_error(prop_info);
621623
retval = &EG(uninitialized_zval);
@@ -812,8 +814,8 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
812814

813815
if (UNEXPECTED(prop_info)) {
814816
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
815-
if (zobj->in_clone && !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINIT)) {
816-
Z_PROP_FLAG_P(variable_ptr) |= IS_PROP_REINIT;
817+
if (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINITABLE) {
818+
Z_PROP_FLAG_P(variable_ptr) &= ~IS_PROP_REINITABLE;
817819
} else {
818820
Z_TRY_DELREF_P(value);
819821
zend_readonly_property_modification_error(prop_info);
@@ -1131,8 +1133,12 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
11311133

11321134
if (Z_TYPE_P(slot) != IS_UNDEF) {
11331135
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY))) {
1134-
zend_readonly_property_unset_error(prop_info->ce, name);
1135-
return;
1136+
if (Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE) {
1137+
Z_PROP_FLAG_P(slot) &= ~IS_PROP_REINITABLE;
1138+
} else {
1139+
zend_readonly_property_unset_error(prop_info->ce, name);
1140+
return;
1141+
}
11361142
}
11371143
if (UNEXPECTED(Z_ISREF_P(slot)) &&
11381144
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) {

Zend/zend_objects.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c
3333
object->ce = ce;
3434
object->handlers = ce->default_object_handlers;
3535
object->properties = NULL;
36-
object->in_clone = 0;
3736
zend_objects_store_put(object);
3837
if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
3938
ZVAL_UNDEF(object->properties_table + object->ce->default_properties_count);
@@ -193,16 +192,21 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce)
193192

194193
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object)
195194
{
195+
bool has_clone_method = old_object->ce->clone != NULL;
196+
196197
if (old_object->ce->default_properties_count) {
197198
zval *src = old_object->properties_table;
198199
zval *dst = new_object->properties_table;
199200
zval *end = src + old_object->ce->default_properties_count;
200201

201202
do {
202-
Z_PROP_FLAG_P(src) &= ~IS_PROP_REINIT;
203203
i_zval_ptr_dtor(dst);
204204
ZVAL_COPY_VALUE_PROP(dst, src);
205205
zval_add_ref(dst);
206+
if (has_clone_method) {
207+
Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE;
208+
}
209+
206210
if (UNEXPECTED(Z_ISREF_P(dst)) &&
207211
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(dst)))) {
208212
zend_property_info *prop_info = zend_get_property_info_for_slot(new_object, dst);
@@ -213,7 +217,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
213217
src++;
214218
dst++;
215219
} while (src != end);
216-
} else if (old_object->properties && !old_object->ce->clone) {
220+
} else if (old_object->properties && !has_clone_method) {
217221
/* fast copy */
218222
if (EXPECTED(old_object->handlers == &std_object_handlers)) {
219223
if (EXPECTED(!(GC_FLAGS(old_object->properties) & IS_ARRAY_IMMUTABLE))) {
@@ -247,6 +251,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
247251
ZVAL_COPY_VALUE(&new_prop, prop);
248252
zval_add_ref(&new_prop);
249253
}
254+
if (has_clone_method) {
255+
Z_PROP_FLAG_P(&new_prop) |= IS_PROP_REINITABLE;
256+
}
250257
if (EXPECTED(key)) {
251258
_zend_hash_append(new_object->properties, key, &new_prop);
252259
} else {
@@ -255,11 +262,17 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
255262
} ZEND_HASH_FOREACH_END();
256263
}
257264

258-
if (old_object->ce->clone) {
265+
if (has_clone_method) {
266+
zval *prop;
267+
259268
GC_ADDREF(new_object);
260-
new_object->in_clone = 1;
261269
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
262-
new_object->in_clone = 0;
270+
271+
rebuild_object_properties(new_object);
272+
ZEND_HASH_FOREACH_PTR(new_object->properties, prop) {
273+
Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE;
274+
} ZEND_HASH_FOREACH_END();
275+
263276
OBJ_RELEASE(new_object);
264277
}
265278
}

Zend/zend_types.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,6 @@ struct _zend_object {
503503
const zend_object_handlers *handlers;
504504
HashTable *properties;
505505
zval properties_table[1];
506-
bool in_clone;
507506
};
508507

509508
struct _zend_resource {
@@ -1439,14 +1438,14 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) {
14391438
* the Z_EXTRA space when copying property default values etc. We define separate
14401439
* macros for this purpose, so this workaround is easier to remove in the future. */
14411440
#define IS_PROP_UNINIT 1
1442-
#define IS_PROP_REINIT 2
1441+
#define IS_PROP_REINITABLE 2
14431442
#define Z_PROP_FLAG_P(z) Z_EXTRA_P(z)
14441443
#define ZVAL_COPY_VALUE_PROP(z, v) \
14451444
do { *(z) = *(v); } while (0)
14461445
#define ZVAL_COPY_PROP(z, v) \
1447-
do { ZVAL_COPY(z, v); Z_PROP_FLAG_P(z) = Z_PROP_FLAG_P(v) & IS_PROP_UNINIT; } while (0)
1446+
do { ZVAL_COPY(z, v); Z_PROP_FLAG_P(z) = Z_PROP_FLAG_P(v); } while (0)
14481447
#define ZVAL_COPY_OR_DUP_PROP(z, v) \
1449-
do { ZVAL_COPY_OR_DUP(z, v); Z_PROP_FLAG_P(z) = Z_PROP_FLAG_P(v) & IS_PROP_UNINIT; } while (0)
1448+
do { ZVAL_COPY_OR_DUP(z, v); Z_PROP_FLAG_P(z) = Z_PROP_FLAG_P(v); } while (0)
14501449

14511450

14521451
#endif /* ZEND_TYPES_H */

0 commit comments

Comments
 (0)