Skip to content

Abort in cleanup leads to unwinding failure #61945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nikic opened this issue Apr 5, 2023 · 6 comments
Closed

Abort in cleanup leads to unwinding failure #61945

nikic opened this issue Apr 5, 2023 · 6 comments

Comments

@nikic
Copy link
Contributor

nikic commented Apr 5, 2023

Consider the following example:

#include <cstdlib>
#include <cstdio>

struct Abort {
  ~Abort() {
    puts("cleanup");
    abort();
  }
};

__attribute__((noinline))
static void abort_in_dtor() {
  Abort abort;
  throw "test";
}

int main() {
  try {
    abort_in_dtor();
  } catch (...) {
    puts("caught");
  }
}

This should print "cleanup" followed by abort. Instead this happens when using Clang 17:

terminate called after throwing an instance of 'char const*'
Aborted (core dumped)

The reason for this is that we now (correctly) infer that abort_in_dtor() is nounwind. However, as phase 1 unwind skips cleanups, this means that the unwind walk will try to go past a nounwind frame, which may not have an LSDA entry, resulting in some form of unwinding failure. (In this specific case we return _URC_END_OF_STACK, but a similar case in Rust results in _URC_FATAL_PHASE1_ERROR.)

@llvmbot
Copy link
Member

llvmbot commented Apr 5, 2023

@llvm/issue-subscribers-clang-codegen

@nikic nikic changed the title Abort in cleanup leads to Abort in cleanup leads to unwinding failure Apr 5, 2023
@nikic
Copy link
Contributor Author

nikic commented Apr 5, 2023

cc @efriedma-quic for unwinding

This regressed due to https://reviews.llvm.org/D145210, but that's only because we are now better at inferring nounwind. The problem itself is not new.

@EugeneZelenko EugeneZelenko added llvm:optimizations and removed clang:codegen IR generation bugs: mangling, exceptions, etc. labels Apr 5, 2023
@efriedma-quic
Copy link
Collaborator

From C++ [except.handle]: If no matching handler is found, the function std​::​terminate is invoked; whether or not the stack is unwound before this invocation of std​::​terminate is implementation-defined

I think libunwind prefers to terminate before unwinding.

Given that, I think we should not infer nounwind for abort_in_dtor; even though control flow never actually unwinds past it, the first pass of unwinding does try to look through it.

@nikic
Copy link
Contributor Author

nikic commented Apr 5, 2023

Given that, I think we should not infer nounwind for abort_in_dtor; even though control flow never actually unwinds past it, the first pass of unwinding does try to look through it.

Yeah, that sounds like a pragmatic fix. Would it be fine to just special-case cleanup landingpads in FunctionAttrs inference, without actually making them mayThrow()?

@efriedma-quic
Copy link
Collaborator

Special-case seems fine; it's not actually control flow in the sense most users of mayThrow() care about.

@nikic
Copy link
Contributor Author

nikic commented Apr 6, 2023

Candidate patch: https://reviews.llvm.org/D147694

@nikic nikic self-assigned this Apr 6, 2023
@nikic nikic closed this as completed in 9fe78db Apr 14, 2023
flemairen6 pushed a commit to Xilinx/llvm-project that referenced this issue May 10, 2023
Currently, FunctionAttrs treats landingpads as non-throwing, and
will infer nounwind for functions with landingpads (assuming they
can't unwind in some other way, e.g. via resum). There are two
problems with this:

* Non-cleanup landingpads with catch/filter clauses do not
  necessarily catch all exceptions. Unless there are catch ptr null
  or filter [0 x ptr] zeroinitializer clauses, we should assume
  that we may unwind past this landingpad. This seems like an
  outright bug.
* Cleanup landingpads are skipped during phase one unwinding, so
  we effectively need to support unwinding past them. Marking these
  nounwind is technically correct, but not compatible with how
  unwinding works in reality.

Fixes llvm#61945.

Differential Revision: https://reviews.llvm.org/D147694
veselypeta pushed a commit to veselypeta/cherillvm that referenced this issue Aug 21, 2024
Currently, FunctionAttrs treats landingpads as non-throwing, and
will infer nounwind for functions with landingpads (assuming they
can't unwind in some other way, e.g. via resum). There are two
problems with this:

* Non-cleanup landingpads with catch/filter clauses do not
  necessarily catch all exceptions. Unless there are catch ptr null
  or filter [0 x ptr] zeroinitializer clauses, we should assume
  that we may unwind past this landingpad. This seems like an
  outright bug.
* Cleanup landingpads are skipped during phase one unwinding, so
  we effectively need to support unwinding past them. Marking these
  nounwind is technically correct, but not compatible with how
  unwinding works in reality.

Fixes llvm/llvm-project#61945.

Differential Revision: https://reviews.llvm.org/D147694
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants