Author: Willem Kaufmann Date: 2026-04-03T16:56:08+03:00 New Revision: e1f6dc4b2361ab6815f15c08e9a8b97f152a82cf
URL: https://github.com/llvm/llvm-project/commit/e1f6dc4b2361ab6815f15c08e9a8b97f152a82cf DIFF: https://github.com/llvm/llvm-project/commit/e1f6dc4b2361ab6815f15c08e9a8b97f152a82cf.diff LOG: [clang-tidy] Add `AllowExplicitObjectParameters` option to `avoid-capturing-lambda-coroutines` (#182916) Add an off-by-default `AllowExplicitObjectParameters` option to the existing `cppcoreguidelines-avoid-capturing-lambda-coroutines` check. When enabled, lambda coroutines that use C++23 "deducing this" (explicit object parameter) are not flagged, since captures are moved into the coroutine frame ([1], [2], [3]). In C++23 mode, the check also provides fix-it hints to add `this auto` as the first parameter for lambdas that don't use it. The option is off by default to match the current C++ Core Guidelines, which do not yet recognize explicit object parameters as a solution ([4]). Once the guidelines adopt the proposal, the default can be flipped. [1]: https://github.com/scylladb/seastar/blob/master/doc/lambda-coroutine-fiasco.md#solution-c23-and-up [2]: https://www.scs.stanford.edu/~dm/blog/vexing-capture.html [3]: https://lists.isocpp.org/std-proposals/2020/05/1391.php [4]: https://github.com/isocpp/CppCoreGuidelines/pull/2289#issuecomment-3756500251 Added: clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines-allow-explicit-object-parameters.cpp Modified: clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.cpp clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.h clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.rst clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.cpp index 618554663ab91..8726ba0508dfd 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.cpp @@ -21,12 +21,29 @@ AST_MATCHER(LambdaExpr, hasCoroutineBody) { } AST_MATCHER(LambdaExpr, hasCaptures) { return Node.capture_size() != 0U; } + +AST_MATCHER(LambdaExpr, hasDeducingThis) { + return Node.getCallOperator()->isExplicitObjectMemberFunction(); +} } // namespace +AvoidCapturingLambdaCoroutinesCheck::AvoidCapturingLambdaCoroutinesCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowExplicitObjectParameters( + Options.get("AllowExplicitObjectParameters", false)) {} + void AvoidCapturingLambdaCoroutinesCheck::registerMatchers( MatchFinder *Finder) { + using LambdaExprMatcher = ast_matchers::internal::Matcher<LambdaExpr>; + const auto ExplicitObjectFilter = + AllowExplicitObjectParameters + ? LambdaExprMatcher(unless(hasDeducingThis())) + : LambdaExprMatcher(anything()); Finder->addMatcher( - lambdaExpr(hasCaptures(), hasCoroutineBody()).bind("lambda"), this); + lambdaExpr(hasCaptures(), hasCoroutineBody(), ExplicitObjectFilter) + .bind("lambda"), + this); } bool AvoidCapturingLambdaCoroutinesCheck::isLanguageVersionSupported( @@ -34,6 +51,12 @@ bool AvoidCapturingLambdaCoroutinesCheck::isLanguageVersionSupported( return LangOpts.CPlusPlus20; } +void AvoidCapturingLambdaCoroutinesCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowExplicitObjectParameters", + AllowExplicitObjectParameters); +} + void AvoidCapturingLambdaCoroutinesCheck::check( const MatchFinder::MatchResult &Result) { const auto *MatchedLambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.h index de59ff189c595..72e5ff3168719 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidCapturingLambdaCoroutinesCheck.h @@ -21,11 +21,15 @@ namespace clang::tidy::cppcoreguidelines { /// https://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.html class AvoidCapturingLambdaCoroutinesCheck : public ClangTidyCheck { public: - AvoidCapturingLambdaCoroutinesCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + AvoidCapturingLambdaCoroutinesCheck(StringRef Name, + ClangTidyContext *Context); void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + +private: + const bool AllowExplicitObjectParameters; }; } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 69dc5b9633398..ce0b9b17c87e9 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -275,6 +275,12 @@ Changes in existing checks - Add support for annotation of user-defined types as having the same moved-from semantics as standard smart pointers. +- Improved :doc:`cppcoreguidelines-avoid-capturing-lambda-coroutines + <clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines>` + check by adding the `AllowExplicitObjectParameters` option. When enabled, + lambda coroutines using C++23 deducing ``this`` (explicit object parameter) + are not flagged. + - Improved :doc:`cppcoreguidelines-init-variables <clang-tidy/checks/cppcoreguidelines/init-variables>` check by ensuring that member pointers are correctly flagged as uninitialized. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.rst index 58bfc35c557dc..f54b33c0a7de8 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines.rst @@ -52,3 +52,32 @@ captures or ensuring the lambda closure object has a guaranteed lifetime. Following these guidelines can help ensure the safe and reliable use of coroutine lambdas in C++ code. + +Options +------- + +.. option:: AllowExplicitObjectParameters + + When set to `true`, lambda coroutines that use C++23 "deducing this" + (explicit object parameter, e.g. ``this auto``) are not flagged by this + check, because the captures are moved into the coroutine frame, decoupling + their lifetime from the lambda object. + + Default is `false`. + + The example from above can be made safe and will pass this check with the + following change: + + .. code-block:: c++ + + int value = get_value(); + std::shared_ptr<Foo> sharedFoo = get_foo(); + { + // Pass "this auto" as the first argument to the lambda + const auto lambda = [value, sharedFoo](this auto) -> std::future<void> + { + co_await something(); + }; + lambda(); + } // the lambda closure object has now gone out of scope, but captures are + // no longer coupled to its lifetime diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines-allow-explicit-object-parameters.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines-allow-explicit-object-parameters.cpp new file mode 100644 index 0000000000000..b90785fa28be5 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines-allow-explicit-object-parameters.cpp @@ -0,0 +1,93 @@ +// RUN: %check_clang_tidy -std=c++23-or-later %s cppcoreguidelines-avoid-capturing-lambda-coroutines %t \ +// RUN: -- -config='{CheckOptions: {cppcoreguidelines-avoid-capturing-lambda-coroutines.AllowExplicitObjectParameters: true}}' \ +// RUN: -- -isystem %S/Inputs/system + +#include <coroutines.h> + +// --- Cases that SHOULD still trigger the warning --- + +void test_capture_coroutine_no_deducing_this() { + int x = 42; + [&x]() -> task { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: coroutine lambda may cause use-after-free, avoid captures or ensure lambda closure object has guaranteed lifetime [cppcoreguidelines-avoid-capturing-lambda-coroutines] + co_return; + }; +} + +void test_capture_coroutine_with_params() { + int x = 42; + [&x](int a) -> task { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: coroutine lambda may cause use-after-free + co_return; + }; +} + +void test_default_capture_ref() { + int x = 42; + [&]() -> task { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: coroutine lambda may cause use-after-free + (void)x; + co_return; + }; +} + +void test_default_capture_copy() { + int x = 42; + [=]() -> task { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: coroutine lambda may cause use-after-free + (void)x; + co_return; + }; +} + +struct S { + void test_this_capture() { + [this]() -> task { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: coroutine lambda may cause use-after-free + co_return; + }; + } +}; + +// --- Cases that should NOT trigger the warning --- + +void test_deducing_this_coroutine() { + int x = 42; + [&x](this auto) -> task { co_return; }; +} + +void test_deducing_this_with_params() { + int x = 42; + [&x](this auto, int a) -> task { co_return; }; +} + +void test_deducing_this_with_template() { + int x = 42; + [&x]<typename T>(this auto, T a) -> task { co_return; }; +} + +void test_deducing_this_ref_qualified() { + int x = 42; + [&x](this auto&&) -> task { co_return; }; +} + +template<typename T> +concept Integral = requires(T t) { t + 1; }; + +void test_deducing_this_with_requires() { + int x = 42; + [&x]<typename T>(this auto, T a) -> task requires Integral<T> { co_return; }; +} + +void test_no_captures_no_coroutine() { + []() { return; }; +} + +void test_no_captures_coroutine() { + []() -> task { co_return; }; +} + +void test_captures_not_coroutine() { + int x = 42; + [&x]() { (void)x; }; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines.cpp index 6670f4a5420be..ab10122347a82 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/avoid-capturing-lambda-coroutines.cpp @@ -1,4 +1,8 @@ // RUN: %check_clang_tidy -std=c++20-or-later %s cppcoreguidelines-avoid-capturing-lambda-coroutines %t -- -- -isystem %S/Inputs/system +// RUN: %check_clang_tidy -std=c++23-or-later -check-suffix=,CPP23 %s cppcoreguidelines-avoid-capturing-lambda-coroutines %t \ +// RUN: -- -config='{CheckOptions: {cppcoreguidelines-avoid-capturing-lambda-coroutines.AllowExplicitObjectParameters: false}}' \ +// RUN: -- -isystem %S/Inputs/system -DCPP23 + #include <coroutines.h> @@ -17,6 +21,10 @@ void Caught() { // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine lambda may cause use-after-free, avoid captures or ensure lambda closure object has guaranteed lifetime [cppcoreguidelines-avoid-capturing-lambda-coroutines] [y{v}] () -> task { co_return; }; // CHECK-MESSAGES: [[@LINE-1]]:5: warning: coroutine lambda may cause use-after-free, avoid captures or ensure lambda closure object has guaranteed lifetime [cppcoreguidelines-avoid-capturing-lambda-coroutines] +#ifdef CPP23 + [&v](this auto) -> task { co_return; }; + // CHECK-MESSAGES-CPP23: [[@LINE-1]]:5: warning: coroutine lambda may cause use-after-free, avoid captures or ensure lambda closure object has guaranteed lifetime [cppcoreguidelines-avoid-capturing-lambda-coroutines] +#endif } struct S { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
