This is an archive of the discontinued LLVM Phabricator instance.

implement C++ dr388 for the Itanium C++ ABI: proper handling of catching exceptions by reference
AbandonedPublic

Authored by rsmith on Jan 15 2018, 9:49 PM.

Details

Reviewers
rjmccall
rnk
Summary

This patch fixes three issues with how Clang emits code to catch exceptions by reference:

  1. We would sometimes bind the reference directly to the exception object when a temporary is required (when a pointer-to-member is converted by a qualification conversion but not adjusted).
  2. We would sometimes bind the reference to a temporary when a direct binding is required (when catching a pointer-to-class-object where no derived-to-base or qualification conversion is required).
  3. We would incorrectly allow a non-const (or volatile) reference to catch an exception of a different type that can be converted to the right type.

A single strategy is used to fix all three issues: where necessary, we extract the type_info of the exception object from the __cxa_exception header, and compare it directly against the reference's pointee type_info. For cases #1 and #2, this tells us whether to bind to a temporary object or to the exception object; for case #3, if the types don't match and the reference can't bind to a temporary, we rethrow the exception through the remaining catch clauses of the try statement.

This patch is insufficiently tested (no changes to the test suite, and no testing has been done whatsoever for the ARM64 non-unique type_info case), but I'm posting it now to gauge whether this is a direction we want to pursue for DR388 (as opposed to continuing to get this wrong until the Itanium C++ ABI has a chance to take an ABI break to properly handle catch-by-reference).

[Despite the different approach to determining catchability, the MS ABI appears to have exactly the same bugs (it too does not distinguish between catch by value and catch by reference), and I would assume the same approach would also work there, assuming the actual type of the exception object can be discerned, but I've not looked at all at implementing this in that ABI.]

Diff Detail

Repository
rC Clang

Event Timeline

rsmith created this revision.Jan 15 2018, 9:49 PM
rsmith added inline comments.Jan 15 2018, 9:59 PM
lib/CodeGen/ItaniumCXXABI.cpp
3878

There are a lot of other optimizations possible here. For example: if there are no later catches, and no enclosing EH scopes, we can just resume (before the __cxa_begin_catch) call. Or if the first such later catch will definitely catch the exception (eg, catch (int*&) {} catch (int*) {}), we could branch directly to it.

I'm inclined to think we should keep the frontend emission simpler and implement these cases as EHABI-aware middle-end optimization passes if we care enough about them.

rsmith added a reviewer: rnk.Jan 16 2018, 1:27 PM
rnk added inline comments.Jan 16 2018, 5:34 PM
lib/CodeGen/ItaniumCXXABI.cpp
3867

Based on our conversation, this fix feels a bit heroic. I think it would be better to find a way to put the const-ness in the LSDA and let new versions of the EH runtime deal with this.

This is definitely something that the personality function should handle. If we want to do heroic things in the absence of personality function support, we can, but the code should at least be written to be conditional on personality support.

If we can rev the personality function, we should also fix the longstanding problem with references to pointers to classes.

rsmith added a comment.Apr 4 2018, 4:33 PM

This is definitely something that the personality function should handle. If we want to do heroic things in the absence of personality function support, we can, but the code should at least be written to be conditional on personality support.

I suppose the question is, do we want to do such heroic things?

Also, we don't have a personality function that can get this right, nor even a concrete design proposed for one. It doesn't seem too hard -- we'd need to extend the EH table to indicate catch-by-reference -- but there's nothing for this to be made conditional on right now. I can easily add a CodeGenOption that turns this all off, if you'd like.

If we can rev the personality function, we should also fix the longstanding problem with references to pointers to classes.

Which issue is that? (And does this patch fail to address it? It should prevent catching Derived* by (non-const) Base *& while still allowing catch by Base *const&, if that's the issue you're referring to.)

This is definitely something that the personality function should handle. If we want to do heroic things in the absence of personality function support, we can, but the code should at least be written to be conditional on personality support.

I suppose the question is, do we want to do such heroic things?

Also, we don't have a personality function that can get this right, nor even a concrete design proposed for one. It doesn't seem too hard -- we'd need to extend the EH table to indicate catch-by-reference -- but there's nothing for this to be made conditional on right now. I can easily add a CodeGenOption that turns this all off, if you'd like.

Well, I don't have a use case for an option like that. If we're going to add the code to support this even without a cooperative personality, I think we can enable it unconditionally for now.

Issue #3 is tricky; it's arguably not okay to force a landing at a landing pad if we're not actually catching anything.

For what it's worth, I don't think the Itanium ABI needs any changes here. The existence of any particular personality function, and that function's expectations about the LSDA, are not official ABI. __gxx_personality_v0 is a sort of private ABI, a standard solution offered by libsupc++ / libc++abi for the convenience of compilers, but we could totally just write a new personality and add it to compiler-rt (or libc++abi, enabled by deployment-target-specific checks) if that was the best solution.

If we can rev the personality function, we should also fix the longstanding problem with references to pointers to classes.

Which issue is that? (And does this patch fail to address it? It should prevent catching Derived* by (non-const) Base *& while still allowing catch by Base *const&, if that's the issue you're referring to.)

Yes, that's the issue I'm referring to, and yeah, I see that you're addressing it here.

rsmith abandoned this revision.Apr 5 2018, 1:05 PM

Issue #3 is tricky; it's arguably not okay to force a landing at a landing pad if we're not actually catching anything.

I think the only way this is observable (at least within the bounds of well-defined C++ code) is whether -- and how much -- the stack is unwound before a call to terminate() in the case where an exception is not caught.

[except.handle]p9 says: "If no matching handler is found, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined (18.5.1)."
[except.terminate]p2 says: " In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler (18.3) encounters the outermost block of a function with a non-throwing exception specification (18.4), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called."

So I think you're right; this solution to issue #3 is non-conforming, because it will result in the stack being partially unwound on the path to a call to std::terminate() for an unhandled exception. Even if we changed the rule for terminate to allow partial unwinding when no handler is found, this patch would still give a QoI regression for that case (because the call to the terminate_handler would happen after some unwinding had been performed).

I'm not particularly interested in pushing this patch further if it can't actually make catch-by-reference work properly. Let's drop this for now and encourage interested parties to work on an alternative personality function.

Issue #3 is tricky; it's arguably not okay to force a landing at a landing pad if we're not actually catching anything.

I think the only way this is observable (at least within the bounds of well-defined C++ code) is whether -- and how much -- the stack is unwound before a call to terminate() in the case where an exception is not caught.

[except.handle]p9 says: "If no matching handler is found, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined (18.5.1)."
[except.terminate]p2 says: " In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler (18.3) encounters the outermost block of a function with a non-throwing exception specification (18.4), it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called."

So I think you're right; this solution to issue #3 is non-conforming, because it will result in the stack being partially unwound on the path to a call to std::terminate() for an unhandled exception. Even if we changed the rule for terminate to allow partial unwinding when no handler is found, this patch would still give a QoI regression for that case (because the call to the terminate_handler would happen after some unwinding had been performed).

I'm not particularly interested in pushing this patch further if it can't actually make catch-by-reference work properly. Let's drop this for now and encourage interested parties to work on an alternative personality function.

Agreed.