Skip to content

Fix GH-23911: Cached static closure keeps static:: of the first called class - #24143

Open
Roman3349 wants to merge 1 commit into
php:PHP-8.6from
Roman3349:fix/gh-23911-static-closure-cache-called-scope
Open

Roman3349 wants to merge 1 commit into
php:PHP-8.6from
Roman3349:fix/gh-23911-static-closure-cache-called-scope

Conversation

@Roman3349

Copy link
Copy Markdown

Fixes GH-23911

The stateless closure cache introduced in GH-23203 stores a single closure per
ZEND_DECLARE_LAMBDA_FUNCTION opline. All subclasses share the declaring
op_array, so the closure created for the first called scope was returned for
every other called scope. This broke static::, new static() and
get_called_class() inside static closures in inherited methods:

class A {
    public static function name(): string {
        return (static fn () => static::class)();
    }
}
class B extends A {}
class C extends A {}

var_dump(B::name(), C::name()); // "B", "B" instead of "B", "C"

The fix stores the called scope together with the cached closure (a two-slot
cache, like other polymorphic runtime caches) and only reuses the closure when
the called scope matches. The slot is only filled once. Replacing the cached
closure on a mismatch would push a new entry onto EG(lambda_cache) each time,
which is only freed at request shutdown, so alternating called scopes would
grow it without bounds. Other called scopes take the uncached path, which is
how every call behaved before 8.6.

The JIT does not compile this opcode (it calls the VM handler), so no JIT changes
are needed.

@Roman3349
Roman3349 requested a review from dstogov as a code owner October 5, 2026 19:30
…lled class

The stateless closure cache in ZEND_DECLARE_LAMBDA_FUNCTION stored a single
closure per opline. Since the declaring op_array is shared by all subclasses,
the closure created for the first called scope was returned for every other
called scope, breaking static::, new static() and get_called_class().

Store the called scope alongside the cached closure and only reuse the closure
for a matching called scope. The slot is only filled once, as replacing the
cached closure on a mismatch would grow EG(lambda_cache) without bounds.

Fixes phpGH-23911

Signed-off-by: Roman Ondráček <mail@romanondracek.cz>
@Roman3349
Roman3349 force-pushed the fix/gh-23911-static-closure-cache-called-scope branch from 7e7a0e6 to c194f64 Compare October 5, 2026 19:30
@Roman3349
Roman3349 changed the base branch from master to PHP-8.6 October 5, 2026 19:57
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.

PHP 8.6: cached static closure keeps static:: of the first called class

1 participant