[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