From 8e6667be4ef671cdb7cc88b7227f2d9c67a68c88 Mon Sep 17 00:00:00 2001 From: higher-performance Date: Wed, 12 Nov 2025 14:16:40 -0800 Subject: [PATCH] Add the ability to exempt callees from the misc-coroutine-hostile-raii clang-tidy check --- .../misc/CoroutineHostileRAIICheck.cpp | 10 ++++++++-- .../misc/CoroutineHostileRAIICheck.h | 3 +++ clang-tools-extra/docs/ReleaseNotes.rst | 5 +++++ .../checks/misc/coroutine-hostile-raii.rst | 20 +++++++++++++++++++ .../checkers/misc/coroutine-hostile-raii.cpp | 9 ++++++++- 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.cpp b/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.cpp index a2d3d3ff1512d..a7b74944690b4 100644 --- a/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.cpp @@ -73,7 +73,9 @@ CoroutineHostileRAIICheck::CoroutineHostileRAIICheck(StringRef Name, RAIITypesList(utils::options::parseStringList( Options.get("RAIITypesList", "std::lock_guard;std::scoped_lock"))), AllowedAwaitablesList(utils::options::parseStringList( - Options.get("AllowedAwaitablesList", ""))) {} + Options.get("AllowedAwaitablesList", ""))), + AllowedCallees( + utils::options::parseStringList(Options.get("AllowedCallees", ""))) {} void CoroutineHostileRAIICheck::registerMatchers(MatchFinder *Finder) { // A suspension happens with co_await or co_yield. @@ -81,7 +83,9 @@ void CoroutineHostileRAIICheck::registerMatchers(MatchFinder *Finder) { hasAttr(attr::Kind::ScopedLockable))))) .bind("scoped-lockable"); auto OtherRAII = varDecl(typeWithNameIn(RAIITypesList)).bind("raii"); - auto AllowedSuspend = awaitable(typeWithNameIn(AllowedAwaitablesList)); + auto AllowedSuspend = awaitable( + anyOf(typeWithNameIn(AllowedAwaitablesList), + callExpr(callee(functionDecl(hasAnyName(AllowedCallees)))))); Finder->addMatcher( expr(anyOf(coawaitExpr(unless(AllowedSuspend)), coyieldExpr()), forEachPrevStmt( @@ -111,5 +115,7 @@ void CoroutineHostileRAIICheck::storeOptions( utils::options::serializeStringList(RAIITypesList)); Options.store(Opts, "SafeAwaitableList", utils::options::serializeStringList(AllowedAwaitablesList)); + Options.store(Opts, "SafeCallees", + utils::options::serializeStringList(AllowedCallees)); } } // namespace clang::tidy::misc diff --git a/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.h b/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.h index 768b62ef07f90..12ad1b1e0e220 100644 --- a/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.h +++ b/clang-tools-extra/clang-tidy/misc/CoroutineHostileRAIICheck.h @@ -46,6 +46,9 @@ class CoroutineHostileRAIICheck : public ClangTidyCheck { // List of fully qualified awaitable types which are considered safe to // co_await. std::vector AllowedAwaitablesList; + // List of callees whose return values are considered safe to directly + // co_await. + std::vector AllowedCallees; }; } // namespace clang::tidy::misc diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 8637a9ab6d9f6..f25c4cacdacb7 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -423,6 +423,11 @@ Changes in existing checks positives on return of non-const pointer and fix false positives on pointer-to-member operator. +- Improved :doc:`misc-coroutine-hostile-raii + ` check by adding the option + `AllowedCallees`, that allows exempting safely awaitable callees from the + check. + - Improved :doc:`misc-header-include-cycle ` check performance. diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/coroutine-hostile-raii.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/coroutine-hostile-raii.rst index 0b054e4e20bd6..be80d39e4abf9 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/misc/coroutine-hostile-raii.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/misc/coroutine-hostile-raii.rst @@ -81,3 +81,23 @@ Options Eg: `my::safe::awaitable;other::awaitable` Default is an empty string. +.. option:: AllowedCallees + + A semicolon-separated list of callee function names which can + be safely awaited while having hostile RAII objects in scope. + Example usage: + + .. code-block:: c++ + + // Consider option AllowedCallees = "noop" + task noop() { co_return; } + + task coro() { + // This persists across the co_await but is not flagged + // because the awaitable is considered safe to await on. + const std::lock_guard l(&mu_); + co_await noop(); + } + + Eg: `my::safe::await;other::await` + Default is an empty string. diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp index c23c355dac1b2..ec6ddec56e1f2 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/coroutine-hostile-raii.cpp @@ -1,7 +1,8 @@ // 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: misc-coroutine-hostile-raii.AllowedAwaitablesList: 'safe::awaitable; ::transformable::awaitable', \ +// RUN: misc-coroutine-hostile-raii.AllowedCallees: 'safe::AwaitFunc; ::safe::Obj::AwaitMethod' \ // RUN: }}" namespace std { @@ -145,12 +146,18 @@ namespace safe { void await_suspend(std::coroutine_handle<>) noexcept {} void await_resume() noexcept {} }; + std::suspend_always AwaitFunc(); + struct Obj { + std::suspend_always AwaitMethod(); + }; } // namespace safe ReturnObject RAIISafeSuspendTest() { absl::Mutex a; co_await safe::awaitable{}; using other = safe::awaitable; co_await other{}; + co_await safe::AwaitFunc(); + co_await safe::Obj().AwaitMethod(); } // ================================================================================