Commit a0ddc2aa577 for php.net

commit a0ddc2aa577de0dc8018d6b068dddae9cb0f1ab2
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Jun 25 17:24:38 2026 -0400

    JIT: Fix run_time_cache offset stored without map_ptr dereference

    zend_jit_do_fcall() loaded the callee run_time_cache through the closure
    direct-pointer shortcut whenever the call frame was flagged as a closure
    call. That flag is set for every ZEND_INIT_DYNAMIC_CALL, but a dynamic
    call may resolve to a non-closure function whose run_time_cache is a
    zend_map_ptr offset. The raw offset was then stored into
    EX(run_time_cache) without resolving it through CG(map_ptr_base), and a
    later cache lookup dereferenced a bogus address.

    Restrict the shortcut to statically-known closures. Unknown dynamic
    calls fall through to the general path, which resolves both offsets and
    direct pointers.

    Fixes GH-22443
    Closes GH-22459

diff --git a/NEWS b/NEWS
index ef8d40d00f8..4022869c0b0 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,8 @@ PHP                                                                        NEWS
   . Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler
     through the wrong run_time_cache slot on megamorphic calls). (ptondereau,
     iliaal)
+  . Fixed bug GH-22443 (Tracing JIT SIGSEGV on megamorphic dynamic calls from
+    an undereferenced run_time_cache map_ptr offset). (iliaal)

 - Intl:
   . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 5e8a71cd48c..f8503a13173 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -10221,10 +10221,7 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen
 			 && ZEND_MAP_PTR_IS_OFFSET(func->op_array.run_time_cache)) {
 				run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CG(map_ptr_base)),
 					(uintptr_t)ZEND_MAP_PTR(func->op_array.run_time_cache)));
-			} else if ((func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) ||
-					(JIT_G(current_frame) &&
-					 JIT_G(current_frame)->call &&
-					 TRACE_FRAME_IS_CLOSURE_CALL(JIT_G(current_frame)->call))) {
+			} else if (func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
 				/* Closures always use direct pointers */
 				ir_ref local_func_ref = func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func));

diff --git a/ext/opcache/tests/jit/gh22443.phpt b/ext/opcache/tests/jit/gh22443.phpt
new file mode 100644
index 00000000000..869329b79b5
--- /dev/null
+++ b/ext/opcache/tests/jit/gh22443.phpt
@@ -0,0 +1,57 @@
+--TEST--
+GH-22443 (SIGSEGV in tracing JIT: run_time_cache offset stored without map_ptr dereference)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=tracing
+opcache.jit_buffer_size=64M
+opcache.jit_max_polymorphic_calls=0
+--FILE--
+<?php
+final class Svc {
+    public function m0(...$a): int { return (int) array_sum($a) + 0; }
+    public function m1(...$a): int { return (int) array_sum($a) + 1; }
+    public function m2(...$a): int { return (int) array_sum($a) + 2; }
+    public function m3(...$a): int { return (int) array_sum($a) + 3; }
+    public function m4(...$a): int { return (int) array_sum($a) + 4; }
+    public function m5(...$a): int { return (int) array_sum($a) + 5; }
+    public function m6(...$a): int { return (int) array_sum($a) + 6; }
+    public function m7(...$a): int { return (int) array_sum($a) + 7; }
+    public function coldMethod(...$a): int { return $this->helper((int) array_sum($a)); }
+    private function helper(int $x): int { return $x + 1; }
+}
+
+function makeListener($listener): Closure {
+    return function ($event, $payload) use ($listener) {
+        return $listener(...array_values($payload));
+    };
+}
+
+function invokeListeners(array $listeners, string $event, array $payload): int {
+    $r = 0;
+    foreach ($listeners as $l) {
+        $r += $l($event, $payload);
+    }
+    return $r;
+}
+
+$svc = new Svc();
+$warm = [];
+for ($k = 0; $k < 8; $k++) {
+    $warm[] = makeListener([$svc, 'm' . $k]);
+}
+$cold = [makeListener([$svc, 'coldMethod'])];
+
+$s = 0;
+for ($i = 0; $i < 4000000; $i++) {
+    $s += invokeListeners($warm, 'e', [$i & 7, ($i >> 2) & 7]);
+}
+for ($j = 0; $j < 5; $j++) {
+    $s += invokeListeners($cold, 'e', [$j, $j + 1]);
+}
+echo "done\n";
+?>
+--EXPECT--
+done