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

From: Date: Thu, 08 Oct 2026 17:54:26 +0000
Subject: [php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-142100@lists.php.net to get a copy of this message
Author: ndossche (ndossche) Date: 2026-10-08T19:54:14+02:00 Commit: https://github.com/php/php-src/commit/b4f14f40ac4d1220ac8f74e6cedd01d92b7c917e Raw diff: https://github.com/php/php-src/commit/b4f14f40ac4d1220ac8f74e6cedd01d92b7c917e.diff Merge branch 'PHP-8.5' into PHP-8.6 * PHP-8.5: 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 c895aa7b1dd0..fcfee542f6b6 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 e9737dabedbf..fe8f978ff59c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2489,10 +2489,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) /* {{{ */ @@ -3212,6 +3217,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); zend_mark_const_object_fetch(obj_ast); opline = zend_delayed_compile_var(&obj_node, obj_ast, type, false); @@ -3228,10 +3234,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 (#142100) next »