[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-12T06:34:19-04:00

Commit: https://github.com/php/php-src/commit/636c1dd985fe6dfdf83d7d329932811264bdd839
Raw diff: https://github.com/php/php-src/commit/636c1dd985fe6dfdf83d7d329932811264bdd839.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix GH-15375: nested "yield from" skips items after valid()/next()

Changed paths:
  A  Zend/tests/generators/gh15375.phpt
  M  NEWS
  M  Zend/zend_generators.c


Diff:

diff --git a/NEWS b/NEWS
index d9f1281d594f..e569af4b6a0b 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.6.0beta2
 
+- Core:
+  . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
+    next() call on the inner generator). (iliaal)
+
 - PDO_PGSQL:
   . Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
     loop when cleaning up a fetch left in a COPY, a use-after-free when a
diff --git a/Zend/tests/generators/gh15375.phpt b/Zend/tests/generators/gh15375.phpt
new file mode 100644
index 000000000000..48919083f227
--- /dev/null
+++ b/Zend/tests/generators/gh15375.phpt
@@ -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
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index 52d409f2e69b..2bfbbf687d3c 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -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;
@@ -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;
 	}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.