Commit b704c6cfaad for php.net
commit b704c6cfaad17929563d51f1247849e628129bf4
Author: NickSdot <32384907+NickSdot@users.noreply.github.com>
Date: Sat Aug 8 00:37:47 2026 +0700
Zend: add readonly reinitable helper (#22718)
Centralising the logic for {un}locking readonly properties for `clone` and `clone with`.
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 6554c752b8a..28cefb1e0af 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -197,6 +197,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES
zend_reflection_property_set_raw_value() to expose the functionality of
ReflectionProperty::setRawValueWithoutLazyInitialization() and
ReflectionProperty::setRawValue() to C extensions.
+ . Added zend_object_set_properties_reinitable() to centralise temporarily
+ allowing reinitialisation of initialised readonly properties during
+ controlled operations such as cloning and unserialisation.
. Added zend_argument_error_ex(), zend_argument_type_error_ex(),
zend_argument_value_error_ex().
. Added zend_ast_dup().
diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c
index 474157e73d3..03ac5de67c7 100644
--- a/Zend/zend_objects.c
+++ b/Zend/zend_objects.c
@@ -193,6 +193,24 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce)
return object;
}
+ZEND_API void ZEND_FASTCALL zend_object_set_properties_reinitable(zend_object *object, bool reinitable)
+{
+ if (!ZEND_CLASS_HAS_READONLY_PROPS(object->ce)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < object->ce->default_properties_count; i++) {
+ zval *prop = OBJ_PROP_NUM(object, i);
+ if (reinitable) {
+ if (!Z_ISUNDEF_P(prop)) {
+ Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE;
+ }
+ } else {
+ Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE;
+ }
+ }
+}
+
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, const zend_object *old_object)
{
bool has_clone_method = old_object->ce->clone != NULL;
@@ -206,10 +224,6 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
i_zval_ptr_dtor(dst);
ZVAL_COPY_VALUE_PROP(dst, src);
zval_add_ref(dst);
- if (has_clone_method) {
- /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
- Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE;
- }
if (UNEXPECTED(Z_ISREF_P(dst)) &&
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(dst)))) {
@@ -255,10 +269,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
ZVAL_COPY_VALUE(&new_prop, prop);
zval_add_ref(&new_prop);
}
- if (has_clone_method) {
- /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
- Z_PROP_FLAG_P(&new_prop) |= IS_PROP_REINITABLE;
- }
+
if (EXPECTED(key)) {
_zend_hash_append(new_object->properties, key, &new_prop);
} else {
@@ -268,15 +279,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
}
if (has_clone_method) {
+ zend_object_set_properties_reinitable(new_object, /* reinitable */ true);
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
-
- 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);
- /* Unconditionally remove the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
- Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE;
- }
- }
+ zend_object_set_properties_reinitable(new_object, /* reinitable */ false);
}
}
@@ -285,13 +290,8 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const
zend_object *new_object = old_object->handlers->clone_obj(old_object);
if (EXPECTED(!EG(exception))) {
- /* Unlock 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;
- }
- }
+
+ zend_object_set_properties_reinitable(new_object, /* reinitable */ true);
const zend_class_entry *old_scope = EG(fake_scope);
@@ -322,13 +322,7 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const
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;
- }
- }
+ zend_object_set_properties_reinitable(new_object, /* reinitable */ false);
}
return new_object;
diff --git a/Zend/zend_objects.h b/Zend/zend_objects.h
index 0930fa04310..8bb6c053b0c 100644
--- a/Zend/zend_objects.h
+++ b/Zend/zend_objects.h
@@ -25,6 +25,7 @@ BEGIN_EXTERN_C()
ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce);
ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce);
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, const zend_object *old_object);
+ZEND_API void ZEND_FASTCALL zend_object_set_properties_reinitable(zend_object *object, bool reinitable);
ZEND_API void zend_object_std_dtor(zend_object *object);
ZEND_API void zend_objects_destroy_object(zend_object *object);