Skip to content

Commit 35df1c6

Browse files
committed
Bugprone-default-lambda-capture
on-behalf-of: @amd <[email protected]>
1 parent 4a8bb08 commit 35df1c6

File tree

8 files changed

+215
-0
lines changed

8 files changed

+215
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "CopyConstructorInitCheck.h"
2424
#include "CrtpConstructorAccessibilityCheck.h"
2525
#include "DanglingHandleCheck.h"
26+
#include "DefaultLambdaCaptureCheck.h"
2627
#include "DynamicStaticInitializersCheck.h"
2728
#include "EasilySwappableParametersCheck.h"
2829
#include "EmptyCatchCheck.h"
@@ -134,6 +135,8 @@ class BugproneModule : public ClangTidyModule {
134135
"bugprone-copy-constructor-init");
135136
CheckFactories.registerCheck<DanglingHandleCheck>(
136137
"bugprone-dangling-handle");
138+
CheckFactories.registerCheck<DefaultLambdaCaptureCheck>(
139+
"bugprone-default-lambda-capture");
137140
CheckFactories.registerCheck<DynamicStaticInitializersCheck>(
138141
"bugprone-dynamic-static-initializers");
139142
CheckFactories.registerCheck<EasilySwappableParametersCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_clang_library(clangTidyBugproneModule STATIC
1919
CopyConstructorInitCheck.cpp
2020
CrtpConstructorAccessibilityCheck.cpp
2121
DanglingHandleCheck.cpp
22+
DefaultLambdaCaptureCheck.cpp
2223
DynamicStaticInitializersCheck.cpp
2324
EasilySwappableParametersCheck.cpp
2425
EmptyCatchCheck.cpp
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "DefaultLambdaCaptureCheck.h"
2+
#include "clang/AST/ASTContext.h"
3+
#include "clang/ASTMatchers/ASTMatchFinder.h"
4+
5+
using namespace clang::ast_matchers;
6+
7+
namespace clang::tidy::bugprone {
8+
9+
void DefaultLambdaCaptureCheck::registerMatchers(MatchFinder *Finder) {
10+
// Match any lambda expression
11+
Finder->addMatcher(lambdaExpr().bind("lambda"), this);
12+
}
13+
14+
void DefaultLambdaCaptureCheck::check(const MatchFinder::MatchResult &Result) {
15+
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
16+
if (!Lambda)
17+
return;
18+
19+
// Check if lambda has a default capture
20+
if (Lambda->getCaptureDefault() == LCD_None)
21+
return;
22+
23+
SourceLocation DefaultCaptureLoc = Lambda->getCaptureDefaultLoc();
24+
if (DefaultCaptureLoc.isInvalid())
25+
return;
26+
27+
const char *CaptureKind =
28+
(Lambda->getCaptureDefault() == LCD_ByCopy) ? "by-copy" : "by-reference";
29+
30+
diag(DefaultCaptureLoc, "lambda %0 default capture is discouraged; "
31+
"prefer to capture specific variables explicitly")
32+
<< CaptureKind;
33+
}
34+
35+
} // namespace clang::tidy::bugprone
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H
2+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H
3+
4+
#include "../ClangTidyCheck.h"
5+
6+
namespace clang::tidy::bugprone {
7+
8+
/** Flags lambdas that use default capture modes
9+
*
10+
* For the user-facing documentation see:
11+
* http://clang.llvm.org/extra/clang-tidy/checks/bugprone/default-lambda-capture.html
12+
*/
13+
class DefaultLambdaCaptureCheck : public ClangTidyCheck {
14+
public:
15+
DefaultLambdaCaptureCheck(StringRef Name, ClangTidyContext *Context)
16+
: ClangTidyCheck(Name, Context) {}
17+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
18+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
19+
std::optional<TraversalKind> getCheckTraversalKind() const override {
20+
return TK_IgnoreUnlessSpelledInSource;
21+
}
22+
};
23+
24+
} // namespace clang::tidy::bugprone
25+
26+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DEFAULT_LAMBDA_CAPTURE_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ New checks
194194
Finds virtual function overrides with different visibility than the function
195195
in the base class.
196196

197+
- New :doc:`bugprone-default-lambda-capture
198+
<clang-tidy/checks/bugprone/default-lambda-capture>` check.
199+
200+
Finds lambda expressions that use default capture modes (``[=]`` or ``[&]``)
201+
and suggests using explicit captures instead. Default captures can lead to
202+
subtle bugs including dangling references with ``[&]``, unnecessary copies
203+
with ``[=]``, and make code less maintainable by hiding which variables are
204+
actually being captured.
205+
197206
New check aliases
198207
^^^^^^^^^^^^^^^^^
199208

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.. title:: clang-tidy - bugprone-default-lambda-capture
2+
3+
bugprone-default-lambda-capture
4+
===============================
5+
6+
Warns when lambda expressions use default capture modes (``[=]`` or ``[&]``)
7+
instead of explicitly capturing specific variables. Default captures can make
8+
code less readable and more error-prone by making it unclear which variables
9+
are being captured and how.
10+
11+
Implements Item 31 of Effective Modern C++ by Scott Meyers.
12+
13+
Example
14+
-------
15+
16+
.. code-block:: c++
17+
18+
void example() {
19+
int x = 1;
20+
int y = 2;
21+
22+
// Bad - default capture by copy
23+
auto lambda1 = [=]() { return x + y; };
24+
25+
// Bad - default capture by reference
26+
auto lambda2 = [&]() { return x + y; };
27+
28+
// Good - explicit captures
29+
auto lambda3 = [x, y]() { return x + y; };
30+
auto lambda4 = [&x, &y]() { return x + y; };
31+
}
32+
33+
The check will warn on:
34+
35+
- Default capture by copy: ``[=]``
36+
- Default capture by reference: ``[&]``
37+
- Mixed captures with defaults: ``[=, &x]`` or ``[&, x]``
38+
39+
The check will not warn on:
40+
41+
- Explicit captures: ``[x]``, ``[&x]``, ``[x, y]``, ``[this]``
42+
- Empty capture lists: ``[]``

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Clang-Tidy Checks
9191
:doc:`bugprone-copy-constructor-init <bugprone/copy-constructor-init>`, "Yes"
9292
:doc:`bugprone-crtp-constructor-accessibility <bugprone/crtp-constructor-accessibility>`, "Yes"
9393
:doc:`bugprone-dangling-handle <bugprone/dangling-handle>`,
94+
:doc:`bugprone-default-lambda-capture <bugprone/default-lambda-capture>`,
9495
:doc:`bugprone-dynamic-static-initializers <bugprone/dynamic-static-initializers>`,
9596
:doc:`bugprone-easily-swappable-parameters <bugprone/easily-swappable-parameters>`,
9697
:doc:`bugprone-empty-catch <bugprone/empty-catch>`,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %check_clang_tidy %s bugprone-default-lambda-capture %t
2+
3+
void test_default_captures() {
4+
int value = 42;
5+
int another = 10;
6+
7+
auto lambda1 = [=](int x) { return value + x; };
8+
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
9+
10+
auto lambda2 = [&](int x) { return value + x; };
11+
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
12+
13+
auto lambda3 = [=, &another](int x) { return value + another + x; };
14+
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
15+
16+
auto lambda4 = [&, value](int x) { return value + another + x; };
17+
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
18+
}
19+
20+
void test_acceptable_captures() {
21+
int value = 42;
22+
int another = 10;
23+
24+
auto lambda1 = [value](int x) { return value + x; };
25+
auto lambda2 = [&value](int x) { return value + x; };
26+
auto lambda3 = [value, another](int x) { return value + another + x; };
27+
auto lambda4 = [&value, &another](int x) { return value + another + x; };
28+
29+
auto lambda5 = [](int x, int y) { return x + y; };
30+
31+
struct S {
32+
int member = 5;
33+
void foo() {
34+
auto lambda = [this]() { return member; };
35+
}
36+
};
37+
}
38+
39+
void test_nested_lambdas() {
40+
int outer_var = 1;
41+
int middle_var = 2;
42+
int inner_var = 3;
43+
44+
auto outer = [=]() {
45+
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
46+
47+
auto inner = [&](int x) { return outer_var + middle_var + inner_var + x; };
48+
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
49+
50+
return inner(10);
51+
};
52+
}
53+
54+
void test_lambda_returns() {
55+
int a = 1, b = 2, c = 3;
56+
57+
auto create_adder = [=](int x) {
58+
// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
59+
return [x](int y) { return x + y; }; // Inner lambda is fine - explicit capture
60+
};
61+
62+
auto func1 = [&]() { return a; };
63+
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
64+
65+
auto func2 = [=]() { return b; };
66+
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
67+
}
68+
69+
class TestClass {
70+
int member = 42;
71+
72+
public:
73+
void test_member_function_lambdas() {
74+
int local = 10;
75+
76+
auto lambda1 = [=]() { return member + local; };
77+
// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
78+
79+
auto lambda2 = [&]() { return member + local; };
80+
// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: lambda by-reference default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
81+
82+
auto lambda3 = [this, local]() { return member + local; };
83+
auto lambda4 = [this, &local]() { return member + local; };
84+
}
85+
};
86+
87+
template<typename T>
88+
void test_template_lambdas() {
89+
T value{};
90+
91+
auto lambda = [=](T x) { return value + x; };
92+
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: lambda by-copy default capture is discouraged; prefer to capture specific variables explicitly [bugprone-default-lambda-capture]
93+
}
94+
95+
void instantiate_templates() {
96+
test_template_lambdas<int>();
97+
test_template_lambdas<double>();
98+
}

0 commit comments

Comments
 (0)