Commit 19f595fd9ea for php.net
commit 19f595fd9ea9974e649c43819ffb08a47fc3fd77
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Jun 8 08:43:42 2026 -0400
Fix GH-22158: JIT observer dispatch through wrong run_time_cache slot
For a megamorphic call the tracing JIT built the observer handler pointer
with the ir_PHI_2 operands reversed relative to the ir_MERGE_WITH
predecessor order, so a user function read the internal_function_extension
slot instead of op_array_extension. When an extension reserves an op_array
handle before the observer registers the two indices differ, that slot
holds 0, and the JIT calls a NULL begin handler.
The default-off zend_test.observer.reserve_op_array_handle INI forces that
index mismatch so the regression test can reproduce the crash.
Fixes GH-22158
Closes GH-22258
diff --git a/NEWS b/NEWS
index df93cb5b77b..9a1b6d35d1f 100644
--- a/NEWS
+++ b/NEWS
@@ -58,6 +58,9 @@ PHP NEWS
- Opcache:
. Fixed bug GH-20469 (Unsafe inheritance cache replay with reentrant
autoloading). (Levi Morrison)
+ . Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler
+ through the wrong run_time_cache slot on megamorphic calls). (ptondereau,
+ iliaal)
- OpenSSL:
. Fixed bug GH-22187 (Memory corruption (zend_mm_heap corrupted) in
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index f99605ee420..2cc6a01c410 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -4815,7 +4815,7 @@ static struct jit_observer_fcall_is_unobserved_data jit_observer_fcall_is_unobse
ir_ref observer_handler_user = ir_ADD_OFFSET(run_time_cache, zend_observer_fcall_op_array_extension * sizeof(void *));
ir_MERGE_WITH(if_internal_func_end);
- *observer_handler = ir_PHI_2(IR_ADDR, observer_handler_internal, observer_handler_user);
+ *observer_handler = ir_PHI_2(IR_ADDR, observer_handler_user, observer_handler_internal);
}
// JIT: if (*observer_handler == ZEND_OBSERVER_NONE_OBSERVED) {
diff --git a/ext/opcache/tests/jit/gh22158.phpt b/ext/opcache/tests/jit/gh22158.phpt
new file mode 100644
index 00000000000..ce7f9bc5593
--- /dev/null
+++ b/ext/opcache/tests/jit/gh22158.phpt
@@ -0,0 +1,33 @@
+--TEST--
+GH-22158 (Tracing JIT dispatches observer begin handler through the wrong run_time_cache slot on megamorphic calls)
+--EXTENSIONS--
+opcache
+zend_test
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=tracing
+opcache.jit_buffer_size=32M
+opcache.jit_max_polymorphic_calls=0
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_output=0
+zend_test.observer.reserve_op_array_handle=1
+--FILE--
+<?php
+interface S { public function f(): int; }
+final class A implements S { public function f(): int { return 1; } }
+final class B implements S { public function f(): int { return 2; } }
+final class C implements S { public function f(): int { return 3; } }
+final class D implements S { public function f(): int { return 4; } }
+final class E implements S { public function f(): int { return 5; } }
+
+$o = [new A, new B, new C, new D, new E];
+$t = 0;
+for ($i = 0; $i < 200000; $i++) {
+ $t += $o[$i % 5]->f();
+}
+echo $t, "\n";
+?>
+--EXPECT--
+600000
diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c
index 0dfb62723bc..a7ab0c51e53 100644
--- a/ext/zend_test/observer.c
+++ b/ext/zend_test/observer.c
@@ -18,6 +18,7 @@
#include "php_test.h"
#include "observer.h"
#include "zend_observer.h"
+#include "zend_extensions.h"
#include "zend_smart_str.h"
#include "ext/standard/php_var.h"
#include "zend_generators.h"
@@ -378,6 +379,7 @@ PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_switch", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_switch, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_destroy", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_destroy, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.execute_internal", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_execute_internal, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.reserve_op_array_handle", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_reserve_op_array_handle, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()
void zend_test_observer_init(INIT_FUNC_ARGS)
@@ -386,6 +388,9 @@ void zend_test_observer_init(INIT_FUNC_ARGS)
if (type != MODULE_TEMPORARY) {
REGISTER_INI_ENTRIES();
if (ZT_G(observer_enabled)) {
+ if (ZT_G(observer_reserve_op_array_handle)) {
+ zend_get_op_array_extension_handle("zend_test");
+ }
zend_observer_fcall_register(observer_fcall_init);
}
} else {
diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h
index c1310db7bd7..f32c6338d5c 100644
--- a/ext/zend_test/php_test.h
+++ b/ext/zend_test/php_test.h
@@ -51,6 +51,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
int observer_fiber_switch;
int observer_fiber_destroy;
int observer_execute_internal;
+ int observer_reserve_op_array_handle;
HashTable *global_weakmap;
int replace_zend_execute_ex;
int register_passes;