Commit 1fb0b45ddeb for php
commit 1fb0b45ddeb1cb5d8562b0c09ffa95c9bfd985bf
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date: Sun Oct 18 00:51:18 2026 +0200
Fix GH-24391: JIT: switch on constant null takes default instead of case 0
There was simply a mismatch in how the VM and JIT handled the types.
I reworked how the jump target is chose, and hopefully made the code
easier to understand in the process.
Closes GH-24393.
diff --git a/NEWS b/NEWS
index 0c80ffefb1b..a55a4c4d1ff 100644
--- a/NEWS
+++ b/NEWS
@@ -75,6 +75,8 @@ PHP NEWS
. Fix SIGFPE on LONG_MIN % -1 with a constant divisor. (waroad)
. Fixed bug GH-24388 (Tracing JIT: segfault when a trace runs out of side
exits). (Lazizbek Ergashev)
+ . Fixed bug GH-24391 (JIT switch on constant null takes default instead of
+ case 0). (ndossche)
- PDO:
. Fixed a duplicate warning when ATTR_STATEMENT_CLASS is rejected on a
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index e94a86000a0..236192c9933 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -15997,15 +15997,22 @@ static int zend_jit_switch(zend_jit_ctx *jit, const zend_op *opline, const zend_
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
zval *jump_zv = NULL;
+ const zend_op *target;
int b;
+ /* Wrong type for SWITCH_LONG/SWITCH_STRING falls back to the ZEND_CASE chain */
+ target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
if (opline->opcode == ZEND_SWITCH_LONG) {
if (Z_TYPE_P(zv) == IS_LONG) {
jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(zv));
+ } else {
+ target = opline + 1;
}
} else if (opline->opcode == ZEND_SWITCH_STRING) {
if (Z_TYPE_P(zv) == IS_STRING) {
jump_zv = zend_hash_find_known_hash(jumptable, Z_STR_P(zv));
+ } else {
+ target = opline + 1;
}
} else if (opline->opcode == ZEND_MATCH) {
if (Z_TYPE_P(zv) == IS_LONG) {
@@ -16016,21 +16023,13 @@ static int zend_jit_switch(zend_jit_ctx *jit, const zend_op *opline, const zend_
} else {
ZEND_UNREACHABLE();
}
+ if (jump_zv != NULL) {
+ target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(jump_zv));
+ }
if (next_opline) {
- const zend_op *target;
-
- if (jump_zv != NULL) {
- target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(jump_zv));
- } else {
- target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
- }
ZEND_ASSERT(target == next_opline);
} else {
- if (jump_zv != NULL) {
- b = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(jump_zv)) - op_array->opcodes];
- } else {
- b = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) - op_array->opcodes];
- }
+ b = ssa->cfg.map[target - op_array->opcodes];
_zend_jit_add_predecessor_ref(jit, b, jit->b, ir_END());
jit->b = -1;
}
diff --git a/ext/opcache/tests/jit/gh24391.phpt b/ext/opcache/tests/jit/gh24391.phpt
new file mode 100644
index 00000000000..abad5408e5d
--- /dev/null
+++ b/ext/opcache/tests/jit/gh24391.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-24391 (JIT: switch on constant of wrong type takes default instead of falling back to the case chain)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+opcache.optimization_level=0
+--FILE--
+<?php
+function long_switch() {
+ 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;
+ }
+}
+
+function string_switch() {
+ switch (0) {
+ case "aa": return 1;
+ case "bb": return 2;
+ case "cc": return 3;
+ default: return 6;
+ }
+}
+
+var_dump(long_switch());
+var_dump(string_switch());
+?>
+--EXPECT--
+int(1)
+int(6)