1 // This is reduced test case from https://github.com/llvm/llvm-project/issues/59723.
2 // This is not a minimal reproducer intentionally to check the compiler's ability.
3 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fcxx-exceptions\
4 // RUN: -fexceptions -O2 -emit-llvm %s -o - | FileCheck %s
6 #include "Inputs/coroutine.h"
8 // executor and operation base
10 class bug_any_executor
;
12 struct bug_async_op_base
18 ~bug_async_op_base() = default;
21 class bug_any_executor
23 using op_type
= bug_async_op_base
;
27 virtual ~bug_any_executor() = default;
29 // removing noexcept enables clang to find that the pointer has escaped
30 virtual void post(op_type
& op
) noexcept
= 0;
32 virtual void wait() noexcept
= 0;
35 class bug_thread_executor
: public bug_any_executor
45 ~bug_thread_executor()
49 // although this implementation is not realy noexcept due to allocation but I have a real one that is and required to be noexcept
50 virtual void post(bug_async_op_base
& op
) noexcept override
;
52 virtual void wait() noexcept override
60 struct bug_final_suspend_notification
62 virtual std::coroutine_handle
<> get_waiter() = 0;
67 class bug_task_promise
72 bug_task
get_return_object() noexcept
;
74 constexpr std::suspend_always
initial_suspend() noexcept
{ return {}; }
76 std::suspend_always
final_suspend() noexcept
81 void unhandled_exception() noexcept
;
83 constexpr void return_void() const noexcept
{}
85 void get_result() const
91 template <class T
, class U
>
92 T
exchange(T
&&t
, U
&&u
) {
100 friend bug_task_promise
;
101 using handle
= std::coroutine_handle
<>;
102 using promise_t
= bug_task_promise
;
104 bug_task(handle coro
, promise_t
* p
) noexcept
: this_coro
{ coro
}, this_promise
{ p
}
110 using promise_type
= bug_task_promise
;
112 bug_task(bug_task
&& other
) noexcept
113 : this_coro
{ exchange(other
.this_coro
, nullptr) }, this_promise
{ exchange(other
.this_promise
, nullptr) } {
123 constexpr bool await_ready() const noexcept
128 handle
await_suspend(handle waiter
) noexcept
135 return this_promise
->get_result();
139 promise_t
* this_promise
;
142 bug_task
bug_task_promise::get_return_object() noexcept
144 return { std::coroutine_handle
<bug_task_promise
>::from_promise(*this), this };
147 // spawn operation and spawner
149 template<class Handler
>
150 class bug_spawn_op final
: public bug_async_op_base
, bug_final_suspend_notification
157 bug_spawn_op(Handler handler
, bug_task
&& t
)
158 : handler
{ handler
}, task_
{ static_cast<bug_task
&&>(t
) } {}
160 virtual std::coroutine_handle
<> get_waiter() override
163 return std::noop_coroutine();
169 struct bug_spawner_awaiter
172 std::coroutine_handle
<> waiter
;
174 bug_spawner_awaiter(bug_spawner
& s
) : s
{ s
} {}
176 bool await_ready() const noexcept
;
178 void await_suspend(std::coroutine_handle
<> coro
);
180 void await_resume() {}
185 friend bug_spawner_awaiter
;
187 struct final_handler_t
193 s
.awaiter_
->waiter
.resume();
199 bug_spawner(bug_any_executor
& ex
) : ex_
{ ex
} {}
201 void spawn(bug_task
&& t
) {
202 using op_t
= bug_spawn_op
<final_handler_t
>;
203 // move task into ptr
204 op_t
* ptr
= new op_t(final_handler_t
{ *this }, static_cast<bug_task
&&>(t
));
206 ex_
.post(*ptr
); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept
209 bug_spawner_awaiter
wait() noexcept
{ return { *this }; }
212 bug_any_executor
& ex_
; // if bug_thread_executor& is used instead enables clang to detect the escape of the promise
213 bug_spawner_awaiter
* awaiter_
= nullptr;
219 bug_task
bug_spawned_task(int id
, int inc
)
228 void throwing_fn(bug_spawner
& s
) {
229 s
.spawn(bug_spawned_task(1, 2));
233 // Check that the coroutine frame of bug_spawned_task are allocated from operator new.
234 // CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner
236 // CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24)
237 // CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]]