From 35df1c6ea3af8dbaea2d7049ae79b14ce39f0774 Mon Sep 17 00:00:00 2001 From: JJ Marr Date: Fri, 11 Jul 2025 14:40:57 -0400 Subject: [PATCH 01/35] Bugprone-default-lambda-capture on-behalf-of: @AMD --- .../bugprone/BugproneTidyModule.cpp | 3 + .../clang-tidy/bugprone/CMakeLists.txt | 1 + .../bugprone/DefaultLambdaCaptureCheck.cpp | 35 +++++++ .../bugprone/DefaultLambdaCaptureCheck.h | 26 +++++ clang-tools-extra/docs/ReleaseNotes.rst | 9 ++ .../bugprone/default-lambda-capture.rst | 42 ++++++++ .../docs/clang-tidy/checks/list.rst | 1 + .../bugprone/default-lambda-capture.cpp | 98 +++++++++++++++++++ 8 files changed, 215 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 491de6acea2b7..db99d57c511b8 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -23,6 +23,7 @@ #include "CopyConstructorInitCheck.h" #include "CrtpConstructorAccessibilityCheck.h" #include "DanglingHandleCheck.h" +#include "DefaultLambdaCaptureCheck.h" #include "DynamicStaticInitializersCheck.h" #include "EasilySwappableParametersCheck.h" #include "EmptyCatchCheck.h" @@ -134,6 +135,8 @@ class BugproneModule : public ClangTidyModule { "bugprone-copy-constructor-init"); CheckFactories.registerCheck( "bugprone-dangling-handle"); + CheckFactories.registerCheck( + "bugprone-default-lambda-capture"); CheckFactories.registerCheck( "bugprone-dynamic-static-initializers"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index 46bc8efd44bc5..66125c9d22c1c 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -19,6 +19,7 @@ add_clang_library(clangTidyBugproneModule STATIC CopyConstructorInitCheck.cpp CrtpConstructorAccessibilityCheck.cpp DanglingHandleCheck.cpp + DefaultLambdaCaptureCheck.cpp DynamicStaticInitializersCheck.cpp EasilySwappableParametersCheck.cpp EmptyCatchCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp new file mode 100644 index 0000000000000..6c95fbc984c5a --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -0,0 +1,35 @@ +#include "DefaultLambdaCaptureCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::bugprone { + +void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { + // Match any lambda expression + Finder->addMatcher(lambdaExpr().bind("lambda"), this); +} + +void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Lambda = Result.Nodes.getNodeAs("lambda"); + if (!Lambda) + return; + + // Check if lambda has a default capture + if (Lambda->getCaptureDefault() == LCD_None) + return; + + SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); + if (DefaultCaptureLoc.isInvalid()) + return; + + const char *CaptureKind = + (Lambda->getCaptureDefault() == LCD_ByCopy) ? "by-copy" : "by-reference"; + + diag(DefaultCaptureLoc, "lambda %0 default capture is discouraged; " + "prefer to capture specific variables explicitly") + << CaptureKind; +} + +} // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h new file mode 100644 index 0000000000000..ac47c866cfccd --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h @@ -0,0 +1,26 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::bugprone { + +/** Flags lambdas that use default capture modes + * + * For the user-facing documentation see: + * http://clang.llvm.org/extra/clang-tidy/checks/bugprone/default-lambda-capture.html + */ +class DefaultLambdaCaptureCheck : public ClangTidyCheck { +public: + DefaultLambdaCaptureCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; + +} // namespace clang::tidy::bugprone + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 7cdff86beeec6..30d0bffb78f39 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -194,6 +194,15 @@ New checks Finds virtual function overrides with different visibility than the function in the base class. +- New :doc:`bugprone-default-lambda-capture + ` check. + + Finds lambda expressions that use default capture modes (``[=]`` or ``[&]``) + and suggests using explicit captures instead. Default captures can lead to + subtle bugs including dangling references with ``[&]``, unnecessary copies + with ``[=]``, and make code less maintainable by hiding which variables are + actually being captured. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst new file mode 100644 index 0000000000000..026acf9f1894b --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst @@ -0,0 +1,42 @@ +.. title:: clang-tidy - bugprone-default-lambda-capture + +bugprone-default-lambda-capture +=============================== + +Warns when lambda expressions use default capture modes (``[=]`` or ``[&]``) +instead of explicitly capturing specific variables. Default captures can make +code less readable and more error-prone by making it unclear which variables +are being captured and how. + +Implements Item 31 of Effective Modern C++ by Scott Meyers. + +Example +------- + +.. code-block:: c++ + + void example() { + int x = 1; + int y = 2; + + // Bad - default capture by copy + auto lambda1 = [=]() { return x + y; }; + + // Bad - default capture by reference + auto lambda2 = [&]() { return x + y; }; + + // Good - explicit captures + auto lambda3 = [x, y]() { return x + y; }; + auto lambda4 = [&x, &y]() { return x + y; }; + } + +The check will warn on: + +- Default capture by copy: ``[=]`` +- Default capture by reference: ``[&]`` +- Mixed captures with defaults: ``[=, &x]`` or ``[&, x]`` + +The check will not warn on: + +- Explicit captures: ``[x]``, ``[&x]``, ``[x, y]``, ``[this]`` +- Empty capture lists: ``[]`` diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c490d2ece2e0a..cb6254ee36f42 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -91,6 +91,7 @@ Clang-Tidy Checks :doc:`bugprone-copy-constructor-init `, "Yes" :doc:`bugprone-crtp-constructor-accessibility `, "Yes" :doc:`bugprone-dangling-handle `, + :doc:`bugprone-default-lambda-capture `, :doc:`bugprone-dynamic-static-initializers `, :doc:`bugprone-easily-swappable-parameters `, :doc:`bugprone-empty-catch `, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp new file mode 100644 index 0000000000000..f820b4c9b8a0e --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp @@ -0,0 +1,98 @@ +// RUN: %check_clang_tidy %s bugprone-default-lambda-capture %t + +void test_default_captures() { + int value = 42; + int another = 10; + + auto lambda1 = [=](int x) { return value + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto lambda2 = [&](int x) { return value + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto lambda3 = [=, &another](int x) { return value + another + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto lambda4 = [&, value](int x) { return value + another + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] +} + +void test_acceptable_captures() { + int value = 42; + int another = 10; + + auto lambda1 = [value](int x) { return value + x; }; + auto lambda2 = [&value](int x) { return value + x; }; + auto lambda3 = [value, another](int x) { return value + another + x; }; + auto lambda4 = [&value, &another](int x) { return value + another + x; }; + + auto lambda5 = [](int x, int y) { return x + y; }; + + struct S { + int member = 5; + void foo() { + auto lambda = [this]() { return member; }; + } + }; +} + +void test_nested_lambdas() { + int outer_var = 1; + int middle_var = 2; + int inner_var = 3; + + auto outer = [=]() { + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + return inner(10); + }; +} + +void test_lambda_returns() { + int a = 1, b = 2, c = 3; + + auto create_adder = [=](int x) { + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture + }; + + auto func1 = [&]() { return a; }; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto func2 = [=]() { return b; }; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] +} + +class TestClass { + int member = 42; + +public: + void test_member_function_lambdas() { + int local = 10; + + auto lambda1 = [=]() { return member + local; }; + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto lambda2 = [&]() { return member + local; }; + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + + auto lambda3 = [this, local]() { return member + local; }; + auto lambda4 = [this, &local]() { return member + local; }; + } +}; + +template +void test_template_lambdas() { + T value{}; + + auto lambda = [=](T x) { return value + x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] +} + +void instantiate_templates() { + test_template_lambdas(); + test_template_lambdas(); +} From 689f95b2ba46d6ca319e0735b5f5474bfc14f999 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 09:58:59 -0400 Subject: [PATCH 02/35] Fix headers on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp | 8 ++++++++ .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 6c95fbc984c5a..0ee65df4b9860 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -1,3 +1,11 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + #include "DefaultLambdaCaptureCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h index ac47c866cfccd..9af861aaf2e93 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h @@ -1,3 +1,11 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H From 9991b9ceda13c036e78c0e95978fe16f0bde5959 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 10:21:44 -0400 Subject: [PATCH 03/35] remove superfluous comments. on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 0ee65df4b9860..6db5cc8fc10bb 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -15,7 +15,6 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { - // Match any lambda expression Finder->addMatcher(lambdaExpr().bind("lambda"), this); } @@ -24,7 +23,6 @@ void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { if (!Lambda) return; - // Check if lambda has a default capture if (Lambda->getCaptureDefault() == LCD_None) return; From db17a4c5a29faa47c69ddb48edb2b0316287c5bc Mon Sep 17 00:00:00 2001 From: "Marr, J J" Date: Mon, 22 Sep 2025 10:37:30 -0400 Subject: [PATCH 04/35] add new matcher on-behalf-of: @AMD --- .../bugprone/DefaultLambdaCaptureCheck.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 6db5cc8fc10bb..7fc3e571db220 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -14,8 +14,15 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { +namespace { +AST_MATCHER(LambdaExpr, hasDefaultCapture) { + return Node.getCaptureDefault() != LCD_None; +} + +} // namespace + void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(lambdaExpr().bind("lambda"), this); + Finder->addMatcher(lambdaExpr(hasDefaultCapture()).bind("lambda"), this); } void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { @@ -23,8 +30,8 @@ void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { if (!Lambda) return; - if (Lambda->getCaptureDefault() == LCD_None) - return; + // No need to check getCaptureDefault() != LCD_None since our custom matcher + // hasDefaultCapture() already ensures this condition SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) From 7dcf2162c2a6df4057585e2898bc8feb4536a67c Mon Sep 17 00:00:00 2001 From: "Marr, J J" Date: Mon, 22 Sep 2025 11:51:27 -0400 Subject: [PATCH 05/35] Replace if with assert on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 7fc3e571db220..4db95101025cc 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -27,11 +27,7 @@ void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { const auto *Lambda = Result.Nodes.getNodeAs("lambda"); - if (!Lambda) - return; - - // No need to check getCaptureDefault() != LCD_None since our custom matcher - // hasDefaultCapture() already ensures this condition + assert(Lambda); SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) From 176d195cc753b3c95b9a82305e1fe1267e181cfa Mon Sep 17 00:00:00 2001 From: "Marr, J J" Date: Mon, 22 Sep 2025 11:52:40 -0400 Subject: [PATCH 06/35] Remove specific warning type on-behalf-of: @amd --- .../bugprone/DefaultLambdaCaptureCheck.cpp | 8 ++----- .../bugprone/default-lambda-capture.cpp | 24 +++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 4db95101025cc..288feb7853e0b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -33,12 +33,8 @@ void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { if (DefaultCaptureLoc.isInvalid()) return; - const char *CaptureKind = - (Lambda->getCaptureDefault() == LCD_ByCopy) ? "by-copy" : "by-reference"; - - diag(DefaultCaptureLoc, "lambda %0 default capture is discouraged; " - "prefer to capture specific variables explicitly") - << CaptureKind; + diag(DefaultCaptureLoc, "lambda default captures are discouraged; " + "prefer to capture specific variables explicitly"); } } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp index f820b4c9b8a0e..010edf11f0a2b 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp @@ -5,16 +5,16 @@ void test_default_captures() { int another = 10; auto lambda1 = [=](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto lambda2 = [&](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto lambda3 = [=, &another](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto lambda4 = [&, value](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] } void test_acceptable_captures() { @@ -42,10 +42,10 @@ void test_nested_lambdas() { int inner_var = 3; auto outer = [=]() { - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] return inner(10); }; @@ -55,15 +55,15 @@ void test_lambda_returns() { int a = 1, b = 2, c = 3; auto create_adder = [=](int x) { - // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture }; auto func1 = [&]() { return a; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto func2 = [=]() { return b; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] } class TestClass { @@ -74,10 +74,10 @@ class TestClass { int local = 10; auto lambda1 = [=]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto lambda2 = [&]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] auto lambda3 = [this, local]() { return member + local; }; auto lambda4 = [this, &local]() { return member + local; }; @@ -89,7 +89,7 @@ void test_template_lambdas() { T value{}; auto lambda = [=](T x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] } void instantiate_templates() { From 5f23dfff1a8368ebca7c2da691201ea1277ad128 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 12:25:50 -0400 Subject: [PATCH 07/35] Sync documentation to release notes on-behalf-of: @amd --- .../checks/bugprone/default-lambda-capture.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst index 026acf9f1894b..f1fcf3ec52948 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst @@ -3,10 +3,11 @@ bugprone-default-lambda-capture =============================== -Warns when lambda expressions use default capture modes (``[=]`` or ``[&]``) -instead of explicitly capturing specific variables. Default captures can make -code less readable and more error-prone by making it unclear which variables -are being captured and how. + Finds lambda expressions that use default capture modes (``[=]`` or ``[&]``) + and suggests using explicit captures instead. Default captures can lead to + subtle bugs including dangling references with ``[&]``, unnecessary copies + with ``[=]``, and make code less maintainable by hiding which variables are + actually being captured. Implements Item 31 of Effective Modern C++ by Scott Meyers. From 4c413327a64669d5254f0d6e343ca5c394701611 Mon Sep 17 00:00:00 2001 From: JJ Marr Date: Mon, 22 Sep 2025 13:00:22 -0400 Subject: [PATCH 08/35] Update clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h Co-authored-by: EugeneZelenko --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h index 9af861aaf2e93..ad48316708b1b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h @@ -16,7 +16,7 @@ namespace clang::tidy::bugprone { /** Flags lambdas that use default capture modes * * For the user-facing documentation see: - * http://clang.llvm.org/extra/clang-tidy/checks/bugprone/default-lambda-capture.html + * https://clang.llvm.org/extra/clang-tidy/checks/bugprone/default-lambda-capture.html */ class DefaultLambdaCaptureCheck : public ClangTidyCheck { public: From 330dbd8ca5eeed45d63d1a44c15eeb5357a8a4ef Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 13:01:23 -0400 Subject: [PATCH 09/35] Remove superfluous include on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 288feb7853e0b..8f52aeb59ead6 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "DefaultLambdaCaptureCheck.h" -#include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; From 31366e6b34dce3ab7e5f24d07f81ad10ecd9b875 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 13:09:55 -0400 Subject: [PATCH 10/35] Fix doc and release notes on-behalf-of: @amd --- clang-tools-extra/docs/ReleaseNotes.rst | 14 +++++--------- .../checks/bugprone/default-lambda-capture.rst | 10 +++++----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 780cf41373128..9d2e8dcad7045 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -167,6 +167,11 @@ New checks Detects default initialization (to 0) of variables with ``enum`` type where the enum has no enumerator with value of 0. +- New :doc:`bugprone-default-lambda-capture + ` check. + + Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) + - New :doc:`bugprone-derived-method-shadowing-base-method ` check. @@ -203,15 +208,6 @@ New checks Finds virtual function overrides with different visibility than the function in the base class. -- New :doc:`bugprone-default-lambda-capture - ` check. - - Finds lambda expressions that use default capture modes (``[=]`` or ``[&]``) - and suggests using explicit captures instead. Default captures can lead to - subtle bugs including dangling references with ``[&]``, unnecessary copies - with ``[=]``, and make code less maintainable by hiding which variables are - actually being captured. - New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst index f1fcf3ec52948..9cedc6276e1b3 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst @@ -3,11 +3,11 @@ bugprone-default-lambda-capture =============================== - Finds lambda expressions that use default capture modes (``[=]`` or ``[&]``) - and suggests using explicit captures instead. Default captures can lead to - subtle bugs including dangling references with ``[&]``, unnecessary copies - with ``[=]``, and make code less maintainable by hiding which variables are - actually being captured. +Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) + +Default captures can lead to subtle bugs including dangling references with +``[&]``, unnecessary copies with ``[=]``, and make code less maintainable by +hiding which variables are actually being captured. Implements Item 31 of Effective Modern C++ by Scott Meyers. From 70df9dff73c3b3de4dcdfbed8b44186ff2000997 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 22 Sep 2025 14:43:19 -0400 Subject: [PATCH 11/35] Fix const on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp index 8f52aeb59ead6..a7a18ccb30864 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp @@ -28,7 +28,7 @@ void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { const auto *Lambda = Result.Nodes.getNodeAs("lambda"); assert(Lambda); - SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); + const SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) return; From 0bcdbc4373a6d320f49f30a64c647b1da3956756 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Tue, 23 Sep 2025 13:15:11 -0400 Subject: [PATCH 12/35] Fix header guard on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h index ad48316708b1b..fa69245b91c94 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H #include "../ClangTidyCheck.h" @@ -31,4 +31,4 @@ class DefaultLambdaCaptureCheck : public ClangTidyCheck { } // namespace clang::tidy::bugprone -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H From ac709f4fcf092da2b240b9e0a62d1f8c314fdebb Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Tue, 23 Sep 2025 16:07:45 -0400 Subject: [PATCH 13/35] Fix header spelling on-behalf-of: @amd --- .../clang-tidy/bugprone/DefaultLambdaCaptureCheck.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h index fa69245b91c94..c41d7257592d5 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H #include "../ClangTidyCheck.h" @@ -31,4 +31,4 @@ class DefaultLambdaCaptureCheck : public ClangTidyCheck { } // namespace clang::tidy::bugprone -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURE_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H From 5d4c2ca704cce01a491ac38ecd25bf722b94ad78 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Wed, 24 Sep 2025 13:24:17 -0400 Subject: [PATCH 14/35] Rename check to readability-default-lambda-capture on-behalf-of: @amd --- .../bugprone/BugproneTidyModule.cpp | 3 --- .../clang-tidy/bugprone/CMakeLists.txt | 1 - .../clang-tidy/readability/CMakeLists.txt | 1 + .../DefaultLambdaCaptureCheck.cpp | 4 +-- .../DefaultLambdaCaptureCheck.h | 12 ++++----- .../readability/ReadabilityTidyModule.cpp | 3 +++ clang-tools-extra/docs/ReleaseNotes.rst | 10 +++---- .../docs/clang-tidy/checks/list.rst | 2 +- .../default-lambda-capture.rst | 4 +-- .../default-lambda-capture.cpp | 26 +++++++++---------- 10 files changed, 33 insertions(+), 33 deletions(-) rename clang-tools-extra/clang-tidy/{bugprone => readability}/DefaultLambdaCaptureCheck.cpp (94%) rename clang-tools-extra/clang-tidy/{bugprone => readability}/DefaultLambdaCaptureCheck.h (69%) rename clang-tools-extra/docs/clang-tidy/checks/{bugprone => readability}/default-lambda-capture.rst (91%) rename clang-tools-extra/test/clang-tidy/checkers/{bugprone => readability}/default-lambda-capture.cpp (84%) diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 67222cbafb5f9..8baa8f6b35d4c 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -23,7 +23,6 @@ #include "CopyConstructorInitCheck.h" #include "CrtpConstructorAccessibilityCheck.h" #include "DanglingHandleCheck.h" -#include "DefaultLambdaCaptureCheck.h" #include "DerivedMethodShadowingBaseMethodCheck.h" #include "DynamicStaticInitializersCheck.h" #include "EasilySwappableParametersCheck.h" @@ -137,8 +136,6 @@ class BugproneModule : public ClangTidyModule { "bugprone-copy-constructor-init"); CheckFactories.registerCheck( "bugprone-dangling-handle"); - CheckFactories.registerCheck( - "bugprone-default-lambda-capture"); CheckFactories.registerCheck( "bugprone-derived-method-shadowing-base-method"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index c8c31f9f96bb0..b0dbe84a16cd4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -19,7 +19,6 @@ add_clang_library(clangTidyBugproneModule STATIC CopyConstructorInitCheck.cpp CrtpConstructorAccessibilityCheck.cpp DanglingHandleCheck.cpp - DefaultLambdaCaptureCheck.cpp DerivedMethodShadowingBaseMethodCheck.cpp DynamicStaticInitializersCheck.cpp EasilySwappableParametersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt index 4b4c49d3b17d1..829caa15377e6 100644 --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -15,6 +15,7 @@ add_clang_library(clangTidyReadabilityModule STATIC ContainerDataPointerCheck.cpp ContainerSizeEmptyCheck.cpp ConvertMemberFunctionsToStatic.cpp + DefaultLambdaCaptureCheck.cpp DeleteNullPointerCheck.cpp DuplicateIncludeCheck.cpp ElseAfterReturnCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp similarity index 94% rename from clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp rename to clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp index a7a18ccb30864..60914fc7e7069 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp @@ -11,7 +11,7 @@ using namespace clang::ast_matchers; -namespace clang::tidy::bugprone { +namespace clang::tidy::readability { namespace { AST_MATCHER(LambdaExpr, hasDefaultCapture) { @@ -36,4 +36,4 @@ void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { "prefer to capture specific variables explicitly"); } -} // namespace clang::tidy::bugprone +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h similarity index 69% rename from clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h rename to clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h index c41d7257592d5..fd29d71b7c542 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h @@ -6,17 +6,17 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H #include "../ClangTidyCheck.h" -namespace clang::tidy::bugprone { +namespace clang::tidy::readability { /** Flags lambdas that use default capture modes * * For the user-facing documentation see: - * https://clang.llvm.org/extra/clang-tidy/checks/bugprone/default-lambda-capture.html + * https://clang.llvm.org/extra/clang-tidy/checks/readability/default-lambda-capture.html */ class DefaultLambdaCaptureCheck : public ClangTidyCheck { public: @@ -29,6 +29,6 @@ class DefaultLambdaCaptureCheck : public ClangTidyCheck { } }; -} // namespace clang::tidy::bugprone +} // namespace clang::tidy::readability -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULTLAMBDACAPTURECHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp index d01882dfc9daa..4b9bf3d417a7b 100644 --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -20,6 +20,7 @@ #include "ContainerDataPointerCheck.h" #include "ContainerSizeEmptyCheck.h" #include "ConvertMemberFunctionsToStatic.h" +#include "DefaultLambdaCaptureCheck.h" #include "DeleteNullPointerCheck.h" #include "DuplicateIncludeCheck.h" #include "ElseAfterReturnCheck.h" @@ -92,6 +93,8 @@ class ReadabilityModule : public ClangTidyModule { "readability-container-size-empty"); CheckFactories.registerCheck( "readability-convert-member-functions-to-static"); + CheckFactories.registerCheck( + "readability-default-lambda-capture"); CheckFactories.registerCheck( "readability-delete-null-pointer"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 9d2e8dcad7045..6f29f0e99d6f7 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -167,11 +167,6 @@ New checks Detects default initialization (to 0) of variables with ``enum`` type where the enum has no enumerator with value of 0. -- New :doc:`bugprone-default-lambda-capture - ` check. - - Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) - - New :doc:`bugprone-derived-method-shadowing-base-method ` check. @@ -356,6 +351,11 @@ Changes in existing checks ` check to recognize literal suffixes added in C++23 and C23. +- New :doc:`readability-default-lambda-capture + ` check. + + Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) + Removed checks ^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 5232f650f6579..e72cbf75fa03f 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -91,7 +91,6 @@ Clang-Tidy Checks :doc:`bugprone-copy-constructor-init `, "Yes" :doc:`bugprone-crtp-constructor-accessibility `, "Yes" :doc:`bugprone-dangling-handle `, - :doc:`bugprone-default-lambda-capture `, :doc:`bugprone-derived-method-shadowing-base-method `, :doc:`bugprone-dynamic-static-initializers `, :doc:`bugprone-easily-swappable-parameters `, @@ -378,6 +377,7 @@ Clang-Tidy Checks :doc:`readability-container-data-pointer `, "Yes" :doc:`readability-container-size-empty `, "Yes" :doc:`readability-convert-member-functions-to-static `, "Yes" + :doc:`readability-default-lambda-capture `, :doc:`readability-delete-null-pointer `, "Yes" :doc:`readability-duplicate-include `, "Yes" :doc:`readability-else-after-return `, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst similarity index 91% rename from clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst rename to clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst index 9cedc6276e1b3..00734b427d131 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst @@ -1,6 +1,6 @@ -.. title:: clang-tidy - bugprone-default-lambda-capture +.. title:: clang-tidy - readability-default-lambda-capture -bugprone-default-lambda-capture +readability-default-lambda-capture =============================== Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp similarity index 84% rename from clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp rename to clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp index 010edf11f0a2b..ecc45130a6cdf 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp @@ -1,20 +1,20 @@ -// RUN: %check_clang_tidy %s bugprone-default-lambda-capture %t +// RUN: %check_clang_tidy %s readability-default-lambda-capture %t void test_default_captures() { int value = 42; int another = 10; auto lambda1 = [=](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto lambda2 = [&](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto lambda3 = [=, &another](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto lambda4 = [&, value](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] } void test_acceptable_captures() { @@ -42,10 +42,10 @@ void test_nested_lambdas() { int inner_var = 3; auto outer = [=]() { - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] return inner(10); }; @@ -55,15 +55,15 @@ void test_lambda_returns() { int a = 1, b = 2, c = 3; auto create_adder = [=](int x) { - // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture }; auto func1 = [&]() { return a; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto func2 = [=]() { return b; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] } class TestClass { @@ -74,10 +74,10 @@ class TestClass { int local = 10; auto lambda1 = [=]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto lambda2 = [&]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] auto lambda3 = [this, local]() { return member + local; }; auto lambda4 = [this, &local]() { return member + local; }; @@ -89,7 +89,7 @@ void test_template_lambdas() { T value{}; auto lambda = [=](T x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] } void instantiate_templates() { From c0b90d1d0a20ac71d4f6944e0818a1d56eba4acd Mon Sep 17 00:00:00 2001 From: JJ Marr Date: Wed, 24 Sep 2025 13:32:04 -0400 Subject: [PATCH 15/35] Update clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst Co-authored-by: EugeneZelenko --- .../clang-tidy/checks/readability/default-lambda-capture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst index 00734b427d131..785f112a70b11 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst @@ -1,7 +1,7 @@ .. title:: clang-tidy - readability-default-lambda-capture readability-default-lambda-capture -=============================== +================================== Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) From 1172f730ba67151e560dc4ec46a203a8bf83a52b Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Thu, 25 Sep 2025 10:29:37 -0400 Subject: [PATCH 16/35] update doc on-behalf-of: @amd --- .../readability/default-lambda-capture.rst | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst index 785f112a70b11..daabdd1a92995 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst @@ -5,39 +5,51 @@ readability-default-lambda-capture Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) -Default captures can lead to subtle bugs including dangling references with -``[&]``, unnecessary copies with ``[=]``, and make code less maintainable by -hiding which variables are actually being captured. +Captures can lead to subtle bugs including dangling references and unnecessary +copies. Writing out the name of the variables being captured reminds programmers +and reviewers to know what is being captured. And knowing is half the battle. -Implements Item 31 of Effective Modern C++ by Scott Meyers. +Coding guidelines that recommend against defaulted lambda captures include: + +* Item 31 of Effective Modern C++ by Scott Meyers +* `AUTOSAR C++ Rule A5-1-2 `__ Example ------- .. code-block:: c++ - void example() { - int x = 1; - int y = 2; - - // Bad - default capture by copy - auto lambda1 = [=]() { return x + y; }; - - // Bad - default capture by reference - auto lambda2 = [&]() { return x + y; }; - - // Good - explicit captures - auto lambda3 = [x, y]() { return x + y; }; - auto lambda4 = [&x, &y]() { return x + y; }; + #include + + class Widget { + std::vector> callbacks; + int widgetId; + void addCallback(int factoryId) { + callbacks.emplace_back( + [&](){ + std::cout << "Widget " << widgetId << " made in factory " << factoryId; + } + ); + } } -The check will warn on: - -- Default capture by copy: ``[=]`` -- Default capture by reference: ``[&]`` -- Mixed captures with defaults: ``[=, &x]`` or ``[&, x]`` +When ``callbacks`` is executed, ``factoryId`` will dangle. Writing the name of +``factoryId`` in the capture list makes it easy to review the captures and +detect obvious bugs. -The check will not warn on: +.. code-block:: c++ -- Explicit captures: ``[x]``, ``[&x]``, ``[x, y]``, ``[this]`` -- Empty capture lists: ``[]`` + #include + + class Widget { + std::vector> callbacks; + int widgetId; + void addCallback(int factoryId) { + callbacks.emplace_back( + [&factoryId, &widgetId](){ // Why isn't factoryId captured by value?? + std::cout << "Widget " << widgetId << " made in factory " << factoryId; + } + ); + } + } From 66e6aaa168928f18e8a427b0cf4abe21fb52fb9b Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Thu, 25 Sep 2025 11:04:26 -0400 Subject: [PATCH 17/35] rename check to readability-avoid-default-lambda-capture on-behalf-of: @amd --- ...cpp => AvoidDefaultLambdaCaptureCheck.cpp} | 7 ++-- ...eck.h => AvoidDefaultLambdaCaptureCheck.h} | 12 +++---- .../clang-tidy/readability/CMakeLists.txt | 2 +- .../readability/ReadabilityTidyModule.cpp | 6 ++-- clang-tools-extra/docs/ReleaseNotes.rst | 4 +-- .../docs/clang-tidy/checks/list.rst | 2 +- ...e.rst => avoid-default-lambda-capture.rst} | 6 ++-- ...e.cpp => avoid-default-lambda-capture.cpp} | 32 +++++++++---------- 8 files changed, 36 insertions(+), 35 deletions(-) rename clang-tools-extra/clang-tidy/readability/{DefaultLambdaCaptureCheck.cpp => AvoidDefaultLambdaCaptureCheck.cpp} (83%) rename clang-tools-extra/clang-tidy/readability/{DefaultLambdaCaptureCheck.h => AvoidDefaultLambdaCaptureCheck.h} (64%) rename clang-tools-extra/docs/clang-tidy/checks/readability/{default-lambda-capture.rst => avoid-default-lambda-capture.rst} (91%) rename clang-tools-extra/test/clang-tidy/checkers/readability/{default-lambda-capture.cpp => avoid-default-lambda-capture.cpp} (83%) diff --git a/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp similarity index 83% rename from clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp rename to clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 60914fc7e7069..1036d764497b4 100644 --- a/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "DefaultLambdaCaptureCheck.h" +#include "AvoidDefaultLambdaCaptureCheck.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; @@ -20,11 +20,12 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) { } // namespace -void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { +void AvoidDefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(lambdaExpr(hasDefaultCapture()).bind("lambda"), this); } -void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) { +void AvoidDefaultLambdaCaptureCheck::check( + const MatchFinder::MatchResult &Result) { const auto *Lambda = Result.Nodes.getNodeAs("lambda"); assert(Lambda); diff --git a/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h similarity index 64% rename from clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h rename to clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h index fd29d71b7c542..604cf85b2a71f 100644 --- a/clang-tools-extra/clang-tidy/readability/DefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDDEFAULTLAMBDACAPTURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDDEFAULTLAMBDACAPTURECHECK_H #include "../ClangTidyCheck.h" @@ -16,11 +16,11 @@ namespace clang::tidy::readability { /** Flags lambdas that use default capture modes * * For the user-facing documentation see: - * https://clang.llvm.org/extra/clang-tidy/checks/readability/default-lambda-capture.html + * https://clang.llvm.org/extra/clang-tidy/checks/readability/avoid-default-lambda-capture.html */ -class DefaultLambdaCaptureCheck : public ClangTidyCheck { +class AvoidDefaultLambdaCaptureCheck : public ClangTidyCheck { public: - DefaultLambdaCaptureCheck(StringRef Name, ClangTidyContext *Context) + AvoidDefaultLambdaCaptureCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; @@ -31,4 +31,4 @@ class DefaultLambdaCaptureCheck : public ClangTidyCheck { } // namespace clang::tidy::readability -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DEFAULTLAMBDACAPTURECHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDDEFAULTLAMBDACAPTURECHECK_H diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt index 829caa15377e6..b4e0c7b8a4a43 100644 --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangTidyReadabilityModule STATIC AmbiguousSmartptrResetCallCheck.cpp AvoidConstParamsInDecls.cpp + AvoidDefaultLambdaCaptureCheck.cpp AvoidNestedConditionalOperatorCheck.cpp AvoidReturnWithVoidValueCheck.cpp AvoidUnconditionalPreprocessorIfCheck.cpp @@ -15,7 +16,6 @@ add_clang_library(clangTidyReadabilityModule STATIC ContainerDataPointerCheck.cpp ContainerSizeEmptyCheck.cpp ConvertMemberFunctionsToStatic.cpp - DefaultLambdaCaptureCheck.cpp DeleteNullPointerCheck.cpp DuplicateIncludeCheck.cpp ElseAfterReturnCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp index 4b9bf3d417a7b..34c9e71926d18 100644 --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -11,6 +11,7 @@ #include "../ClangTidyModuleRegistry.h" #include "AmbiguousSmartptrResetCallCheck.h" #include "AvoidConstParamsInDecls.h" +#include "AvoidDefaultLambdaCaptureCheck.h" #include "AvoidNestedConditionalOperatorCheck.h" #include "AvoidReturnWithVoidValueCheck.h" #include "AvoidUnconditionalPreprocessorIfCheck.h" @@ -20,7 +21,6 @@ #include "ContainerDataPointerCheck.h" #include "ContainerSizeEmptyCheck.h" #include "ConvertMemberFunctionsToStatic.h" -#include "DefaultLambdaCaptureCheck.h" #include "DeleteNullPointerCheck.h" #include "DuplicateIncludeCheck.h" #include "ElseAfterReturnCheck.h" @@ -93,8 +93,8 @@ class ReadabilityModule : public ClangTidyModule { "readability-container-size-empty"); CheckFactories.registerCheck( "readability-convert-member-functions-to-static"); - CheckFactories.registerCheck( - "readability-default-lambda-capture"); + CheckFactories.registerCheck( + "readability-avoid-default-lambda-capture"); CheckFactories.registerCheck( "readability-delete-null-pointer"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 6f29f0e99d6f7..d3edc1a9865ab 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -351,8 +351,8 @@ Changes in existing checks ` check to recognize literal suffixes added in C++23 and C23. -- New :doc:`readability-default-lambda-capture - ` check. +- New :doc:`readability-avoid-default-lambda-capture + ` check. Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index e72cbf75fa03f..0b8dcd440e1f6 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -377,7 +377,7 @@ Clang-Tidy Checks :doc:`readability-container-data-pointer `, "Yes" :doc:`readability-container-size-empty `, "Yes" :doc:`readability-convert-member-functions-to-static `, "Yes" - :doc:`readability-default-lambda-capture `, + :doc:`readability-avoid-default-lambda-capture `, :doc:`readability-delete-null-pointer `, "Yes" :doc:`readability-duplicate-include `, "Yes" :doc:`readability-else-after-return `, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst similarity index 91% rename from clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst rename to clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index daabdd1a92995..9851896026be5 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -1,7 +1,7 @@ -.. title:: clang-tidy - readability-default-lambda-capture +.. title:: clang-tidy - readability-avoid-default-lambda-capture -readability-default-lambda-capture -================================== +readability-avoid-default-lambda-capture +========================================= Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp similarity index 83% rename from clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp rename to clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp index ecc45130a6cdf..f452ea6f6ccb1 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp @@ -1,20 +1,20 @@ -// RUN: %check_clang_tidy %s readability-default-lambda-capture %t +// RUN: %check_clang_tidy %s readability-avoid-default-lambda-capture %t void test_default_captures() { int value = 42; int another = 10; auto lambda1 = [=](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] auto lambda2 = [&](int x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] auto lambda3 = [=, &another](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] auto lambda4 = [&, value](int x) { return value + another + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] } void test_acceptable_captures() { @@ -42,10 +42,10 @@ void test_nested_lambdas() { int inner_var = 3; auto outer = [=]() { - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] - + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] return inner(10); }; @@ -55,15 +55,15 @@ void test_lambda_returns() { int a = 1, b = 2, c = 3; auto create_adder = [=](int x) { - // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture }; auto func1 = [&]() { return a; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] - + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + auto func2 = [=]() { return b; }; - // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] } class TestClass { @@ -74,10 +74,10 @@ class TestClass { int local = 10; auto lambda1 = [=]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] - + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + auto lambda2 = [&]() { return member + local; }; - // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] auto lambda3 = [this, local]() { return member + local; }; auto lambda4 = [this, &local]() { return member + local; }; @@ -89,7 +89,7 @@ void test_template_lambdas() { T value{}; auto lambda = [=](T x) { return value + x; }; - // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-default-lambda-capture] + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] } void instantiate_templates() { From 4bf58b6eddee9b1027654653fa409d6bd668e79c Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Thu, 25 Sep 2025 11:05:23 -0400 Subject: [PATCH 18/35] use C++ style comments on-behalf-of: @amd --- .../readability/AvoidDefaultLambdaCaptureCheck.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h index 604cf85b2a71f..82262a210e4c4 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.h @@ -13,11 +13,10 @@ namespace clang::tidy::readability { -/** Flags lambdas that use default capture modes - * - * For the user-facing documentation see: - * https://clang.llvm.org/extra/clang-tidy/checks/readability/avoid-default-lambda-capture.html - */ +/// Flags lambdas that use default capture modes +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/readability/avoid-default-lambda-capture.html class AvoidDefaultLambdaCaptureCheck : public ClangTidyCheck { public: AvoidDefaultLambdaCaptureCheck(StringRef Name, ClangTidyContext *Context) From a807dd0e29f5af7af8cc6747082bac606b3ea2ec Mon Sep 17 00:00:00 2001 From: JJ Marr Date: Thu, 25 Sep 2025 11:19:41 -0400 Subject: [PATCH 19/35] Update clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst Co-authored-by: EugeneZelenko --- .../checks/readability/avoid-default-lambda-capture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index 9851896026be5..4513e5d1ef305 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -1,7 +1,7 @@ .. title:: clang-tidy - readability-avoid-default-lambda-capture readability-avoid-default-lambda-capture -========================================= +======================================== Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) From 8fdc0974ed84e8177ab61c6dbeeccf6b43ed1593 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Thu, 25 Sep 2025 11:20:22 -0400 Subject: [PATCH 20/35] fix doc again on-behalf-of: @amd --- clang-tools-extra/docs/ReleaseNotes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index d3edc1a9865ab..583d4de022218 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -203,6 +203,11 @@ New checks Finds virtual function overrides with different visibility than the function in the base class. +- New :doc:`readability-avoid-default-lambda-capture + ` check. + + Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) + New check aliases ^^^^^^^^^^^^^^^^^ @@ -351,11 +356,6 @@ Changes in existing checks ` check to recognize literal suffixes added in C++23 and C23. -- New :doc:`readability-avoid-default-lambda-capture - ` check. - - Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) - Removed checks ^^^^^^^^^^^^^^ From ab8f4b9db4011587bc51407abf4f2a4ad9d7920c Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Thu, 25 Sep 2025 12:58:26 -0400 Subject: [PATCH 21/35] remove reference to coding guideline on-behalf-of: @amd --- .../checks/readability/avoid-default-lambda-capture.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index 4513e5d1ef305..6b63e103e588e 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -12,8 +12,6 @@ and reviewers to know what is being captured. And knowing is half the battle. Coding guidelines that recommend against defaulted lambda captures include: * Item 31 of Effective Modern C++ by Scott Meyers -* `AUTOSAR C++ Rule A5-1-2 `__ Example ------- From 13b50491bbb329fa24b714d1af0203ab6ce85ce3 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 09:50:39 -0400 Subject: [PATCH 22/35] rephrase on-behalf-of: @amd --- .../checks/readability/avoid-default-lambda-capture.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index 6b63e103e588e..d4ecd4c7eb27d 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -3,7 +3,7 @@ readability-avoid-default-lambda-capture ======================================== -Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``) +Warns on default lambda captures (e.g. ``[&](){ ... }``, ``[=](){ ... }``). Captures can lead to subtle bugs including dangling references and unnecessary copies. Writing out the name of the variables being captured reminds programmers @@ -33,8 +33,8 @@ Example } When ``callbacks`` is executed, ``factoryId`` will dangle. Writing the name of -``factoryId`` in the capture list makes it easy to review the captures and -detect obvious bugs. +``factoryId`` in the capture list reminds the reader that it is being captured, +which will hopefully lead to the bug being fixed during code review. .. code-block:: c++ From 043df7c76564f55e5ff76e5139ca29fa827e402c Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 10:27:18 -0400 Subject: [PATCH 23/35] commit vibe coded work on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 57 ++++++++++++++++++- .../avoid-default-lambda-capture.cpp | 12 ++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 1036d764497b4..361af6794a9b5 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -8,6 +8,8 @@ #include "AvoidDefaultLambdaCaptureCheck.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Lambda.h" +#include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -18,6 +20,45 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) { return Node.getCaptureDefault() != LCD_None; } +std::string getCaptureString(const LambdaCapture &Capture) { + if (Capture.capturesThis()) { + return Capture.getCaptureKind() == LCK_StarThis ? "*this" : "this"; + } + + if (Capture.capturesVariable()) { + std::string Result; + if (Capture.getCaptureKind() == LCK_ByRef) { + Result += "&"; + } + Result += Capture.getCapturedVar()->getName().str(); + return Result; + } + + // Handle VLA captures - these are rare but possible + return "/* VLA capture */"; +} + +std::string buildExplicitCaptureList(const LambdaExpr *Lambda) { + std::vector CaptureStrings; + + // Add explicit captures first (preserve their order and syntax) + for (const auto &Capture : Lambda->explicit_captures()) { + CaptureStrings.push_back(getCaptureString(Capture)); + } + + // Add implicit captures (convert to explicit syntax) + for (const auto &Capture : Lambda->implicit_captures()) { + CaptureStrings.push_back(getCaptureString(Capture)); + } + + return "[" + llvm::join(CaptureStrings, ", ") + "]"; +} + +SourceRange getCaptureListRange(const LambdaExpr *Lambda) { + SourceRange IntroducerRange = Lambda->getIntroducerRange(); + return IntroducerRange; +} + } // namespace void AvoidDefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { @@ -33,8 +74,20 @@ void AvoidDefaultLambdaCaptureCheck::check( if (DefaultCaptureLoc.isInvalid()) return; - diag(DefaultCaptureLoc, "lambda default captures are discouraged; " - "prefer to capture specific variables explicitly"); + // Build the replacement capture list + std::string NewCaptureList = buildExplicitCaptureList(Lambda); + + // Get the range of the entire capture list [...] + SourceRange CaptureListRange = getCaptureListRange(Lambda); + + auto Diag = diag(DefaultCaptureLoc, + "lambda default captures are discouraged; " + "prefer to capture specific variables explicitly"); + + // Only provide fixit if we can determine a valid replacement + if (CaptureListRange.isValid() && !NewCaptureList.empty()) { + Diag << FixItHint::CreateReplacement(CaptureListRange, NewCaptureList); + } } } // namespace clang::tidy::readability diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp index f452ea6f6ccb1..6e81a9eb2b3d4 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp @@ -6,15 +6,19 @@ void test_default_captures() { auto lambda1 = [=](int x) { return value + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda1 = [value](int x) { return value + x; }; auto lambda2 = [&](int x) { return value + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda2 = [&value](int x) { return value + x; }; auto lambda3 = [=, &another](int x) { return value + another + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda3 = [&another, value](int x) { return value + another + x; }; auto lambda4 = [&, value](int x) { return value + another + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda4 = [value, &another](int x) { return value + another + x; }; } void test_acceptable_captures() { @@ -43,9 +47,11 @@ void test_nested_lambdas() { auto outer = [=]() { // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto outer = [outer_var, middle_var, inner_var]() { auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto inner = [&outer_var, &middle_var, &inner_var](int x) { return outer_var + middle_var + inner_var + x; }; return inner(10); }; @@ -56,14 +62,17 @@ void test_lambda_returns() { auto create_adder = [=](int x) { // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto create_adder = [](int x) { return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture }; auto func1 = [&]() { return a; }; // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto func1 = [&a]() { return a; }; auto func2 = [=]() { return b; }; // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto func2 = [b]() { return b; }; } class TestClass { @@ -75,9 +84,11 @@ class TestClass { auto lambda1 = [=]() { return member + local; }; // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda1 = [this, local]() { return member + local; }; auto lambda2 = [&]() { return member + local; }; // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda2 = [this, &local]() { return member + local; }; auto lambda3 = [this, local]() { return member + local; }; auto lambda4 = [this, &local]() { return member + local; }; @@ -90,6 +101,7 @@ void test_template_lambdas() { auto lambda = [=](T x) { return value + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: auto lambda = [](T x) { return value + x; }; } void instantiate_templates() { From 33af58bf9f683c21f0263f2d1e9b8810c76d30f3 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 10:37:55 -0400 Subject: [PATCH 24/35] attempt to fix to use something that isn't weird string manipulation on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 361af6794a9b5..b64717534acd6 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -20,6 +20,29 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) { return Node.getCaptureDefault() != LCD_None; } +std::string getExplicitCaptureText(const LambdaCapture &Capture, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (!Capture.isExplicit() || !Capture.getLocation().isValid()) { + return ""; + } + + // For explicit captures, extract the actual source text to preserve + // original formatting, spacing, and comments + SourceLocation CaptureBegin = Capture.getLocation(); + + // Find the end of this capture by looking for the next comma or closing + // bracket This is a simplified approach - a more robust implementation would + // parse tokens + SourceLocation CaptureEnd = + Lexer::getLocForEndOfToken(CaptureBegin, 0, SM, LangOpts); + + // For now, we'll still reconstruct to ensure correctness + // but this framework allows for future enhancement to preserve exact source + // text + return ""; +} + std::string getCaptureString(const LambdaCapture &Capture) { if (Capture.capturesThis()) { return Capture.getCaptureKind() == LCK_StarThis ? "*this" : "this"; @@ -38,12 +61,21 @@ std::string getCaptureString(const LambdaCapture &Capture) { return "/* VLA capture */"; } -std::string buildExplicitCaptureList(const LambdaExpr *Lambda) { +std::string buildExplicitCaptureList(const LambdaExpr *Lambda, + const SourceManager &SM, + const LangOptions &LangOpts) { std::vector CaptureStrings; // Add explicit captures first (preserve their order and syntax) for (const auto &Capture : Lambda->explicit_captures()) { - CaptureStrings.push_back(getCaptureString(Capture)); + // Try to get the original source text first + std::string ExplicitText = getExplicitCaptureText(Capture, SM, LangOpts); + if (!ExplicitText.empty()) { + CaptureStrings.push_back(ExplicitText); + } else { + // Fall back to reconstructed text + CaptureStrings.push_back(getCaptureString(Capture)); + } } // Add implicit captures (convert to explicit syntax) @@ -75,7 +107,8 @@ void AvoidDefaultLambdaCaptureCheck::check( return; // Build the replacement capture list - std::string NewCaptureList = buildExplicitCaptureList(Lambda); + std::string NewCaptureList = buildExplicitCaptureList( + Lambda, *Result.SourceManager, Result.Context->getLangOpts()); // Get the range of the entire capture list [...] SourceRange CaptureListRange = getCaptureListRange(Lambda); From 51aa1820f59dbd749807900ea53eb3710477d9ab Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 12:58:51 -0400 Subject: [PATCH 25/35] more vibe coding with human intervention on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 156 +++++++++++------- 1 file changed, 99 insertions(+), 57 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index b64717534acd6..72294cc164360 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -20,30 +20,47 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) { return Node.getCaptureDefault() != LCD_None; } -std::string getExplicitCaptureText(const LambdaCapture &Capture, +// Find the source range of the default capture token (= or &) +SourceRange getDefaultCaptureRange(const LambdaExpr *Lambda, const SourceManager &SM, const LangOptions &LangOpts) { - if (!Capture.isExplicit() || !Capture.getLocation().isValid()) { - return ""; - } + SourceLocation DefaultLoc = Lambda->getCaptureDefaultLoc(); + if (DefaultLoc.isInvalid()) + return SourceRange(); + + // The default capture is a single token + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(DefaultLoc, 0, SM, LangOpts); + return SourceRange(DefaultLoc, EndLoc); +} - // For explicit captures, extract the actual source text to preserve - // original formatting, spacing, and comments - SourceLocation CaptureBegin = Capture.getLocation(); +// Find where to insert implicit captures +SourceLocation getImplicitCaptureInsertionLoc(const LambdaExpr *Lambda, + const SourceManager &SM, + const LangOptions &LangOpts) { + // If there are explicit captures, insert after the last one + if (Lambda->explicit_capture_begin() != Lambda->explicit_capture_end()) { + // Find the location after the last explicit capture + const auto *LastExplicit = Lambda->explicit_capture_end() - 1; + SourceLocation LastLoc = LastExplicit->getLocation(); + if (LastLoc.isValid()) { + return Lexer::getLocForEndOfToken(LastLoc, 0, SM, LangOpts); + } + } - // Find the end of this capture by looking for the next comma or closing - // bracket This is a simplified approach - a more robust implementation would - // parse tokens - SourceLocation CaptureEnd = - Lexer::getLocForEndOfToken(CaptureBegin, 0, SM, LangOpts); + // If no explicit captures, insert after the default capture + SourceLocation DefaultLoc = Lambda->getCaptureDefaultLoc(); + if (DefaultLoc.isValid()) { + return Lexer::getLocForEndOfToken(DefaultLoc, 0, SM, LangOpts); + } - // For now, we'll still reconstruct to ensure correctness - // but this framework allows for future enhancement to preserve exact source - // text - return ""; + // Fallback: insert at the beginning of the capture list + return Lambda->getIntroducerRange().getBegin().getLocWithOffset(1); } -std::string getCaptureString(const LambdaCapture &Capture) { +// Generate the text for an implicit capture +std::optional +generateImplicitCaptureText(const LambdaCapture &Capture) { if (Capture.capturesThis()) { return Capture.getCaptureKind() == LCK_StarThis ? "*this" : "this"; } @@ -57,38 +74,15 @@ std::string getCaptureString(const LambdaCapture &Capture) { return Result; } - // Handle VLA captures - these are rare but possible - return "/* VLA capture */"; -} - -std::string buildExplicitCaptureList(const LambdaExpr *Lambda, - const SourceManager &SM, - const LangOptions &LangOpts) { - std::vector CaptureStrings; - - // Add explicit captures first (preserve their order and syntax) - for (const auto &Capture : Lambda->explicit_captures()) { - // Try to get the original source text first - std::string ExplicitText = getExplicitCaptureText(Capture, SM, LangOpts); - if (!ExplicitText.empty()) { - CaptureStrings.push_back(ExplicitText); - } else { - // Fall back to reconstructed text - CaptureStrings.push_back(getCaptureString(Capture)); - } - } - - // Add implicit captures (convert to explicit syntax) - for (const auto &Capture : Lambda->implicit_captures()) { - CaptureStrings.push_back(getCaptureString(Capture)); - } - - return "[" + llvm::join(CaptureStrings, ", ") + "]"; + // TODO: handle VLAs and other weird captures + return std::nullopt; } -SourceRange getCaptureListRange(const LambdaExpr *Lambda) { - SourceRange IntroducerRange = Lambda->getIntroducerRange(); - return IntroducerRange; +// Check if we need a comma before inserting captures +bool needsCommaBefore(const LambdaExpr *Lambda, SourceLocation InsertLoc, + const SourceManager &SM, const LangOptions &LangOpts) { + // If there are explicit captures, we need a comma + return Lambda->explicit_capture_begin() != Lambda->explicit_capture_end(); } } // namespace @@ -102,24 +96,72 @@ void AvoidDefaultLambdaCaptureCheck::check( const auto *Lambda = Result.Nodes.getNodeAs("lambda"); assert(Lambda); + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = Result.Context->getLangOpts(); + const SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) return; - // Build the replacement capture list - std::string NewCaptureList = buildExplicitCaptureList( - Lambda, *Result.SourceManager, Result.Context->getLangOpts()); - - // Get the range of the entire capture list [...] - SourceRange CaptureListRange = getCaptureListRange(Lambda); - auto Diag = diag(DefaultCaptureLoc, "lambda default captures are discouraged; " "prefer to capture specific variables explicitly"); - // Only provide fixit if we can determine a valid replacement - if (CaptureListRange.isValid() && !NewCaptureList.empty()) { - Diag << FixItHint::CreateReplacement(CaptureListRange, NewCaptureList); + // Get the range of the default capture token to remove + SourceRange DefaultRange = getDefaultCaptureRange(Lambda, SM, LangOpts); + if (!DefaultRange.isValid()) + return; + + // Collect all implicit captures that need to be made explicit + std::vector ImplicitCaptureTexts; + for (const auto &Capture : Lambda->implicit_captures()) { + if (const auto CaptureText = generateImplicitCaptureText(Capture)) { + ImplicitCaptureTexts.push_back(CaptureText.value()); + } + } + + // If there are no implicit captures, just remove the default capture + if (ImplicitCaptureTexts.empty()) { + // Also remove any trailing comma if it exists + SourceLocation AfterDefault = DefaultRange.getEnd(); + SourceLocation CommaLoc = Lexer::findLocationAfterToken( + AfterDefault, tok::comma, SM, LangOpts, false); + + if (CommaLoc.isValid()) { + // Remove default capture and the comma + SourceRange RemovalRange(DefaultRange.getBegin(), CommaLoc); + Diag << FixItHint::CreateRemoval(RemovalRange); + } else { + // Just remove the default capture + Diag << FixItHint::CreateRemoval(DefaultRange); + } + return; + } + + // Find where to insert the implicit captures + SourceLocation InsertLoc = + getImplicitCaptureInsertionLoc(Lambda, SM, LangOpts); + if (!InsertLoc.isValid()) + return; + + // Apply the transformations: + // 1. Remove the default capture + Diag << FixItHint::CreateRemoval(DefaultRange); + + // 2. Insert the explicit captures if any + if (!ImplicitCaptureTexts.empty()) { + // Build the insertion text for implicit captures + std::string InsertionText; + bool NeedsComma = needsCommaBefore(Lambda, InsertLoc, SM, LangOpts); + + for (size_t I = 0; I < ImplicitCaptureTexts.size(); ++I) { + if (NeedsComma || I > 0) { + InsertionText += ", "; + } + InsertionText += ImplicitCaptureTexts[I]; + } + + Diag << FixItHint::CreateInsertion(InsertLoc, InsertionText); } } From dd65b903b04d4bc2cd5587723eb55fc8ea5400e9 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 13:16:40 -0400 Subject: [PATCH 26/35] go back to the hacky string manipulation on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 122 ++++-------------- 1 file changed, 26 insertions(+), 96 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 72294cc164360..e0b9a8b44a29f 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -20,45 +20,6 @@ AST_MATCHER(LambdaExpr, hasDefaultCapture) { return Node.getCaptureDefault() != LCD_None; } -// Find the source range of the default capture token (= or &) -SourceRange getDefaultCaptureRange(const LambdaExpr *Lambda, - const SourceManager &SM, - const LangOptions &LangOpts) { - SourceLocation DefaultLoc = Lambda->getCaptureDefaultLoc(); - if (DefaultLoc.isInvalid()) - return SourceRange(); - - // The default capture is a single token - SourceLocation EndLoc = - Lexer::getLocForEndOfToken(DefaultLoc, 0, SM, LangOpts); - return SourceRange(DefaultLoc, EndLoc); -} - -// Find where to insert implicit captures -SourceLocation getImplicitCaptureInsertionLoc(const LambdaExpr *Lambda, - const SourceManager &SM, - const LangOptions &LangOpts) { - // If there are explicit captures, insert after the last one - if (Lambda->explicit_capture_begin() != Lambda->explicit_capture_end()) { - // Find the location after the last explicit capture - const auto *LastExplicit = Lambda->explicit_capture_end() - 1; - SourceLocation LastLoc = LastExplicit->getLocation(); - if (LastLoc.isValid()) { - return Lexer::getLocForEndOfToken(LastLoc, 0, SM, LangOpts); - } - } - - // If no explicit captures, insert after the default capture - SourceLocation DefaultLoc = Lambda->getCaptureDefaultLoc(); - if (DefaultLoc.isValid()) { - return Lexer::getLocForEndOfToken(DefaultLoc, 0, SM, LangOpts); - } - - // Fallback: insert at the beginning of the capture list - return Lambda->getIntroducerRange().getBegin().getLocWithOffset(1); -} - -// Generate the text for an implicit capture std::optional generateImplicitCaptureText(const LambdaCapture &Capture) { if (Capture.capturesThis()) { @@ -74,15 +35,13 @@ generateImplicitCaptureText(const LambdaCapture &Capture) { return Result; } - // TODO: handle VLAs and other weird captures - return std::nullopt; -} + if (Capture.capturesVLAType()) { + // VLA captures are rare and complex - for now we skip them + // A full implementation would need to handle the VLA type properly + return std::nullopt; + } -// Check if we need a comma before inserting captures -bool needsCommaBefore(const LambdaExpr *Lambda, SourceLocation InsertLoc, - const SourceManager &SM, const LangOptions &LangOpts) { - // If there are explicit captures, we need a comma - return Lambda->explicit_capture_begin() != Lambda->explicit_capture_end(); + return std::nullopt; } } // namespace @@ -96,9 +55,6 @@ void AvoidDefaultLambdaCaptureCheck::check( const auto *Lambda = Result.Nodes.getNodeAs("lambda"); assert(Lambda); - const SourceManager &SM = *Result.SourceManager; - const LangOptions &LangOpts = Result.Context->getLangOpts(); - const SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) return; @@ -107,61 +63,35 @@ void AvoidDefaultLambdaCaptureCheck::check( "lambda default captures are discouraged; " "prefer to capture specific variables explicitly"); - // Get the range of the default capture token to remove - SourceRange DefaultRange = getDefaultCaptureRange(Lambda, SM, LangOpts); - if (!DefaultRange.isValid()) - return; + // Build the complete replacement capture list + std::vector AllCaptures; - // Collect all implicit captures that need to be made explicit - std::vector ImplicitCaptureTexts; - for (const auto &Capture : Lambda->implicit_captures()) { + // Add explicit captures first (preserve their order) + for (const auto &Capture : Lambda->explicit_captures()) { if (const auto CaptureText = generateImplicitCaptureText(Capture)) { - ImplicitCaptureTexts.push_back(CaptureText.value()); + AllCaptures.push_back(CaptureText.value()); } } - // If there are no implicit captures, just remove the default capture - if (ImplicitCaptureTexts.empty()) { - // Also remove any trailing comma if it exists - SourceLocation AfterDefault = DefaultRange.getEnd(); - SourceLocation CommaLoc = Lexer::findLocationAfterToken( - AfterDefault, tok::comma, SM, LangOpts, false); - - if (CommaLoc.isValid()) { - // Remove default capture and the comma - SourceRange RemovalRange(DefaultRange.getBegin(), CommaLoc); - Diag << FixItHint::CreateRemoval(RemovalRange); - } else { - // Just remove the default capture - Diag << FixItHint::CreateRemoval(DefaultRange); + // Add implicit captures (convert to explicit) + for (const auto &Capture : Lambda->implicit_captures()) { + if (const auto CaptureText = generateImplicitCaptureText(Capture)) { + AllCaptures.push_back(CaptureText.value()); } - return; } - // Find where to insert the implicit captures - SourceLocation InsertLoc = - getImplicitCaptureInsertionLoc(Lambda, SM, LangOpts); - if (!InsertLoc.isValid()) - return; - - // Apply the transformations: - // 1. Remove the default capture - Diag << FixItHint::CreateRemoval(DefaultRange); - - // 2. Insert the explicit captures if any - if (!ImplicitCaptureTexts.empty()) { - // Build the insertion text for implicit captures - std::string InsertionText; - bool NeedsComma = needsCommaBefore(Lambda, InsertLoc, SM, LangOpts); - - for (size_t I = 0; I < ImplicitCaptureTexts.size(); ++I) { - if (NeedsComma || I > 0) { - InsertionText += ", "; - } - InsertionText += ImplicitCaptureTexts[I]; - } + // Build the final capture list + std::string ReplacementText; + if (AllCaptures.empty()) { + ReplacementText = "[]"; + } else { + ReplacementText = "[" + llvm::join(AllCaptures, ", ") + "]"; + } - Diag << FixItHint::CreateInsertion(InsertLoc, InsertionText); + // Replace the entire capture list with the explicit version + SourceRange IntroducerRange = Lambda->getIntroducerRange(); + if (IntroducerRange.isValid()) { + Diag << FixItHint::CreateReplacement(IntroducerRange, ReplacementText); } } From a9509f3a37fa9350e1de1ee0223f10dc17ff9a1d Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 13:17:28 -0400 Subject: [PATCH 27/35] remove AI comments on-behalf-of: @amd --- .../readability/AvoidDefaultLambdaCaptureCheck.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index e0b9a8b44a29f..66d4c9653860a 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -63,24 +63,21 @@ void AvoidDefaultLambdaCaptureCheck::check( "lambda default captures are discouraged; " "prefer to capture specific variables explicitly"); - // Build the complete replacement capture list std::vector AllCaptures; - // Add explicit captures first (preserve their order) for (const auto &Capture : Lambda->explicit_captures()) { if (const auto CaptureText = generateImplicitCaptureText(Capture)) { AllCaptures.push_back(CaptureText.value()); } } - // Add implicit captures (convert to explicit) for (const auto &Capture : Lambda->implicit_captures()) { if (const auto CaptureText = generateImplicitCaptureText(Capture)) { AllCaptures.push_back(CaptureText.value()); } } - // Build the final capture list + // Replace with new capture list std::string ReplacementText; if (AllCaptures.empty()) { ReplacementText = "[]"; @@ -88,7 +85,6 @@ void AvoidDefaultLambdaCaptureCheck::check( ReplacementText = "[" + llvm::join(AllCaptures, ", ") + "]"; } - // Replace the entire capture list with the explicit version SourceRange IntroducerRange = Lambda->getIntroducerRange(); if (IntroducerRange.isValid()) { Diag << FixItHint::CreateReplacement(IntroducerRange, ReplacementText); From e9e0f9ac4d6d272f22a961ab17028849e6a03142 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 14:03:59 -0400 Subject: [PATCH 28/35] fix namespacing issues on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 38 +++++++++---------- clang/include/clang/ASTMatchers/ASTMatchers.h | 14 +++++++ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 66d4c9653860a..7dfe07a8cf7b1 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -11,24 +11,18 @@ #include "clang/Basic/Lambda.h" #include "clang/Lex/Lexer.h" -using namespace clang::ast_matchers; - -namespace clang::tidy::readability { +using namespace clang::tidy::readability; namespace { -AST_MATCHER(LambdaExpr, hasDefaultCapture) { - return Node.getCaptureDefault() != LCD_None; -} - -std::optional -generateImplicitCaptureText(const LambdaCapture &Capture) { +static std::optional +generateImplicitCaptureText(const clang::LambdaCapture &Capture) { if (Capture.capturesThis()) { - return Capture.getCaptureKind() == LCK_StarThis ? "*this" : "this"; + return Capture.getCaptureKind() == clang::LCK_StarThis ? "*this" : "this"; } if (Capture.capturesVariable()) { std::string Result; - if (Capture.getCaptureKind() == LCK_ByRef) { + if (Capture.getCaptureKind() == clang::LCK_ByRef) { Result += "&"; } Result += Capture.getCapturedVar()->getName().str(); @@ -46,16 +40,21 @@ generateImplicitCaptureText(const LambdaCapture &Capture) { } // namespace -void AvoidDefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(lambdaExpr(hasDefaultCapture()).bind("lambda"), this); +void AvoidDefaultLambdaCaptureCheck::registerMatchers( + clang::ast_matchers::MatchFinder *Finder) { + Finder->addMatcher( + clang::ast_matchers::lambdaExpr(clang::ast_matchers::hasDefaultCapture()) + .bind("lambda"), + this); } void AvoidDefaultLambdaCaptureCheck::check( - const MatchFinder::MatchResult &Result) { - const auto *Lambda = Result.Nodes.getNodeAs("lambda"); + const clang::ast_matchers::MatchFinder::MatchResult &Result) { + const auto *Lambda = Result.Nodes.getNodeAs("lambda"); assert(Lambda); - const SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc(); + const clang::SourceLocation DefaultCaptureLoc = + Lambda->getCaptureDefaultLoc(); if (DefaultCaptureLoc.isInvalid()) return; @@ -85,10 +84,9 @@ void AvoidDefaultLambdaCaptureCheck::check( ReplacementText = "[" + llvm::join(AllCaptures, ", ") + "]"; } - SourceRange IntroducerRange = Lambda->getIntroducerRange(); + clang::SourceRange IntroducerRange = Lambda->getIntroducerRange(); if (IntroducerRange.isValid()) { - Diag << FixItHint::CreateReplacement(IntroducerRange, ReplacementText); + Diag << clang::FixItHint::CreateReplacement(IntroducerRange, + ReplacementText); } } - -} // namespace clang::tidy::readability diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index 492863ddfc4a1..0a0d42ca259b8 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -5096,6 +5096,20 @@ AST_MATCHER_P(LambdaCapture, capturesVar, internal::Matcher, /// matches `[this]() { return cc; }`. AST_MATCHER(LambdaCapture, capturesThis) { return Node.capturesThis(); } +/// Matches lambda expressions that have default capture modes. +/// +/// Given +/// \code +/// auto l1 = [=]() {}; // matches +/// auto l2 = [&]() {}; // matches +/// auto l3 = []() {}; // does not match +/// \endcode +/// lambdaExpr(hasDefaultCapture()) +/// matches l1 and l2, but not l3. +AST_MATCHER(LambdaExpr, hasDefaultCapture) { + return Node.getCaptureDefault() != LCD_None; +} + /// Matches a constructor call expression which uses list initialization. AST_MATCHER(CXXConstructExpr, isListInitialization) { return Node.isListInitialization(); From b2c3cc2e8e927a9d10598b2c625324e4df180b69 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Mon, 29 Sep 2025 14:31:34 -0400 Subject: [PATCH 29/35] remove anonymous namespace on-behalf-of: @amd --- .../clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 7dfe07a8cf7b1..e75acb9551701 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -13,7 +13,6 @@ using namespace clang::tidy::readability; -namespace { static std::optional generateImplicitCaptureText(const clang::LambdaCapture &Capture) { if (Capture.capturesThis()) { @@ -38,8 +37,6 @@ generateImplicitCaptureText(const clang::LambdaCapture &Capture) { return std::nullopt; } -} // namespace - void AvoidDefaultLambdaCaptureCheck::registerMatchers( clang::ast_matchers::MatchFinder *Finder) { Finder->addMatcher( From 969558e11e859721f4f77e1dd23888a7a9eb81f9 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Wed, 1 Oct 2025 10:46:54 -0400 Subject: [PATCH 30/35] simplify per code review on-behalf-of: @amd --- .../readability/AvoidDefaultLambdaCaptureCheck.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index e75acb9551701..eea67233be318 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -14,7 +14,7 @@ using namespace clang::tidy::readability; static std::optional -generateImplicitCaptureText(const clang::LambdaCapture &Capture) { +generateCaptureText(const clang::LambdaCapture &Capture) { if (Capture.capturesThis()) { return Capture.getCaptureKind() == clang::LCK_StarThis ? "*this" : "this"; } @@ -61,14 +61,8 @@ void AvoidDefaultLambdaCaptureCheck::check( std::vector AllCaptures; - for (const auto &Capture : Lambda->explicit_captures()) { - if (const auto CaptureText = generateImplicitCaptureText(Capture)) { - AllCaptures.push_back(CaptureText.value()); - } - } - - for (const auto &Capture : Lambda->implicit_captures()) { - if (const auto CaptureText = generateImplicitCaptureText(Capture)) { + for (const auto &Capture : Lambda->captures()) { + if (const auto CaptureText = generateCaptureText(Capture)) { AllCaptures.push_back(CaptureText.value()); } } From 359fd6c01c5f679560620c117b42e11a782b1486 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Wed, 1 Oct 2025 10:52:18 -0400 Subject: [PATCH 31/35] Use IIFE so ReplacementText can be const. on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index eea67233be318..ac49de1f9b08e 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -67,15 +67,14 @@ void AvoidDefaultLambdaCaptureCheck::check( } } - // Replace with new capture list - std::string ReplacementText; - if (AllCaptures.empty()) { - ReplacementText = "[]"; - } else { - ReplacementText = "[" + llvm::join(AllCaptures, ", ") + "]"; - } + const auto ReplacementText = [&AllCaptures]() -> std::string { + if (AllCaptures.empty()) { + return "[]"; + } + return "[" + llvm::join(AllCaptures, ", ") + "]"; + }(); - clang::SourceRange IntroducerRange = Lambda->getIntroducerRange(); + const clang::SourceRange IntroducerRange = Lambda->getIntroducerRange(); if (IntroducerRange.isValid()) { Diag << clang::FixItHint::CreateReplacement(IntroducerRange, ReplacementText); From 4e10e7812ccd40aae112f2a38874ee9dd5c0fab7 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Fri, 3 Oct 2025 12:29:52 -0400 Subject: [PATCH 32/35] simplify code even more on-behalf-of: @amd --- .../AvoidDefaultLambdaCaptureCheck.cpp | 45 ++++++------------- .../avoid-default-lambda-capture.cpp | 27 +++++++++-- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index ac49de1f9b08e..2eacf1ed8935e 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -13,28 +13,17 @@ using namespace clang::tidy::readability; -static std::optional -generateCaptureText(const clang::LambdaCapture &Capture) { +static std::string generateCaptureText(const clang::LambdaCapture &Capture) { if (Capture.capturesThis()) { return Capture.getCaptureKind() == clang::LCK_StarThis ? "*this" : "this"; } - if (Capture.capturesVariable()) { - std::string Result; - if (Capture.getCaptureKind() == clang::LCK_ByRef) { - Result += "&"; - } - Result += Capture.getCapturedVar()->getName().str(); - return Result; + std::string Result; + if (Capture.getCaptureKind() == clang::LCK_ByRef) { + Result += "&"; } - - if (Capture.capturesVLAType()) { - // VLA captures are rare and complex - for now we skip them - // A full implementation would need to handle the VLA type properly - return std::nullopt; - } - - return std::nullopt; + Result += Capture.getCapturedVar()->getName().str(); + return Result; } void AvoidDefaultLambdaCaptureCheck::registerMatchers( @@ -59,24 +48,16 @@ void AvoidDefaultLambdaCaptureCheck::check( "lambda default captures are discouraged; " "prefer to capture specific variables explicitly"); - std::vector AllCaptures; + std::vector ImplicitCaptures; - for (const auto &Capture : Lambda->captures()) { - if (const auto CaptureText = generateCaptureText(Capture)) { - AllCaptures.push_back(CaptureText.value()); - } + for (const auto &Capture : Lambda->implicit_captures()) { + ImplicitCaptures.push_back(generateCaptureText(Capture)); } - const auto ReplacementText = [&AllCaptures]() -> std::string { - if (AllCaptures.empty()) { - return "[]"; - } - return "[" + llvm::join(AllCaptures, ", ") + "]"; + const auto ReplacementText = [&ImplicitCaptures]() { + return llvm::join(ImplicitCaptures, ", "); }(); - const clang::SourceRange IntroducerRange = Lambda->getIntroducerRange(); - if (IntroducerRange.isValid()) { - Diag << clang::FixItHint::CreateReplacement(IntroducerRange, - ReplacementText); - } + Diag << clang::FixItHint::CreateReplacement(Lambda->getCaptureDefaultLoc(), + ReplacementText); } diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp index 6e81a9eb2b3d4..f33f06b656132 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp @@ -14,11 +14,11 @@ void test_default_captures() { auto lambda3 = [=, &another](int x) { return value + another + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] - // CHECK-FIXES: auto lambda3 = [&another, value](int x) { return value + another + x; }; + // CHECK-FIXES: auto lambda3 = [value, &another](int x) { return value + another + x; }; auto lambda4 = [&, value](int x) { return value + another + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] - // CHECK-FIXES: auto lambda4 = [value, &another](int x) { return value + another + x; }; + // CHECK-FIXES: auto lambda4 = [&another, value](int x) { return value + another + x; }; } void test_acceptable_captures() { @@ -101,10 +101,31 @@ void test_template_lambdas() { auto lambda = [=](T x) { return value + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] - // CHECK-FIXES: auto lambda = [](T x) { return value + x; }; + // CHECK-FIXES: auto lambda = [value](T x) { return value + x; }; } void instantiate_templates() { test_template_lambdas(); test_template_lambdas(); } + +void test_init_captures() { + int x = 3; + int nx = 5; + + int y1 = [&, z = x + 5]() -> int { + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: int y1 = [&nx, z = x + 5]() -> int { + return z * z + nx; + }(); + + int y2 = [=, &ref = x]() { + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] + // CHECK-FIXES: int y2 = [nx, &ref = x]() { + ref += 1; + return nx - ref; + }(); + + (void)y1; + (void)y2; +} From 9765c90ba30bc8467169d870fe3139058e4fe5a8 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Fri, 3 Oct 2025 16:53:46 -0400 Subject: [PATCH 33/35] Turns out this check doesn't like templated lambdas on-behalf-of: @amd --- .../readability/AvoidDefaultLambdaCaptureCheck.cpp | 5 +++++ .../checkers/readability/avoid-default-lambda-capture.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 2eacf1ed8935e..43a6cfe13d45b 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -54,6 +54,11 @@ void AvoidDefaultLambdaCaptureCheck::check( ImplicitCaptures.push_back(generateCaptureText(Capture)); } + // For template-dependent lambdas, the list of captures hasn't been created + // yet, so the list of implicit captures is empty. + if (ImplicitCaptures.empty() && Lambda->isGenericLambda()) + return; + const auto ReplacementText = [&ImplicitCaptures]() { return llvm::join(ImplicitCaptures, ", "); }(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp index f33f06b656132..c6d3de7e481b9 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp @@ -95,13 +95,13 @@ class TestClass { } }; +// Lambda captures dependent on a template parameter don't have a fix it template void test_template_lambdas() { T value{}; auto lambda = [=](T x) { return value + x; }; // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda default captures are discouraged; prefer to capture specific variables explicitly [readability-avoid-default-lambda-capture] - // CHECK-FIXES: auto lambda = [value](T x) { return value + x; }; } void instantiate_templates() { From 33a79a06d56e8f7cd98d7823ca4f651e8d5d0d49 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Wed, 8 Oct 2025 11:17:40 -0400 Subject: [PATCH 34/35] ignore VLAs --- .../readability/AvoidDefaultLambdaCaptureCheck.cpp | 13 ++++++++----- .../readability/avoid-default-lambda-capture.rst | 4 ++++ .../readability/avoid-default-lambda-capture.cpp | 14 +++++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp index 43a6cfe13d45b..7e19ec9d98979 100644 --- a/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AvoidDefaultLambdaCaptureCheck.cpp @@ -44,16 +44,19 @@ void AvoidDefaultLambdaCaptureCheck::check( if (DefaultCaptureLoc.isInvalid()) return; - auto Diag = diag(DefaultCaptureLoc, - "lambda default captures are discouraged; " - "prefer to capture specific variables explicitly"); - std::vector ImplicitCaptures; - for (const auto &Capture : Lambda->implicit_captures()) { + // It is impossible to explicitly capture a VLA in C++, since VLAs don't + // exist in ISO C++ and so the syntax was never created to capture them. + if (Capture.getCaptureKind() == LCK_VLAType) + return; ImplicitCaptures.push_back(generateCaptureText(Capture)); } + auto Diag = diag(DefaultCaptureLoc, + "lambda default captures are discouraged; " + "prefer to capture specific variables explicitly"); + // For template-dependent lambdas, the list of captures hasn't been created // yet, so the list of implicit captures is empty. if (ImplicitCaptures.empty() && Lambda->isGenericLambda()) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index d4ecd4c7eb27d..646fbf625361a 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -13,6 +13,10 @@ Coding guidelines that recommend against defaulted lambda captures include: * Item 31 of Effective Modern C++ by Scott Meyers +This check does not lint for variable-length array (VLA) captures. VLAs are not +ISO C++, and it is impossible to explicitly capture them as the syntax does not +exist. + Example ------- diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp index c6d3de7e481b9..140e00960e8b2 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/avoid-default-lambda-capture.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s readability-avoid-default-lambda-capture %t +// RUN: %check_clang_tidy %s readability-avoid-default-lambda-capture %t -- -- -Wno-vla-extension void test_default_captures() { int value = 42; @@ -129,3 +129,15 @@ void test_init_captures() { (void)y1; (void)y2; } + +void test_vla_no_crash() { + // VLAs create implicit VLA bound captures that cannot be written explicitly. + // No warning should be issued. + int n = 5; + int vla[n]; + for (int i = 0; i < n; ++i) { + vla[i] = i * 10; + } + + auto lambda = [&]() { return vla[0]; }; +} From 8f21201a966ce51ef52486cf1acf9018ef1c71b8 Mon Sep 17 00:00:00 2001 From: jjmarr-amd Date: Wed, 8 Oct 2025 11:25:44 -0400 Subject: [PATCH 35/35] move around doc paragraphs on-behalf-of: @amd --- .../checks/readability/avoid-default-lambda-capture.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst index 646fbf625361a..b345a566488c6 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/avoid-default-lambda-capture.rst @@ -9,14 +9,14 @@ Captures can lead to subtle bugs including dangling references and unnecessary copies. Writing out the name of the variables being captured reminds programmers and reviewers to know what is being captured. And knowing is half the battle. +This check does not lint for variable-length array (VLA) captures. VLAs are not +ISO C++, and it is impossible to explicitly capture them as the syntax for doing +so does not exist. + Coding guidelines that recommend against defaulted lambda captures include: * Item 31 of Effective Modern C++ by Scott Meyers -This check does not lint for variable-length array (VLA) captures. VLAs are not -ISO C++, and it is impossible to explicitly capture them as the syntax does not -exist. - Example -------