Repository navigation
fix(reflection): skip closures over internal functions when walking the live closures - #290
Merged
Merged
Conversation
…he live closures A swap walks the live closure objects to find the copies of its entry (fake closures share the entry's run-time cache and borrow its live statics). The walk built a user-function view over every closure and asked it whether it was user-defined - but a closure over an INTERNAL function (a first-class callable of strlen(), Closure::fromCallable() over an internal function, the closure of an internal method) keeps its name in the internal_function member, and building the view threw "Attempt to read undefined field 'function_name'" before the question could be asked. Every redefine() that released a minted cache, took over live statics or retired an alias failed while such a closure was alive; PHP 8.6 keeps such closures in its callable caches, so there it failed always. The function type is now read off the union before any view is made, and internal closures are skipped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VLQcsvgdQUzwBFsoKUAcG2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
A body swap walks the live closure objects to find the copies of its entry (fake closures share the entry's run-time cache and borrow its live statics; see
FunctionBodySwap::liveClosureFunctions()). The walk built a user-function view over every closure and only then asked whether it was user-defined. A closure over an internal function (a first-class callablestrlen(...),Closure::fromCallable('strlen'), the closure of an internal method) keeps its name in theinternal_functionmember, so building the view threwFFI\Exception: Attempt to read undefined field 'function_name'first. Everyredefine()that released a minted run-time cache, let apreserveAsalias take over live statics, or retired a removed alias failed while such a closure was alive anywhere in the process.Found through the cascade of
8.5intomaster(#285/#289): PHP 8.6 keeps closures over internal functions in its callable caches, so there every one of those paths failed (14 errors). Reproduced on PHP 8.4 with astrlen(...)kept alive across two swaps. It exists on every branch since the feature landed, so the fix targets8.4and cascades.The function type is now read off the
zend_functionunion before any view is made, with the sameZEND_USER_FUNCTIONtestFunctionLikeTrait::isUserDefined()uses, and internal closures are skipped.RedefineLiveInternalClosureTestholds the three kinds of internal closure alive through the cache release, the alias take-over of live statics andremoveMethods(), in both runner modes.The same hunk is ported into #289 so the 8.6 cascade can go green without waiting for this one; it no-ops once the cascade carries it.
Environment it was verified on
php -v): PHP 8.4.19 (cli) (built: Oct 10 2026) (NTS)--enable-debug)? noVerified: the new test in both runner modes,
composer test(minus the pre-existingExecutionDataTest::testGetThisabort of this build),composer phpstan,composer cs:check.Checklist
master= newestsupported minor,
8.4= PHP 8.4, …) — fixes cascade upward, neverdownward
composer testpasses on the matching PHP minorcomposer phpstan(level max) andcomposer cs:checkare greenlayout_structsintools/generator/symbols.php(nothing new)tools/generator/symbols.phpchanged: regenerated withcomposer gen-headersand committed the result — nothing underinclude/,stubs/or.phpstorm.meta.phpwas hand-edited (unchanged)🤖 Generated with Claude Code
https://claude.ai/code/session_01VLQcsvgdQUzwBFsoKUAcG2
Generated by Claude Code