itanium-cxx-abi / cxx-abi

C++ ABI Summary
508 stars 96 forks source link

Rules for nesting foreign exceptions are unclear #145

Open CAD97 opened 2 years ago

CAD97 commented 2 years ago

Concerning just the level 1 base [ABI-EH]. I haven't consulted the [psABI] directly, and perhaps it offers clarifications when interpreted together with [ABI-EH], but [ABI-EH] should ideally be sufficient on its own to use the unwinding runtime.

§1.2 Data Structures; Exception Header; exception_cleanup

_URC_FOREIGN_EXCEPTION_CAUGHT = 1: This indicates that a different runtime caught this exception. Nested foreign exceptions, or rethrowing a foreign exception, result in undefined behaviour. [source; emphasis mine]

§1.6.4 Rules for Correct Inter-Language Operation

The behavior is undefined in the following cases:

  • A __foreign_exception is active at the same time as another exception (either there is a nested exception while catching the foreign exception, or the foreign exception was itself nested). [source; emphasis mine]

This is unfortunately insufficiently clear at defining when an exception is considered nested, and at which operation behavior becomes undefined. There are at least three different scenarios:

  1. A personality routine is running for an exception of class $A$ and an exception of class $B$ is raised.
  2. A _URC_CONTINUE_UNWIND frame landing pad is running for an exception of class $A$ and an exception of class $B$ is raised.
  3. A _URC_HANDLER_FOUND frame landing pad is running for an exception of class $A$ and an exception of class $B$ is raised.

All three cases have the additional variable of if any intervening frame would register a handler for the newly raised exception, as well as if that handler is native or foreign to exception class $B$.

Case (1) of a personality routine raising an exception itself is likely problematic for other reasons. In C++, case (2) results from a throw within a destructor where that destructor is run by a cleanup landing pad for the prior exception, and case (3) results from a throw within a catch clause where $A$ is foreign to the C++ runtime (and thus not yet handled). In Rust, case (2) results from a panic! within a Drop::drop run by a cleanup landing pad where $A$ is not a Rust panic (that case would abort). Case (3) is impossible by construction; the catch equivalent does not run user code inside the handler landing pad.

I have one concrete ask for the specification: directly define when _Unwind_RaiseException is sound to call. By my reading, I think the answer is that _Unwind_RaiseException causes undefined behavior when

_Unwind_RaiseException is sound to call when

This operationally defines the nesting of foreign exceptions which produces undefined behavior in a way such that the undefined behavior can be prevented.