From 7b34e84bdb45e3856c5a47ae975ac901eeabf250 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Thu, 20 Aug 2026 13:28:54 +0500 Subject: [PATCH 1/2] Fix GH-23301: nested "yield from" repeats a value after "yield from []" (#23302) The ZEND_GENERATOR_DO_INIT flag is added to a delegator to indicate that its next resume shouldn't advance an already initialized delegatee. The flag must be cleared on that next resume, but this was done on the wrong generator in some cases. Fixes GH-23301. Co-Authored-By: Arnaud Le Blanc --- NEWS | 2 + .../backtrace_multi_yield_from.phpt | 4 +- Zend/tests/generators/gh15375.phpt | 18 ++++---- Zend/tests/generators/gh23301.phpt | 44 +++++++++++++++++++ Zend/zend_generators.c | 15 ++++--- 5 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 Zend/tests/generators/gh23301.phpt diff --git a/NEWS b/NEWS index e273a86dfca6..a672360f0aef 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or next() call on the inner generator). (iliaal) + . Fixed bug GH-23301 (Nested "yield from" yields a value twice when the + middle generator delegates again). (Lazizbek Ergashev) - DOM: . Fixed a use-after-free when cloning a DOMNameSpaceNode after diff --git a/Zend/tests/generators/backtrace_multi_yield_from.phpt b/Zend/tests/generators/backtrace_multi_yield_from.phpt index 4fbaa2f05bb4..2cb898b3aac2 100644 --- a/Zend/tests/generators/backtrace_multi_yield_from.phpt +++ b/Zend/tests/generators/backtrace_multi_yield_from.phpt @@ -25,8 +25,8 @@ var_dump($gen2->current()); ?> --EXPECTF-- int(1) -int(1) #0 %s(10): gen() #1 [internal function]: from(Object(Generator)) -#2 %s(19): Generator->next() +#2 %s(17): Generator->next() int(2) +NULL diff --git a/Zend/tests/generators/gh15375.phpt b/Zend/tests/generators/gh15375.phpt index 48919083f227..f0a2fe56c16c 100644 --- a/Zend/tests/generators/gh15375.phpt +++ b/Zend/tests/generators/gh15375.phpt @@ -46,9 +46,9 @@ foreach (outer(withNext()) as $s) { echo $s, "\n"; } -// A shared, pre-primed generator consumed through two nested "yield from" -// levels must still present its current value once to each consumer (the fix -// must not over-clear the middle level's first-touch). +// Reading a pre-primed generator through two nested "yield from" levels primes +// the whole chain but advances nothing. A later next() on a middle level then +// advances the shared generator, like any other next() would. echo "shared primed:\n"; function counter() { yield 1; @@ -58,11 +58,11 @@ $gen1 = counter(); $gen1->valid(); $gen2 = outer($gen1); $gen3 = outer($gen2); -echo "gen3 current: ", $gen3->current(), "\n"; +var_dump($gen3->current()); $gen2->next(); -echo "gen2 current: ", $gen2->current(), "\n"; +var_dump($gen2->current()); $gen2->next(); -echo "gen2 current: ", $gen2->current(), "\n"; +var_dump($gen2->current()); ?> --EXPECT-- @@ -84,6 +84,6 @@ six eight nine shared primed: -gen3 current: 1 -gen2 current: 1 -gen2 current: 2 +int(1) +int(2) +NULL diff --git a/Zend/tests/generators/gh23301.phpt b/Zend/tests/generators/gh23301.phpt new file mode 100644 index 000000000000..02aaf2fe2022 --- /dev/null +++ b/Zend/tests/generators/gh23301.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-23301 (Nested "yield from" yields a value twice when the middle generator delegates again) +--FILE-- +current()); +$middle->next(); +var_dump($middle->current()); + +?> +--EXPECT-- +A +B +C +D +string(1) "B" +string(1) "E" diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 83815773b912..ef645f8f9a3a 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -776,10 +776,12 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ return; } - if (UNEXPECTED((delegator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) { - /* We must not advance Generator if we yield from a Generator being currently run */ - orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; - return; + if (UNEXPECTED((delegator->flags & ZEND_GENERATOR_DO_INIT) != 0)) { + delegator->flags &= ~ZEND_GENERATOR_DO_INIT; + if (UNEXPECTED(!Z_ISUNDEF(generator->value))) { + /* We must not advance an already initialized delegate on first resumption */ + return; + } } if (EG(active_fiber)) { @@ -819,7 +821,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ EG(current_execute_data) = original_execute_data; EG(jit_trace_num) = original_jit_trace_num; - orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); + orig_generator->flags &= ~ZEND_GENERATOR_IN_FIBER; generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); return; } @@ -882,7 +884,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } else { generator = zend_generator_get_current(orig_generator); zend_generator_throw_exception(generator, NULL); - orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; delegator = orig_generator; goto try_again; } @@ -900,7 +901,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ goto try_again; } - orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); + orig_generator->flags &= ~ZEND_GENERATOR_IN_FIBER; } /* }}} */ From 1ac495b13785ce53d3fbe6f8812d331b97b0cf47 Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:25:06 +0200 Subject: [PATCH 2/2] [skip ci] UPGRADING: add missing RFC link for namespace as constant name (#23389) Co-authored-by: jrfnl --- UPGRADING | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING b/UPGRADING index 67024bad7e66..a99299098f98 100644 --- a/UPGRADING +++ b/UPGRADING @@ -462,6 +462,7 @@ PHP 8.6 UPGRADE NOTES - Core: . Using "namespace" as a class constant name is deprecated. + RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_using_namespace_as_a_class_constant_name . Using the return statement in a finally block is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_returning_from_a_finally_block . Specifying a return type of array|null / ?array for __debugInfo() is now