Why does std::function cause the stack to be unwound only when an exception escapes the current function?

408 views Asked by At

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
0

There are 0 answers