diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index d6a456db395b..191213f86e38 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -185,6 +185,12 @@ typedef zend_object* (*zend_object_clone_obj_with_t)(zend_object *object, const typedef enum { ZEND_OBJECT_TRANSFER, /* emalloc → pemalloc (send to another thread) */ ZEND_OBJECT_LOAD, /* pemalloc → emalloc (receive in target thread) */ + /* The transit shell is being freed: release what TRANSFER acquired into it. + * The handler frees its own C state and returns NULL; it must not call + * default_fn, and `ctx` may be NULL because a release runs outside a + * request. Without this kind, anything a handler stores in the shell is + * unreachable — free_obj never runs for a shell. */ + ZEND_OBJECT_TRANSFER_RELEASE, } zend_object_transfer_kind_t; typedef zend_object* (*zend_object_transfer_default_fn)( diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 36453c5128fb..3bf961586c2f 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -818,6 +818,12 @@ static zend_object *zend_weakref_transfer_obj( { (void) default_fn; + if (kind == ZEND_OBJECT_TRANSFER_RELEASE) { + /* The transit wrapper owns nothing of its own: the referent slot and the + * class name are freed by the generic release walk. */ + return NULL; + } + if (kind == ZEND_OBJECT_TRANSFER) { zend_weakref *wr = zend_weakref_from(object); zend_object *referent = wr->referent; @@ -875,6 +881,12 @@ static zend_object *zend_weakmap_transfer_obj( { (void) default_fn; + if (kind == ZEND_OBJECT_TRANSFER_RELEASE) { + /* The transit wrapper owns nothing of its own: the key/value slots and + * the class name are freed by the generic release walk. */ + return NULL; + } + if (kind == ZEND_OBJECT_TRANSFER) { zend_weakmap *wm = zend_weakmap_from(object); const uint32_t num = zend_hash_num_elements(&wm->ht);