[php-src] PR #24287: Fix GH-24270: JIT loses called scope of self::/parent:: in closures
| From: | RV7PR | Date: | Tue, 13 Oct 2026 05:51:29 +0000 |
| Subject: | [php-src] PR #24287: Fix GH-24270: JIT loses called scope of self::/parent:: in closures | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39372@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24287
Author: RV7PR
Fixes #24270
### The bug
For an
INIT_STATIC_METHOD_CALL with self::/parent:: in a
non-static closure, zend_jit_init_static_method_call() computes the called scope as a
PHI of Z_OBJCE(EX(This)) (bound $this) and Z_CE(EX(This)) (no
$this), but the result of ir_PHI_list(values) was discarded instead of
being assigned to scope_ref, as the two sibling branches do. scope_ref
kept the resolved class, so the callee ran with e.g. Base as called scope instead of
the forwarded Child: static::class returns the wrong class, and new
static() creates the wrong class or throws "Cannot instantiate abstract class" (the
case in the report).
The code is shared by function and tracing JIT, so both are affected. static fn
closures take the ZEND_ACC_STATIC branch and are not affected. Introduced by
972d6b6664e (Fix JIT for INIT_STATIC_METHOD_CALL in a closure), so PHP-8.5 and up; PHP-8.4 does not
have this branch.
### The fix
Assign the PHI to scope_ref.
### Tests
gh24270_001.phpt (function JIT) and gh24270_002.phpt (tracing JIT,
jit_hot_func=1/jit_hot_loop=1) run the same cases from
gh24270.inc and print the called scope seen by each closure over 100 calls:
- self:: from a closure without $this (the report), with a bound
$this, and parent:: with a bound $this;
- self:: and parent:: from closures declared in a trait method, with the
trait used by two classes with different parents.
Each case is called through a subclass, so the expected called scope differs from both the
closure's scope and the class the method is resolved in; a result taken from either of those
fails the test.
The trait cases cover a different code shape: for a trait, zend_get_known_class()
returns the trait, the method lookup is skipped and func stays unknown, so function JIT
emits the runtime static/non-static split, while tracing JIT takes func from the trace
and emits a guard. The trait closure's opcodes are also shared by every using class, so the
scope has to come from EX(This) at runtime.
Without the fix every case fails in both tests (function JIT:
Base/Mid/A/ParentA etc. on every call; tracing
JIT: correct until the trace is compiled, then wrong).
Verified on PHP-8.5 (bbfdd69f601), debug build, macOS arm64: both new tests fail without the fix and
pass with it; the reproducer from the report prints OK with JIT off, function and
tracing JIT. ext/opcache/tests (default INI) and Zend/tests +
ext/opcache/tests under function and tracing JIT show no failures that are not also
present without the change.
### Disclaimer
The root-cause analysis, the fix, the tests and this write-up were produced with Claude Opus 5.5
(Anthropic) at my direction; I ran the builds and tests on my machine and verified the results, but
I cannot review the C myself. I will relay any review questions and apply requested changes.