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

Reply via email to