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)