3

I am trying to work around this std::thread bug, which causes the stack to be unwound in an older version of gcc, by applying the noexcept annotation:

#include <functional>
#include <stdexcept>

void thrower() {
    throw std::runtime_error("A message is useful but where's my stack?");
}

void run() {
    // Culprit:
    auto j = std::function<void()>([] { thrower(); });
    // Works fine:
    // auto j = [] { thrower(); };
    j();
}

void wrap() noexcept {
    run();
    // Works fine too:
    // auto j = std::function<void()>([] { thrower(); });
    // j();
}

int main() {
    wrap();
}

Since the run function is in a library, I need to apply noexcept to a wrapper function, wrap. However, I observe that when the run function uses std::function, the stack is still partly unwound and I don't see the crucial bit telling me that the exception originated in the thrower:

#0  0x000000000041fb41 in raise ()
#1  0x0000000000401b30 in abort ()
#2  0x00000000004012cb in __gnu_cxx::__verbose_terminate_handler() [clone .cold] ()
#3  0x0000000000403876 in __cxxabiv1::__terminate(void (*)()) ()
#4  0x0000000000414d69 in __cxa_call_terminate ()
#5  0x0000000000403531 in __gxx_personality_v0 ()
#6  0x0000000000416fbe in _Unwind_RaiseException_Phase2 ()
#7  0x0000000000417ab6 in _Unwind_Resume ()
#8  0x0000000000402668 in std::function<void ()>::~function() (this=<optimized out>, __in_chrg=<optimized out>)
    at /usr/include/c++/10/bits/std_function.h:303
#9  run () at main.cpp:10
#10 0x0000000000402671 in wrap () at main.cpp:17
#11 0x000000000040267f in main () at main.cpp:24

Note the _Unwind_Resume in frame 7 and std::function destructor in frame 8 (the latter I also saw this with gcc 7 but with gcc 10 I had to set optimization level to debug to see it).

What about std::function is causing the stack to be unwound and why is this not happening with a lambda? Is this allowed by the standard? I don't know much about the rules around unwinding, so please refer to me to relevant resources where you can.

What can I do to prevent the stack unwinding, assuming that I can't modify run?


More experiments:

If I use a lambda instead, I do see the thrower and instead of _Unwind_Resume there is _Unwind_RaiseException:

... (as above) ...
#6  0x0000000000416e5e in _Unwind_RaiseException_Phase2 ()
#7  0x0000000000417630 in _Unwind_RaiseException ()
#8  0x00000000004036c8 in __cxa_throw ()
#9  0x0000000000402575 in thrower () at main.cpp:5
#10 0x0000000000402591 in operator() (__closure=<synthetic pointer>) at main.cpp:13
#11 run () at main.cpp:13
#12 0x000000000040259a in wrap () at main.cpp:17
#13 0x00000000004025a3 in main () at main.cpp:24

The stack is also complete if I copy the body of run into wrap, so the issue seems to be specifically about an exception thrown from the invocation of a std::function escaping a function not annotated with noexcept.

... (as above) ...
#11 std::__invoke_impl<void, wrap()::<lambda()>&> (__f=...) at /usr/include/c++/10/bits/invoke.h:60
#12 std::__invoke_r<void, wrap()::<lambda()>&> (__fn=...) at /usr/include/c++/10/bits/invoke.h:153
#13 std::_Function_handler<void(), wrap()::<lambda()> >::_M_invoke(const std::_Any_data &) (__functor=...)
    at /usr/include/c++/10/bits/std_function.h:291
#14 0x0000000000402680 in std::function<void ()>::operator()() const (this=this@entry=0x7fffffffd780)
    at /usr/include/c++/10/bits/std_function.h:248
#15 0x0000000000402641 in wrap () at main.cpp:20
#16 0x0000000000402667 in main () at main.cpp:24
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
jszopi
  • 31
  • 2
  • 2
    Perhaps you compiled with optimizations, and gcc inlined the function call? – Sam Varshavchik Oct 16 '20 at 15:01
  • 2
    When `noexcept` is violated `std::terminate` is called and it is implementation defined whether or not unwinding occurs. I'm not sure what GCC claims is its definition for that behavior but finding that out seems like it is essential for this question. – François Andrieux Oct 16 '20 at 15:03
  • If `noexcept` is violated, it's a game over. What exactly are you trying to improve here? Postmortem analysis? – n. m. could be an AI Oct 16 '20 at 15:24
  • @SamVarshavchik I have tried with `-O0`. @n.'pronouns'm. yes, postmortem – jszopi Oct 16 '20 at 15:35
  • The most straightforward (IMHO) way would be replacing __cxa_throw with your own version which would save the stack trace using e.g. libunwind and then call __cxa_throw provided by the implementation. – n. m. could be an AI Oct 16 '20 at 15:46

0 Answers0