106 lines · plain
1.. title:: clang-tidy - misc-coroutine-hostile-raii2 3misc-coroutine-hostile-raii4===========================5 6Detects when objects of certain hostile RAII types persists across suspension7points in a coroutine. Such hostile types include scoped-lockable types and8types belonging to a configurable denylist.9 10Some objects require that they be destroyed on the same thread that created11them. Traditionally this requirement was often phrased as "must be a local12variable", under the assumption that local variables always work this way.13However this is incorrect with C++20 coroutines, since an intervening14``co_await`` may cause the coroutine to suspend and later be resumed on15another thread.16 17The lifetime of an object that requires being destroyed on the same thread18must not encompass a ``co_await`` or ``co_yield`` point. If you create/destroy19an object, you must do so without allowing the coroutine to suspend in the20meantime.21 22Following types are considered as hostile:23 24 - Scoped-lockable types: A scoped-lockable object persisting across a25 suspension point is problematic as the lock held by this object could26 be unlocked by a different thread. This would be undefined behaviour.27 This includes all types annotated with the ``scoped_lockable`` attribute.28 29 - Types belonging to a configurable denylist.30 31.. code-block:: c++32 33 // Call some async API while holding a lock.34 task coro() {35 const std::lock_guard l(&mu_);36 37 // Oops! The async Bar function may finish on a different38 // thread from the one that created the lock_guard (and called39 // Mutex::Lock). After suspension, Mutex::Unlock will be called on the wrong thread.40 co_await Bar();41 }42 43Options44-------45 46.. option:: RAIITypesList47 48 A semicolon-separated list of qualified types which should not be allowed to49 persist across suspension points.50 Eg: `my::lockable;a::b;::my::other::lockable`51 The default value of this option is `std::lock_guard;std::scoped_lock`.52 53.. option:: AllowedAwaitablesList54 55 A semicolon-separated list of qualified types of awaitables types which can56 be safely awaited while having hostile RAII objects in scope.57 58 ``co_await``-ing an expression of ``awaitable`` type is considered59 safe if the ``awaitable`` type is part of this list.60 RAII objects persisting across such a ``co_await`` expression are61 considered safe and hence are not flagged.62 63 Example usage:64 65 .. code-block:: c++66 67 // Consider option AllowedAwaitablesList = "safe_awaitable"68 struct safe_awaitable {69 bool await_ready() noexcept { return false; }70 void await_suspend(std::coroutine_handle<>) noexcept {}71 void await_resume() noexcept {}72 };73 auto wait() { return safe_awaitable{}; }74 75 task coro() {76 // This persists across both the co_await's but is not flagged77 // because the awaitable is considered safe to await on.78 const std::lock_guard l(&mu_);79 co_await safe_awaitable{};80 co_await wait();81 }82 83 Eg: `my::safe::awaitable;other::awaitable`84 Default is an empty string.85 86.. option:: AllowedCallees87 88 A semicolon-separated list of callee function names which can89 be safely awaited while having hostile RAII objects in scope.90 Example usage:91 92 .. code-block:: c++93 94 // Consider option AllowedCallees = "noop"95 task noop() { co_return; }96 97 task coro() {98 // This persists across the co_await but is not flagged99 // because the awaitable is considered safe to await on.100 const std::lock_guard l(&mu_);101 co_await noop();102 }103 104 Eg: `my::safe::await;other::await`105 Default is an empty string.106