1 | // RUN: %check_clang_tidy -std=c++20 %s cppcoreguidelines-no-suspend-with-lock %t -- -- -fno-delayed-template-parsing -fexceptions |
2 | |
3 | // NOLINTBEGIN |
4 | namespace std { |
5 | template <typename T, typename... Args> |
6 | struct coroutine_traits { |
7 | using promise_type = typename T::promise_type; |
8 | }; |
9 | template <typename T = void> |
10 | struct coroutine_handle; |
11 | template <> |
12 | struct coroutine_handle<void> { |
13 | coroutine_handle() noexcept; |
14 | coroutine_handle(decltype(nullptr)) noexcept; |
15 | static constexpr coroutine_handle from_address(void*); |
16 | }; |
17 | template <typename T> |
18 | struct coroutine_handle { |
19 | coroutine_handle() noexcept; |
20 | coroutine_handle(decltype(nullptr)) noexcept; |
21 | static constexpr coroutine_handle from_address(void*); |
22 | operator coroutine_handle<>() const noexcept; |
23 | }; |
24 | |
25 | template <class Mutex> |
26 | class unique_lock { |
27 | public: |
28 | unique_lock() noexcept; |
29 | explicit unique_lock(Mutex &m); |
30 | unique_lock& operator=(unique_lock&&); |
31 | void unlock(); |
32 | Mutex* release() noexcept; |
33 | Mutex* mutex() const noexcept; |
34 | void swap(unique_lock& other) noexcept; |
35 | }; |
36 | |
37 | class mutex { |
38 | public: |
39 | mutex() noexcept; |
40 | ~mutex(); |
41 | mutex(const mutex &) = delete; |
42 | mutex &operator=(const mutex &) = delete; |
43 | |
44 | void lock(); |
45 | void unlock(); |
46 | }; |
47 | } // namespace std |
48 | |
49 | class my_own_mutex { |
50 | public: |
51 | void lock(); |
52 | void unlock(); |
53 | }; |
54 | |
55 | struct Awaiter { |
56 | bool await_ready() noexcept; |
57 | void await_suspend(std::coroutine_handle<>) noexcept; |
58 | void await_resume() noexcept; |
59 | }; |
60 | |
61 | struct Coro { |
62 | struct promise_type { |
63 | Awaiter initial_suspend(); |
64 | Awaiter final_suspend() noexcept; |
65 | void return_void(); |
66 | Coro get_return_object(); |
67 | void unhandled_exception(); |
68 | Awaiter yield_value(int); |
69 | }; |
70 | }; |
71 | // NOLINTEND |
72 | |
73 | std::mutex mtx; |
74 | std::mutex mtx2; |
75 | |
76 | Coro awaits_with_lock() { |
77 | std::unique_lock<std::mutex> lock(mtx); |
78 | |
79 | co_await Awaiter{}; |
80 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
81 | |
82 | co_await Awaiter{}; |
83 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
84 | |
85 | if (true) { |
86 | co_await Awaiter{}; |
87 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
88 | } |
89 | |
90 | if (true) { |
91 | std::unique_lock<std::mutex> lock2; |
92 | lock2.unlock(); |
93 | co_await Awaiter{}; |
94 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock2' held [cppcoreguidelines-no-suspend-with-lock] |
95 | } |
96 | } |
97 | |
98 | Coro awaits_with_lock_in_try() try { |
99 | std::unique_lock<std::mutex> lock(mtx); |
100 | co_await Awaiter{}; |
101 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
102 | } catch (...) {} |
103 | |
104 | Coro lock_possibly_unlocked() { |
105 | // CppCoreGuideline CP.52's enforcement strictly requires flagging |
106 | // code that suspends while any lock guard is not destructed. |
107 | |
108 | { |
109 | std::unique_lock<std::mutex> lock(mtx); |
110 | lock.unlock(); |
111 | co_await Awaiter{}; |
112 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
113 | } |
114 | |
115 | { |
116 | std::unique_lock<std::mutex> lock(mtx); |
117 | lock.release(); |
118 | co_await Awaiter{}; |
119 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
120 | } |
121 | |
122 | { |
123 | std::unique_lock<std::mutex> lock(mtx); |
124 | std::unique_lock<std::mutex> lock2; |
125 | lock.swap(other&: lock2); |
126 | co_await Awaiter{}; |
127 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
128 | } |
129 | |
130 | { |
131 | std::unique_lock<std::mutex> lock(mtx); |
132 | std::unique_lock<std::mutex> lock2{mtx2}; |
133 | lock.swap(other&: lock2); |
134 | co_await Awaiter{}; |
135 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
136 | } |
137 | |
138 | { |
139 | std::unique_lock<std::mutex> lock(mtx); |
140 | lock = std::unique_lock<std::mutex>{}; |
141 | co_await Awaiter{}; |
142 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
143 | } |
144 | |
145 | { |
146 | std::unique_lock<std::mutex> lock(mtx); |
147 | lock = std::unique_lock<std::mutex>{mtx2}; |
148 | co_await Awaiter{}; |
149 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
150 | } |
151 | } |
152 | |
153 | Coro await_with_underlying_mutex_unlocked() { |
154 | std::unique_lock<std::mutex> lock(mtx); |
155 | |
156 | // Even though we unlock the mutex here, 'lock' is still active unless |
157 | // there is a call to lock.unlock(). This is a bug in the program since |
158 | // it will result in locking the mutex twice. The check does not track |
159 | // unlock calls on the underlying mutex held by a lock guard object. |
160 | mtx.unlock(); |
161 | |
162 | co_await Awaiter{}; |
163 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
164 | } |
165 | |
166 | Coro await_with_empty_lock() { |
167 | std::unique_lock<std::mutex> lock; |
168 | co_await Awaiter{}; |
169 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
170 | } |
171 | |
172 | Coro await_before_lock() { |
173 | co_await Awaiter{}; |
174 | std::unique_lock<std::mutex> lock(mtx); |
175 | } |
176 | |
177 | Coro await_with_lock_different_scope() { |
178 | { |
179 | std::unique_lock<std::mutex> lock(mtx); |
180 | } |
181 | co_await Awaiter{}; |
182 | } |
183 | |
184 | Coro await_with_goto() { |
185 | first: |
186 | co_await Awaiter{}; |
187 | std::unique_lock<std::mutex> lock(mtx); |
188 | goto first; |
189 | } |
190 | |
191 | void await_in_lambda() { |
192 | auto f1 = []() -> Coro { |
193 | std::unique_lock<std::mutex> lock(mtx); |
194 | co_await Awaiter{}; |
195 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
196 | }; |
197 | |
198 | auto f2 = [](auto& m) -> Coro { |
199 | std::unique_lock<decltype(m)> lock(m); |
200 | co_await Awaiter{}; |
201 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
202 | }; |
203 | } |
204 | |
205 | void await_in_lambda_without_immediate_mutex() { |
206 | std::unique_lock<std::mutex> lock(mtx); |
207 | |
208 | auto f1 = []() -> Coro { |
209 | co_await Awaiter{}; |
210 | }; |
211 | |
212 | // The check only finds suspension points where there is a lock held in the |
213 | // immediate callable. |
214 | f1(); |
215 | } |
216 | |
217 | Coro yields_with_lock() { |
218 | std::unique_lock<std::mutex> lock(mtx); |
219 | co_yield 0; |
220 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
221 | } |
222 | |
223 | template <class Mutex> |
224 | Coro awaits_templated_type(Mutex& m) { |
225 | std::unique_lock<Mutex> lock(m); |
226 | co_await Awaiter{}; |
227 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
228 | } |
229 | |
230 | template <class T> |
231 | Coro awaits_in_template_function(T) { |
232 | std::unique_lock<std::mutex> lock(mtx); |
233 | co_await Awaiter{}; |
234 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
235 | } |
236 | |
237 | template <class Mutex> |
238 | Coro awaits_in_never_instantiated_template_of_mutex(Mutex& m) { |
239 | // Nothing should instantiate this function |
240 | std::unique_lock<Mutex> lock(m); |
241 | co_await Awaiter{}; |
242 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
243 | } |
244 | |
245 | template <class T> |
246 | Coro awaits_in_never_instantiated_templated_function(T) { |
247 | // Nothing should instantiate this function |
248 | std::unique_lock<std::mutex> lock(mtx); |
249 | co_await Awaiter{}; |
250 | // CHECK-MESSAGES: [[@LINE-1]]:3: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
251 | } |
252 | |
253 | template <class T> |
254 | struct my_container { |
255 | |
256 | Coro push_back() { |
257 | std::unique_lock<std::mutex> lock(mtx_); |
258 | co_await Awaiter{}; |
259 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
260 | } |
261 | |
262 | template <class... Args> |
263 | Coro emplace_back(Args&&...) { |
264 | std::unique_lock<std::mutex> lock(mtx_); |
265 | co_await Awaiter{}; |
266 | // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine suspended with lock 'lock' held [cppcoreguidelines-no-suspend-with-lock] |
267 | } |
268 | |
269 | std::mutex mtx_; |
270 | }; |
271 | |
272 | void calls_templated_functions() { |
273 | my_own_mutex m2; |
274 | awaits_templated_type(m&: mtx); |
275 | awaits_templated_type(m&: m2); |
276 | |
277 | awaits_in_template_function(1); |
278 | awaits_in_template_function(1.0); |
279 | } |
280 | |