Skip to content

Commit 8ef214f

Browse files
committed
[clang-tidy] Add new check bugprone-capture-this-by-field
1 parent ba7e273 commit 8ef214f

File tree

8 files changed

+310
-0
lines changed

8 files changed

+310
-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
@@ -16,6 +16,7 @@
1616
#include "BitwisePointerCastCheck.h"
1717
#include "BoolPointerImplicitConversionCheck.h"
1818
#include "BranchCloneCheck.h"
19+
#include "CaptureThisByFieldCheck.h"
1920
#include "CastingThroughVoidCheck.h"
2021
#include "ChainedComparisonCheck.h"
2122
#include "ComparePointerToMemberVirtualFunctionCheck.h"
@@ -118,6 +119,8 @@ class BugproneModule : public ClangTidyModule {
118119
CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>(
119120
"bugprone-bool-pointer-implicit-conversion");
120121
CheckFactories.registerCheck<BranchCloneCheck>("bugprone-branch-clone");
122+
CheckFactories.registerCheck<CaptureThisByFieldCheck>(
123+
"bugprone-capture-this-by-field");
121124
CheckFactories.registerCheck<CastingThroughVoidCheck>(
122125
"bugprone-casting-through-void");
123126
CheckFactories.registerCheck<ChainedComparisonCheck>(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_clang_library(clangTidyBugproneModule STATIC
1212
BoolPointerImplicitConversionCheck.cpp
1313
BranchCloneCheck.cpp
1414
BugproneTidyModule.cpp
15+
CaptureThisByFieldCheck.cpp
1516
CastingThroughVoidCheck.cpp
1617
ChainedComparisonCheck.cpp
1718
ComparePointerToMemberVirtualFunctionCheck.cpp
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===--- CaptureThisByFieldCheck.cpp - clang-tidy -------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "CaptureThisByFieldCheck.h"
10+
#include "clang/AST/DeclCXX.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/ASTMatchers/ASTMatchers.h"
13+
#include "clang/ASTMatchers/ASTMatchersMacros.h"
14+
15+
using namespace clang::ast_matchers;
16+
17+
namespace clang::tidy::bugprone {
18+
19+
namespace {
20+
21+
AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) {
22+
// unresolved
23+
if (Node.needsOverloadResolutionForCopyConstructor() &&
24+
Node.needsImplicitCopyConstructor())
25+
return false;
26+
if (Node.needsOverloadResolutionForMoveConstructor() &&
27+
Node.needsImplicitMoveConstructor())
28+
return false;
29+
if (Node.needsOverloadResolutionForCopyAssignment() &&
30+
Node.needsImplicitCopyAssignment())
31+
return false;
32+
if (Node.needsOverloadResolutionForMoveAssignment() &&
33+
Node.needsImplicitMoveAssignment())
34+
return false;
35+
// default but not deleted
36+
if (Node.hasSimpleCopyConstructor())
37+
return false;
38+
if (Node.hasSimpleMoveConstructor())
39+
return false;
40+
if (Node.hasSimpleCopyAssignment())
41+
return false;
42+
if (Node.hasSimpleMoveAssignment())
43+
return false;
44+
45+
for (CXXConstructorDecl const *C : Node.ctors()) {
46+
if (C->isCopyOrMoveConstructor() && C->isDefaulted() && !C->isDeleted())
47+
return false;
48+
}
49+
for (CXXMethodDecl const *M : Node.methods()) {
50+
if (M->isCopyAssignmentOperator() && M->isDefaulted() && !M->isDeleted())
51+
return false;
52+
if (M->isMoveAssignmentOperator() && M->isDefaulted() && !M->isDeleted())
53+
return false;
54+
}
55+
// FIXME: find ways to identifier correct handle capture this lambda
56+
return true;
57+
}
58+
59+
} // namespace
60+
61+
void CaptureThisByFieldCheck::registerMatchers(MatchFinder *Finder) {
62+
auto IsStdFunctionField =
63+
fieldDecl(hasType(cxxRecordDecl(hasName("::std::function"))))
64+
.bind("field");
65+
auto CaptureThis = lambdaCapture(anyOf(
66+
// [this]
67+
capturesThis(),
68+
// [self = this]
69+
capturesVar(varDecl(hasInitializer(cxxThisExpr())))));
70+
auto IsInitWithLambda = cxxConstructExpr(hasArgument(
71+
0,
72+
lambdaExpr(hasAnyCapture(CaptureThis.bind("capture"))).bind("lambda")));
73+
Finder->addMatcher(
74+
cxxRecordDecl(
75+
has(cxxConstructorDecl(
76+
unless(isCopyConstructor()), unless(isMoveConstructor()),
77+
hasAnyConstructorInitializer(cxxCtorInitializer(
78+
isMemberInitializer(), forField(IsStdFunctionField),
79+
withInitializer(IsInitWithLambda))))),
80+
unless(correctHandleCaptureThisLambda())),
81+
this);
82+
}
83+
84+
void CaptureThisByFieldCheck::check(const MatchFinder::MatchResult &Result) {
85+
const auto *Capture = Result.Nodes.getNodeAs<LambdaCapture>("capture");
86+
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
87+
const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");
88+
diag(Lambda->getBeginLoc(),
89+
"using lambda expressions to capture this and storing it in class "
90+
"member will cause potential variable lifetime issue when the class "
91+
"instance is moved or copied")
92+
<< Capture->getLocation();
93+
diag(Field->getLocation(),
94+
"'std::function' that stores captured this and becomes invalid during "
95+
"copying or moving",
96+
DiagnosticIDs::Note);
97+
}
98+
99+
} // namespace clang::tidy::bugprone
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===--- CaptureThisByFieldCheck.h - clang-tidy -----------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CAPTURETHISBYFIELDCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CAPTURETHISBYFIELDCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include "clang/AST/ASTTypeTraits.h"
14+
#include <optional>
15+
16+
namespace clang::tidy::bugprone {
17+
18+
/// Finds lambda captures that capture the ``this`` pointer and store it as class
19+
/// members without handle the copy and move constructors and the assignments.
20+
///
21+
/// For the user-facing documentation see:
22+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/capture-this-by-field.html
23+
class CaptureThisByFieldCheck : public ClangTidyCheck {
24+
public:
25+
CaptureThisByFieldCheck(StringRef Name, ClangTidyContext *Context)
26+
: ClangTidyCheck(Name, Context) {}
27+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
28+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
29+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
30+
return LangOpts.CPlusPlus11;
31+
}
32+
std::optional<TraversalKind> getCheckTraversalKind() const override {
33+
return TraversalKind::TK_IgnoreUnlessSpelledInSource;
34+
}
35+
};
36+
37+
} // namespace clang::tidy::bugprone
38+
39+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CAPTURETHISBYFIELDCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ Improvements to clang-tidy
9191
New checks
9292
^^^^^^^^^^
9393

94+
- New :doc:`bugprone-capture-this-by-field
95+
<clang-tidy/checks/bugprone/capture-this-by-field>` check.
96+
97+
Finds lambda captures that capture the ``this`` pointer and store it as class
98+
members without handle the copy and move constructors and the assignments.
99+
94100
- New :doc:`bugprone-unintended-char-ostream-output
95101
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
96102

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.. title:: clang-tidy - bugprone-capture-this-by-field
2+
3+
bugprone-capture-this-by-field
4+
==============================
5+
6+
Finds lambda captures that capture the ``this`` pointer and store it as class
7+
members without handle the copy and move constructors and the assignments.
8+
9+
Capture this in a lambda and store it as a class member is dangerous because the
10+
lambda can outlive the object it captures. Especially when the object is copied
11+
or moved, the captured ``this`` pointer will be implicitly propagated to the
12+
new object. Most of the time, people will believe that the captured ``this``
13+
pointer points to the new object, which will lead to bugs.
14+
15+
16+
.. code-block:: c++
17+
18+
struct C {
19+
C() : Captured([this]() -> C const * { return this; }) {}
20+
std::function<C const *()> Captured;
21+
};
22+
23+
void foo() {
24+
C v1{};
25+
C v2 = v1; // v2.Captured capture v1's this pointer
26+
assert(v2.Captured() == v1.Captured()); // v2.Captured capture v1's this pointer
27+
assert(v2.Captured() == &v2); // assertion failed.
28+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Clang-Tidy Checks
8484
:doc:`bugprone-bitwise-pointer-cast <bugprone/bitwise-pointer-cast>`,
8585
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
8686
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
87+
:doc:`bugprone-capture-this-by-field <bugprone/capture-this-by-field>`,
8788
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
8889
:doc:`bugprone-chained-comparison <bugprone/chained-comparison>`,
8990
:doc:`bugprone-compare-pointer-to-member-virtual-function <bugprone/compare-pointer-to-member-virtual-function>`,
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-capture-this-by-field %t
2+
3+
namespace std {
4+
5+
template<class Fn>
6+
class function;
7+
8+
template<class R, class ...Args>
9+
class function<R(Args...)> {
10+
public:
11+
function() noexcept;
12+
template<class F> function(F &&);
13+
};
14+
15+
} // namespace std
16+
17+
struct Basic {
18+
Basic() : Captured([this]() { static_cast<void>(this); }) {}
19+
// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: using lambda expressions to capture this and storing it in class member
20+
std::function<void()> Captured;
21+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
22+
};
23+
24+
struct AssignCapture {
25+
AssignCapture() : Captured([Self = this]() { static_cast<void>(Self); }) {}
26+
// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: using lambda expressions to capture this and storing it in class member
27+
std::function<void()> Captured;
28+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
29+
};
30+
31+
struct DeleteMoveAndCopy {
32+
DeleteMoveAndCopy() : Captured([this]() { static_cast<void>(this); }) {}
33+
DeleteMoveAndCopy(DeleteMoveAndCopy const&) = delete;
34+
DeleteMoveAndCopy(DeleteMoveAndCopy &&) = delete;
35+
DeleteMoveAndCopy& operator=(DeleteMoveAndCopy const&) = delete;
36+
DeleteMoveAndCopy& operator=(DeleteMoveAndCopy &&) = delete;
37+
std::function<void()> Captured;
38+
};
39+
40+
struct DeleteCopyImplicitDisabledMove {
41+
DeleteCopyImplicitDisabledMove() : Captured([this]() { static_cast<void>(this); }) {}
42+
DeleteCopyImplicitDisabledMove(DeleteCopyImplicitDisabledMove const&) = delete;
43+
DeleteCopyImplicitDisabledMove& operator=(DeleteCopyImplicitDisabledMove const&) = delete;
44+
std::function<void()> Captured;
45+
};
46+
47+
struct DeleteCopyDefaultMove {
48+
DeleteCopyDefaultMove() : Captured([this]() { static_cast<void>(this); }) {}
49+
// CHECK-MESSAGES: :[[@LINE-1]]:38: warning: using lambda expressions to capture this and storing it in class member
50+
DeleteCopyDefaultMove(DeleteCopyDefaultMove const&) = delete;
51+
DeleteCopyDefaultMove(DeleteCopyDefaultMove &&) = default;
52+
DeleteCopyDefaultMove& operator=(DeleteCopyDefaultMove const&) = delete;
53+
DeleteCopyDefaultMove& operator=(DeleteCopyDefaultMove &&) = default;
54+
std::function<void()> Captured;
55+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
56+
};
57+
58+
struct DeleteMoveDefaultCopy {
59+
DeleteMoveDefaultCopy() : Captured([this]() { static_cast<void>(this); }) {}
60+
// CHECK-MESSAGES: :[[@LINE-1]]:38: warning: using lambda expressions to capture this and storing it in class member
61+
DeleteMoveDefaultCopy(DeleteMoveDefaultCopy const&) = default;
62+
DeleteMoveDefaultCopy(DeleteMoveDefaultCopy &&) = delete;
63+
DeleteMoveDefaultCopy& operator=(DeleteMoveDefaultCopy const&) = default;
64+
DeleteMoveDefaultCopy& operator=(DeleteMoveDefaultCopy &&) = delete;
65+
std::function<void()> Captured;
66+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
67+
};
68+
69+
struct DeleteCopyMoveBase {
70+
DeleteCopyMoveBase() = default;
71+
DeleteCopyMoveBase(DeleteCopyMoveBase const&) = delete;
72+
DeleteCopyMoveBase(DeleteCopyMoveBase &&) = delete;
73+
DeleteCopyMoveBase& operator=(DeleteCopyMoveBase const&) = delete;
74+
DeleteCopyMoveBase& operator=(DeleteCopyMoveBase &&) = delete;
75+
};
76+
77+
struct Inherit : DeleteCopyMoveBase {
78+
Inherit() : DeleteCopyMoveBase{}, Captured([this]() { static_cast<void>(this); }) {}
79+
std::function<void()> Captured;
80+
};
81+
82+
struct UserDefinedCopyMove {
83+
UserDefinedCopyMove() : Captured([this]() { static_cast<void>(this); }) {}
84+
UserDefinedCopyMove(UserDefinedCopyMove const&);
85+
UserDefinedCopyMove(UserDefinedCopyMove &&);
86+
UserDefinedCopyMove& operator=(UserDefinedCopyMove const&);
87+
UserDefinedCopyMove& operator=(UserDefinedCopyMove &&);
88+
std::function<void()> Captured;
89+
};
90+
91+
struct UserDefinedCopyMoveWithDefault1 {
92+
UserDefinedCopyMoveWithDefault1() : Captured([this]() { static_cast<void>(this); }) {}
93+
// CHECK-MESSAGES: :[[@LINE-1]]:48: warning: using lambda expressions to capture this and storing it in class member
94+
UserDefinedCopyMoveWithDefault1(UserDefinedCopyMoveWithDefault1 const&) = default;
95+
UserDefinedCopyMoveWithDefault1(UserDefinedCopyMoveWithDefault1 &&);
96+
UserDefinedCopyMoveWithDefault1& operator=(UserDefinedCopyMoveWithDefault1 const&);
97+
UserDefinedCopyMoveWithDefault1& operator=(UserDefinedCopyMoveWithDefault1 &&);
98+
std::function<void()> Captured;
99+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
100+
};
101+
102+
struct UserDefinedCopyMoveWithDefault2 {
103+
UserDefinedCopyMoveWithDefault2() : Captured([this]() { static_cast<void>(this); }) {}
104+
// CHECK-MESSAGES: :[[@LINE-1]]:48: warning: using lambda expressions to capture this and storing it in class member
105+
UserDefinedCopyMoveWithDefault2(UserDefinedCopyMoveWithDefault2 const&);
106+
UserDefinedCopyMoveWithDefault2(UserDefinedCopyMoveWithDefault2 &&) = default;
107+
UserDefinedCopyMoveWithDefault2& operator=(UserDefinedCopyMoveWithDefault2 const&);
108+
UserDefinedCopyMoveWithDefault2& operator=(UserDefinedCopyMoveWithDefault2 &&);
109+
std::function<void()> Captured;
110+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
111+
};
112+
113+
struct UserDefinedCopyMoveWithDefault3 {
114+
UserDefinedCopyMoveWithDefault3() : Captured([this]() { static_cast<void>(this); }) {}
115+
// CHECK-MESSAGES: :[[@LINE-1]]:48: warning: using lambda expressions to capture this and storing it in class member
116+
UserDefinedCopyMoveWithDefault3(UserDefinedCopyMoveWithDefault3 const&);
117+
UserDefinedCopyMoveWithDefault3(UserDefinedCopyMoveWithDefault3 &&);
118+
UserDefinedCopyMoveWithDefault3& operator=(UserDefinedCopyMoveWithDefault3 const&) = default;
119+
UserDefinedCopyMoveWithDefault3& operator=(UserDefinedCopyMoveWithDefault3 &&);
120+
std::function<void()> Captured;
121+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
122+
};
123+
124+
struct UserDefinedCopyMoveWithDefault4 {
125+
UserDefinedCopyMoveWithDefault4() : Captured([this]() { static_cast<void>(this); }) {}
126+
// CHECK-MESSAGES: :[[@LINE-1]]:48: warning: using lambda expressions to capture this and storing it in class member
127+
UserDefinedCopyMoveWithDefault4(UserDefinedCopyMoveWithDefault4 const&);
128+
UserDefinedCopyMoveWithDefault4(UserDefinedCopyMoveWithDefault4 &&);
129+
UserDefinedCopyMoveWithDefault4& operator=(UserDefinedCopyMoveWithDefault4 const&);
130+
UserDefinedCopyMoveWithDefault4& operator=(UserDefinedCopyMoveWithDefault4 &&) = default;
131+
std::function<void()> Captured;
132+
// CHECK-MESSAGES: :[[@LINE-1]]:25: note: 'std::function' that stores captured this
133+
};

0 commit comments

Comments
 (0)