255 lines · cpp
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: misc-coroutine-hostile-raii.AllowedCallees: 'safe::AwaitFunc; ::safe::Obj::AwaitMethod; retExemptedAwaitable' \6// RUN: }}"7 8namespace std {9 10template <typename R, typename...> struct coroutine_traits {11 using promise_type = typename R::promise_type;12};13 14template <typename Promise = void> struct coroutine_handle;15 16template <> struct coroutine_handle<void> {17 static coroutine_handle from_address(void *addr) noexcept {18 coroutine_handle me;19 me.ptr = addr;20 return me;21 }22 void operator()() { resume(); }23 void *address() const noexcept { return ptr; }24 void resume() const { }25 void destroy() const { }26 bool done() const { return true; }27 coroutine_handle &operator=(decltype(nullptr)) {28 ptr = nullptr;29 return *this;30 }31 coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}32 coroutine_handle() : ptr(nullptr) {}33 // void reset() { ptr = nullptr; } // add to P0057?34 explicit operator bool() const { return ptr; }35 36protected:37 void *ptr;38};39 40template <typename Promise> struct coroutine_handle : coroutine_handle<> {41 using coroutine_handle<>::operator=;42 43 static coroutine_handle from_address(void *addr) noexcept {44 coroutine_handle me;45 me.ptr = addr;46 return me;47 }48 49 Promise &promise() const {50 return *reinterpret_cast<Promise *>(51 __builtin_coro_promise(ptr, alignof(Promise), false));52 }53 static coroutine_handle from_promise(Promise &promise) {54 coroutine_handle p;55 p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);56 return p;57 }58};59 60struct suspend_always {61 bool await_ready() noexcept { return false; }62 void await_suspend(std::coroutine_handle<>) noexcept {}63 void await_resume() noexcept {}64};65} // namespace std66 67struct ReturnObject {68 struct promise_type {69 ReturnObject get_return_object() { return {}; }70 std::suspend_always initial_suspend() { return {}; }71 std::suspend_always final_suspend() noexcept { return {}; }72 void unhandled_exception() {}73 std::suspend_always yield_value(int value) { return {}; }74 };75};76 77#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))78 79namespace absl {80class SCOPED_LOCKABLE Mutex {};81using Mutex2 = Mutex;82} // namespace absl83 84ReturnObject BasicWarning() {85 absl::Mutex mtx;86 // 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]87 int no_warning;88 {89 co_yield 1;90 // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here91 }92}93 94ReturnObject BasicNoWarning() {95 co_yield 1;96 { absl::Mutex no_warning; }97 int no_warning;98 {99 co_yield 1;100 absl::Mutex no_warning;101 }102 co_yield 1;103}104 105ReturnObject scopedLockableTest() {106 co_yield 0;107 absl::Mutex a;108 // 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]109 absl::Mutex2 b;110 // 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]111 {112 absl::Mutex no_warning_1;113 { absl::Mutex no_warning_2; }114 }115 116 co_yield 1;117 // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here118 absl::Mutex c;119 // 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]120 co_await std::suspend_always{};121 // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here122 for(int i=1; i<=10; ++i ) {123 absl::Mutex d;124 // 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]125 co_await std::suspend_always{};126 // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here127 co_yield 1;128 absl::Mutex no_warning_3;129 }130 if (true) {131 absl::Mutex e;132 // 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]133 co_yield 1;134 // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here135 absl::Mutex no_warning_4;136 }137 absl::Mutex no_warning_5;138}139 140// ================================================================================141// Safe awaitable142// ================================================================================143namespace safe {144 struct awaitable {145 bool await_ready() noexcept { return false; }146 void await_suspend(std::coroutine_handle<>) noexcept {}147 void await_resume() noexcept {}148};149 std::suspend_always AwaitFunc();150 struct Obj {151 std::suspend_always AwaitMethod();152 };153} // namespace safe154ReturnObject RAIISafeSuspendTest() {155 absl::Mutex a;156 co_await safe::awaitable{};157 using other = safe::awaitable;158 co_await other{};159 co_await safe::AwaitFunc();160 co_await safe::Obj().AwaitMethod();161} 162 163// ================================================================================164// Safe transformable awaitable165// ================================================================================166struct transformable {167 struct awaitable{};168 struct unsafe_awaitable{};169};170using alias_transformable_awaitable = transformable::awaitable;171struct UseTransformAwaitable {172 struct promise_type {173 UseTransformAwaitable get_return_object() { return {}; }174 std::suspend_always initial_suspend() { return {}; }175 std::suspend_always final_suspend() noexcept { return {}; }176 void unhandled_exception() {}177 std::suspend_always await_transform(transformable::awaitable) { return {}; }178 std::suspend_always await_transform(transformable::unsafe_awaitable) {179 return {};180 }181 };182};183 184auto retAwaitable() { return transformable::awaitable{}; }185auto retExemptedAwaitable() { return transformable::unsafe_awaitable{}; }186UseTransformAwaitable RAIISafeSuspendTest2() {187 absl::Mutex a;188 co_await retAwaitable();189 co_await retExemptedAwaitable();190 co_await transformable::awaitable{};191 co_await alias_transformable_awaitable{};192}193 194// ================================================================================195// Lambdas196// ================================================================================197void lambda() {198 absl::Mutex no_warning;199 auto lambda = []() -> ReturnObject {200 co_await std::suspend_always{};201 absl::Mutex a;202 // 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]203 co_yield 1;204 // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here205 co_await std::suspend_always{};206 co_yield 1;207 };208 absl::Mutex no_warning_2;209}210 211// ================================================================================212// Denylisted RAII213// ================================================================================214template<class T>215ReturnObject raii_in_template(){216 T a;217 // 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]218 co_yield 1;219 // CHECK-MESSAGES: :[[@LINE-1]]:3: note: suspension point is here220}221void foo_template() { raii_in_template<absl::Mutex>(); }222 223namespace my {224class Mutex{};225namespace other {226class Mutex{};227} // namespace other228 229using Mutex2 = Mutex;230} // namespace my231 232ReturnObject denyListTest() {233 my::Mutex a;234 // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'a' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]235 my::other::Mutex b;236 // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 'b' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]237 my::Mutex2 c;238 // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 'c' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]239 co_yield 1;240 // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here241}242 243ReturnObject referenceTest(my::Mutex& ref) {244 my::Mutex& a = ref;245 co_yield 1;246}247ReturnObject pointerTest(my::Mutex* ref) {248 my::Mutex* a = ref;249 co_yield 1;250}251 252ReturnObject functionArgTest(my::Mutex ref) {253 co_yield 1;254}255