From 6c7ea459ffaa2a18e2d2f8f3f3f1b75cf82e177d Mon Sep 17 00:00:00 2001 From: NickSdot Date: Tue, 14 Jul 2026 01:55:41 +0700 Subject: [PATCH 1/3] Introduced helper to centralise readonly property un/locking --- Zend/zend_objects.c | 18 ++++++++++++++++++ Zend/zend_objects.h | 1 + 2 files changed, 19 insertions(+) diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 474157e73d39..570ef5a4365d 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; diff --git a/Zend/zend_objects.h b/Zend/zend_objects.h index 0930fa043101..8bb6c053b0c7 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); From 3e5dd4b3e4e3f76e0f44eb9b5f1339626f7c9a4a Mon Sep 17 00:00:00 2001 From: NickSdot Date: Tue, 14 Jul 2026 02:26:42 +0700 Subject: [PATCH 2/3] Made clone and clone-with use new reinitable helper --- Zend/zend_objects.c | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 570ef5a4365d..03ac5de67c78 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -224,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)))) { @@ -273,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 { @@ -286,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); } } @@ -303,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); @@ -340,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; From 41991abf6232b7d00427fb68f178f2778e62f652 Mon Sep 17 00:00:00 2001 From: NickSdot Date: Tue, 14 Jul 2026 03:22:49 +0700 Subject: [PATCH 3/3] Added UPGRADING.INTERNALS entry --- UPGRADING.INTERNALS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 462e567f3f36..60151a2c0a6f 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -129,6 +129,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. ======================== 2. Build system changes