Skip to content

Fix uncaught_exceptions() not accounting for forced_unwind#337

Open
xokdvium wants to merge 1 commit into
boostorg:developfrom
xokdvium:uncaught-exceptions-forced-unwind
Open

Fix uncaught_exceptions() not accounting for forced_unwind#337
xokdvium wants to merge 1 commit into
boostorg:developfrom
xokdvium:uncaught-exceptions-forced-unwind

Conversation

@xokdvium

@xokdvium xokdvium commented Jul 21, 2026

Copy link
Copy Markdown

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.

TODO:

  • Add unit tests for this.
  • Think about performance implications of the catch block.

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.
@xokdvium
xokdvium force-pushed the uncaught-exceptions-forced-unwind branch from fd76812 to 5883212 Compare July 21, 2026 21:21
@xokdvium

Copy link
Copy Markdown
Author

Ok the new test surfaces the bug under the fcontext backend.

@xokdvium
xokdvium marked this pull request as ready for review July 21, 2026 21:24
Comment on lines +407 to +416
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();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if an exception is thrown on the switched-to context it would also need to be handled here - and that's probably in-scope. Happy to tackle this (maybe in a follow-up), but I've started by putting out the immediate fire under our feet in nix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant