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);