[php-src] Issue #24391: JIT: switch on constant null takes default instead of case 0 (opcache.jit=function, optimization_level=0)
| From: | waroad | Date: | Sat, 17 Oct 2026 22:07:32 +0000 |
| Subject: | [php-src] Issue #24391: JIT: switch on constant null takes default instead of case 0 (opcache.jit=function, optimization_level=0) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253016@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24391
Author: waroad
With the function JIT and the optimizer disabled,
switch (null) with enough integer
cases jumps to default instead of taking case 0.
```php
<?php
function f() {
switch (null) {
case 0: return 1;
case 1: return 2;
case 2: return 3;
case 3: return 4;
case 4: return 5;
default: return 6;
}
}
var_dump(f());
```
```
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d
opcache.jit=function -d opcache.optimization_level=0 input.php
```
```
int(6)
```
Expected int(1), since null == 0 is true. The interpreter with the same
settings but opcache.jit=0 prints int(1).
Found at c65321c70f7de5f4a4f810f1d23112374d92d1a7 and it still reproduces at 5ccc56b1e43f. The
debug/sanitizer build and the release build both print int(6) with exit code 0, no
assertion or crash. With the default opcache.optimization_level the output is
int(1), and with 3 cases instead of 5 it is also right (no jump table is emitted).
This looks like the IS_CONST branch of zend_jit_switch in
ext/opcache/jit/zend_jit_ir.c: for a constant of the wrong type for
SWITCH_LONG/SWITCH_STRING it goes to the default target, while the VM
handler continues at opline+1 (the IS_EQUAL chain).
The tracing JIT trips over the same branch. Here switch (0) compiles to
SWITCH_STRING with an int constant, the VM continues at opline+1, and the
JIT's default target does not match the trace:
```php
<?php
function f() {
switch (0) {
case "aa": $b=1;break;
case "bb": $b=2; break;
case "cc": $b=3; break;
default: $b=6;
}
return $b;
}
for ($i=0;$i<80;$i++) $r=f(); echo json_encode($r),"\n";
```
```
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d
opcache.jit=tracing -d opcache.optimization_level=0 input.php
```
```
php: ext/opcache/jit/zend_jit_ir.c:16424: zend_jit_switch: Assertion `target == next_opline'
failed.
```
That is the debug build at c65321c70f7d (exit 134; with opcache.jit=0 it prints
6). I did not run this one on a release build or at 5ccc56b1e43f.
The trigger is contrived (a literal constant as the switch subject, optimizer off); it is not from a
real program.
Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.