// RUN: %check_clang_tidy -std=c++20 %s misc-coroutine-hostile-raii %t \ // RUN: -config="{CheckOptions: {\ // RUN: misc-coroutine-hostile-raii.RAIITypesList: 'my::Mutex; ::my::other::Mutex', \ // RUN: misc-coroutine-hostile-raii.AllowedAwaitablesList: 'safe::awaitable; ::transformable::awaitable' \ // RUN: }}" namespace std { template struct coroutine_traits { using promise_type = typename R::promise_type; }; template struct coroutine_handle; template <> struct coroutine_handle { static coroutine_handle from_address(void *addr) noexcept { coroutine_handle me; me.ptr = addr; return me; } void operator()() { resume(); } void *address() const noexcept { return ptr; } void resume() const { } void destroy() const { } bool done() const { return true; } coroutine_handle &operator=(decltype(nullptr)) { ptr = nullptr; return *this; } coroutine_handle(decltype(nullptr)) : ptr(nullptr) {} coroutine_handle() : ptr(nullptr) {} // void reset() { ptr = nullptr; } // add to P0057? explicit operator bool() const { return ptr; } protected: void *ptr; }; template struct coroutine_handle : coroutine_handle<> { using coroutine_handle<>::operator=; static coroutine_handle from_address(void *addr) noexcept { coroutine_handle me; me.ptr = addr; return me; } Promise &promise() const { return *reinterpret_cast( __builtin_coro_promise(ptr, alignof(Promise), false)); } static coroutine_handle from_promise(Promise &promise) { coroutine_handle p; p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true); return p; } }; struct suspend_always { bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle<>) noexcept {} void await_resume() noexcept {} }; } // namespace std struct ReturnObject { struct promise_type { ReturnObject get_return_object() { return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() {} std::suspend_always yield_value(int value) { return {}; } }; }; #define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) namespace absl { class SCOPED_LOCKABLE Mutex {}; using Mutex2 = Mutex; } // namespace absl ReturnObject BasicWarning() { absl::Mutex mtx; // 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] int no_warning; { co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here } } ReturnObject BasicNoWarning() { co_yield 1; { absl::Mutex no_warning; } int no_warning; { co_yield 1; absl::Mutex no_warning; } co_yield 1; } ReturnObject scopedLockableTest() { co_yield 0; absl::Mutex a; // 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] absl::Mutex2 b; // 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] { absl::Mutex no_warning_1; { absl::Mutex no_warning_2; } } co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here absl::Mutex c; // 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] co_await std::suspend_always{}; // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here for(int i=1; i<=10; ++i ) { absl::Mutex d; // 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] co_await std::suspend_always{}; // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here co_yield 1; absl::Mutex no_warning_3; } if (true) { absl::Mutex e; // 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] co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here absl::Mutex no_warning_4; } absl::Mutex no_warning_5; } // ================================================================================ // Safe awaitable // ================================================================================ namespace safe { struct awaitable { bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle<>) noexcept {} void await_resume() noexcept {} }; } // namespace safe ReturnObject RAIISafeSuspendTest() { absl::Mutex a; co_await safe::awaitable{}; using other = safe::awaitable; co_await other{}; } // ================================================================================ // Safe transformable awaitable // ================================================================================ struct transformable { struct awaitable{}; }; using alias_transformable_awaitable = transformable::awaitable; struct UseTransformAwaitable { struct promise_type { UseTransformAwaitable get_return_object() { return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() {} std::suspend_always await_transform(transformable::awaitable) { return {}; } }; }; auto retAwaitable() { return transformable::awaitable{}; } UseTransformAwaitable RAIISafeSuspendTest2() { absl::Mutex a; co_await retAwaitable(); co_await transformable::awaitable{}; co_await alias_transformable_awaitable{}; } // ================================================================================ // Lambdas // ================================================================================ void lambda() { absl::Mutex no_warning; auto lambda = []() -> ReturnObject { co_await std::suspend_always{}; absl::Mutex a; // 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] co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here co_await std::suspend_always{}; co_yield 1; }; absl::Mutex no_warning_2; } // ================================================================================ // Denylisted RAII // ================================================================================ template ReturnObject raii_in_template(){ T a; // 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] co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:3: note: suspension point is here } void foo_template() { raii_in_template(); } namespace my { class Mutex{}; namespace other { class Mutex{}; } // namespace other using Mutex2 = Mutex; } // namespace my ReturnObject denyListTest() { my::Mutex a; // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'a' persists across a suspension point of coroutine [misc-coroutine-hostile-raii] my::other::Mutex b; // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 'b' persists across a suspension point of coroutine [misc-coroutine-hostile-raii] my::Mutex2 c; // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 'c' persists across a suspension point of coroutine [misc-coroutine-hostile-raii] co_yield 1; // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here } ReturnObject referenceTest(my::Mutex& ref) { my::Mutex& a = ref; co_yield 1; } ReturnObject pointerTest(my::Mutex* ref) { my::Mutex* a = ref; co_yield 1; } ReturnObject functionArgTest(my::Mutex ref) { co_yield 1; }