[php-src] master: Zend: add readonly reinitable helper (#22718)

NickSdot via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: NickSdot (NickSdot)
Committer: GitHub (web-flow)
Pusher: Girgias
Date: 2026-08-07T18:37:47+01:00

Commit: https://github.com/php/php-src/commit/b704c6cfaad17929563d51f1247849e628129bf4
Raw diff: https://github.com/php/php-src/commit/b704c6cfaad17929563d51f1247849e628129bf4.diff

Zend: add readonly reinitable helper (#22718)

Centralising the logic for {un}locking readonly properties for `clone` and `clone with`.

Changed paths:
  M  UPGRADING.INTERNALS
  M  Zend/zend_objects.c
  M  Zend/zend_objects.h


Diff:

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 6554c752b8af..28cefb1e0afb 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 474157e73d39..03ac5de67c78 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 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);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.