Per C++17 [except.spec], 'throw()' has become equivalent to
'noexcept', and should therefore call std::terminate, not
std::unexpected.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
What's the motivation for this change? I believe the current behavior is still conforming: set_unexpected is no longer (officially) part of the standard library (though it still exists as a zombie name), and the default unexpected handler calls terminate, so calling unexpected rather than terminate should have the same effect, except in non-conforming programs that call std::set_unexpected anyway (and for such programs, calling unexpected seems like the behavior the programmer would expect). Do we generate better code if we call terminate rather than unexpected?
Today: no, we don't.
But, I'm planning to propose further changes to improve noexcept codegen in Clang -- which is currently quite bad, compared to what it should be, because it's effectively following the same rules as legacy throw(). This change allows those future optimizations to apply to throw() as well, in C++17 mode, which is the desirable outcome.
Peanut gallery says: It seems like Proto->canThrow() is already returning the correct answer for C++17-and-later: a function declared throw() cannot throw. So from the POV of C++17-and-later, this could be a simple patch:
- if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { + if (Proto->canThrow() == CT_Cannot) {
and the only reason we can't do this is because C++14-and-earlier needs a different behavior.
Meanwhile, it looks as if every other use of isNoexceptExceptionSpec (or its inverse isDynamicExceptionSpec) is related to pretty-printing the exception specification, and isn't used for semantics at all.
Perhaps a cleaner way to preserve the current special case for C++14-and-earlier is to add a "language version" parameter to canThrow, and/or introduce an isEssentiallyNoexcept() helper:
- if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { + if (Proto->isEssentiallyNoexcept(getLangOpts())) {
I see. It seems inconsistent that throw(X) would still call unexpected but that throw() would call terminate, but I suppose in the throw() case there is really not much interesting that an unexpected_handler can do other than (take some logging action and) terminate the program -- in particular, it can't do exception translation. So maybe the inconsistency is not a big deal, and it's more important to get the better code generation, especially given how rare throw(X) is compared to throw(). OK, I think I'm convinced that this is the best direction.
clang/lib/CodeGen/CGException.cpp | ||
---|---|---|
480–487 | Maybe the logic would be clearer if we swap the terminate and unexpected cases: if (EST == EST_Dynamic || (!getLangOpts().CPlusPlus17 && EST == EST_DynamicNone)) { // Prepare to call unexpected } else if (Proto->canThrow() == CT_Cannot) { // Prepare to call terminate } This would keep the syntactic checks of EST separated from the semantic checks of canThrow. |
Well, "throw(X)" is not even permitted by C++17. In Clang, we allow that it only if you turn off the default-error warning option. Given that, I'm not too worried about it keeping the legacy behavior, while "throw()" gets the new behavior.
clang/lib/CodeGen/CGException.cpp | ||
---|---|---|
480–487 | Agreed, that's better. I'll make that change and submit. |
Maybe the logic would be clearer if we swap the terminate and unexpected cases:
This would keep the syntactic checks of EST separated from the semantic checks of canThrow.