[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

From: 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;

« previous php.cvs (#142102) next »