Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with
INT_MAX year). (arshidkv12)

- Core:
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)

- DBA:
. Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)

Expand Down
89 changes: 89 additions & 0 deletions Zend/tests/generators/gh15375.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
--TEST--
GH-15375 (Nested "yield from" skips items after valid()/next() on the inner generator)
--FILE--
<?php

function arrayProvider() {
yield ['one', 'two', 'three'];
yield ['four', 'five', 'six'];
yield ['seven', 'eight', 'nine'];
}

function iterateValues(array $array) {
foreach ($array as $value) {
yield $value;
}
}

function withValid() {
foreach (arrayProvider() as $array) {
$iterator = iterateValues($array);
if ($iterator->valid()) {
yield from $iterator;
}
}
}

function withNext() {
foreach (arrayProvider() as $array) {
$iterator = iterateValues($array);
$iterator->next();
yield from $iterator;
}
}

function outer(Generator $inner) {
yield from $inner;
}

echo "valid():\n";
foreach (outer(withValid()) as $s) {
echo $s, "\n";
}

echo "next():\n";
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).
echo "shared primed:\n";
function counter() {
yield 1;
yield 2;
}
$gen1 = counter();
$gen1->valid();
$gen2 = outer($gen1);
$gen3 = outer($gen2);
echo "gen3 current: ", $gen3->current(), "\n";
$gen2->next();
echo "gen2 current: ", $gen2->current(), "\n";
$gen2->next();
echo "gen2 current: ", $gen2->current(), "\n";

?>
--EXPECT--
valid():
one
two
three
four
five
six
seven
eight
nine
next():
two
three
five
six
eight
nine
shared primed:
gen3 current: 1
gen2 current: 1
gen2 current: 2
13 changes: 11 additions & 2 deletions Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,13 +768,15 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
return;
}

zend_generator *delegator = orig_generator;

try_again:
if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
zend_throw_error(NULL, "Cannot resume an already running generator");
return;
}

if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
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;
Expand Down Expand Up @@ -881,12 +883,19 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
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;
}
}

/* yield from was used, try another resume. */
if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM))) {
if (UNEXPECTED(generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM)) {
delegator = generator;
generator = zend_generator_get_current(orig_generator);
goto try_again;
}
if (UNEXPECTED(generator != orig_generator && !Z_ISUNDEF(generator->retval))) {
delegator = orig_generator;
generator = zend_generator_get_current(orig_generator);
goto try_again;
}
Expand Down
Loading