|  | // This is reduced test case from https://github.com/llvm/llvm-project/issues/59723. | 
|  | // This is not a minimal reproducer intentionally to check the compiler's ability. | 
|  | // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fcxx-exceptions\ | 
|  | // RUN:     -fexceptions -O2 -emit-llvm %s -o - | FileCheck %s | 
|  |  | 
|  | #include "Inputs/coroutine.h" | 
|  |  | 
|  | // executor and operation base | 
|  |  | 
|  | class bug_any_executor; | 
|  |  | 
|  | struct bug_async_op_base | 
|  | { | 
|  | void invoke(); | 
|  |  | 
|  | protected: | 
|  |  | 
|  | ~bug_async_op_base() = default; | 
|  | }; | 
|  |  | 
|  | class bug_any_executor | 
|  | { | 
|  | using op_type = bug_async_op_base; | 
|  |  | 
|  | public: | 
|  |  | 
|  | virtual ~bug_any_executor() = default; | 
|  |  | 
|  | // removing noexcept enables clang to find that the pointer has escaped | 
|  | virtual void post(op_type& op) noexcept = 0; | 
|  |  | 
|  | virtual void wait() noexcept = 0; | 
|  | }; | 
|  |  | 
|  | class bug_thread_executor : public bug_any_executor | 
|  | { | 
|  |  | 
|  | public: | 
|  |  | 
|  | void start() | 
|  | { | 
|  |  | 
|  | } | 
|  |  | 
|  | ~bug_thread_executor() | 
|  | { | 
|  | } | 
|  |  | 
|  | // although this implementation is not realy noexcept due to allocation but I have a real one that is and required to be noexcept | 
|  | virtual void post(bug_async_op_base& op) noexcept override; | 
|  |  | 
|  | virtual void wait() noexcept override | 
|  | { | 
|  |  | 
|  | } | 
|  | }; | 
|  |  | 
|  | // task and promise | 
|  |  | 
|  | struct bug_final_suspend_notification | 
|  | { | 
|  | virtual std::coroutine_handle<> get_waiter() = 0; | 
|  | }; | 
|  |  | 
|  | class bug_task; | 
|  |  | 
|  | class bug_task_promise | 
|  | { | 
|  | friend bug_task; | 
|  | public: | 
|  |  | 
|  | bug_task get_return_object() noexcept; | 
|  |  | 
|  | constexpr std::suspend_always initial_suspend() noexcept { return {}; } | 
|  |  | 
|  | std::suspend_always final_suspend() noexcept | 
|  | { | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | void unhandled_exception() noexcept; | 
|  |  | 
|  | constexpr void return_void() const noexcept {} | 
|  |  | 
|  | void get_result() const | 
|  | { | 
|  |  | 
|  | } | 
|  | }; | 
|  |  | 
|  | template <class T, class U> | 
|  | T exchange(T &&t, U &&u) { | 
|  | T ret = t; | 
|  | t = u; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | class bug_task | 
|  | { | 
|  | friend bug_task_promise; | 
|  | using handle = std::coroutine_handle<>; | 
|  | using promise_t = bug_task_promise; | 
|  |  | 
|  | bug_task(handle coro, promise_t* p) noexcept : this_coro{ coro }, this_promise{ p } | 
|  | { | 
|  |  | 
|  | } | 
|  |  | 
|  | public: | 
|  | using promise_type = bug_task_promise; | 
|  |  | 
|  | bug_task(bug_task&& other) noexcept | 
|  | : this_coro{ exchange(other.this_coro, nullptr) }, this_promise{ exchange(other.this_promise, nullptr) } { | 
|  |  | 
|  | } | 
|  |  | 
|  | ~bug_task() | 
|  | { | 
|  | if (this_coro) | 
|  | this_coro.destroy(); | 
|  | } | 
|  |  | 
|  | constexpr bool await_ready() const noexcept | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | handle await_suspend(handle waiter) noexcept | 
|  | { | 
|  | return this_coro; | 
|  | } | 
|  |  | 
|  | void await_resume() | 
|  | { | 
|  | return this_promise->get_result(); | 
|  | } | 
|  |  | 
|  | handle this_coro; | 
|  | promise_t* this_promise; | 
|  | }; | 
|  |  | 
|  | bug_task bug_task_promise::get_return_object() noexcept | 
|  | { | 
|  | return { std::coroutine_handle<bug_task_promise>::from_promise(*this), this }; | 
|  | } | 
|  |  | 
|  | // spawn operation and spawner | 
|  |  | 
|  | template<class Handler> | 
|  | class bug_spawn_op final : public bug_async_op_base, bug_final_suspend_notification | 
|  | { | 
|  | Handler handler; | 
|  | bug_task task_; | 
|  |  | 
|  | public: | 
|  |  | 
|  | bug_spawn_op(Handler handler, bug_task&& t) | 
|  | : handler { handler }, task_{ static_cast<bug_task&&>(t) } {} | 
|  |  | 
|  | virtual std::coroutine_handle<> get_waiter() override | 
|  | { | 
|  | handler(); | 
|  | return std::noop_coroutine(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class bug_spawner; | 
|  |  | 
|  | struct bug_spawner_awaiter | 
|  | { | 
|  | bug_spawner& s; | 
|  | std::coroutine_handle<> waiter; | 
|  |  | 
|  | bug_spawner_awaiter(bug_spawner& s) : s{ s } {} | 
|  |  | 
|  | bool await_ready() const noexcept; | 
|  |  | 
|  | void await_suspend(std::coroutine_handle<> coro); | 
|  |  | 
|  | void await_resume() {} | 
|  | }; | 
|  |  | 
|  | class bug_spawner | 
|  | { | 
|  | friend bug_spawner_awaiter; | 
|  |  | 
|  | struct final_handler_t | 
|  | { | 
|  | bug_spawner& s; | 
|  |  | 
|  | void operator()() | 
|  | { | 
|  | s.awaiter_->waiter.resume(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | public: | 
|  |  | 
|  | bug_spawner(bug_any_executor& ex) : ex_{ ex } {} | 
|  |  | 
|  | void spawn(bug_task&& t) { | 
|  | using op_t = bug_spawn_op<final_handler_t>; | 
|  | // move task into ptr | 
|  | op_t* ptr = new op_t(final_handler_t{ *this }, static_cast<bug_task&&>(t)); | 
|  | ++count_; | 
|  | ex_.post(*ptr); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept | 
|  | } | 
|  |  | 
|  | bug_spawner_awaiter wait() noexcept { return { *this }; } | 
|  |  | 
|  | private: | 
|  | bug_any_executor& ex_; // if bug_thread_executor& is used instead enables clang to detect the escape of the promise | 
|  | bug_spawner_awaiter* awaiter_ = nullptr; | 
|  | unsigned count_ = 0; | 
|  | }; | 
|  |  | 
|  | // test case | 
|  |  | 
|  | bug_task bug_spawned_task(int id, int inc) | 
|  | { | 
|  | co_return; | 
|  | } | 
|  |  | 
|  | struct A { | 
|  | A(); | 
|  | }; | 
|  |  | 
|  | void throwing_fn(bug_spawner& s) { | 
|  | s.spawn(bug_spawned_task(1, 2)); | 
|  | throw A{}; | 
|  | } | 
|  |  | 
|  | // Check that the coroutine frame of bug_spawned_task are allocated from operator new. | 
|  | // CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner | 
|  | // CHECK-NOT: alloc | 
|  | // CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24) | 
|  | // CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]] |