Skip to content

Commit da6e245

Browse files
authored
[clang-tidy] Improve bugprone-capturing-this-in-member-variable check: add support of bind functions. (#132635)
Improve `bugprone-capturing-this-in-member-variable` check: Added support of `bind`-like functions that capture and store `this` pointer in class member. Closes #131220.
1 parent c9497a2 commit da6e245

File tree

5 files changed

+109
-18
lines changed

5 files changed

+109
-18
lines changed

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

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,23 @@ AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) {
6464

6565
constexpr const char *DefaultFunctionWrapperTypes =
6666
"::std::function;::std::move_only_function;::boost::function";
67+
constexpr const char *DefaultBindFunctions =
68+
"::std::bind;::boost::bind;::std::bind_front;::std::bind_back;"
69+
"::boost::compat::bind_front;::boost::compat::bind_back";
6770

6871
CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck(
6972
StringRef Name, ClangTidyContext *Context)
7073
: ClangTidyCheck(Name, Context),
7174
FunctionWrapperTypes(utils::options::parseStringList(
72-
Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))) {}
75+
Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))),
76+
BindFunctions(utils::options::parseStringList(
77+
Options.get("BindFunctions", DefaultBindFunctions))) {}
7378
void CapturingThisInMemberVariableCheck::storeOptions(
7479
ClangTidyOptions::OptionMap &Opts) {
7580
Options.store(Opts, "FunctionWrapperTypes",
7681
utils::options::serializeStringList(FunctionWrapperTypes));
82+
Options.store(Opts, "BindFunctions",
83+
utils::options::serializeStringList(BindFunctions));
7784
}
7885

7986
void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
@@ -87,33 +94,52 @@ void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
8794
// [self = this]
8895
capturesVar(varDecl(hasInitializer(cxxThisExpr())))));
8996
auto IsLambdaCapturingThis =
90-
lambdaExpr(hasAnyCapture(CaptureThis.bind("capture"))).bind("lambda");
91-
auto IsInitWithLambda =
92-
anyOf(IsLambdaCapturingThis,
93-
cxxConstructExpr(hasArgument(0, IsLambdaCapturingThis)));
97+
lambdaExpr(hasAnyCapture(CaptureThis)).bind("lambda");
98+
99+
auto IsBindCapturingThis =
100+
callExpr(
101+
callee(functionDecl(matchers::matchesAnyListedName(BindFunctions))
102+
.bind("callee")),
103+
hasAnyArgument(cxxThisExpr()))
104+
.bind("bind");
105+
106+
auto IsInitWithLambdaOrBind =
107+
anyOf(IsLambdaCapturingThis, IsBindCapturingThis,
108+
cxxConstructExpr(hasArgument(
109+
0, anyOf(IsLambdaCapturingThis, IsBindCapturingThis))));
110+
94111
Finder->addMatcher(
95112
cxxRecordDecl(
96113
anyOf(has(cxxConstructorDecl(
97114
unless(isCopyConstructor()), unless(isMoveConstructor()),
98115
hasAnyConstructorInitializer(cxxCtorInitializer(
99116
isMemberInitializer(), forField(IsStdFunctionField),
100-
withInitializer(IsInitWithLambda))))),
117+
withInitializer(IsInitWithLambdaOrBind))))),
101118
has(fieldDecl(IsStdFunctionField,
102-
hasInClassInitializer(IsInitWithLambda)))),
119+
hasInClassInitializer(IsInitWithLambdaOrBind)))),
103120
unless(correctHandleCaptureThisLambda())),
104121
this);
105122
}
106-
107123
void CapturingThisInMemberVariableCheck::check(
108124
const MatchFinder::MatchResult &Result) {
109-
const auto *Capture = Result.Nodes.getNodeAs<LambdaCapture>("capture");
110-
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
125+
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {
126+
diag(Lambda->getBeginLoc(),
127+
"'this' captured by a lambda and stored in a class member variable; "
128+
"disable implicit class copying/moving to prevent potential "
129+
"use-after-free");
130+
} else if (const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind")) {
131+
const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>("callee");
132+
assert(Callee);
133+
diag(Bind->getBeginLoc(),
134+
"'this' captured by a '%0' call and stored in a class member "
135+
"variable; disable implicit class copying/moving to prevent potential "
136+
"use-after-free")
137+
<< Callee->getQualifiedNameAsString();
138+
}
139+
111140
const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");
112-
diag(Lambda->getBeginLoc(),
113-
"'this' captured by a lambda and stored in a class member variable; "
114-
"disable implicit class copying/moving to prevent potential "
115-
"use-after-free")
116-
<< Capture->getLocation();
141+
assert(Field);
142+
117143
diag(Field->getLocation(),
118144
"class member of type '%0' that stores captured 'this'",
119145
DiagnosticIDs::Note)

clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class CapturingThisInMemberVariableCheck : public ClangTidyCheck {
3737
private:
3838
///< store the function wrapper types
3939
const std::vector<StringRef> FunctionWrapperTypes;
40+
const std::vector<StringRef> BindFunctions;
4041
};
4142

4243
} // namespace clang::tidy::bugprone

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ New checks
103103
- New :doc:`bugprone-capturing-this-in-member-variable
104104
<clang-tidy/checks/bugprone/capturing-this-in-member-variable>` check.
105105

