[php-src] PR #24117: Fix skipped catch block when switching fibers during exception unwinding

From: Date: Sun, 04 Oct 2026 14:25:47 +0000
Subject: [php-src] PR #24117: Fix skipped catch block when switching fibers during exception unwinding
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39129@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24117 Author: nicolas-grekas Destroying a suspended fiber while an exception unwinds can make the engine skip the catch block that should handle that exception: ```php <?php class D { public function __destruct() {} } function f() { $d = new D(); $fiber = new Fiber(static function () { Fiber::suspend(); }); $fiber->start(); throw new Exception('thrown in f()'); } function c() { try { f(); } catch (Exception $e) { echo "caught in c()\n"; } } c(); // Fatal error: Uncaught Exception: thrown in f() ``` zend_fiber_object_destroy() puts the pending exception aside while it resumes the fiber, but not EG(opline_before_exception), which the fiber overwrites. Because D::__destruct() ran first, the frame of c() is already at ZEND_HANDLE_EXCEPTION, so the zend_rethrow_exception() that follows i_free_compiled_variables() doesn't set it again and the exception is dispatched from the wrong op. gc_call_destructors_in_fiber() has the same issue when the GC runs during unwinding inside a fiber (8.4+). The fix saves and restores the pointer at both places, like zend_objects_destroy_object() and zend_generator_dtor_storage() already do (see GH-20183 and GH-20714 for the generator side). Saving it in zend_fiber_vm_state instead would also work, but backtraces taken in a GC destructor would then report a wrong line for the unwinding frames. Found while investigating a random failure on Symfony's CI, where an exception escaped Worker::run() when the GC destroyed suspended amphp fibers during unwinding (symfony/symfony#66615).

« previous php.git-pulls (#39129) next »