Skip to content

RFC: Clone with v2 #18747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Next Next commit
zend_objects: Add clone_obj_with object handler
  • Loading branch information
TimWolla committed Jun 30, 2025
commit c3bb74c38a6a6b1bb4ad230879c717b4b2005b83
1 change: 1 addition & 0 deletions Zend/zend_iterators.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static const zend_object_handlers iterator_object_handlers = {
iter_wrapper_free,
iter_wrapper_dtor,
NULL, /* clone_obj */
NULL, /* clone_obj_with */
NULL, /* prop read */
NULL, /* prop write */
NULL, /* read dim */
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_lazy_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object)

/* Initialize object and clone it. For proxies, we clone both the proxy and its
* real instance, and we don't call __clone() on the proxy. */
zend_object *zend_lazy_object_clone(zend_object *old_obj)
zend_object *zend_lazy_object_clone(zend_object *old_obj, zend_class_entry *scope, const HashTable *properties)
{
ZEND_ASSERT(zend_object_is_lazy(old_obj));

Expand All @@ -724,7 +724,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj)
}

if (!zend_object_is_lazy_proxy(old_obj)) {
return zend_objects_clone_obj(old_obj);
return zend_objects_clone_obj_with(old_obj, scope, properties);
}

zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj);
Expand All @@ -748,7 +748,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj)

zend_lazy_object_info *new_info = emalloc(sizeof(*info));
*new_info = *info;
new_info->u.instance = zend_objects_clone_obj(info->u.instance);
new_info->u.instance = zend_objects_clone_obj_with(info->u.instance, scope, properties);

zend_lazy_object_set_info(new_proxy, new_info);

Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_lazy_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ zend_object *zend_lazy_object_get_instance(zend_object *obj);
zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj);
void zend_lazy_object_del_info(zend_object *obj);
ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object);
zend_object *zend_lazy_object_clone(zend_object *old_obj);
zend_object *zend_lazy_object_clone(zend_object *old_obj, zend_class_entry *scope, const HashTable *properties);
HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp);
HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n);
bool zend_lazy_object_decr_lazy_props(zend_object *obj);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,7 @@ ZEND_API const zend_object_handlers std_object_handlers = {
zend_object_std_dtor, /* free_obj */
zend_objects_destroy_object, /* dtor_obj */
zend_objects_clone_obj, /* clone_obj */
zend_objects_clone_obj_with, /* clone_obj_with */

zend_std_read_property, /* read_property */
zend_std_write_property, /* write_property */
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_object_handlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ typedef void (*zend_object_free_obj_t)(zend_object *object);
typedef void (*zend_object_dtor_obj_t)(zend_object *object);

typedef zend_object* (*zend_object_clone_obj_t)(zend_object *object);
typedef zend_object* (*zend_object_clone_obj_with_t)(zend_object *object, zend_class_entry *scope, const HashTable *properties);

/* Get class name for display in var_dump and other debugging functions.
* Must be defined and must return a non-NULL value. */
Expand Down Expand Up @@ -209,6 +210,7 @@ struct _zend_object_handlers {
zend_object_free_obj_t free_obj; /* required */
zend_object_dtor_obj_t dtor_obj; /* required */
zend_object_clone_obj_t clone_obj; /* optional */
zend_object_clone_obj_with_t clone_obj_with; /* optional */
zend_object_read_property_t read_property; /* required */
zend_object_write_property_t write_property; /* required */
zend_object_read_dimension_t read_dimension; /* required */
Expand Down
68 changes: 58 additions & 10 deletions Zend/zend_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce)
return object;
}

ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object)
ZEND_API void ZEND_FASTCALL zend_objects_clone_members_ex(zend_object *new_object, zend_object *old_object, zend_class_entry *scope, const HashTable *properties)
{
bool has_clone_method = old_object->ce->clone != NULL;
bool might_update_properties = old_object->ce->clone != NULL || zend_hash_num_elements(properties) > 0;

if (old_object->ce->default_properties_count) {
zval *src = old_object->properties_table;
Expand All @@ -226,7 +226,7 @@ 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) {
if (might_update_properties) {
/* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE;
}
Expand All @@ -241,7 +241,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
src++;
dst++;
} while (src != end);
} else if (old_object->properties && !has_clone_method) {
} else if (old_object->properties && !might_update_properties) {
/* fast copy */
if (EXPECTED(old_object->handlers == &std_object_handlers)) {
if (EXPECTED(!(GC_FLAGS(old_object->properties) & IS_ARRAY_IMMUTABLE))) {
Expand Down Expand Up @@ -275,7 +275,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) {
if (might_update_properties) {
/* 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;
}
Expand All @@ -287,9 +287,31 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
} ZEND_HASH_FOREACH_END();
}

if (has_clone_method) {
if (might_update_properties) {
GC_ADDREF(new_object);
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
if (old_object->ce->clone) {
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
}

if (EXPECTED(!EG(exception)) && zend_hash_num_elements(properties) > 0) {
zend_ulong num_key;
zend_string *key;
zval *val;
ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, val) {
if (UNEXPECTED(key == NULL)) {
key = zend_long_to_str(num_key);
zend_update_property_ex(scope, new_object, key, val);
zend_string_release_ex(key, false);
} else {
zend_update_property_ex(scope, new_object, key, val);
}

if (UNEXPECTED(EG(exception))) {
break;
}
} ZEND_HASH_FOREACH_END();
}


if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) {
for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
Expand All @@ -303,12 +325,33 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
}
}

ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object)
{
ZEND_ASSERT(old_object->ce == new_object->ce);

zend_objects_clone_members_ex(new_object, old_object, old_object->ce, &zend_empty_array);
}

ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, zend_class_entry *scope, const HashTable *properties)
{
zend_object *new_object;

/* Compatibility with code that only overrides clone_obj. */
if (UNEXPECTED(old_object->handlers->clone_obj != zend_objects_clone_obj)) {
if (!old_object->handlers->clone_obj) {
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(old_object->ce->name));
return NULL;
}
if (zend_hash_num_elements(properties) > 0) {
zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(old_object->ce->name));
return NULL;
} else {
return old_object->handlers->clone_obj(old_object);
}
}

if (UNEXPECTED(zend_object_is_lazy(old_object))) {
return zend_lazy_object_clone(old_object);
return zend_lazy_object_clone(old_object, scope, properties);
}

/* assume that create isn't overwritten, so when clone depends on the
Expand All @@ -325,7 +368,12 @@ ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
} while (p != end);
}

zend_objects_clone_members(new_object, old_object);
zend_objects_clone_members_ex(new_object, old_object, scope, properties);

return new_object;
}

ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
{
return zend_objects_clone_obj_with(old_object, old_object->ce, &zend_empty_array);
}
1 change: 1 addition & 0 deletions Zend/zend_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
ZEND_API void zend_object_std_dtor(zend_object *object);
ZEND_API void zend_objects_destroy_object(zend_object *object);
ZEND_API zend_object *zend_objects_clone_obj(zend_object *object);
ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *object, zend_class_entry *scope, const HashTable *properties);

void zend_object_dtor_dynamic_properties(zend_object *object);
void zend_object_dtor_property(zend_object *object, zval *p);
Expand Down