Commit 7873640e08e for php.net

commit 7873640e08e707c2cfbdf72daa10c592244a1cda
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sat Aug 22 13:36:34 2026 -0400

    JIT: persist the SHM op_array in trace exit_info

    exit_info.op_array was taken from the current frame. Methods of linked
    classes that miss the inheritance cache use a heap copy of the op_array
    header, so that pointer is invalid in other processes and later
    requests. Store the original from the JIT extension, as root traces
    already do.

    Closes GH-21710

diff --git a/NEWS b/NEWS
index d25d441ca8b..a2c65685b4c 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@ PHP                                                                        NEWS
   . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
   . Fixed bug GH-23288 (Crash on restart when opcache.interned_strings_buffer
     is overridden in an individual FPM pool). (David Carlier)
+  . Fixed a tracing JIT crash when compiling a side trace for a method of a
+    class that could not be stored in the inheritance cache. (GH-21710)
+    (Arnaud, iliaal)

 - PDO:
   . Fixed a leak when a persistent connection failed a liveness check
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index 225257ecd6a..6a3e8c3a471 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -145,6 +145,11 @@ static uint32_t zend_jit_trace_get_exit_point(const zend_op *to_opline, uint32_t
 	}
 	if (JIT_G(current_frame)) {
 		op_array = &JIT_G(current_frame)->func->op_array;
+		if (!(op_array->fn_flags & ZEND_ACC_IMMUTABLE)) {
+			zend_jit_op_array_trace_extension *jit_extension =
+				(zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+			op_array = jit_extension->op_array;
+		}
 		stack_size = op_array->last_var + op_array->T;
 		if (stack_size) {
 			stack = JIT_G(current_frame)->stack;
diff --git a/ext/opcache/tests/jit/gh21710.inc b/ext/opcache/tests/jit/gh21710.inc
new file mode 100644
index 00000000000..a727c8f1256
--- /dev/null
+++ b/ext/opcache/tests/jit/gh21710.inc
@@ -0,0 +1,15 @@
+<?php
+if (getenv('call_user_func')) {
+    eval('class P {}');
+}
+
+class C extends P {
+    static function f($v) {
+        return $v[0];
+        if ($a) {
+            return 1;
+        } else {
+            return 2;
+        }
+    }
+}
diff --git a/ext/opcache/tests/jit/gh21710.phpt b/ext/opcache/tests/jit/gh21710.phpt
new file mode 100644
index 00000000000..d175ab4c701
--- /dev/null
+++ b/ext/opcache/tests/jit/gh21710.phpt
@@ -0,0 +1,50 @@
+--TEST--
+GH-21710: tracing JIT side-trace compile with a heap-copied linked method
+--EXTENSIONS--
+opcache
+pcntl
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=tracing
+opcache.jit_buffer_size=64M
+--ENV--
+call_user_func=call_user_func
+--SKIPIF--
+<?php
+if (!function_exists('pcntl_fork')) die('skip pcntl_fork() not available');
+if (!(opcache_get_status()['jit']['on'] ?? false)) die('skip JIT is not available');
+?>
+--FILE--
+<?php
+$pid = pcntl_fork();
+if ($pid === 0) {
+    require __DIR__ . '/gh21710.inc';
+    for ($i = 0; $i < 1000; $i++) {
+        getenv('call_user_func')('C::f', [false]);
+    }
+    exit(0);
+}
+if ($pid === -1) {
+    echo "pcntl_fork() failed\n";
+    exit(1);
+}
+
+pcntl_waitpid($pid, $status, 0);
+
+$buf = [];
+for ($i = 0; $i < 100; $i++) {
+    $buf[] = str_repeat('a', $i * 100);
+}
+
+require __DIR__ . '/gh21710.inc';
+
+for ($i = 0; $i < 1000; $i++) {
+    getenv('call_user_func')('C::f', [true]);
+}
+
+var_dump(getenv('call_user_func')('C::f', [true]));
+?>
+--EXPECT--
+bool(true)