Skip to content

Commit 3b1e18c

Browse files
[clang-tidy] Add new check bugprone-capture-this-by-field (#130297)
Finds lambda captures that capture the ``this`` pointer and store it as class members without handle the copy and move constructors and the assignments. Capture this in a lambda and store it as a class member is dangerous because the lambda can outlive the object it captures. Especially when the object is copied or moved, the captured ``this`` pointer will be implicitly propagated to the new object. Most of the time, people will believe that the captured ``this`` pointer points to the new object, which will lead to bugs. Fixes: #120863 --------- Co-authored-by: Baranov Victor <[email protected]> Co-authored-by: Baranov Victor <[email protected]>
1 parent 7eb8b73 commit 3b1e18c

File tree

8 files changed

+427
-0
lines changed

8 files changed

+427
-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 "CapturingThisInMemberVariableCheck.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<CapturingThisInMemberVariableCheck>(
123+
"bugprone-capturing-this-in-member-variable");
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+
CapturingThisInMemberVariableCheck.cpp
1516
CastingThroughVoidCheck.cpp
1617
ChainedComparisonCheck.cpp
1718
ComparePointerToMemberVirtualFunctionCheck.cpp
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//===--- CapturingThisInMemberVariableCheck.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 "CapturingThisInMemberVariableCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
12+
#include "clang/AST/DeclCXX.h"
13+
#include "clang/ASTMatchers/ASTMatchFinder.h"
14+
#include "clang/ASTMatchers/ASTMatchers.h"
15+
#include "clang/ASTMatchers/ASTMatchersMacros.h"
16+
17+
using namespace clang::ast_matchers;
18+
19+
namespace clang::tidy::bugprone {
20+
21+
namespace {
22+
23+
AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) {
24+
// unresolved
25+
if (Node.needsOverloadResolutionForCopyConstructor() &&
26+
Node.needsImplicitCopyConstructor())
27+
return false;
28+
if (Node.needsOverloadResolutionForMoveConstructor() &&
29+
Node.needsImplicitMoveConstructor())
30+
return false;
31+
if (Node.needsOverloadResolutionForCopyAssignment() &&
32+
Node.needsImplicitCopyAssignment())
33+
return false;
34+
if (Node.needsOverloadResolutionForMoveAssignment() &&
35+
Node.needsImplicitMoveAssignment())
36+
return false;
37+
// default but not deleted
38+
if (Node.hasSimpleCopyConstructor())
39+
return false;
40+
if (Node.hasSimpleMoveConstructor())
41+
return false;
42+
if (Node.hasSimpleCopyAssignment())
43+
return false;
44+
if (Node.hasSimpleMoveAssignment())
45+
return false;
46+
47+
for (CXXConstructorDecl const *C : Node.ctors()) {
48+
if (C->isCopyOrMoveConstructor() && C->isDefaulted() && !C->isDeleted())
49+
return false;
50+
}
51+
for (CXXMethodDecl const *M : Node.methods()) {
52+
if (M->isCopyAssignmentOperator())
53+
llvm::errs() << M->isDeleted() << "\n";
54+
if (M->isCopyAssignmentOperator() && M->isDefaulted() && !M->isDeleted())
55+
return false;
56+
if (M->isMoveAssignmentOperator() && M->isDefaulted() && !M->isDeleted())
57+
return false;
58+
}
59+
// FIXME: find ways to identifier correct handle capture this lambda
60+
return true;
61+
}
62+
63+
} // namespace
64+
65+
constexpr const char *DefaultFunctionWrapperTypes =
66+
"::std::function;::std::move_only_function;::boost::function";
67+
68+
CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck(
69+
StringRef Name, ClangTidyContext *Context)
70+
: ClangTidyCheck(Name, Context),
71+
FunctionWrapperTypes(utils::options::parseStringList(
72+
Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))) {}
73+
void CapturingThisInMemberVariableCheck::storeOptions(
74+
ClangTidyOptions::OptionMap &Opts) {
75+
Options.store(Opts, "FunctionWrapperTypes",
76+
utils::options::serializeStringList(FunctionWrapperTypes));
77+
}
78+
79+
void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
80+
auto IsStdFunctionField =
81+
fieldDecl(hasType(cxxRecordDecl(
82+
matchers::matchesAnyListedName(FunctionWrapperTypes))))
83+
.bind("field");
84+
auto CaptureThis = lambdaCapture(anyOf(
85+
// [this]
86+
capturesThis(),
87+
// [self = this]
88+
capturesVar(varDecl(hasInitializer(cxxThisExpr())))));
89+
auto IsLambdaCapturingThis =
90+
lambdaExpr(hasAnyCapture(CaptureThis.bind("capture"))).bind("lambda");
91+
auto IsInitWithLambda =
92+
anyOf(IsLambdaCapturingThis,
93+
cxxConstructExpr(hasArgument(0, IsLambdaCapturingThis)));
94+
Finder->addMatcher(
95+
cxxRecordDecl(
96+
anyOf(has(cxxConstructorDecl(
97+
unless(isCopyConstructor()), unless(isMoveConstructor()),
98+
hasAnyConstructorInitializer(cxxCtorInitializer(
99+
isMemberInitializer(), forField(IsStdFunctionField),
100+
withInitializer(IsInitWithLambda))))),
101+
has(fieldDecl(IsStdFunctionField,
102+
hasInClassInitializer(IsInitWithLambda)))),
103+
unless(correctHandleCaptureThisLambda())),
104+
this);
105+
}
106+
107+
void CapturingThisInMemberVariableCheck::check(
108+
const MatchFinder::MatchResult &Result) {
109+
const auto *Capture = Result.Nodes.getNodeAs<LambdaCapture>("capture");
110+
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
111+
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();
117+
diag(Field->getLocation(),
118+
"class member of type '%0' that stores captured 'this'",
119+
DiagnosticIDs::Note)
120+
<< Field->getType().getAsString();
121+
}
122+
123+
} // namespace clang::tidy::bugprone
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===--- CapturingThisInMemberVariableCheck.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_CAPTURINGTHISINMEMBERVARIABLECHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CAPTURINGTHISINMEMBERVARIABLECHECK_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
19+
/// class members without handle the copy and move constructors and the
20+
/// assignments.
21+
///
22+
/// For the user-facing documentation see:
23+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/capturing-this-in-member-variable.html
24+
class CapturingThisInMemberVariableCheck : public ClangTidyCheck {
25+
public:
26+
CapturingThisInMemberVariableCheck(StringRef Name, ClangTidyContext *Context);
27+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
28+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
29+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
30+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
31+
return LangOpts.CPlusPlus11;
32+
}
33+
std::optional<TraversalKind> getCheckTraversalKind() const override {
34+
return TraversalKind::TK_IgnoreUnlessSpelledInSource;
35+
}
36+
37+
private:
38+
///< store the function wrapper types
39+
const std::vector<StringRef> FunctionWrapperTypes;
40+
};
41+
42+
} // namespace clang::tidy::bugprone
43+
44+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CAPTURINGTHISINMEMBERVARIABLECHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ Improvements to clang-tidy
100100
New checks
101101
^^^^^^^^^^
102102

