Skip to content

Commit 26ae13b

Browse files
committed
Zend: Implement the default clone_with as clone + manual property adjustments
1 parent 6e65275 commit 26ae13b

File tree

8 files changed

+80
-150
lines changed

8 files changed

+80
-150
lines changed

Zend/tests/clone/clone_with_010.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ try {
1818
?>
1919
--EXPECT--
2020
Error: Trying to clone an uncloneable object of class Random\Engine\Secure
21-
Error: Cloning objects of class Random\Engine\Xoshiro256StarStar with updated properties is not supported
21+
Error: Cannot create dynamic property Random\Engine\Xoshiro256StarStar::$with

Zend/zend_builtin_functions.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ ZEND_FUNCTION(clone)
9898
}
9999

100100
zend_object *cloned;
101-
if (zobj->handlers->clone_obj_with) {
102-
cloned = zobj->handlers->clone_obj_with(zobj, scope, with);
103-
} else {
104-
if (UNEXPECTED(zend_hash_num_elements(with) > 0)) {
101+
if (zend_hash_num_elements(with) > 0) {
102+
if (UNEXPECTED(!zobj->handlers->clone_obj_with)) {
105103
zend_throw_error(NULL, "Cloning objects of class %s with updated properties is not supported", ZSTR_VAL(ce->name));
106104
RETURN_THROWS();
107105
}
106+
107+
cloned = zobj->handlers->clone_obj_with(zobj, scope, with);
108+
} else {
108109
cloned = zobj->handlers->clone_obj(zobj);
109110
}
110111

Zend/zend_lazy_objects.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object)
709709

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

@@ -724,7 +724,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj, const zend_class_entry
724724
}
725725

726726
if (!zend_object_is_lazy_proxy(old_obj)) {
727-
return zend_objects_clone_obj_with(old_obj, scope, properties);
727+
return zend_objects_clone_obj(old_obj);
728728
}
729729

730730
zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj);
@@ -748,7 +748,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj, const zend_class_entry
748748

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

753753
zend_lazy_object_set_info(new_proxy, new_info);
754754

Zend/zend_lazy_objects.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ zend_object *zend_lazy_object_get_instance(zend_object *obj);
7171
zend_lazy_object_flags_t zend_lazy_object_get_flags(const zend_object *obj);
7272
void zend_lazy_object_del_info(const zend_object *obj);
7373
ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object);
74-
zend_object *zend_lazy_object_clone(zend_object *old_obj, const zend_class_entry *scope, const HashTable *properties);
74+
zend_object *zend_lazy_object_clone(zend_object *old_obj);
7575
HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp);
7676
HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n);
7777
bool zend_lazy_object_decr_lazy_props(const zend_object *obj);

Zend/zend_objects.c

Lines changed: 50 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce)
189189
return object;
190190
}
191191

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

196196
if (old_object->ce->default_properties_count) {
197197
zval *src = old_object->properties_table;
@@ -202,7 +202,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_obj
202202
i_zval_ptr_dtor(dst);
203203
ZVAL_COPY_VALUE_PROP(dst, src);
204204
zval_add_ref(dst);
205-
if (might_update_properties) {
205+
if (has_clone_method) {
206206
/* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
207207
Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE;
208208
}
@@ -217,7 +217,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_obj
217217
src++;
218218
dst++;
219219
} while (src != end);
220-
} else if (old_object->properties && !might_update_properties) {
220+
} else if (old_object->properties && !has_clone_method) {
221221
/* fast copy */
222222
if (EXPECTED(old_object->handlers == &std_object_handlers)) {
223223
if (EXPECTED(!(GC_FLAGS(old_object->properties) & IS_ARRAY_IMMUTABLE))) {
@@ -251,7 +251,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_obj
251251
ZVAL_COPY_VALUE(&new_prop, prop);
252252
zval_add_ref(&new_prop);
253253
}
254-
if (might_update_properties) {
254+
if (has_clone_method) {
255255
/* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */
256256
Z_PROP_FLAG_P(&new_prop) |= IS_PROP_REINITABLE;
257257
}
@@ -263,45 +263,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_obj
263263
} ZEND_HASH_FOREACH_END();
264264
}
265265

266-
if (might_update_properties) {
266+
if (has_clone_method) {
267267
GC_ADDREF(new_object);
268-
if (old_object->ce->clone) {
269-
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
270-
}
271-
272-
if (EXPECTED(!EG(exception)) && zend_hash_num_elements(properties) > 0) {
273-
/* Unlock readonly properties once more. */
274-
if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce) && old_object->ce->clone) {
275-
for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
276-
zval* prop = OBJ_PROP_NUM(new_object, i);
277-
Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE;
278-
}
279-
}
280-
281-
const zend_class_entry *old_scope = EG(fake_scope);
282-
283-
EG(fake_scope) = scope;
284-
285-
zend_ulong num_key;
286-
zend_string *key;
287-
zval *val;
288-
ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, val) {
289-
if (UNEXPECTED(key == NULL)) {
290-
key = zend_long_to_str(num_key);
291-
new_object->handlers->write_property(new_object, key, val, NULL);
292-
zend_string_release_ex(key, false);
293-
} else {
294-
new_object->handlers->write_property(new_object, key, val, NULL);
295-
}
296-
297-
if (UNEXPECTED(EG(exception))) {
298-
break;
299-
}
300-
} ZEND_HASH_FOREACH_END();
301-
302-
EG(fake_scope) = old_scope;
303-
}
304-
268+
zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL);
305269

