clang, noexcept and terminate

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

These are really interesting points. I have two responses.

The first is that this would be a change in the generic code-generation patterns of the compiler, and it needs to be proposed on the Itanium ABI list (cxx-abi-dev).

The second is that it poses significant backward-compatibility problems, because no existing runtime actually provides this function. That's not completely damning, but for example, on Darwin it means we'd probably have to have a compiler-rt-like library that provides a default implementation (maybe one just calling std::terminate()) when system doesn't provide anything better. That's a lot of complexity for this, although it doesn't mean we shouldn't do it.

John.

I think I've changed my mind on this. With a modified std::terminate() that detected caught-but-unhandled exceptions, and called the right handler based on that detection, I tried to write a test case that would get the wrong answer if we just did the above, and I'm having trouble doing so. The above answer is easy and ABI compatible.

clang just needs to start emitting a call to __cxa_begin_catch prior to calling terminate. If I'm wrong, and there is a test case where this doesn't work, such code is going to be vanishingly rare, and far rarer than code that is broken by our current state: not calling __cxa_begin_catch prior to calling terminate.

Here's a test case that I would like to run:

#include <iostream>
#include <stdexcept>
#include <exception>
#include <cstdlib>

void handler1()
{
    std::cout << "handler 1\n";
    std::abort();
}

void handler2()
{
    std::cout << "handler 2\n";
    std::abort();
}

struct A
{
    ~A() {std::set_terminate(handler2);}
};

void f2()
{
    std::set_terminate(handler1);
    throw std::runtime_error("throw in f2");
}

void f1() throw()
{
    A a;
    f2();
}

int main()
{
    f1();
}

Whether f1() has a noexcept or a throw(), the output should be:

handler 1

A wrong output is:

handler 2

(for either noexcept or throw())

I've just checked in a new std::terminate in libc++abi that will make this possible *if* clang calls __cxa_begin_catch prior to calling std::terminate (revision 149329).

I've filed:

http://llvm.org/bugs/show_bug.cgi?id=11893

to track this work.

Howard