Skip to content

Commit 49c3ba3

Browse files
RV7PRR0WH3L
authored andcommitted
Fix GH-17626: JIT corrupts opline handler when blacklisting root trace
When a trace exits through a ZEND_JIT_EXIT_INVALIDATE guard while ZEND_JIT_TRACE_NUM has already reached opcache.jit_max_root_traces, zend_jit_trace_exit() blacklists the root trace and restores the original VM handler. It wrote that handler to the exit opline (the INIT_* opline whose callee guard failed) instead of the root trace's start opline. The opcodes live in SHM, so every worker keeps executing the foreign handler until restart. For a root trace that starts at the entry of a function with typed parameters the copied handler is ZEND_RECV, which then runs on an INIT_FCALL / INIT_STATIC_METHOD_CALL opline and raises "Too few arguments to function X(), N passed ... and exactly N expected" from a frame that has all its arguments. Other root oplines lead to crashes instead. Introduced by 350af54 (GH-14475).
1 parent 8484a9f commit 49c3ba3

6 files changed

Lines changed: 96 additions & 1 deletion

File tree

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ PHP NEWS
6767
. Fixed a tracing JIT crash when compiling a side trace for a method of a
6868
class that could not be stored in the inheritance cache. (GH-21710)
6969
(Arnaud, iliaal)
70+
. Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a
71+
root trace at the opcache.jit_max_root_traces limit, causing spurious
72+
"Too few arguments" errors and crashes). (RV7PR)
7073

7174
- PDO:
7275
. Fixed a leak when a persistent connection failed a liveness check

ext/opcache/jit/zend_jit_trace.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8780,7 +8780,7 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf
87808780
SHM_UNPROTECT();
87818781
zend_jit_unprotect();
87828782

8783-
((zend_op*)opline)->handler =
8783+
((zend_op*)(t->opline))->handler =
87848784
ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->orig_handler;
87858785

87868786
ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags &= ~ZEND_JIT_TRACE_JITED;

ext/opcache/tests/jit/gh17626.inc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
function gh17626_callee(string $s) { return strtoupper($s); }

ext/opcache/tests/jit/gh17626.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-17626: Opline handler corrupted when a root trace is blacklisted at the max_root_traces limit (fails with --repeat 2)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.revalidate_freq=0
8+
opcache.jit=tracing
9+
opcache.jit_buffer_size=16M
10+
opcache.jit_hot_func=2
11+
opcache.jit_hot_loop=255
12+
opcache.jit_hot_return=255
13+
opcache.jit_hot_side_exit=255
14+
opcache.jit_max_root_traces=2
15+
--EXTENSIONS--
16+
opcache
17+
--FILE--
18+
<?php
19+
namespace GH17626;
20+
21+
// In --repeat 2 the callee is recompiled after the first run, so the function
22+
// guard in the trace compiled for caller() fails with ZEND_JIT_EXIT_INVALIDATE.
23+
24+
require __DIR__ . '/gh17626.inc';
25+
26+
function caller(string $s) {
27+
return gh17626_callee($s) . $s;
28+
}
29+
30+
caller('a');
31+
caller('a');
32+
caller('a');
33+
echo caller('a'), "\n";
34+
echo caller('b'), "\n";
35+
36+
touch(__DIR__ . '/gh17626.inc');
37+
opcache_invalidate(__DIR__ . '/gh17626.inc', true);
38+
?>
39+
--EXPECT--
40+
Aa
41+
Bb
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
class GH17626GrandParent {}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
GH-17626: Opline handler corrupted when a root trace is blacklisted at the max_root_traces limit
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.jit=tracing
8+
opcache.jit_buffer_size=16M
9+
opcache.jit_hot_func=2
10+
opcache.jit_hot_loop=255
11+
opcache.jit_hot_return=255
12+
opcache.jit_hot_side_exit=255
13+
opcache.jit_max_root_traces=2
14+
--EXTENSIONS--
15+
opcache
16+
--FILE--
17+
<?php
18+
require __DIR__ . '/gh17626_002.inc';
19+
20+
class ParentA extends GH17626GrandParent { public static function m() { return 'A'; } }
21+
class ParentB extends GH17626GrandParent { public static function m() { return 'B'; } }
22+
23+
trait T {
24+
public function run(string $s) {
25+
return parent::m() . $s;
26+
}
27+
}
28+
29+
class A extends ParentA { use T; }
30+
class B extends ParentB { use T; }
31+
32+
$a = new A;
33+
$b = new B;
34+
35+
$a->run('x');
36+
$a->run('x');
37+
$a->run('x');
38+
echo $a->run('x'), "\n";
39+
echo $b->run('y'), "\n";
40+
echo $b->run('y'), "\n";
41+
echo $a->run('x'), "\n";
42+
?>
43+
--EXPECT--
44+
Ax
45+
By
46+
By
47+
Ax

0 commit comments

Comments
 (0)