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