https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897
Bug ID: 100897 Summary: Symmetric transfer does not prevent stack-overflow for C++20 coroutines Product: gcc Version: 11.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: l.v.merzljak at gmail dot com Target Milestone: --- Although the following code uses symmetric transfer, it crashes due to a stack-overflow. The crash is also reproducible when using the task<> type of the cppcoro library. The crash does not occur when using clang. ``` // main.cc #include <coroutine> #include <exception> class Task { public: struct promise_type { Task get_return_object() { return Handle::from_promise(*this); } struct FinalAwaitable { bool await_ready() const noexcept { return false; } // Use symmetric transfer. Resuming coro.promise().m_continuation should // not require extra stack space std::coroutine_handle<> await_suspend( std::coroutine_handle<promise_type> coro) noexcept { if (coro.promise().m_continuation) { return coro.promise().m_continuation; } else { // The top-level task started from within main() does not have a // continuation. This will give control back to the main function. return std::noop_coroutine(); } } void await_resume() noexcept {} }; std::suspend_always initial_suspend() noexcept { return {}; } FinalAwaitable final_suspend() noexcept { return {}; } void unhandled_exception() noexcept { std::terminate(); } void set_continuation(std::coroutine_handle<> continuation) noexcept { m_continuation = continuation; } void return_void() noexcept {} private: std::coroutine_handle<> m_continuation; }; using Handle = std::coroutine_handle<promise_type>; Task(Handle coroutine) : m_coroutine(coroutine) {} ~Task() { if (m_coroutine) { m_coroutine.destroy(); } } void start() noexcept { m_coroutine.resume(); } auto operator co_await() const noexcept { return Awaitable{m_coroutine}; } private: struct Awaitable { Handle m_coroutine; Awaitable(Handle coroutine) noexcept : m_coroutine(coroutine) {} bool await_ready() const noexcept { return false; } // Use symmetric transfer. Resuming m_coroutine should not require extra // stack space std::coroutine_handle<> await_suspend( std::coroutine_handle<> awaitingCoroutine) noexcept { m_coroutine.promise().set_continuation(awaitingCoroutine); return m_coroutine; } void await_resume() {} }; Handle m_coroutine; }; Task inner() { co_return; } Task outer() { // Use large number of iterations to trigger stack-overflow for (int i = 0; i != 50000000; ++i) { co_await inner(); } } int main() { auto task = outer(); task.start(); } ``` I compile the code with `g++-11 main.cc -std=c++20 -O3 -fsanitize=address`. Here is the output: ``` $ ./a.out AddressSanitizer:DEADLYSIGNAL ================================================================= ==21002==ERROR: AddressSanitizer: stack-overflow on address 0x7fffc666dff8 (pc 0x7f6ec2dfa16d bp 0x7fffc666e870 sp 0x7fffc666e000 T0) #0 0x7f6ec2dfa16d in __sanitizer::BufferedStackTrace::UnwindImpl(unsigned long, unsigned long, void*, bool, unsigned int) ../../../../src/libsanitizer/asan/asan_stack.cpp:57 #1 0x7f6ec2df00eb in __sanitizer::BufferedStackTrace::Unwind(unsigned long, unsigned long, void*, bool, unsigned int) ../../../../src/libsanitizer/sanitizer_common/sanitizer_stacktrace.h:122 #2 0x7f6ec2df00eb in operator delete(void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:160 #3 0x560193552e57 in _Z5innerv.destroy(inner()::_Z5innerv.frame*) (/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x1e57) #4 0x560193553b30 in _Z5outerv.actor(outer()::_Z5outerv.frame*) (/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x2b30) #5 0x560193552bbb in _Z5innerv.actor(inner()::_Z5innerv.frame*) (/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x1bbb) ... ```