Commit 07d308a0deb for php.net

commit 07d308a0debc1219d4ef4b789d6a46ec4fb740d3
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Fri Jul 11 15:33:16 2025 +0200

    Tweak lineno compilation

    Fixes GH-18985
    Closes GH-22833

    zend_compile_expr() and zend_compile_var() now restore CG(zend_lineno) after
    compilation of the node has finished. This makes the compiled lineno more
    predictable.

    Note that there are many cases left that are incorrect. For example:

        <?php

        return
            foo();

        L0004 0000 INIT_FCALL_BY_NAME 0 string("foo")
        L0004 0001 T0 = DO_FCALL_BY_NAME
        L0004 0002 RETURN T0
        L0007 0003 RETURN int(1)

    The zend_ast_create_n() functions set lineno to the lineno of the first non-null
    child. In this case that's foo(), even though it's not on the same line as
    return. What we'd need instead is backing up CG(zend_lineno) after the return
    keyword in the parser so we can restore it later. However, this would be a
    rather invasive change, given the same issue exists for many ast kinds, so a
    better solution might just be to re-commit
    e528762c1c59bc0bd0bd6d78246c14269630cf0f.

    I don't feel comfortable backporting this as a bug fix, but we might address the
    issue in a simpler, less comprehensive manner for older branches.

diff --git a/NEWS b/NEWS
index 79a15820a90..b2f7d0220a8 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ PHP                                                                        NEWS
   . Implemented partial function application RFC. (Arnaud)
   . Fixed bug GH-22263 (reset typed property default on every unserialize
     failure path). (David Carlier)
+  . Fixed bug GH-18985 (Wrong line numbers for match with constant arms).
+    (ilutov)

 - DOM:
   . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
index 0173d2ef47a..862236c923b 100644
--- a/Zend/Optimizer/zend_optimizer.c
+++ b/Zend/Optimizer/zend_optimizer.c
@@ -1052,7 +1052,10 @@ static void zend_optimize(zend_op_array      *op_array,
 	}

 	if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) {
-		zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL);
+		uint32_t additional_dump_flags = (ctx->debug_level & ZEND_DUMP_LINE_NUMBERS_PASSTHRU)
+			? ZEND_DUMP_LINE_NUMBERS
+			: 0;
+		zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES|additional_dump_flags, "before optimizer", NULL);
 	}

 	/* pass 1 (Simple local optimizations)
diff --git a/Zend/Optimizer/zend_optimizer.h b/Zend/Optimizer/zend_optimizer.h
index d2847c92869..54acec940f5 100644
--- a/Zend/Optimizer/zend_optimizer.h
+++ b/Zend/Optimizer/zend_optimizer.h
@@ -80,6 +80,7 @@
 #define ZEND_DUMP_DFA_SSA           (1<<27)
 #define ZEND_DUMP_DFA_SSA_VARS      (1<<28)
 #define ZEND_DUMP_SCCP              (1<<29)
+#define ZEND_DUMP_LINE_NUMBERS_PASSTHRU (1<<30)

 typedef struct _zend_script {
 	zend_string   *filename;
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 6a677e71a6b..450b192ac48 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -7125,6 +7125,8 @@ static void zend_compile_match(znode *result, zend_ast *ast)
 		zend_ast *arm_ast = arms->child[i];
 		zend_ast *body_ast = arm_ast->child[1];

+		CG(zend_lineno) = zend_ast_get_lineno(arm_ast);
+
 		if (arm_ast->child[0] != NULL) {
 			zend_ast_list *conds = zend_ast_get_list(arm_ast->child[0]);

@@ -10728,6 +10730,8 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */
 	zend_compile_expr(&left_node, left_ast);
 	zend_compile_expr(&right_node, right_ast);

