xref: /llvm-project/clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp (revision 729657d6e15d0455557f35485deb87313ccdde10)
1 // RUN: %check_clang_tidy -std=c++20 %s misc-coroutine-hostile-raii %t \
2 // RUN:   -config="{CheckOptions: {\
3 // RUN:             misc-coroutine-hostile-raii.RAIITypesList: 'my::Mutex; ::my::other::Mutex', \
4 // RUN:             misc-coroutine-hostile-raii.AllowedAwaitablesList: 'safe::awaitable; ::transformable::awaitable' \
5 // RUN:             }}"
6 
7 namespace std {
8 
9 template <typename R, typename...> struct coroutine_traits {
10   using promise_type = typename R::promise_type;
11 };
12 
13 template <typename Promise = void> struct coroutine_handle;
14 
15 template <> struct coroutine_handle<void> {
from_addressstd::coroutine_handle16   static coroutine_handle from_address(void *addr) noexcept {
17     coroutine_handle me;
18     me.ptr = addr;
19     return me;
20   }
operator ()std::coroutine_handle21   void operator()() { resume(); }
addressstd::coroutine_handle22   void *address() const noexcept { return ptr; }
resumestd::coroutine_handle23   void resume() const {  }
destroystd::coroutine_handle24   void destroy() const { }
donestd::coroutine_handle25   bool done() const { return true; }
operator =std::coroutine_handle26   coroutine_handle &operator=(decltype(nullptr)) {
27     ptr = nullptr;
28     return *this;
29   }
coroutine_handlestd::coroutine_handle30   coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
coroutine_handlestd::coroutine_handle31   coroutine_handle() : ptr(nullptr) {}
32   //  void reset() { ptr = nullptr; } // add to P0057?
operator boolstd::coroutine_handle33   explicit operator bool() const { return ptr; }
34 
35 protected:
36   void *ptr;
37 };
38 
39 template <typename Promise> struct coroutine_handle : coroutine_handle<> {
40   using coroutine_handle<>::operator=;
41 
from_addressstd::coroutine_handle42   static coroutine_handle from_address(void *addr) noexcept {
43     coroutine_handle me;
44     me.ptr = addr;
45     return me;
46   }
47 
promisestd::coroutine_handle48   Promise &promise() const {
49     return *reinterpret_cast<Promise *>(
50         __builtin_coro_promise(ptr, alignof(Promise), false));
51   }
from_promisestd::coroutine_handle52   static coroutine_handle from_promise(Promise &promise) {
53     coroutine_handle p;
54     p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
55     return p;
56   }
57 };
58 
59 struct suspend_always {
await_readystd::suspend_always60   bool await_ready() noexcept { return false; }
await_suspendstd::suspend_always61   void await_suspend(std::coroutine_handle<>) noexcept {}
await_resumestd::suspend_always62   void await_resume() noexcept {}
63 };
64 } // namespace std
65 
66 struct ReturnObject {
67     struct promise_type {
get_return_objectReturnObject::promise_type68         ReturnObject get_return_object() { return {}; }
initial_suspendReturnObject::promise_type69         std::suspend_always initial_suspend() { return {}; }
final_suspendReturnObject::promise_type70         std::suspend_always final_suspend() noexcept { return {}; }
unhandled_exceptionReturnObject::promise_type71         void unhandled_exception() {}
yield_valueReturnObject::promise_type72         std::suspend_always yield_value(int value) { return {}; }
73     };
74 };
75 
76 #define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))
77 
78 namespace absl {
79 class SCOPED_LOCKABLE Mutex {};
80 using Mutex2 = Mutex;
81 } // namespace absl
82 
BasicWarning()83 ReturnObject BasicWarning() {
84   absl::Mutex mtx;
85   // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mtx' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
86   int no_warning;
87   {
88     co_yield 1;
89     // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
90   }
91 }
92 
BasicNoWarning()93 ReturnObject BasicNoWarning() {
94   co_yield 1;
95   {  absl::Mutex no_warning; }
96   int no_warning;
97   {
98     co_yield 1;
99     absl::Mutex no_warning;
100   }
101   co_yield 1;
102 }
103 
scopedLockableTest()104 ReturnObject scopedLockableTest() {
105     co_yield 0;
106     absl::Mutex a;
107     // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
108     absl::Mutex2 b;
109     // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 'b' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
110     {
111         absl::Mutex no_warning_1;
112         { absl::Mutex no_warning_2; }
113     }
114 
115     co_yield 1;
116     // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
117     absl::Mutex c;
118     // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'c' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
119     co_await std::suspend_always{};
120     // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
121     for(int i=1; i<=10; ++i ) {
122       absl::Mutex d;
123       // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'd' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
124       co_await std::suspend_always{};
125       // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here
126       co_yield 1;
127       absl::Mutex no_warning_3;
128     }
129     if (true) {
130       absl::Mutex e;
131       // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'e' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
132       co_yield 1;
133       // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here
134       absl::Mutex no_warning_4;
135     }
136     absl::Mutex no_warning_5;
137 }
138 
139 // ================================================================================
140 // Safe awaitable
141 // ================================================================================
142 namespace safe {
143   struct awaitable {
await_readysafe::awaitable144   bool await_ready() noexcept { return false; }
await_suspendsafe::awaitable145   void await_suspend(std::coroutine_handle<>) noexcept {}
await_resumesafe::awaitable146   void await_resume() noexcept {}
147 };
148 } // namespace safe
RAIISafeSuspendTest()149 ReturnObject RAIISafeSuspendTest() {
150   absl::Mutex a;
151   co_await safe::awaitable{};
152   using other = safe::awaitable;
153   co_await other{};
154 }
155 
156 // ================================================================================
157 // Safe transformable awaitable
158 // ================================================================================
159 struct transformable { struct awaitable{}; };
160 using alias_transformable_awaitable = transformable::awaitable;
161 struct UseTransformAwaitable {
162   struct promise_type {
get_return_objectUseTransformAwaitable::promise_type163     UseTransformAwaitable get_return_object() { return {}; }
initial_suspendUseTransformAwaitable::promise_type164     std::suspend_always initial_suspend() { return {}; }
final_suspendUseTransformAwaitable::promise_type165     std::suspend_always final_suspend() noexcept { return {}; }
unhandled_exceptionUseTransformAwaitable::promise_type166     void unhandled_exception() {}
await_transformUseTransformAwaitable::promise_type167     std::suspend_always await_transform(transformable::awaitable) { return {}; }
168   };
169 };
170 
retAwaitable()171 auto retAwaitable() { return transformable::awaitable{}; }
RAIISafeSuspendTest2()172 UseTransformAwaitable RAIISafeSuspendTest2() {
173   absl::Mutex a;
174   co_await retAwaitable();
175   co_await transformable::awaitable{};
176   co_await alias_transformable_awaitable{};
177 }
178 
179 // ================================================================================
180 // Lambdas
181 // ================================================================================
lambda()182 void lambda() {
183   absl::Mutex no_warning;
184   auto lambda = []() -> ReturnObject {
185     co_await std::suspend_always{};
186     absl::Mutex a;
187     // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
188     co_yield 1;
189     // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
190     co_await std::suspend_always{};
191     co_yield 1;
192   };
193   absl::Mutex no_warning_2;
194 }
195 
196 // ================================================================================
197 // Denylisted RAII
198 // ================================================================================
199 template<class T>
raii_in_template()200 ReturnObject raii_in_template(){
201   T a;
202   // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
203   co_yield 1;
204   // CHECK-MESSAGES: :[[@LINE-1]]:3: note: suspension point is here
205 }
foo_template()206 void foo_template() { raii_in_template<absl::Mutex>(); }
207 
208 namespace my {
209 class Mutex{};
210 namespace other {
211 class Mutex{};
212 } // namespace other
213 
214 using Mutex2 = Mutex;
215 } // namespace my
216 
denyListTest()217 ReturnObject denyListTest() {
218     my::Mutex a;
219     // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'a' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
220     my::other::Mutex b;
221     // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 'b' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
222     my::Mutex2 c;
223     // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 'c' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
224     co_yield 1;
225     // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
226 }
227 
referenceTest(my::Mutex & ref)228 ReturnObject referenceTest(my::Mutex& ref) {
229     my::Mutex& a = ref;
230     co_yield 1;
231 }
pointerTest(my::Mutex * ref)232 ReturnObject pointerTest(my::Mutex* ref) {
233     my::Mutex* a = ref;
234     co_yield 1;
235 }
236 
functionArgTest(my::Mutex ref)237 ReturnObject functionArgTest(my::Mutex ref) {
238     co_yield 1;
239 }
240