This is an archive of the discontinued LLVM Phabricator instance.

[ItaniumCXXABI] Add -fassume-nothrow-exception-dtor to assume that all exception objects' destructors are non-throwing
ClosedPublic

Authored by MaskRay on Aug 29 2021, 11:36 PM.

Details

Summary

Link: https://lists.llvm.org/pipermail/cfe-dev/2021-August/068740.html ("[Exception Handling] Could we mark __cxa_end_catch as nounwind conditionally?"
Link: https://github.com/llvm/llvm-project/issues/57375

A catch handler calls __cxa_begin_catch and __cxa_end_catch. For a catch-all
clause or a catch clause matching a record type, we:

  • assume that the exception object may have a throwing destructor
  • emit invoke void @__cxa_end_catch (as the call is not marked as the nounwind attribute).
  • emit a landing pad to destroy local variables and call _Unwind_Resume
struct A { ~A(); };
struct B { int x; };
void opaque();
void foo() {
  A a;
  try { opaque(); } catch (...) { } // the exception object has an unknown type and may throw
  try { opaque(); } catch (B b) { } // B::~B is nothrow, but we do not utilize this
}

Per C++ [dcl.fct.def.coroutine], a coroutine's function body implies a catch (...).
Our code generation pessimizes even simple code, like:

UserFacing foo() {
  A a;
  opaque();
  co_return;
  // For `invoke void @__cxa_end_catch()`, the landing pad destroys the
  // promise_type and deletes the coro frame.
}

Throwing destructors are typically discouraged. In many environments, the
destructors of exception objects are guaranteed to never throw, making our
conservative code generation approach seem wasteful.

Furthermore, throwing destructors tend not to work well in practice:

  • GCC does not emit call site records for the region containing __cxa_end_catch. This has been a long time, since 2000.
  • If a catch-all clause catches an exception object that throws, both GCC and Clang using libstdc++ leak the allocated exception object.

To avoid code generation pessimization, add an opt-in driver option
-fassume-nothrow-exception-dtor to assume that __cxa_end_catch calls have the
nounwind attribute. This implies that thrown exception objects' destructors
will never throw.

To detect misuses, diagnose throw expressions with a potentially-throwing
destructor. Technically, it is possible that a potentially-throwing destructor
never throws when called transitively by __cxa_end_catch, but these cases seem
rare enough to justify a relaxed mode.

Diff Detail

Event Timeline

MaskRay requested review of this revision.Aug 29 2021, 11:36 PM
MaskRay created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptAug 29 2021, 11:36 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
MaskRay edited the summary of this revision. (Show Details)Aug 29 2021, 11:43 PM
MaskRay edited the summary of this revision. (Show Details)
MaskRay edited the summary of this revision. (Show Details)

Thanks for looking into this!
I am not familiar with exception handling. But from the comments, I worried that if it is a problem if there are exceptions whose destructor could throw (although it is not usual nor good).

MaskRay edited the summary of this revision. (Show Details)Aug 29 2021, 11:50 PM
MaskRay edited the summary of this revision. (Show Details)Aug 29 2021, 11:56 PM

Thanks for looking into this!
I am not familiar with exception handling. But from the comments, I worried that if it is a problem if there are exceptions whose destructor could throw (although it is not usual nor good).

GCC does not emit call site records for the __cxa_end_catch region, so I believe it makes the same assumption.
Setting nounwind should be safe...

MaskRay retitled this revision from [ItaniumCXXABI] Set nounwind on __cxa_begin_catch/__cxa_end_catch nounwind to [ItaniumCXXABI] Set nounwind on __cxa_begin_catch/__cxa_end_catch.Aug 30 2021, 12:00 AM
MaskRay edited the summary of this revision. (Show Details)
ChuanqiXu accepted this revision.Aug 30 2021, 12:01 AM

Thanks for looking into this!
I am not familiar with exception handling. But from the comments, I worried that if it is a problem if there are exceptions whose destructor could throw (although it is not usual nor good).

GCC does not emit call site records for the __cxa_end_catch region, so I believe it makes the same assumption.
Setting nounwind should be safe...

Make sense to me. Thanks for looking into this!

This revision is now accepted and ready to land.Aug 30 2021, 12:01 AM

I confirm that GCC has had this behavior since 2000 (3.0).

ChuanqiXu added inline comments.Aug 30 2021, 1:00 AM
clang/lib/CodeGen/ItaniumCXXABI.cpp
4495–4508

If __cxa_end_catch is nounwind always now, do we need to refactor this one? Like:

struct CallEndCatch final : EHScopeStack::Cleanup {
    CallEndCatch() {}

    void Emit(CodeGenFunction &CGF, Flags flags) override {
      CGF.EmitNounwindRuntimeCall(getEndCatchFn(CGF.CGM));
    }
  };

Yes, this seems extremely reasonable; it was really just an oversight. Patch is semantically fine, although what we do in several other places is use EmitNounwindRuntimeCall to emit the call, and I think I'd like to do that here: I'd like to eventually eliminate EmitRuntimeCall entirely in favor of expecting the client to be explicit about whether the runtime call is allowed to throw.

Although... actually, it occurs to me that there is a reason __cxa_end_catch *could* throw in the abstract, which is that the exception destructor could throw. My first instinct is that this should call std::terminate, and if so, the runtime should handle that; but I don't see anything in either the standard or the Itanium ABI that directly supports that.

I'm not really sure what the standard expects to happen if an exception destructor throws. The standard tells us when the destruction happens — the latest point it can — but that clause doesn't mention exceptions from the destructor. If there's a specific clause on this, I can't find it. [except.throw]p7 says that "if the exception handling mechanism handling an uncaught exception directly invokes a function that exits via an exception, the function std::terminate is called", which is meant to cover exceptions thrown when initializing a catch variable. Once the catch clause is entered, though, the exception is considered caught unless it's rethrown, so this clause doesn't apply when destroying the exception at the end of the clause. If the catch variable's destructor throws, that seems to be specified to unwind normally (including destroying the exception, and if the destructor throws at that point then std::terminate gets called, as normal for exceptions during unwinding).

Neither libc++abi nor libsupc++ seem to handle the possibility of the exception destructor throwing when destroying exceptions; they'll both happily leak the exception object if it does. That's reasonable if the assumption is that the destructor function passed to __cxa_throw never throws, which is itself reasonable for the frontend to ensure if indeed the language rule is that std::terminate is supposed to be called. Clang does not actually do that, though: it just passes the address of the destructor, which would only be legal if the destructor was noexcept (which is, of course, now the default).

So tentatively I think this is okay, but it looks like it's uncovered some bugs in our handling of throwing destructors for exceptions.

MaskRay updated this revision to Diff 369467.Aug 30 2021, 9:39 AM
MaskRay retitled this revision from [ItaniumCXXABI] Set nounwind on __cxa_begin_catch/__cxa_end_catch to [ItaniumCXXABI] Make __cxa_end_catch calls unconditionally nounwind.
MaskRay edited the summary of this revision. (Show Details)

set nounwind in call sites

Peanut gallery says: Does this godbolt demonstrate the situation where "destroying an exception can throw an exception"?
https://godbolt.org/z/Ghjz53rrj
You're not proposing to change the behavior of this program, are you?
(If you're proposing to change it: I don't think you should. If you're not proposing to change it, but rather the issue is something subtler: never mind me, then.)

Well, I'm saying two things. First, it is not clear to me what the expected behavior of that code is under the standard. The fact that it appears to work in one particular implementation is not in any way conclusive; we have to look at the specification. Second, I think it only appears to work: looking at the runtime code in both libc++abi and libsupc++, it leaks the memory of the exception object, which it's clearly not allowed to do. You should be able to fairly easy prove that by looking at heap usage if you do it in a loop.

Well, I'm saying two things. First, it is not clear to me what the expected behavior of that code is under the standard. The fact that it appears to work in one particular implementation is not in any way conclusive; we have to look at the specification.

Aha, I had thought it worked on "both Clang and GCC," but now I see that Godbolt uses libsupc++ for both Clang and GCC (and that's where the issue is). Switching to a branch that uses libc++abi, I see that libc++abi just calls std::terminate in this situation: https://godbolt.org/z/4s8aMvr3K

Second, I think it only appears to work: looking at the runtime code in both libc++abi and libsupc++, it leaks the memory of the exception object, which it's clearly not allowed to do. You should be able to fairly easy prove that by looking at heap usage if you do it in a loop.

Yep, memory leak confirmed. (Via the same Godbolt: https://godbolt.org/z/4s8aMvr3K ) So okay, never mind me.

MaskRay planned changes to this revision.Aug 30 2021, 11:23 AM

I'm not really sure what the standard expects to happen if an exception destructor throws. The standard tells us when the destruction happens — the latest point it can — but that clause doesn't mention exceptions from the destructor. If there's a specific clause on this, I can't find it. [except.throw]p7 says that "if the exception handling mechanism handling an uncaught exception directly invokes a function that exits via an exception, the function std::terminate is called", which is meant to cover exceptions thrown when initializing a catch variable. Once the catch clause is entered, though, the exception is considered caught unless it's rethrown, so this clause doesn't apply when destroying the exception at the end of the clause.

Scanning through the standard this to me also looks like an overlooked corner case in the standard (TBF this is very corner case).

If the catch variable's destructor throws, that seems to be specified to unwind normally (including destroying the exception, and if the destructor throws at that point then std::terminate gets called, as normal for exceptions during unwinding).

In this case, the destructor is throwing during normal scope exit so I don't think terminate behavior from [except.throw]p7 is enforced since we're not currently handling an uncaught exception. The handler is already active since we're past the initialization of the catch. Given that, I think this is akin to the example in [except.ctor]p2 and if the destructor is noexcept(false) should trigger a proper unwind like how an exception in the destructor of a simple automatic variable inside the handler scope would also do.

I'm not really sure what the standard expects to happen if an exception destructor throws. The standard tells us when the destruction happens — the latest point it can — but that clause doesn't mention exceptions from the destructor. If there's a specific clause on this, I can't find it. [except.throw]p7 says that "if the exception handling mechanism handling an uncaught exception directly invokes a function that exits via an exception, the function std::terminate is called", which is meant to cover exceptions thrown when initializing a catch variable. Once the catch clause is entered, though, the exception is considered caught unless it's rethrown, so this clause doesn't apply when destroying the exception at the end of the clause.

Scanning through the standard this to me also looks like an overlooked corner case in the standard (TBF this is very corner case).

If the catch variable's destructor throws, that seems to be specified to unwind normally (including destroying the exception, and if the destructor throws at that point then std::terminate gets called, as normal for exceptions during unwinding).

In this case, the destructor is throwing during normal scope exit so I don't think terminate behavior from [except.throw]p7 is enforced since we're not currently handling an uncaught exception. The handler is already active since we're past the initialization of the catch. Given that, I think this is akin to the example in [except.ctor]p2 and if the destructor is noexcept(false) should trigger a proper unwind like how an exception in the destructor of a simple automatic variable inside the handler scope would also do.

Yeah, I think this is the most natural interpretation of the current standard. But that would be a very unfortunate rule, because people who write catch (...) {} do reasonably expect that that code will never throw. In fact, it would be quite difficult — perhaps impossible — to write code that actually swallowed all exceptions: even try { try { foo() } catch(...) {} } catch (...) {} wouldn't work, because you could throw an exception whose destructor throws an exception whose destructor throws an exception ad infinitum.

Yeah, I think this is the most natural interpretation of the current standard. But that would be a very unfortunate rule, because people who write catch (...) {} do reasonably expect that that code will never throw. In fact, it would be quite difficult — perhaps impossible — to write code that actually swallowed all exceptions: even try { try { foo() } catch(...) {} } catch (...) {} wouldn't work, because you could throw an exception whose destructor throws an exception whose destructor throws an exception ad infinitum.

Yeah it's not great and also something that practically will never happen. I think terminate guards are the only thing that really swallows all exceptions except here you can't guard the catch variable destructor unless you want to change up and depend on library implementation. My immediate thought is something like catch(...) noexcept {} to express this but it's a solution to a problem that really shouldn't exist.

Yeah, I think this is the most natural interpretation of the current standard. But that would be a very unfortunate rule, because people who write catch (...) {} do reasonably expect that that code will never throw. In fact, it would be quite difficult — perhaps impossible — to write code that actually swallowed all exceptions: even try { try { foo() } catch(...) {} } catch (...) {} wouldn't work, because you could throw an exception whose destructor throws an exception whose destructor throws an exception ad infinitum.

Yeah it's not great and also something that practically will never happen. I think terminate guards are the only thing that really swallows all exceptions except here you can't guard the catch variable destructor unless you want to change up and depend on library implementation. My immediate thought is something like catch(...) noexcept {} to express this but it's a solution to a problem that really shouldn't exist.

Well, I think catch (...) { std::terminate(); } should work to express that outer catch, if only because the end of the catch is never reached. But yeah, I agree that this is clearly a problem that shouldn't exist.

We might be able to require exception types to have noexcept destructors. Formally, it would have source compatibility / language-version incompatibility implications, but in practice I doubt it would cause many problems, especially if it was just a "if this actually throws it'll have UB" warning in pre-C++23 language modes for throwing types with explicitly noexcept destructors.

I've forwarded the question here onto CWG, with Arthur's example and the suggestion that maybe we shouldn't allow throwing an exception with a non-noexcept destructor.

Thanks, Richard. Fangrui, I think we can't do anything on this patch without a CWG decision about the right semantics, so this will have to sit.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Is the committee comfortable with implementations causing potentially-throwing exception destructors to trigger std::terminate? I understand that this is a weird question because it implies the use of / interoperation with an old language standard, but we do need to know how to compile in C++98 mode, and we may need to demote this to a warning pre-C++23. If it's not an error in old modes, but the committee doesn't approve of calling std::terminate if it happens, then we still need to compile catch (...) as throwing in case we're interoperating.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Is the committee comfortable with implementations causing potentially-throwing exception destructors to trigger std::terminate? I understand that this is a weird question because it implies the use of / interoperation with an old language standard, but we do need to know how to compile in C++98 mode, and we may need to demote this to a warning pre-C++23. If it's not an error in old modes, but the committee doesn't approve of calling std::terminate if it happens, then we still need to compile catch (...) as throwing in case we're interoperating.

@rjmccall @rsmith Do you think it makes sense to make __cxa_end_catch nounwind conditionally? I mean, in higher standard than C++98, we could make __cxa_end_catch nounwind.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Were there any updates here? This seems like a useful change for us, and I was wondering if it could be unblocked now.

Herald added a project: Restricted Project. · View Herald TranscriptOct 18 2023, 5:47 PM
Herald added subscribers: pmatos, asb. · View Herald Transcript

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Were there any updates here? This seems like a useful change for us, and I was wondering if it could be unblocked now.

What @rsmith talked about is in the standard-wise. I mean, if you feel this is useful, it is possible to add an compiler option to enable this. I've filed an issue to track this: https://github.com/llvm/llvm-project/issues/57375. My plan was to make this happen this year.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Were there any updates here? This seems like a useful change for us, and I was wondering if it could be unblocked now.

What @rsmith talked about is in the standard-wise. I mean, if you feel this is useful, it is possible to add an compiler option to enable this. I've filed an issue to track this: https://github.com/llvm/llvm-project/issues/57375. My plan was to make this happen this year.

Yup, an option to enable this would be great for us.

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Were there any updates here? This seems like a useful change for us, and I was wondering if it could be unblocked now.

What @rsmith talked about is in the standard-wise. I mean, if you feel this is useful, it is possible to add an compiler option to enable this. I've filed an issue to track this: https://github.com/llvm/llvm-project/issues/57375. My plan was to make this happen this year.

Yup, an option to enable this would be great for us.

I am happy to revive the patch with an option? Any suggestion to the name? CC1 or Driver?

No decision as yet, but so far it looks very likely that we'll settle on the rule that exceptions cannot have potentially-throwing destructors, and that we should reject throws of such types. I don't think that should be applied retroactively to C++98, though, because destructors were not implicitly non-throwing back then.

Were there any updates here? This seems like a useful change for us, and I was wondering if it could be unblocked now.

What @rsmith talked about is in the standard-wise. I mean, if you feel this is useful, it is possible to add an compiler option to enable this. I've filed an issue to track this: https://github.com/llvm/llvm-project/issues/57375. My plan was to make this happen this year.

Yup, an option to enable this would be great for us.

I am happy to revive the patch with an option? Any suggestion to the name? CC1 or Driver?

In our downstream, we use the name -fdisable-exception-with-may-throw-dtor as a Driver option.

I'd vote for something like -fassume-nothrow-exception-dtor or -fenforce-nothrow-exception-dtor depending on if the patch will also implement the enforcement mentioned in https://github.com/llvm/llvm-project/issues/57375#issuecomment-1230590204, but we can bikeshed that on the patch :)

MaskRay updated this revision to Diff 557798.Oct 19 2023, 7:48 PM
MaskRay retitled this revision from [ItaniumCXXABI] Make __cxa_end_catch calls unconditionally nounwind to [ItaniumCXXABI] Add -fassume-nothrow-exception-dtor to assume that an exception object' destructor is nothrow.
MaskRay edited the summary of this revision. (Show Details)

Switch to opt-in

Add -fassume-nothrow-exception-dtor

This revision is now accepted and ready to land.Oct 19 2023, 7:48 PM

This looks great to me, thanks. @rjmccall should sign off on it though.

MaskRay edited the summary of this revision. (Show Details)Oct 19 2023, 11:56 PM
MaskRay edited the summary of this revision. (Show Details)Oct 20 2023, 12:18 AM
This comment was removed by ChuanqiXu.

LGTM since this is exactly what we do in the downstream. The effects of the change in our workloads is 4% size reduction in a coroutine intensive project. (But it requires the coroutine's final suspend can't except via symetric transfer. I was meant to send a paper to WG21 to discuss this). After all, this should be a win for most projects.

We need to mention this in the docs and the ReleaseNotes since we add a new flag.

To make this self contained, we need to add a check in the frontend and emit errors if the compiler find the throwing exception's destructor may throw. This is not required in the current patch but it may be good to add a FIXME or TODO somewhere.

(BTW, the Phab itself is extremly slow now. So if we want more discuss on this, let's move this to Github?)

MaskRay added a comment.EditedOct 23 2023, 7:45 PM

This looks great to me, thanks. @rjmccall should sign off on it though.

@rjmccall Are you happy with this opt-in option?

LGTM since this is exactly what we do in the downstream. The effects of the change in our workloads is 4% size reduction in a coroutine intensive project. (But it requires the coroutine's final suspend can't except via symetric transfer. I was meant to send a paper to WG21 to discuss this). After all, this should be a win for most projects.

We need to mention this in the docs and the ReleaseNotes since we add a new flag.

To make this self contained, we need to add a check in the frontend and emit errors if the compiler find the throwing exception's destructor may throw. This is not required in the current patch but it may be good to add a FIXME or TODO somewhere.

(BTW, the Phab itself is extremly slow now. So if we want more discuss on this, let's move this to Github?)

The slowness was due to a malicious/aggressive distributed crawler. I have fixed it and updated php-fpm config:) https://discourse.llvm.org/t/phabricator-is-very-slow/73132/14

I'll land this patch next week if there is no objection. This seems very useful to a few parties and the current behavior is opt-in.

We need to mention this in the docs and the ReleaseNotes since we add a new flag.

To make this self contained, we need to add a check in the frontend and emit errors if the compiler find the throwing exception's destructor may throw. This is not required in the current patch but it may be good to add a FIXME or TODO somewhere.

There are 2 comments not addressed yet : )

MaskRay marked an inline comment as done.Oct 30 2023, 11:47 PM
MaskRay added inline comments.
clang/lib/CodeGen/ItaniumCXXABI.cpp
4495–4508

Since __cxa_end_catch is no longer nounwind in the latest revision, we cannot simplify the code...

Oh, I am not saying the legacy and old comment. I mean you need to touch ReleaseNotes.rst and UserManual.rst since we add a new flag. Also we need either add a TODO/FIXME saying we need to emit an error in Sema if we find the the dtor of the exception we're throwing may throw or implement the semantics actually.

MaskRay updated this revision to Diff 557968.Nov 1 2023, 7:02 PM
MaskRay marked an inline comment as done.
MaskRay edited the summary of this revision. (Show Details)

Update clang/docs/UsersManual.rst and clang/docs/ReleaseNotes.rst

ChuanqiXu added inline comments.Nov 1 2023, 7:09 PM
clang/docs/UsersManual.rst
2145–2147

I think it is better to treat the program as invalid if the compiler find the exception object's destructor is `noexcept(false)`. The earlier error message is better than debugging and understanding what happened actually.

MaskRay added a comment.EditedNov 1 2023, 7:13 PM

Oh, I am not saying the legacy and old comment. I mean you need to touch ReleaseNotes.rst and UserManual.rst since we add a new flag. Also we need either add a TODO/FIXME saying we need to emit an error in Sema if we find the the dtor of the exception we're throwing may throw or implement the semantics actually.

Thanks for the reminder about ReleaseNotes.rst and UsersManual.rst!

I think many changes don't update UsersManual.rst but this option is probably quite useful and therefore deserves an entry. Added.
The primary change is to clang/lib/CodeGen/ItaniumCXXABI.cpp, which does not report warnings.
If we want to implement a warning, we should probably do it in clang/lib/Sema/SemaDeclCXX.cpp Sema::BuildExceptionDeclaration, which is not touched in this file, so a TODO seems not appropriate...

Is the warning to warn about noexcept(false) destructor in an exception-declaration? When?
If at catch handlers, unfortunately we are cannot determine the exception object for a catch (...) { ... } (used by coroutines).
Technically, even if a destructor is noexcept(false), the destructor may not throw when __cxa_end_catch destroys the object.

So we probably should warn about throw expressions, which can be done in Sema::CheckCXXThrowOperand and I will investigate it.
However, this warning appears orthogonal to -fassume-nothrow-exception-dtor.
We can argue that even without -fassume-nothrow-exception-dtor, an thrown object with a noexcept(false) destructor is probably not a good idea.
However, I am on the fence whether the warning should be enabled-by-default.

Oh, I am not saying the legacy and old comment. I mean you need to touch ReleaseNotes.rst and UserManual.rst since we add a new flag. Also we need either add a TODO/FIXME saying we need to emit an error in Sema if we find the the dtor of the exception we're throwing may throw or implement the semantics actually.

Thanks for the reminder about ReleaseNotes.rst and UsersManual.rst!

I think many changes don't update UsersManual.rst but this option is probably quite useful and therefore deserves an entry. Added.
The primary change is to clang/lib/CodeGen/ItaniumCXXABI.cpp, which does not report warnings.
If we want to implement a warning, we should probably do it in clang/lib/Sema/SemaDeclCXX.cpp Sema::BuildExceptionDeclaration, which is not touched in this file, so a TODO seems not appropriate...

Is the warning to warn about noexcept(false) destructor in an exception-declaration? When?
If at catch handlers, unfortunately we are cannot determine the exception object for a catch (...) { ... } (used by coroutines).
Technically, even if a destructor is noexcept(false), the destructor may not throw when __cxa_end_catch destroys the object.

So we probably should warn about throw expressions, which can be done in Sema::CheckCXXThrowOperand and I will investigate it.
However, this warning appears orthogonal to -fassume-nothrow-exception-dtor.
We can argue that even without -fassume-nothrow-exception-dtor, an thrown object with a noexcept(false) destructor is probably not a good idea.
However, I am on the fence whether the warning should be enabled-by-default.

The idea of diagnosing such cases is originally raised by @rjmccall in https://github.com/llvm/llvm-project/issues/57375#issuecomment-1230590204. And in fact, John is suggesting an error instead of a warning. To be honest, in our downstream implementation, we didn't implement that semantical check neither.

For the implementation, I think Sema::CheckCXXThrowOperand may be a good place and we can left a TODO there.

Technically, we're using a dialect after we enable -fassume-nothrow-exception-dtor. In our dialect, we don't want to see an exception to throw exceptions. This is a rule for our dialect. And generally we have 2 strategies for implementing such language rules :

  • Detect it and emit errors after we found users violating the rules.
  • Don't detect it and claims it is the fault of the users.

No matter what the strategy we choose, it is not orthogonal to -fassume-nothrow-exception-dtor for sure. It is highly related. Then, I think the first strategy is always a better choice. Generally we only choose 2 when we can't diagnose such cases.

MaskRay updated this revision to Diff 557975.Nov 1 2023, 10:28 PM
MaskRay marked an inline comment as done.
MaskRay retitled this revision from [ItaniumCXXABI] Add -fassume-nothrow-exception-dtor to assume that an exception object' destructor is nothrow to [ItaniumCXXABI] Add -fassume-nothrow-exception-dtor to assume that all exception objects' destructors are non-throwing.
MaskRay edited the summary of this revision. (Show Details)

Add a diagnostic

ChuanqiXu accepted this revision.Nov 1 2023, 10:35 PM

LGTM. Thanks.

clang/lib/Sema/SemaExprCXX.cpp
1110

It looks like err_throw_object_throwing_dtor doesn't require any parameters?

MaskRay updated this revision to Diff 557976.Nov 1 2023, 11:29 PM
MaskRay marked an inline comment as done.

remove unused err_ arguments.
fix and test throw new B