[llvm-bugs] [Bug 50544] New: Symmetric transfer does not prevent stack-overflow for C++20 coroutines

via llvm-bugs llvm-bugs at lists.llvm.org
Mon May 31 15:00:52 PDT 2021


https://bugs.llvm.org/show_bug.cgi?id=50544

            Bug ID: 50544
           Summary: Symmetric transfer does not prevent stack-overflow for
                    C++20 coroutines
           Product: clang
           Version: 12.0
          Hardware: PC
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P
         Component: C++2a
          Assignee: unassignedclangbugs at nondot.org
          Reporter: leonard.von-merzljak at tum.de
                CC: blitzrakete at gmail.com, erik.pilkington at gmail.com,
                    llvm-bugs at lists.llvm.org, richard-llvm at metafoo.co.uk

Even though the following code uses symmetric transfer, it crashes due to a
stack-overflow. Please note that the code below is a minimal example to
reproduce the stack-overflow: e.g., if I include the definition of the
destructor of type `Type` in the header file, then I don't get a
stack-overflow. The crash can also be reproduced when using the task<> type of
the cppcoro library.

```
// type.h
#pragma once

struct Type {
  ~Type();
};
```

```
// type.cc
#include "type.h"

Type::~Type() {}
```

```
// main.cc
#include <cstdint>
#include <exception>
#include <type_traits>
#include <utility>

#include "type.h"

#if __has_include(<coroutine>)  // when using g++
#include <coroutine>
namespace coro {
using std::coroutine_handle;
using std::noop_coroutine;
using std::suspend_always;
}  // namespace coro
#elif __has_include(<experimental/coroutine>)  // when using clang++
#include <experimental/coroutine>
namespace coro {
using std::experimental::coroutine_handle;
using std::experimental::noop_coroutine;
using std::experimental::suspend_always;
}  // namespace coro
#endif

template <typename T = void>
class Task {
 public:
  struct PromiseBase {
    friend struct final_awaitable;

    struct final_awaitable {
      bool await_ready() const noexcept { return false; }

      template <typename PROMISE>
      coro::coroutine_handle<> await_suspend(
          coro::coroutine_handle<PROMISE> 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 coro::noop_coroutine();
        }
      }

      void await_resume() noexcept {}
    };

    coro::suspend_always initial_suspend() noexcept { return {}; }

    auto final_suspend() noexcept { return final_awaitable{}; }

    void unhandled_exception() noexcept { std::terminate(); }

    void set_continuation(coro::coroutine_handle<> continuation) noexcept {
      m_continuation = continuation;
    }

   private:
    coro::coroutine_handle<> m_continuation;
  };

  struct PromiseVoid : public PromiseBase {
    auto get_return_object() { return coroutine_handle_t::from_promise(*this);
}

    void return_void() noexcept {}

    void result() {}
  };

  struct PromiseT : public PromiseBase {
    auto get_return_object() { return coroutine_handle_t::from_promise(*this);
}

    void return_value(T&& v) { value = std::move(v); }

    T&& result() && { return std::move(value); }

    T value;
  };

  using promise_type =
      std::conditional_t<std::is_same_v<T, void>, PromiseVoid, PromiseT>;

  using coroutine_handle_t = coro::coroutine_handle<promise_type>;

  Task(coroutine_handle_t 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 {
    coroutine_handle_t m_coroutine;

    awaitable(coroutine_handle_t coroutine) noexcept : m_coroutine(coroutine)
{}

    bool await_ready() const noexcept { return false; }

    coro::coroutine_handle<> await_suspend(
        coro::coroutine_handle<> awaitingCoroutine) noexcept {
      m_coroutine.promise().set_continuation(awaitingCoroutine);
      return m_coroutine;
    }

    auto await_resume() { return std::move(m_coroutine.promise()).result(); }
  };
  coroutine_handle_t m_coroutine;
};

Task<Type> coro2() { co_return Type{}; }

Task<> coro1() { auto s = co_await coro2(); }

Task<> test() {
  for (std::uint64_t i = 0; i != 50000000; ++i) {
    co_await coro1();
  }
}

int main() {
  auto task = test();
  task.start();
}
```


I compile the code with `clang++-12 main.cc type.cc -std=c++20 -stdlib=libc++
-O3 -fsanitize=address`.

Here is the output:
```
$ ./a.out 

AddressSanitizer:DEADLYSIGNAL
=================================================================
==20846==ERROR: AddressSanitizer: stack-overflow on address 0x7ffc76b1aff8 (pc
0x0000004cb7ab bp 0x7ffc76b1b050 sp 0x7ffc76b1afa0 T0)
    #0 0x4cb7ab in coro1() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cb7ab)
    #1 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #2 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #3 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #4 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #5 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #6 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #7 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #8 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #9 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
    #10 0x4cbe4a in test() (.resume)
(/home/leonard/Desktop/hiwi/async_io_uring/stack_overflow/a.out+0x4cbe4a)
...
```

Interestingly, if I compile with `-O0` the program does not trigger a
stack-overflow and exits without any errors.

Further information about the problem and a workaround can be found here:
https://stackoverflow.com/questions/67446478/symmetric-transfer-does-not-prevent-stack-overflow-for-c20-coroutines

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20210531/2b2ed7f7/attachment-0001.html>


More information about the llvm-bugs mailing list