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> { |
16 | static coroutine_handle from_address(void *addr) noexcept { |
17 | coroutine_handle me; |
18 | me.ptr = addr; |
19 | return me; |
20 | } |
21 | void operator()() { resume(); } |
22 | void *address() const noexcept { return ptr; } |
23 | void resume() const { } |
24 | void destroy() const { } |
25 | bool done() const { return true; } |
26 | coroutine_handle &operator=(decltype(nullptr)) { |
27 | ptr = nullptr; |
28 | return *this; |
29 | } |
30 | coroutine_handle(decltype(nullptr)) : ptr(nullptr) {} |
31 | coroutine_handle() : ptr(nullptr) {} |
32 | // void reset() { ptr = nullptr; } // add to P0057? |
33 | 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 | |
42 | static coroutine_handle from_address(void *addr) noexcept { |
43 | coroutine_handle me; |
44 | me.ptr = addr; |
45 | return me; |
46 | } |
47 | |
48 | Promise &promise() const { |
49 | return *reinterpret_cast<Promise *>( |
50 | __builtin_coro_promise(ptr, alignof(Promise), false)); |
51 | } |
52 | 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 { |
60 | bool await_ready() noexcept { return false; } |
61 | void await_suspend(std::coroutine_handle<>) noexcept {} |
62 | void await_resume() noexcept {} |
63 | }; |
64 | } // namespace std |
65 | |
66 | struct ReturnObject { |
67 | struct promise_type { |
68 | ReturnObject get_return_object() { return {}; } |
69 | std::suspend_always initial_suspend() { return {}; } |
70 | std::suspend_always final_suspend() noexcept { return {}; } |
71 | void unhandled_exception() {} |
72 | 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 | |
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 | |
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 | |
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 { |
144 | bool await_ready() noexcept { return false; } |
145 | void await_suspend(std::coroutine_handle<>) noexcept {} |
146 | void await_resume() noexcept {} |
147 | }; |
148 | } // namespace safe |
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 { |
163 | UseTransformAwaitable get_return_object() { return {}; } |
164 | std::suspend_always initial_suspend() { return {}; } |
165 | std::suspend_always final_suspend() noexcept { return {}; } |
166 | void unhandled_exception() {} |
167 | std::suspend_always await_transform(transformable::awaitable) { return {}; } |
168 | }; |
169 | }; |
170 | |
171 | auto retAwaitable() { return transformable::awaitable{}; } |
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 | // ================================================================================ |
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> |
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 | } |
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 | |
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 | |
228 | ReturnObject referenceTest(my::Mutex& ref) { |
229 | my::Mutex& a = ref; |
230 | co_yield 1; |
231 | } |
232 | ReturnObject pointerTest(my::Mutex* ref) { |
233 | my::Mutex* a = ref; |
234 | co_yield 1; |
235 | } |
236 | |
237 | ReturnObject functionArgTest(my::Mutex ref) { |
238 | co_yield 1; |
239 | } |
240 | |