306270
if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) {
307271
for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
@@ -315,34 +279,58 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_obj
315279
}
316280
}
317281

318-
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object)
319-
{
320-
ZEND_ASSERT(old_object->ce == new_object->ce);
321-
322-
zend_objects_clone_members_with(new_object, old_object, NULL, &zend_empty_array);
323-
}
324-
325282
ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const zend_class_entry *scope, const HashTable *properties)
326283
{
327284
zend_object *new_object;
328285

329-
/* Compatibility with code that only overrides clone_obj. */
330-
if (UNEXPECTED(old_object->handlers->clone_obj != zend_objects_clone_obj)) {
331-
if (!old_object->handlers->clone_obj) {
332-
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(old_object->ce->name));
333-
return NULL;
334-
}
286+
new_object = old_object->handlers->clone_obj(old_object);
287+
288+
if (EXPECTED(!EG(exception)) && zend_hash_num_elements(properties) > 0) {
289+
GC_ADDREF(new_object);
335290

336-
if (zend_hash_num_elements(properties) > 0) {
337-
zend_throw_error(NULL, "Cloning objects of class %s with updated properties is not supported", ZSTR_VAL(old_object->ce->name));
338-
return NULL;
291+
/* Unlock readonly properties once more. */
292+
if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) {
293+
for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
294+
zval* prop = OBJ_PROP_NUM(new_object, i);
295+
Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE;
296+
}
339297
}
340298

341-
return old_object->handlers->clone_obj(old_object);
299+
const zend_class_entry *old_scope = EG(fake_scope);
300+
301+
EG(fake_scope) = scope;
302+
303+
zend_ulong num_key;
304+
zend_string *key;
305+
zval *val;
306+
ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, val) {
307+
if (UNEXPECTED(key == NULL)) {
308+
key = zend_long_to_str(num_key);
309+
new_object->handlers->write_property(new_object, key, val, NULL);
310+
zend_string_release_ex(key, false);
311+
} else {
312+
new_object->handlers->write_property(new_object, key, val, NULL);
313+
}
314+
315+
if (UNEXPECTED(EG(exception))) {
316+
break;
317+
}
318+
} ZEND_HASH_FOREACH_END();
319+
320+
EG(fake_scope) = old_scope;
321+
322+
OBJ_RELEASE(new_object);
342323
}
343324

325+
return new_object;
326+
}
327+
328+
ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
329+
{
330+
zend_object *new_object;
331+
344332
if (UNEXPECTED(zend_object_is_lazy(old_object))) {
345-
return zend_lazy_object_clone(old_object, scope, properties);
333+
return zend_lazy_object_clone(old_object);
346334
}
347335

348336
/* assume that create isn't overwritten, so when clone depends on the
@@ -359,12 +347,7 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const
359347
} while (p != end);
360348
}
361349

362-
zend_objects_clone_members_with(new_object, old_object, scope, properties);
350+
zend_objects_clone_members(new_object, old_object);
363351

364352
return new_object;
365353
}
366-
367-
ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
368-
{
369-
return zend_objects_clone_obj_with(old_object, NULL, &zend_empty_array);
370-
}

Zend/zend_objects.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ BEGIN_EXTERN_C()
2626
ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce);
2727
ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce);
2828
ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object);
29-
ZEND_API void ZEND_FASTCALL zend_objects_clone_members_with(zend_object *new_object, zend_object *old_object, const zend_class_entry *scope, const HashTable *properties);
3029

3130
ZEND_API void zend_object_std_dtor(zend_object *object);
3231
ZEND_API void zend_objects_destroy_object(zend_object *object);

Zend/zend_vm_def.h

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6001,6 +6001,7 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY)
60016001
zend_object *zobj;
60026002
zend_class_entry *ce, *scope;
60036003
zend_function *clone;
6004+
zend_object_clone_obj_t clone_call;
60046005

60056006
SAVE_OPLINE();
60066007
obj = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
@@ -6033,7 +6034,8 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY)
60336034
zobj = Z_OBJ_P(obj);
60346035
ce = zobj->ce;
60356036
clone = ce->clone;
6036-
if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) {
6037+
clone_call = zobj->handlers->clone_obj;
6038+
if (UNEXPECTED(clone_call == NULL)) {
60376039
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name));
60386040
FREE_OP1();
60396041
ZVAL_UNDEF(EX_VAR(opline->result.var));
@@ -6051,20 +6053,8 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY)
60516053
}
60526054
}
60536055

6054-
zend_object *cloned;
6055-
if (zobj->handlers->clone_obj_with) {
6056-
scope = EX(func)->op_array.scope;
6057-
cloned = zobj->handlers->clone_obj_with(zobj, scope, &zend_empty_array);
6058-
} else {
6059-
cloned = zobj->handlers->clone_obj(zobj);
6060-
}
6056+
ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj));
60616057

6062-
ZEND_ASSERT(cloned || EG(exception));
6063-
if (EXPECTED(cloned)) {
6064-
ZVAL_OBJ(EX_VAR(opline->result.var), cloned);
6065-
} else {
6066-
ZVAL_UNDEF(EX_VAR(opline->result.var));
6067-
}
60686058
FREE_OP1();
60696059
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
60706060
}

0 commit comments

Comments
 (0)