Commit 6441f89857b for php.net

commit 6441f89857b3528e6b71a5f9ce6fa1ade3e67d7e
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Apr 9 15:02:50 2026 -0400

    Fix GH-21691: OPcache CFG optimizer drops QM_ASSIGN feeding JMPZ/JMPZ_EX with IS_VAR

    The CFG optimizer (pass 5) substituted a QM_ASSIGN's source operand
    into its consumer without checking the source's operand type. When
    ASSIGN_REF produces IS_VAR and a QM_ASSIGN converts it to IS_TMP_VAR
    before JMPZ/JMPNZ or JMPZ_EX/JMPNZ_EX, eliminating the QM_ASSIGN
    leaves an IS_VAR operand on a consumer whose handler is declared
    CONST|TMP|CV, producing "Invalid opcode 43/4/0" (or 46/4/0 for the
    _EX variants).

    Guard both substitution sites with src->op1_type != IS_VAR, folded
    into the enclosing condition (per dstogov's review) so the BOOL_NOT
    branch is defensively covered as well. Test exercises both JMPZ and
    JMPZ_EX paths.

    Closes GH-21691
    Closes GH-21696

diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c
index 02c28ead33e..3775f8165c3 100644
--- a/Zend/Optimizer/block_pass.c
+++ b/Zend/Optimizer/block_pass.c
@@ -648,7 +648,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
 					} else if (opline->op1_type == IS_TMP_VAR &&
 					           !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
 						src = VAR_SOURCE(opline->op1);
-						if (src) {
+						if (src && (src->op1_type != IS_VAR)) {
 							if (src->opcode == ZEND_BOOL_NOT) {
 								VAR_SOURCE(opline->op1) = NULL;
 								COPY_NODE(opline->op1, src->op1);
@@ -692,7 +692,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
 					           (!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) ||
 					            opline->result.var == opline->op1.var)) {
 						src = VAR_SOURCE(opline->op1);
-						if (src) {
+						if (src && (src->op1_type != IS_VAR)) {
 							if (src->opcode == ZEND_BOOL ||
 							    src->opcode == ZEND_QM_ASSIGN) {
 								VAR_SOURCE(opline->op1) = NULL;
diff --git a/ext/opcache/tests/gh21691.phpt b/ext/opcache/tests/gh21691.phpt
new file mode 100644
index 00000000000..3926b76c7a1
--- /dev/null
+++ b/ext/opcache/tests/gh21691.phpt
@@ -0,0 +1,20 @@
+--TEST--
+GH-21691 (OPcache CFG optimizer breaks reference returns with JMPZ/JMPZ_EX)
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+
+function &getData() {
+    return $x;
+}
+
+$data = &getData() && isset($data['key']);
+var_dump($data);
+
+?>
+--EXPECT--
+NULL