106-
Finds lambda captures that capture the ``this`` pointer and store it as class
107-
members without handle the copy and move constructors and the assignments.
106+
Finds lambda captures and ``bind`` function calls that capture the ``this``
107+
pointer and store it as class members without handle the copy and move
108+
constructors and the assignments.
108109

109110
- New :doc:`bugprone-unintended-char-ostream-output
110111
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.

clang-tools-extra/docs/clang-tidy/checks/bugprone/capturing-this-in-member-variable.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,10 @@ Options
4040
A semicolon-separated list of names of types. Used to specify function
4141
wrapper that can hold lambda expressions.
4242
Default is `::std::function;::std::move_only_function;::boost::function`.
43+
44+
.. option:: BindFunctions
45+
46+
A semicolon-separated list of fully qualified names of functions that can
47+
capture ``this`` pointer.
48+
Default is `::std::bind;::boost::bind;::std::bind_front;::std::bind_back;
49+
::boost::compat::bind_front;::boost::compat::bind_back`.

clang-tools-extra/test/clang-tidy/checkers/bugprone/capturing-this-in-member-variable.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-capturing-this-in-member-variable %t -- -config="{CheckOptions: {bugprone-capturing-this-in-member-variable.FunctionWrapperTypes: '::std::function;::Fn'}}" --
1+
// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-capturing-this-in-member-variable %t -- -config="{CheckOptions: {bugprone-capturing-this-in-member-variable.FunctionWrapperTypes: '::std::function;::Fn', bugprone-capturing-this-in-member-variable.BindFunctions: '::std::bind;::Bind'}}" --
22

33
namespace std {
44

@@ -12,12 +12,22 @@ class function<R(Args...)> {
1212
template<class F> function(F &&);
1313
};
1414

15+
template <typename F, typename... Args>
16+
function<F(Args...)> bind(F&&, Args&&...) {
17+
return {};
18+
}
19+
1520
} // namespace std
1621

1722
struct Fn {
1823
template<class F> Fn(F &&);
1924
};
2025

26+
template <typename F, typename... Args>
27+
std::function<F(Args...)> Bind(F&&, Args&&...) {
28+
return {};
29+
}
30+
2131
struct BasicConstructor {
2232
BasicConstructor() : Captured([this]() { static_cast<void>(this); }) {}
2333
// CHECK-MESSAGES: :[[@LINE-1]]:33: warning: 'this' captured by a lambda and stored in a class member variable;
@@ -208,3 +218,49 @@ struct CustomFunctionWrapper {
208218
Fn Captured;
209219
// CHECK-MESSAGES: :[[@LINE-1]]:6: note: class member of type 'Fn' that stores captured 'this'
210220
};
221+
222+
struct BindConstructor {
223+
BindConstructor() : Captured(std::bind(&BindConstructor::method, this)) {}
224+
// CHECK-MESSAGES: :[[@LINE-1]]:32: warning: 'this' captured by a 'std::bind' call and stored in a class member variable;
225+
void method() {}
226+
std::function<void()> Captured;
227+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: class member of type 'std::function<void (void)>' that stores captured 'this'
228+
};
229+
230+
struct BindField1 {
231+
void method() {}
232+
std::function<void()> Captured = std::bind(&BindField1::method, this);
233+
// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: 'this' captured by a 'std::bind' call and stored in a class member variable;
234+
// CHECK-MESSAGES: :[[@LINE-2]]:25: note: class member of type 'std::function<void (void)>' that stores captured 'this'
235+
};
236+
237+
struct BindField2 {
238+
void method() {}
239+
std::function<void()> Captured{std::bind(&BindField2::method, this)};
240+
// CHECK-MESSAGES: :[[@LINE-1]]:34: warning: 'this' captured by a 'std::bind' call and stored in a class member variable;
241+
// CHECK-MESSAGES: :[[@LINE-2]]:25: note: class member of type 'std::function<void (void)>' that stores captured 'this'
242+
};
243+
244+
struct BindCustom {
245+
BindCustom() : Captured(Bind(&BindCustom::method, this)) {}
246+
// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: 'this' captured by a 'Bind' call and stored in a class member variable;
247+
void method() {}
248+
std::function<void()> Captured;
249+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: class member of type 'std::function<void (void)>' that stores captured 'this'
250+
};
251+
252+
struct BindNotCapturingThis {
253+
void method(int) {}
254+
BindNotCapturingThis(int V) : Captured(std::bind(&BindNotCapturingThis::method, V)) {}
255+
std::function<void()> Captured;
256+
};
257+
258+
struct DeletedCopyMoveWithBind {
259+
DeletedCopyMoveWithBind() : Captured(std::bind(&DeletedCopyMoveWithBind::method, this)) {}
260+
DeletedCopyMoveWithBind(DeletedCopyMoveWithBind const&) = delete;
261+
DeletedCopyMoveWithBind(DeletedCopyMoveWithBind &&) = delete;
262+
DeletedCopyMoveWithBind& operator=(DeletedCopyMoveWithBind const&) = delete;
263+
DeletedCopyMoveWithBind& operator=(DeletedCopyMoveWithBind &&) = delete;
264+
void method() {}
265+
std::function<void()> Captured;
266+
};

0 commit comments

Comments
 (0)