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
5
6 #include "Inputs/coroutine.h"
7
8 // executor and operation base
9
10 class bug_any_executor;
11
12 struct bug_async_op_base
13 {
14 void invoke();
15
16 protected:
17
18 ~bug_async_op_base() = default;
19 };
20
21 class bug_any_executor
22 {
23 using op_type = bug_async_op_base;
24
25 public:
26
27 virtual ~bug_any_executor() = default;
28
29 // removing noexcept enables clang to find that the pointer has escaped
30 virtual void post(op_type& op) noexcept = 0;
31
32 virtual void wait() noexcept = 0;
33 };
34
35 class bug_thread_executor : public bug_any_executor
36 {
37
38 public:
39
start()40 void start()
41 {
42
43 }
44
~bug_thread_executor()45 ~bug_thread_executor()
46 {
47 }
48
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;
51
wait()52 virtual void wait() noexcept override
53 {
54
55 }
56 };
57
58 // task and promise
59
60 struct bug_final_suspend_notification
61 {
62 virtual std::coroutine_handle<> get_waiter() = 0;
63 };
64
65 class bug_task;
66
67 class bug_task_promise
68 {
69 friend bug_task;
70 public:
71
72 bug_task get_return_object() noexcept;
73
initial_suspend()74 constexpr std::suspend_always initial_suspend() noexcept { return {}; }
75
final_suspend()76 std::suspend_always final_suspend() noexcept
77 {
78 return {};
79 }
80
81 void unhandled_exception() noexcept;
82
return_void() const83 constexpr void return_void() const noexcept {}
84
get_result() const85 void get_result() const
86 {
87
88 }
89 };
90
91 template <class T, class U>
exchange(T && t,U && u)92 T exchange(T &&t, U &&u) {
93 T ret = t;
94 t = u;
95 return ret;
96 }
97
98 class bug_task
99 {
100 friend bug_task_promise;
101 using handle = std::coroutine_handle<>;
102 using promise_t = bug_task_promise;
103
bug_task(handle coro,promise_t * p)104 bug_task(handle coro, promise_t* p) noexcept : this_coro{ coro }, this_promise{ p }
105 {
106
107 }
108
109 public:
110 using promise_type = bug_task_promise;
111
bug_task(bug_task && other)112 bug_task(bug_task&& other) noexcept
113 : this_coro{ exchange(other.this_coro, nullptr) }, this_promise{ exchange(other.this_promise, nullptr) } {
114
115 }
116
~bug_task()117 ~bug_task()
118 {
119 if (this_coro)
120 this_coro.destroy();
121 }
122
await_ready() const123 constexpr bool await_ready() const noexcept
124 {
125 return false;
126 }
127
await_suspend(handle waiter)128 handle await_suspend(handle waiter) noexcept
129 {
130 return this_coro;
131 }
132
await_resume()133 void await_resume()
134 {
135 return this_promise->get_result();
136 }
137
138 handle this_coro;
139 promise_t* this_promise;
140 };
141
get_return_object()142 bug_task bug_task_promise::get_return_object() noexcept
143 {
144 return { std::coroutine_handle<bug_task_promise>::from_promise(*this), this };
145 }
146
147 // spawn operation and spawner
148
149 template<class Handler>
150 class bug_spawn_op final : public bug_async_op_base, bug_final_suspend_notification
151 {
152 Handler handler;
153 bug_task task_;
154
155 public:
156
bug_spawn_op(Handler handler,bug_task && t)157 bug_spawn_op(Handler handler, bug_task&& t)
158 : handler { handler }, task_{ static_cast<bug_task&&>(t) } {}
159
get_waiter()160 virtual std::coroutine_handle<> get_waiter() override
161 {
162 handler();
163 return std::noop_coroutine();
164 }
165 };
166
167 class bug_spawner;
168
169 struct bug_spawner_awaiter
170 {
171 bug_spawner& s;
172 std::coroutine_handle<> waiter;
173
bug_spawner_awaiterbug_spawner_awaiter174 bug_spawner_awaiter(bug_spawner& s) : s{ s } {}
175
176 bool await_ready() const noexcept;
177
178 void await_suspend(std::coroutine_handle<> coro);
179
await_resumebug_spawner_awaiter180 void await_resume() {}
181 };
182
183 class bug_spawner
184 {
185 friend bug_spawner_awaiter;
186
187 struct final_handler_t
188 {
189 bug_spawner& s;
190
operator ()bug_spawner::final_handler_t191 void operator()()
192 {
193 s.awaiter_->waiter.resume();
194 }
195 };
196
197 public:
198
bug_spawner(bug_any_executor & ex)199 bug_spawner(bug_any_executor& ex) : ex_{ ex } {}
200
spawn(bug_task && t)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));
205 ++count_;
206 ex_.post(*ptr); // ptr escapes here thus task escapes but clang can't deduce that unless post() is not noexcept
207 }
208
wait()209 bug_spawner_awaiter wait() noexcept { return { *this }; }
210
211 private:
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;
214 unsigned count_ = 0;
215 };
216
217 // test case
218
bug_spawned_task(int id,int inc)219 bug_task bug_spawned_task(int id, int inc)
220 {
221 co_return;
222 }
223
224 struct A {
225 A();
226 };
227
throwing_fn(bug_spawner & s)228 void throwing_fn(bug_spawner& s) {
229 s.spawn(bug_spawned_task(1, 2));
230 throw A{};
231 }
232
233 // Check that the coroutine frame of bug_spawned_task are allocated from operator new.
234 // CHECK: define{{.*}}@_Z11throwing_fnR11bug_spawner
235 // CHECK-NOT: alloc
236 // CHECK: %[[CALL:.+]] = {{.*}}@_Znwm(i64{{.*}} 24)
237 // CHECK: store ptr @_Z16bug_spawned_taskii.resume, ptr %[[CALL]]
238