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()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 BasicNoWarning()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 scopedLockableTest()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 // ================================================================================ 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()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 // ================================================================================ 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()171auto retAwaitable() { return transformable::awaitable{}; } RAIISafeSuspendTest2()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 // ================================================================================ lambda()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 // ================================================================================ 199 template<class T> raii_in_template()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 } foo_template()206void 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()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 referenceTest(my::Mutex & ref)228ReturnObject referenceTest(my::Mutex& ref) { 229 my::Mutex& a = ref; 230 co_yield 1; 231 } pointerTest(my::Mutex * ref)232ReturnObject pointerTest(my::Mutex* ref) { 233 my::Mutex* a = ref; 234 co_yield 1; 235 } 236 functionArgTest(my::Mutex ref)237ReturnObject functionArgTest(my::Mutex ref) { 238 co_yield 1; 239 } 240