+	CG(zend_lineno) = ast->lineno;
+
 	if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) {
 		if (zend_try_ct_eval_binary_op(&result->u.constant, opcode,
 				&left_node.u.constant, &right_node.u.constant)
@@ -12335,9 +12339,6 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */

 static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
 {
-	/* CG(zend_lineno) = ast->lineno; */
-	CG(zend_lineno) = zend_ast_get_lineno(ast);
-
 	if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) {
 		zend_compile_memoized_expr(result, ast, BP_VAR_R);
 		return;
@@ -12479,6 +12480,9 @@ static void zend_compile_expr(znode *result, zend_ast *ast)
 {
 	zend_check_stack_limit();

+	uint32_t prev_lineno = CG(zend_lineno);
+	CG(zend_lineno) = zend_ast_get_lineno(ast);
+
 	uint32_t checkpoint = zend_short_circuiting_checkpoint();
 	zend_compile_expr_inner(result, ast);
 	zend_short_circuiting_commit(checkpoint, result, ast);
@@ -12488,12 +12492,12 @@ static void zend_compile_expr(znode *result, zend_ast *ast)
 		ZEND_ASSERT(result->op_type != IS_VAR);
 	}
 #endif
+
+	CG(zend_lineno) = prev_lineno;
 }

 static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t type, bool by_ref)
 {
-	CG(zend_lineno) = zend_ast_get_lineno(ast);
-
 	if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) {
 		switch (ast->kind) {
 			case ZEND_AST_CALL:
@@ -12554,6 +12558,9 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo
 {
 	zend_check_stack_limit();

+	uint32_t prev_lineno = CG(zend_lineno);
+	CG(zend_lineno) = zend_ast_get_lineno(ast);
+
 	uint32_t checkpoint = zend_short_circuiting_checkpoint();
 	zend_op *opcode = zend_compile_var_inner(result, ast, type, by_ref);
 	zend_short_circuiting_commit(checkpoint, result, ast);
@@ -12567,6 +12574,9 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo
 		ZEND_ASSERT(result->op_type != IS_VAR);
 	}
 #endif
+
+	CG(zend_lineno) = prev_lineno;
+
 	return opcode;
 }

@@ -12574,25 +12584,37 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t
 {
 	zend_check_stack_limit();

+	uint32_t prev_lineno = CG(zend_lineno);
+	CG(zend_lineno) = zend_ast_get_lineno(ast);
+
+	zend_op *opline;
 	switch (ast->kind) {
 		case ZEND_AST_VAR:
-			return zend_compile_simple_var(result, ast, type, true);
+			opline = zend_compile_simple_var(result, ast, type, true);
+			break;
 		case ZEND_AST_DIM:
-			return zend_delayed_compile_dim(result, ast, type, by_ref);
+			opline = zend_delayed_compile_dim(result, ast, type, by_ref);
+			break;
 		case ZEND_AST_PROP:
 		case ZEND_AST_NULLSAFE_PROP:
 		{
-			zend_op *opline = zend_delayed_compile_prop(result, ast, type);
+			opline = zend_delayed_compile_prop(result, ast, type);
 			if (by_ref) {
 				opline->extended_value |= ZEND_FETCH_REF;
 			}
-			return opline;
+			break;
 		}
 		case ZEND_AST_STATIC_PROP:
-			return zend_compile_static_prop(result, ast, type, by_ref, true);
+			opline = zend_compile_static_prop(result, ast, type, by_ref, true);
+			break;
 		default:
-			return zend_compile_var(result, ast, type, false);
+			opline = zend_compile_var(result, ast, type, false);
+			break;
 	}
+
+	CG(zend_lineno) = prev_lineno;
+
+	return opline;
 }
 /* }}} */

diff --git a/ext/opcache/tests/gh18985.phpt b/ext/opcache/tests/gh18985.phpt
new file mode 100644
index 00000000000..22c7783cf77
--- /dev/null
+++ b/ext/opcache/tests/gh18985.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-18985: Wrong lineno for multiline expressions
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable_cli=1
+opcache.opt_debug_level=0x40010000
+--FILE--
+<?php
+
+echo match(15) {
+    13 => "A",
+    15 => "B",
+    default => "C",
+};
+
+?>
+--EXPECTF--
+$_main:
+     ; (lines=9, args=0, vars=0, tmps=%s)
+     ; (before optimizer)
+     ; %sgh18985.php:1-10
+     ; return  [] RANGE[0..0]
+L0003 0000 MATCH int(15) 13: 0001, 15: 0003, default: 0005
+L0004 0001 T1 = QM_ASSIGN string("A")
+L0004 0002 JMP 0007
+L0005 0003 T1 = QM_ASSIGN string("B")
+L0005 0004 JMP 0007
+L0006 0005 T1 = QM_ASSIGN string("C")
+L0006 0006 JMP 0007
+L0003 0007 ECHO T1
+L0010 0008 RETURN int(1)
+LIVE RANGES:
+     1: 0006 - 0007 (tmp/var)
+B
diff --git a/ext/opcache/tests/jit/shift_right_004.phpt b/ext/opcache/tests/jit/shift_right_004.phpt
index df65b747ca4..5b816893c53 100644
--- a/ext/opcache/tests/jit/shift_right_004.phpt
+++ b/ext/opcache/tests/jit/shift_right_004.phpt
@@ -30,9 +30,9 @@ function test() {

 Deprecated: Implicit conversion from float %f to int loses precision in %sshift_right_004.php on line 8

-Warning: A non-numeric value encountered in %sshift_right_004.php on line 7
+Warning: A non-numeric value encountered in %sshift_right_004.php on line 6

-Warning: A non-numeric value encountered in %sshift_right_004.php on line 7
+Warning: A non-numeric value encountered in %sshift_right_004.php on line 6

 Fatal error: Uncaught ArithmeticError: Bit shift by negative number in %sshift_right_004.php:8
 Stack trace:
diff --git a/ext/opcache/tests/jit/switch_001.phpt b/ext/opcache/tests/jit/switch_001.phpt
index 57ee3a40b84..898ebb363f2 100644
--- a/ext/opcache/tests/jit/switch_001.phpt
+++ b/ext/opcache/tests/jit/switch_001.phpt
@@ -16,7 +16,7 @@ function foo() {
 ?>
 DONE
 --EXPECTF--
-Warning: Undefined variable $y in %sswitch_001.php on line 4
+Warning: Undefined variable $y in %sswitch_001.php on line 3

-Warning: Undefined variable $y in %sswitch_001.php on line 5
+Warning: Undefined variable $y in %sswitch_001.php on line 3
 DONE