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

[email protected] (Arnaud Le Blanc)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-20T10:31:05+02:00

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

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix GH-23301: nested "yield from" repeats a value after "yield from []" (#23302)

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


Diff:

diff --git a/NEWS b/NEWS
index 790e4ef21dad..e058e33a9e90 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--
+<?php
+
+function inner() {
+    yield "B";
+}
+
+function middle() {
+    yield "A";
+    yield from inner();
+    yield "C";
+    yield from ["D"];
+}
+
+function delegate($gen) {
+    yield from $gen;
+}
+
+foreach (delegate(middle()) as $value) {
+    echo $value, "\n";
+}
+
+function tail($inner) {
+    yield from $inner;
+    yield from ["E"];
+}
+
+$middle = tail(inner());
+$outer = delegate($middle);
+
+var_dump($outer->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;
 }
 /* }}} */
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.