.. title:: clang-tidy - misc-coroutine-hostile-raii
misc-coroutine-hostile-raii
===========================
Detects when objects of certain hostile RAII types persists across suspension
points in a coroutine. Such hostile types include scoped-lockable types and
types belonging to a configurable denylist.
Some objects require that they be destroyed on the same thread that created them.
Traditionally this requirement was often phrased as "must be a local variable",
under the assumption that local variables always work this way. However this is
incorrect with C++20 coroutines, since an intervening ``co_await`` may cause the
coroutine to suspend and later be resumed on another thread.
The lifetime of an object that requires being destroyed on the same thread must
not encompass a ``co_await`` or ``co_yield`` point. If you create/destroy an object,
you must do so without allowing the coroutine to suspend in the meantime.
Following types are considered as hostile:
- Scoped-lockable types: A scoped-lockable object persisting across a suspension
point is problematic as the lock held by this object could be unlocked by a
different thread. This would be undefined behaviour.
This includes all types annotated with the ``scoped_lockable`` attribute.
- Types belonging to a configurable denylist.
.. code-block:: c++
// Call some async API while holding a lock.
task coro() {
const std::lock_guard l(&mu_);
// Oops! The async Bar function may finish on a different
// thread from the one that created the lock_guard (and called
// Mutex::Lock). After suspension, Mutex::Unlock will be called on the wrong thread.
co_await Bar();
}
Options
-------
.. option:: RAIITypesList
A semicolon-separated list of qualified types which should not be allowed to
persist across suspension points.
Eg: ``my::lockable; a::b;::my::other::lockable;``
The default value of this option is `"std::lock_guard;std::scoped_lock"`.
.. option:: AllowedAwaitablesList
A semicolon-separated list of qualified types of awaitables types which can
be safely awaited while having hostile RAII objects in scope.
``co_await``-ing an expression of ``awaitable`` type is considered
safe if the ``awaitable`` type is part of this list.
RAII objects persisting across such a ``co_await`` expression are
considered safe and hence are not flagged.
Example usage:
.. code-block:: c++
// Consider option AllowedAwaitablesList = "safe_awaitable"
struct safe_awaitable {
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<>) noexcept {}
void await_resume() noexcept {}
};
auto wait() { return safe_awaitable{}; }
task coro() {
// This persists across both the co_await's but is not flagged
// because the awaitable is considered safe to await on.
const std::lock_guard l(&mu_);
co_await safe_awaitable{};
co_await wait();
}
Eg: ``my::safe::awaitable;other::awaitable``
The default value of this option is empty string `""`.