[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
| From: | ndossche | Date: | Thu, 08 Oct 2026 17:54:26 +0000 |
| Subject: | [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142102@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-08T19:54:00+02:00
Commit: https://github.com/php/php-src/commit/fbed672173ff77bcbb567d287cdfbe2aec74a02c
Raw diff: https://github.com/php/php-src/commit/fbed672173ff77bcbb567d287cdfbe2aec74a02c.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
Fix GH-23979: Nullsafe operator must not flush delayed oplines of an enclosing function
Changed paths:
A Zend/tests/nullsafe_operator/gh23979.phpt
M NEWS
M Zend/zend_compile.c
Diff:
diff --git a/NEWS b/NEWS
index 7b8426b2b51a..56d20ae2212e 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PHP NEWS
- Core:
. Fixed memory leak when an exception already contains the pending exception
in its previous chain. (Edmond)
+ . Fixed bug GH-23979 (Nullsafe operator must not flush delayed oplines of an
+ enclosing function). (ndossche)
- Opcache:
. Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a
diff --git a/Zend/tests/nullsafe_operator/gh23979.phpt b/Zend/tests/nullsafe_operator/gh23979.phpt
new file mode 100644
index 000000000000..ba5e78feb1a2
--- /dev/null
+++ b/Zend/tests/nullsafe_operator/gh23979.phpt
@@ -0,0 +1,16 @@
+--TEST--
+GH-23979 (Nullsafe operator must not flush delayed oplines of an enclosing function)
+--FILE--
+<?php
+function test($name) {
+ $arr = ['foo' => 'bar'];
+ return ${$name}[(function () {
+ return A . B?->prop;
+ })()];
+}
+const A = 'foo';
+const B = null;
+var_dump(test('arr'));
+?>
+--EXPECT--
+string(3) "bar"
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 941873663848..e0ac8e23b9bb 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -2461,10 +2461,15 @@ static inline zend_op *zend_delayed_emit_op(znode *result, uint8_t opcode,
znode
}
/* }}} */
-static inline uint32_t zend_delayed_compile_begin(void) /* {{{ */
+static zend_always_inline uint32_t zend_delayed_oplines_stack_size(void)
{
return zend_stack_count(&CG(delayed_oplines_stack));
}
+
+static inline uint32_t zend_delayed_compile_begin(void) /* {{{ */
+{
+ return zend_delayed_oplines_stack_size();
+}
/* }}} */
static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
@@ -3140,6 +3145,7 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast,
uint32_t
/* We will throw if $this doesn't exist, so there's no need to emit a JMP_NULL
* check for a nullsafe access. */
} else {
+ uint32_t offset = zend_delayed_oplines_stack_size();
zend_short_circuiting_mark_inner(obj_ast);
opline = zend_delayed_compile_var(&obj_node, obj_ast, type, 0);
if (opline && (opline->opcode == ZEND_FETCH_DIM_W
@@ -3155,10 +3161,11 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast,
uint32_t
/* Flush delayed oplines */
zend_op *opline = NULL, *oplines = zend_stack_base(&CG(delayed_oplines_stack));
uint32_t var = obj_node.u.op.var;
- uint32_t count = zend_stack_count(&CG(delayed_oplines_stack));
+ uint32_t count = zend_delayed_oplines_stack_size();
uint32_t i = count;
- while (i > 0 && oplines[i-1].result_type == IS_TMP_VAR &&
oplines[i-1].result.var == var) {
+ /* Only consider the oplines delayed while compiling obj_ast. */
+ while (i > offset && oplines[i-1].result_type == IS_TMP_VAR &&
oplines[i-1].result.var == var) {
i--;
if (oplines[i].op1_type == IS_TMP_VAR) {
var = oplines[i].op1.var;