Commit b15c5972fbe for php.net

commit b15c5972fbe2ffbbe3d203c757101ab0f717fbdc
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Wed Mar 25 12:21:12 2026 -0400

    Fix GH-19983: GC assertion failure with fibers, generators and destructors

    When GC runs inside a fiber handling an exception (e.g. during
    zend_fiber_object_destroy), EG(exception) is set.
    gc_call_destructors_in_fiber() saved and cleared the exception after
    creating the destructor fiber. Since zend_call_function() returns early
    when EG(exception) is set, the destructor fiber's handler never ran,
    leaving DTOR_GARBAGE entries in the root buffer. On the next GC cycle,
    gc_collect_roots() hit an alignment assertion on these stale entries.

    Move remember_prev_exception() before the destructor fiber
    creation/resume so EG(exception) is cleared before zend_call_function()
    runs inside the fiber.

    Closes GH-21529

diff --git a/NEWS b/NEWS
index b7cc2595711..e86c5132350 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,9 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.4.21

+- Core:
+  . Fixed bug GH-19983 (GC assertion failure with fibers, generators and
+    destructors). (iliaal)

 09 Apr 2026, PHP 8.4.20

diff --git a/Zend/tests/fibers/gh19983.phpt b/Zend/tests/fibers/gh19983.phpt
new file mode 100644
index 00000000000..156edc39181
--- /dev/null
+++ b/Zend/tests/fibers/gh19983.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GH-19983 (GC Assertion Failure with fibers, generators and destructors)
+--SKIPIF--
+<?php if (PHP_INT_SIZE < 8) die("skip 64-bit only - fiber stacks exhaust 32-bit address space"); ?>
+--INI--
+memory_limit=128M
+--FILE--
+<?php
+class a
+{
+    function __destruct()
+    {
+        $gen = (function () {
+            $from = (function () {
+                $cv = [new a];
+                Fiber::suspend();
+            })();
+            yield from $from;
+        })();
+        $fiber = new Fiber(function () use ($gen, &$fiber) {
+            $gen->current();
+        });
+        $fiber->start();
+    }
+}
+new a;
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted%s
diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c
index 7b2ff49a736..ffd427eac5b 100644
--- a/Zend/zend_gc.c
+++ b/Zend/zend_gc.c
@@ -1893,13 +1893,15 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
 	GC_G(dtor_idx) = GC_FIRST_ROOT;
 	GC_G(dtor_end) = GC_G(first_unused);

+	zend_object *exception = NULL;
+	remember_prev_exception(&exception);
+
 	if (UNEXPECTED(!fiber)) {
 		fiber = gc_create_destructor_fiber();
 	} else {
 		zend_fiber_resume(fiber, NULL, NULL);
 	}

-	zend_object *exception = NULL;
 	remember_prev_exception(&exception);

 	for (;;) {