103+
- New :doc:`bugprone-capturing-this-in-member-variable
104+
<clang-tidy/checks/bugprone/capturing-this-in-member-variable>` check.
105+
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.
108+
103109
- New :doc:`bugprone-unintended-char-ostream-output
104110
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
105111

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.. title:: clang-tidy - bugprone-capturing-this-in-member-variable
2+
3+
bugprone-capturing-this-in-member-variable
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+
.. code-block:: c++
16+
17+
struct C {
18+
C() : Captured([this]() -> C const * { return this; }) {}
19+
std::function<C const *()> Captured;
20+
};
21+
22+
void foo() {
23+
C v1{};
24+
C v2 = v1; // v2.Captured capture v1's 'this' pointer
25+
assert(v2.Captured() == v1.Captured()); // v2.Captured capture v1's 'this' pointer
26+
assert(v2.Captured() == &v2); // assertion failed.
27+
}
28+
29+
Possible fixes:
30+
- marking copy and move constructors and assignment operators deleted.
31+
- using class member method instead of class member variable with function
32+
object types.
33+
- passing ``this`` pointer as parameter
34+
35+
.. option:: FunctionWrapperTypes
36+
37+
A semicolon-separated list of names of types. Used to specify function
38+
wrapper that can hold lambda expressions.
39+
Default is `::std::function;::std::move_only_function;::boost::function`.

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-capturing-this-in-member-variable <bugprone/capturing-this-in-member-variable>`,
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>`,

0 commit comments

Comments
 (0)