[cfe-dev] clang, noexcept and terminate

Howard Hinnant hhinnant at apple.com
Mon Jan 30 13:11:45 PST 2012


Consider this program:

#include <stdexcept>

int func2()
{
    throw std::runtime_error("Because I feel like throwing");
    return 0;
}

int func1() throw()
{
    return func2();
}

int main()
{
    return func1();
}

Using the libc++abi I'm working on (http://libcxxabi.llvm.org/), this program outputs:

libc++abi.dylib: terminating with unexpected exception of type St13runtime_error: Because I feel like throwing

I consider this pretty good.  The crash log says that I'm terminating via unexpected, gives the type of the exception, and prints out the what() of the std::runtime_error.

Now if I change the throw() to noexcept, the same program prints out:

libc++abi.dylib: terminating

That's not nearly as good.  I would much prefer to print out:

libc++abi.dylib: terminating with uncaught exception of type St13runtime_error: Because I feel like throwing

What is keeping me from doing that?

When clang sees an exception spec:

int func1() noexcept
{...}

It creates an action table entry in the exception handling table.  This sets up a call to:

  void __cxa_call_unexpected(void* arg);

in the case of a throw(), where arg is a _Unwind_Exception*.

But in the case of noexcept it sets up a call to:

  void std::terminate();

Now the FDIS says we should call terminate.  But I would prefer:

  void __cxa_call_terminate(void* arg);

where arg is a _Unwind_Exception*, instead.

1.  Calls to terminate() from the implementation during stack unwinding are considered exception handlers:

[except.handle]/p7:

> Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw.

This means, among other things, that uncaught_exceptions() should return false from within a terminate handler.  There are also other ways for portable C++ code to detect whether or not it is within a handler from within a terminate handler.

That's the technical justification for this change (conforming).

However the real motivation is so that the default terminate_handler can print out a better error message!  You see, if you're in a handler, it means that somebody has called:

   void* __cxa_begin_catch(void* unwind_arg);

and that means the caught exception is on a stack in the libc++abi library.  Prior to __cxa_begin_catch, the exception is not on this stack.

__cxa_call_unexpected() calls __cxa_begin_catch first thing, and then later calls terminate, and THAT is how I'm getting a good error message for the throw() case.  But the compiler goes straight to terminate() without a call to __cxa_begin_catch in the noexcept case.

Wait, it gets worse.  The compiler can't just do:

    __cxa_begin_catch(unwind_arg);
    std::terminate();

In C++98/03, the terminate_handler that gets used in a call to terminate during exception unwinding is the terminate_handler in effect at the time the exception is thrown, not when the implementation "catches" the exception and calls terminate.  libc++abi-like libraries handle this by storing the terminate_handler and unexpected_handler in the thrown exception object at the time the exception object is created.

Wait, it gets worse.  These rules were accidentally changed in C++11.  There's a LWG issue on it:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2111

This accidental change will almost certainly be reverted.  So now the compiler, on noexcept violation needs to:

    __cxa_begin_catch(unwind_arg);
    // get the terminate_handler out of unwind_arg
    // call a variation of std::terminate where you can specify the terminate_handler

Getting the terminate_handler out of unwind_arg is complicated by the fact that the exception may be foreign.  I.e. it may not have a terminate_handler stored in it.  In which case the current terminate_handler is fine.

The next problem is that our (Apple's) current libc++abi.dylib or libc++.dylib don't currently export a way for the compiler to call terminate with a custom terminate_handler.

That last problem is also the problem with having the compiler just call this new function which would handle all of these nasty details:

  void __cxa_call_terminate(void* arg);  // do the right thing and get better error messages!

So:

Can we transition to a compiler that calls __cxa_call_terminate on noexcept?  And how would that transition be managed?

Here is how I would implement it:

__attribute__((noreturn))
void
__cxa_call_terminate(void* arg)
{
    _Unwind_Exception* unwind_exception = static_cast<_Unwind_Exception*>(arg);
    if (unwind_exception == 0)
        std::terminate();
    __cxa_begin_catch(unwind_exception);
    bool native_exception = (unwind_exception->exception_class & 0xFFFFFF00) == 0x432B2B00;
    if (native_exception)
    {
        __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception+1) - 1;
        std::__terminate(exception_header->terminateHandler);
    }
    std::terminate();
}

Howard




More information about the cfe-dev mailing list