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
#include
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 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;
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 != 5000; ++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)
...
```