Commit 861d2b6c4e1 for php

commit 861d2b6c4e15d20f59181e797cb5c6f8797bfdcd
Author: lazerg <lazerg2@gmail.com>
Date:   Mon Oct 12 08:57:23 2026 +0500

    Fix GH-24264: Unwind/GracefulExit discarded in spl_iterators.c

    Closes GH-24268.

diff --git a/NEWS b/NEWS
index 4a3383c25a4..6ddb492d18b 100644
--- a/NEWS
+++ b/NEWS
@@ -153,6 +153,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-24488 (GC double-frees the SimpleXMLElement properties
     table). (evankford)

+- SPL:
+  . Fixed bug GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c).
+    (Lazizbek Ergashev)
+
 - Standard:
   . Fixed bug GH-24308 (Request shutdown calls closelog() even when syslog
     was not opened by PHP). (lazerg)
diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c
index 425bd99f37c..05bdfdb34ba 100644
--- a/ext/spl/spl_iterators.c
+++ b/ext/spl/spl_iterators.c
@@ -251,6 +251,13 @@ static void spl_recursive_it_get_current_key(zend_object_iterator *iter, zval *k
 	}
 }

+static bool spl_can_catch_exception(zend_long flags)
+{
+	return (flags & CIT_CATCH_GET_CHILD)
+		&& !zend_is_unwind_exit(EG(exception))
+		&& !zend_is_graceful_exit(EG(exception));
+}
+
 static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zval *zthis)
 {
 	zend_object_iterator      *iterator;
@@ -271,7 +278,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 			case RS_NEXT:
 				iterator->funcs->move_forward(iterator);
 				if (EG(exception)) {
-					if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+					if (!spl_can_catch_exception(object->flags)) {
 						return;
 					} else {
 						zend_clear_exception();
@@ -308,7 +315,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 					zend_call_method_with_0_params(obj, ce, cache, "haschildren", &retval);
 				}
 				if (EG(exception)) {
-					if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+					if (!spl_can_catch_exception(object->flags)) {
 						object->iterators[object->level].state = RS_NEXT;
 						return;
 					} else {
@@ -344,7 +351,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 				}
 				object->iterators[object->level].state = RS_NEXT;
 				if (EG(exception)) {
-					if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+					if (!spl_can_catch_exception(object->flags)) {
 						return;
 					} else {
 						zend_clear_exception();
@@ -373,7 +380,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 				}

 				if (EG(exception)) {
-					if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+					if (!spl_can_catch_exception(object->flags)) {
 						return;
 					} else {
 						zend_clear_exception();
@@ -417,7 +424,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 				if (object->beginChildren) {
 					zend_call_method_with_0_params(Z_OBJ_P(zthis), object->ce, &object->beginChildren, "beginchildren", NULL);
 					if (EG(exception)) {
-						if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+						if (!spl_can_catch_exception(object->flags)) {
 							return;
 						} else {
 							zend_clear_exception();
@@ -431,7 +438,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 			if (object->endChildren) {
 				zend_call_method_with_0_params(Z_OBJ_P(zthis), object->ce, &object->endChildren, "endchildren", NULL);
 				if (EG(exception)) {
-					if (!(object->flags & RIT_CATCH_GET_CHILD)) {
+					if (!spl_can_catch_exception(object->flags)) {
 						return;
 					} else {
 						zend_clear_exception();
@@ -2300,7 +2307,7 @@ static inline void spl_caching_it_next(spl_dual_it_object *intern)
 			zend_call_method_with_0_params(Z_OBJ(intern->inner.zobject), intern->inner.ce, NULL, "haschildren", &retval);
 			if (EG(exception)) {
 				zval_ptr_dtor(&retval);
-				if (intern->u.caching.flags & CIT_CATCH_GET_CHILD) {
+				if (spl_can_catch_exception(intern->u.caching.flags)) {
 					zend_clear_exception();
 				} else {
 					return;
@@ -2316,7 +2323,7 @@ static inline void spl_caching_it_next(spl_dual_it_object *intern)
 					zend_call_method_with_0_params(Z_OBJ(intern->inner.zobject), intern->inner.ce, NULL, "getchildren", &args[0]);
 					if (EG(exception)) {
 						zval_ptr_dtor(&args[0]);
-						if (intern->u.caching.flags & CIT_CATCH_GET_CHILD) {
+						if (spl_can_catch_exception(intern->u.caching.flags)) {
 							zend_clear_exception();
 						} else {
 							return;
@@ -2333,7 +2340,7 @@ static inline void spl_caching_it_next(spl_dual_it_object *intern)
 						);
 						zval_ptr_dtor(&args[0]);
 						if (is_initialized == FAILURE) {
-							if (intern->u.caching.flags & CIT_CATCH_GET_CHILD) {
+							if (spl_can_catch_exception(intern->u.caching.flags)) {
 								zend_clear_exception();
 							} else {
 								return;
diff --git a/ext/spl/tests/gh24264_1.phpt b/ext/spl/tests/gh24264_1.phpt
new file mode 100644
index 00000000000..dd0005a8158
--- /dev/null
+++ b/ext/spl/tests/gh24264_1.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c): RecursiveIteratorIterator hasChildren()
+--FILE--
+<?php
+
+class ReproIterator extends RecursiveArrayIterator {
+    public function hasChildren(): bool {
+        echo "exit\n";
+        exit(1);
+    }
+}
+
+$it = new RecursiveIteratorIterator(
+    new ReproIterator([1]),
+    RecursiveIteratorIterator::LEAVES_ONLY,
+    RecursiveIteratorIterator::CATCH_GET_CHILD,
+);
+
+foreach ($it as $_) {}
+
+echo "Not reached\n";
+?>
+--EXPECT--
+exit
diff --git a/ext/spl/tests/gh24264_2.phpt b/ext/spl/tests/gh24264_2.phpt
new file mode 100644
index 00000000000..d71c282a641
--- /dev/null
+++ b/ext/spl/tests/gh24264_2.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c): RecursiveCachingIterator hasChildren()
+--FILE--
+<?php
+
+class ReproIterator extends RecursiveArrayIterator {
+    public function hasChildren(): bool {
+        echo "exit\n";
+        exit(1);
+    }
+}
+
+$it = new RecursiveCachingIterator(
+    new ReproIterator([1]),
+    CachingIterator::CATCH_GET_CHILD,
+);
+
+foreach ($it as $_) {}
+
+echo "Not reached\n";
+?>
+--EXPECT--
+exit
diff --git a/ext/spl/tests/gh24264_3.phpt b/ext/spl/tests/gh24264_3.phpt
new file mode 100644
index 00000000000..985ef5399e1
--- /dev/null
+++ b/ext/spl/tests/gh24264_3.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c): RecursiveIteratorIterator getChildren()
+--FILE--
+<?php
+
+class ReproIterator extends RecursiveArrayIterator {
+    public function getChildren(): ?RecursiveArrayIterator {
+        echo "exit\n";
+        exit(1);
+    }
+}
+
+$it = new RecursiveIteratorIterator(
+    new ReproIterator([[1]]),
+    RecursiveIteratorIterator::LEAVES_ONLY,
+    RecursiveIteratorIterator::CATCH_GET_CHILD,
+);
+
+foreach ($it as $_) {}
+
+echo "Not reached\n";
+?>
+--EXPECT--
+exit
diff --git a/ext/spl/tests/gh24264_4.phpt b/ext/spl/tests/gh24264_4.phpt
new file mode 100644
index 00000000000..b59950bc2a5
--- /dev/null
+++ b/ext/spl/tests/gh24264_4.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c): RecursiveCachingIterator getChildren()
+--FILE--
+<?php
+
+class ReproIterator extends RecursiveArrayIterator {
+    public function getChildren(): ?RecursiveArrayIterator {
+        echo "exit\n";
+        exit(1);
+    }
+}
+
+$it = new RecursiveCachingIterator(
+    new ReproIterator([[1]]),
+    CachingIterator::CATCH_GET_CHILD,
+);
+
+foreach ($it as $_) {}
+
+echo "Not reached\n";
+?>
+--EXPECT--
+exit
diff --git a/ext/spl/tests/gh24264_5.phpt b/ext/spl/tests/gh24264_5.phpt
new file mode 100644
index 00000000000..620ee4a4340
--- /dev/null
+++ b/ext/spl/tests/gh24264_5.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c): destroyed fiber suspended in getChildren()
+--FILE--
+<?php
+
+class ReproIterator extends RecursiveArrayIterator {
+    public function getChildren(): ?RecursiveArrayIterator {
+        try {
+            Fiber::suspend();
+        } finally {
+            echo "finally\n";
+        }
+    }
+}
+
+$fiber = new Fiber(function () {
+    $it = new RecursiveIteratorIterator(
+        new ReproIterator([[1], [2]]),
+        RecursiveIteratorIterator::LEAVES_ONLY,
+        RecursiveIteratorIterator::CATCH_GET_CHILD,
+    );
+
+    foreach ($it as $_) {}
+
+    echo "Not reached\n";
+});
+
+$fiber->start();
+unset($fiber);
+
+echo "Done\n";
+?>
+--EXPECT--
+finally
+Done