Skip to content

Commit 9acaa92

Browse files
Route writes on lazy objects through zend_update_property_ex()
Per @arnaudlb's review: a lazy ghost has its backing slots uninitialized until the realization hook fires. Writing directly via OBJ_PROP / slot manipulation on such an object would bypass that hook and leave it in a half-initialized state (lazy counters out of sync, lazy-init callback never called). At the top of dc_write_backed_property(), check zend_lazy_object_initialized() and, in the default mode, delegate to the engine's property write path which handles realization. The DEEPCLONE_HYDRATE_NO_LAZY_INIT flag keeps its existing opt-out fast path (skips lazy-init intentionally via Reflection).
1 parent 8278442 commit 9acaa92

1 file changed

Lines changed: 9 additions & 0 deletions

File tree

deepclone.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,15 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
732732
#if PHP_VERSION_ID >= 80400
733733
bool call_hooks = (flags & DEEPCLONE_HYDRATE_CALL_HOOKS) != 0;
734734
bool no_lazy_init = (flags & DEEPCLONE_HYDRATE_NO_LAZY_INIT) != 0;
735+
736+
/* Lazy objects: a direct slot write would bypass the engine's realization
737+
* hook and leave the object in a half-initialized state. Route through
738+
* zend_update_property_ex() which triggers realization on first write.
739+
* DEEPCLONE_HYDRATE_NO_LAZY_INIT has its own opt-out fast path below. */
740+
if (!no_lazy_init && UNEXPECTED(!zend_lazy_object_initialized(obj))) {
741+
zend_update_property_ex(pi->ce, obj, name, value);
742+
return !EG(exception);
743+
}
735744
#endif
736745
zval *slot = OBJ_PROP(obj, pi->offset);
737746

0 commit comments

Comments
 (0)