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
7namespace std {
8
9template <typename R, typename...> struct coroutine_traits {
10 using promise_type = typename R::promise_type;
11};
12
13template <typename Promise = void> struct coroutine_handle;
14
15template <> 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
35protected:
36 void *ptr;
37};
38
39template <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
59struct 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
66struct 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
78namespace absl {
79class SCOPED_LOCKABLE Mutex {};
80using Mutex2 = Mutex;
81} // namespace absl
82
83ReturnObject 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
93ReturnObject 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
104ReturnObject 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// ================================================================================
142namespace 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
149ReturnObject 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// ================================================================================
159struct transformable { struct awaitable{}; };
160using alias_transformable_awaitable = transformable::awaitable;
161struct 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
171auto retAwaitable() { return transformable::awaitable{}; }
172UseTransformAwaitable 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// ================================================================================
182void 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// ================================================================================
199template<class T>
200ReturnObject 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}
206void foo_template() { raii_in_template<absl::Mutex>(); }
207
208namespace my {
209class Mutex{};
210namespace other {
211class Mutex{};
212} // namespace other
213
214using Mutex2 = Mutex;
215} // namespace my
216
217ReturnObject 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
228ReturnObject referenceTest(my::Mutex& ref) {
229 my::Mutex& a = ref;
230 co_yield 1;
231}
232ReturnObject pointerTest(my::Mutex* ref) {
233 my::Mutex* a = ref;
234 co_yield 1;
235}
236
237ReturnObject functionArgTest(my::Mutex ref) {
238 co_yield 1;
239}
240

source code of clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp