Skip to content

Commit 275f490

Browse files
deepclone_hydrate: skip null→uninit shortcut on lazy objects
The shortcut sets IS_PROP_UNINIT on OBJ_PROP(obj, pi->offset) directly. On a lazy object with NO_LAZY_INIT set, the entry to dc_write_backed_property doesn't route through zend_update_property_ex, and the shortcut would fire before the Reflection-based lazy path at the bottom of the function — so a null value for a non-nullable typed prop on a lazy object could end up mutating the slot while the lazy-props counter and IS_PROP_LAZY flags remained in their pre-write state. Gating the shortcut on zend_lazy_object_initialized(obj) lets the Reflection path handle lazy objects consistently (it will TypeError on null for a non-nullable prop, matching what a real write would do).
1 parent 59777e8 commit 275f490

1 file changed

Lines changed: 9 additions & 3 deletions

File tree

deepclone.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,12 +753,18 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
753753
}
754754

755755
/* null → uninitialized for non-nullable typed slots; hooked props excluded
756-
* (no backing slot to "unset", and the set hook may handle null itself). */
756+
* (no backing slot to "unset", and the set hook may handle null itself).
757+
* Skip the shortcut on lazy objects — a direct slot write would bypass
758+
* the lazy-props bookkeeping. On NO_LAZY_INIT + lazy we fall through to
759+
* the Reflection-based path below, which enforces type semantics. */
757760
if (Z_TYPE_P(value) == IS_NULL
758761
&& ZEND_TYPE_IS_SET(pi->type)
759762
&& !ZEND_TYPE_ALLOW_NULL(pi->type)
760-
&& !DC_PROP_HAS_HOOKS(pi))
761-
{
763+
&& !DC_PROP_HAS_HOOKS(pi)
764+
#if PHP_VERSION_ID >= 80400
765+
&& zend_lazy_object_initialized(obj)
766+
#endif
767+
) {
762768
if (Z_TYPE_P(slot) != IS_UNDEF) {
763769
zval old;
764770
ZVAL_COPY_VALUE(&old, slot);

0 commit comments

Comments
 (0)