Skip to content

[skip ci] Document the closure $this internals change in UPGRADING.INTERNALS - #23434

Merged
Girgias merged 1 commit into
php:masterfrom
nicolas-grekas:closure-this-ptr-upgrading
Aug 24, 2026
Merged

[skip ci] Document the closure $this internals change in UPGRADING.INTERNALS#23434
Girgias merged 1 commit into
php:masterfrom
nicolas-grekas:closure-this-ptr-upgrading

Conversation

@nicolas-grekas

Copy link
Copy Markdown
Contributor

zend_create_closure(), zend_create_fake_closure() and zend_create_partial_closure() take the bound $this as a zend_object* since fbb2e1f, and zend_get_closure_this_ptr() returns one — NULL when the closure is unbound, where it used to return a zval* that is IS_UNDEF — since 7a5e452. Neither commit added a note to UPGRADING.INTERNALS, so this adds one to the "Changed" list of section 1, next to the other signature changes.

Worth documenting because the change is silent for extension authors: passing a zval* where a zend_object* is now expected is only a -Wincompatible-pointer-types warning, so an extension still builds and then reads the wrong struct at runtime.

Concretely, ext-deepclone built clean-looking against master and segfaulted:

zend_hash_index_find_bucket (ht=0x7ffff2c87e61, h=757)
zim_DeepClone_HydrationContext_hydrate (deepclone.c:4007)
zend_call_known_fcc_ex (fcc->object = 0x7ffff2c87eb9)
zend_lazy_object_init (zend_lazy_objects.c:661)

Its lazy-object initializer was created with zend_create_fake_closure(..., &this_zval); the engine stored that zval* as the closure's this_ptr and handed it back as $this, so Z_OBJ_P(ZEND_THIS) in the initializer dereferenced a zval as an object.

…TERNALS

zend_create_closure(), zend_create_fake_closure() and
zend_create_partial_closure() take a zend_object* since fbb2e1f, and
zend_get_closure_this_ptr() returns one since 7a5e452. Neither change
was listed, so extensions built against the new headers get only a
-Wincompatible-pointer-types warning and read garbage at runtime.
nicolas-grekas added a commit to symfony/php-ext-deepclone that referenced this pull request Aug 24, 2026
… master

php-src turned the $this a closure is bound to from a zval* into a
zend_object*: zend_create_closure() and friends take one since fbb2e1f23d6
and zend_get_closure_this_ptr() returns one since 7a5e452f14c, both landed
during 8.6-dev and without a ZEND_MODULE_API_NO bump to key off. Passing or
reading the wrong flavor is only a -Wincompatible-pointer-types warning, so
the extension still built and then read the wrong struct at runtime: the
closure encoder saw every bound closure as unbound, and the lazy ghost
initializer handed the engine a zval* where it now expects the object,
segfaulting in zend_lazy_object_init() on the first touch of a ghost.

Route both directions through shims that pick the flavor from the type the
engine declares -- via _Generic where the compiler has C11, via the version
otherwise. php-src is documenting the change in php/php-src#23434.
@Girgias
Girgias merged commit 9983490 into php:master Aug 24, 2026
2 checks passed
@Girgias

Girgias commented Aug 24, 2026

Copy link
Copy Markdown
Member

Thanks, I didn't expect an extension to be messing around with closures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants