[php-src] PR #24287: Fix GH-24270: JIT loses called scope of self::/parent:: in closures

From: 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.

« previous php.git-pulls (#39372) next »