Commit e2c5b3c684f for php.net
commit e2c5b3c684fddb7c22a47f6e518435176ec715fb
Author: NickSdot <32384907+NickSdot@users.noreply.github.com>
Date: Mon Jul 13 13:52:27 2026 +0700
zend_objects: Readonly properties must be re-locked after clone-with (#22654)
diff --git a/NEWS b/NEWS
index 46f23c3aa6d..ed10d3bf259 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PHP NEWS
containing NUL). (iliaal)
. Fixed bug GH-22206 (missing return in global register detection).
(P3p111n0)
+ . Lock unmodified readonly properties for modification after clone-with.
+ (NickSdot)
- Calendar:
. Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with
diff --git a/Zend/tests/clone/clone_with_014.phpt b/Zend/tests/clone/clone_with_014.phpt
new file mode 100644
index 00000000000..bfbee40e163
--- /dev/null
+++ b/Zend/tests/clone/clone_with_014.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Properties are still readonly after clone-with
+--FILE--
+<?php
+
+readonly class Test {
+ public public(set) int $a;
+ public public(set) int $b;
+
+ public function __construct() {
+ $this->a = 1;
+ $this->b = 2;
+ }
+}
+
+$test = clone(new Test(), ['a' => 3]);
+var_dump($test);
+
+try {
+ $test->b = 4;
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+var_dump($test);
+
+?>
+--EXPECT--
+object(Test)#2 (2) {
+ ["a"]=>
+ int(3)
+ ["b"]=>
+ int(2)
+}
+Error: Cannot modify readonly property Test::$b
+object(Test)#2 (2) {
+ ["a"]=>
+ int(3)
+ ["b"]=>
+ int(2)
+}
diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c
index 6f6a8263894..d92d54d3f4a 100644
--- a/Zend/zend_objects.c
+++ b/Zend/zend_objects.c
@@ -322,6 +322,14 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const
} ZEND_HASH_FOREACH_END();
EG(fake_scope) = old_scope;
+
+ /* Lock readonly properties once more. */
+ if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) {
+ for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
+ zval* prop = OBJ_PROP_NUM(new_object, i);
+ Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE;
+ }
+ }
}
return new_object;