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)