[php-src] Issue #24388: Tracing JIT: segfault when a trace runs out of side exits (switch with ~500 cases followed by ++$a)
| From: | waroad | Date: | Sat, 17 Oct 2026 22:04:48 +0000 |
| Subject: | [php-src] Issue #24388: Tracing JIT: segfault when a trace runs out of side exits (switch with ~500 cases followed by ++$a) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253013@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24388
Author: waroad
With the tracing JIT, a hot function containing a
switch with about 500 cases followed
by ++$a crashes PHP at JIT-compile time (SIGSEGV in a release build, assertion failure
in a debug build).
A testing tool found this; it is not from a real program. The 11 KB reproducer is generated (507
cases; the case count matters, 480-506 and 511-530 do not trigger it with this shape):
```php
<?php
$s = '<?php' . "\n" . 'function f(int $k,int $a) {switch($k) {';
for ($i = 0; $i < 507; $i++) $s .= "case $i:\$l=$i;break;";
$s .= '}return ++$a;}' . "\n" . 'for($i=0;$i<200;$i++)
{$a=3;f(0,$a);}' . "\n";
file_put_contents('input.php', $s);
```
```
php gen.php
php -n -d opcache.enable_cli=1 -d opcache.jit_buffer_size=64M -d opcache.jit=tracing -d
opcache.file_update_protection=0 input.php
```
Debug build (backtrace from the earlier build where it was found; the assertion is the same at
5ccc56b1e43f):
```
php: ext/opcache/jit/zend_jit_ir.c:627: jit_SNAPSHOT: Assertion `exit_point != -1' failed.
jit_SNAPSHOT zend_jit_ir.c:627
_ir_GUARD_NOT ir/ir.c:3501
zend_jit_inc_dec zend_jit_ir.c:4765
zend_jit_trace zend_jit_trace.c:4501
zend_jit_compile_root_trace zend_jit_trace.c:7571
```
I expected the JIT to give up on the trace (TOO_MANY_EXITS) and the script to finish normally, as it
does with opcache.jit=off (exit 0).
It reproduces at 5ccc56b1e43f: the debug build aborts with the assertion above (exit 134), the
release build segfaults (exit 139) with no output.
Looks like zend_jit_inc_dec() in ext/opcache/jit/zend_jit_ir.c does not check the
result of zend_jit_trace_get_exit_addr(), which is NULL once the trace has used its
ZEND_JIT_TRACE_MAX_EXITS (512) exits, so it emits a guard to address 0; the
neighbouring code does if (!exit_addr) return 0;. Open PR #18392 may be related; I did
not test it.
I can send a patch if that helps.
Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.