diff --git a/NEWS b/NEWS index ecb4d105e917..ee3bebda09ee 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) - Opcache: . Fixed opcache.protect_memory race under ZTS. (realFlowControl) 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; } /* }}} */