From 5883212311535a0046031d74d1568ae173c1e35b Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Tue, 21 Jul 2026 21:15:51 +0000 Subject: [PATCH] Fix uncaught_exceptions() not accounting for forced_unwind Unwound fibers would see std::uncaught_exceptions() == 0, while a forced_unwind exception is in "flight". This goes against the contract of std::uncaught_exceptions() that scope guards rely upon. Failing to report the correct number of uncaught exceptions (especially misreporting zero) will lead to scope guards to misbehave badly and skip running cleanup code which branches on whether the destructor is called during stack unwinding or not. This is because the "throw" would happen before the destructor is run on the fiber stack being switched to, but the increment would be clobbered by the destructor of manage_exception_state. I'm not sure what the contract of run ontop_fcontext is wrt to whether the the caller provided function can throw or not, but in my best understanding the forced_unwind mechanism is mostly internal and so is throwing from ontop_fcontext in the switched-to fiber. Thus, I've kept the catch block scoped to detail::forced_unwind. --- include/boost/context/fiber_fcontext.hpp | 37 +++++++++++++++++------- test/test_fiber.cpp | 24 +++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/include/boost/context/fiber_fcontext.hpp b/include/boost/context/fiber_fcontext.hpp index 543ba6cf..38476c9c 100644 --- a/include/boost/context/fiber_fcontext.hpp +++ b/include/boost/context/fiber_fcontext.hpp @@ -70,7 +70,9 @@ namespace context { namespace detail { // manage_exception_state is a dummy struct unless we have specific support -struct manage_exception_state {}; +struct manage_exception_state { + void from_forced_unwind() noexcept {} +}; } // namespace detail } // namespace context @@ -90,6 +92,11 @@ class manage_exception_state { BOOST_NOINLINE manage_exception_state() { exception_state_ = *__cxa_get_globals(); } + // Hack to account for the forced_unwind exception thrown in fiber_unwind + // that's run ontop before the destructor. + void from_forced_unwind() noexcept { + exception_state_.uncaughtExceptions += 1; + } BOOST_NOINLINE ~manage_exception_state() { *__cxa_get_globals() = exception_state_; } @@ -376,13 +383,18 @@ class fiber { BOOST_ASSERT( nullptr != fctx_); detail::manage_exception_state exstate; boost::ignore_unused(exstate); - return { detail::jump_fcontext( + try { + return { detail::jump_fcontext( #if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( fctx_, nullptr), + detail::exchange( fctx_, nullptr), #else - std::exchange( fctx_, nullptr), + std::exchange( fctx_, nullptr), #endif - nullptr).fctx }; + nullptr).fctx }; + } catch ( detail::forced_unwind const& ) { + exstate.from_forced_unwind(); + throw; + } } template< typename Fn > @@ -391,14 +403,19 @@ class fiber { detail::manage_exception_state exstate; boost::ignore_unused(exstate); auto p = std::forward< Fn >( fn); - return { detail::ontop_fcontext( + try { + return { detail::ontop_fcontext( #if defined(BOOST_NO_CXX14_STD_EXCHANGE) - detail::exchange( fctx_, nullptr), + detail::exchange( fctx_, nullptr), #else - std::exchange( fctx_, nullptr), + std::exchange( fctx_, nullptr), #endif - & p, - detail::fiber_ontop< fiber, decltype(p) >).fctx }; + & p, + detail::fiber_ontop< fiber, decltype(p) >).fctx }; + } catch ( detail::forced_unwind const& ) { + exstate.from_forced_unwind(); + throw; + } } explicit operator bool() const noexcept { diff --git a/test/test_fiber.cpp b/test/test_fiber.cpp index e5f1e9e7..0da605fe 100644 --- a/test/test_fiber.cpp +++ b/test/test_fiber.cpp @@ -510,6 +510,29 @@ void test_badcatch() { #endif } +void test_uncaught_exceptions() { + int i = 42; + { + ctx::fiber f{ + [&i](ctx::fiber && f ) { + struct scope_test { + int &i_; + scope_test( int &i) : + i_(i) { + } + ~scope_test() { + i_ = std::uncaught_exceptions(); + } + }; + scope_test scope( i); + return std::move( f).resume(); + i = 84; + }}; + f = std::move(f).resume(); + } + BOOST_CHECK_EQUAL( i, 1); +} + int main() { test_move(); @@ -529,6 +552,7 @@ int main() #endif test_goodcatch(); test_badcatch(); + test_uncaught_exceptions(); return boost::report_errors(); }