Skip to content

Commit c91ad61

Browse files
committed
[clang-tidy] Add support of bind function calls that capture this
1 parent a27da0a commit c91ad61

File tree

5 files changed

+110
-18
lines changed

5 files changed

+110
-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,21 @@ AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) {
6464

6565
constexpr const char *DefaultFunctionWrapperTypes =
6666
"::std::function;::std::move_only_function;::boost::function";
67+
constexpr const char *DefaultBindFunctions = "::std::bind;::boost::bind";
6768

6869
CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck(
6970
StringRef Name, ClangTidyContext *Context)
7071
: ClangTidyCheck(Name, Context),
7172
FunctionWrapperTypes(utils::options::parseStringList(
72-
Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))) {}
73+
Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))),
74+
BindFunctions(utils::options::parseStringList(
75+
Options.get("BindFunctions", DefaultBindFunctions))) {}
7376
void CapturingThisInMemberVariableCheck::storeOptions(
7477
ClangTidyOptions::OptionMap &Opts) {
7578
Options.store(Opts, "FunctionWrapperTypes",
7679
utils::options::serializeStringList(FunctionWrapperTypes));
80+
Options.store(Opts, "BindFunctions",
81+
utils::options::serializeStringList(BindFunctions));
7782
}
7883

7984
void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
@@ -87,33 +92,54 @@ void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
8792
// [self = this]
8893
capturesVar(varDecl(hasInitializer(cxxThisExpr())))));
8994
auto IsLambdaCapturingThis =
90-
lambdaExpr(hasAnyCapture(CaptureThis.bind("capture"))).bind("lambda");
91-
auto IsInitWithLambda =
92-
anyOf(IsLambdaCapturingThis,
93-
cxxConstructExpr(hasArgument(0, IsLambdaCapturingThis)));
95+
lambdaExpr(hasAnyCapture(CaptureThis)).bind("lambda");
96+
97+
auto IsBindCapturingThis =
98+
callExpr(
99+
callee(functionDecl(matchers::matchesAnyListedName(BindFunctions))
100+
.bind("callee")),
101+
hasAnyArgument(cxxThisExpr()))
102+
.bind("bind");
103+
104+
auto IsInitWithLambdaOrBind =
105+
anyOf(IsLambdaCapturingThis, IsBindCapturingThis,
106+
cxxConstructExpr(hasArgument(
107+
0, anyOf(IsLambdaCapturingThis, IsBindCapturingThis))));
108+
94109
Finder->addMatcher(
95110
cxxRecordDecl(
96111
anyOf(has(cxxConstructorDecl(
97112
unless(isCopyConstructor()), unless(isMoveConstructor()),
98113
hasAnyConstructorInitializer(cxxCtorInitializer(
99114
isMemberInitializer(), forField(IsStdFunctionField),
100-
withInitializer(IsInitWithLambda))))),
115+
withInitializer(IsInitWithLambdaOrBind))))),
101116
has(fieldDecl(IsStdFunctionField,
102-
hasInClassInitializer(IsInitWithLambda)))),
117+
hasInClassInitializer(IsInitWithLambdaOrBind)))),
103118
unless(correctHandleCaptureThisLambda())),
104119
this);
105120
}
106-
107121
void CapturingThisInMemberVariableCheck::check(
108122
const MatchFinder::MatchResult &Result) {
109-
const auto *Capture = Result.Nodes.getNodeAs<LambdaCapture>("capture");
110-
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
123+
const auto EmitDiag = [this](const SourceLocation &Location,
124+
const FunctionDecl *Bind) {
125+
const std::string BindName = Bind ? Bind->getQualifiedNameAsString() : "";
126+
diag(Location, "'this' captured by a %select{lambda|'%1' call}0 and "
127+
"stored in a class member variable; disable implicit class "
128+
"copying/moving to prevent potential use-after-free")
129+
<< (Bind ? 1 : 0) << BindName;
130+
};
131+
132+
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {
133+
EmitDiag(Lambda->getBeginLoc(), nullptr);
134+
} else if (const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind")) {
135+
const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>("callee");
136+
assert(Callee);
137+
EmitDiag(Bind->getBeginLoc(), Callee);
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
@@ -106,8 +106,9 @@ New checks
106106
- New :doc:`bugprone-capturing-this-in-member-variable
107107
<clang-tidy/checks/bugprone/capturing-this-in-member-variable>` check.
108108

109-
Finds lambda captures that capture the ``this`` pointer and store it as class
110-
members without handle the copy and move constructors and the assignments.
109+
Finds lambda captures and ``bind`` function calls that capture the ``this``
110+
pointer and store it as class members without handle the copy and move
111+
constructors and the assignments.
111112

112113
- New :doc:`bugprone-unintended-char-ostream-output
113114
<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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,16 @@ Possible fixes:
3232
object types.
3333
- passing ``this`` pointer as parameter
3434

35+
Options
36+
-------
37+
3538
.. option:: FunctionWrapperTypes
3639

3740
A semicolon-separated list of names of types. Used to specify function
3841
wrapper that can hold lambda expressions.
3942
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. Default is `::std::bind;::boost::bind`.

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)