clang-tools 22.0.0git
CapturingThisInMemberVariableCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "../utils/Matchers.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
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::bugprone {
20
21namespace {
22
23AST_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 if (llvm::any_of(Node.ctors(), [](const CXXConstructorDecl *C) {
48 return C->isCopyOrMoveConstructor() && C->isDefaulted() &&
49 !C->isDeleted();
50 }))
51 return false;
52 if (llvm::any_of(Node.methods(), [](const CXXMethodDecl *M) {
53 return (M->isCopyAssignmentOperator() ||
54 M->isMoveAssignmentOperator()) &&
55 M->isDefaulted() && !M->isDeleted();
56 }))
57 return false;
58 // FIXME: find ways to identifier correct handle capture this lambda
59 return true;
60}
61
62} // namespace
63
64constexpr const char *DefaultFunctionWrapperTypes =
65 "::std::function;::std::move_only_function;::boost::function";
66constexpr const char *DefaultBindFunctions =
67 "::std::bind;::boost::bind;::std::bind_front;::std::bind_back;"
68 "::boost::compat::bind_front;::boost::compat::bind_back";
69
71 StringRef Name, ClangTidyContext *Context)
72 : ClangTidyCheck(Name, Context),
73 FunctionWrapperTypes(utils::options::parseStringList(
74 Options.get("FunctionWrapperTypes", DefaultFunctionWrapperTypes))),
75 BindFunctions(utils::options::parseStringList(
76 Options.get("BindFunctions", DefaultBindFunctions))) {}
79 Options.store(Opts, "FunctionWrapperTypes",
80 utils::options::serializeStringList(FunctionWrapperTypes));
81 Options.store(Opts, "BindFunctions",
83}
84
86 auto IsStdFunctionField =
87 fieldDecl(hasType(cxxRecordDecl(
88 matchers::matchesAnyListedName(FunctionWrapperTypes))))
89 .bind("field");
90 auto CaptureThis = lambdaCapture(anyOf(
91 // [this]
92 capturesThis(),
93 // [self = this]
94 capturesVar(varDecl(hasInitializer(cxxThisExpr())))));
95 auto IsLambdaCapturingThis =
96 lambdaExpr(hasAnyCapture(CaptureThis)).bind("lambda");
97
98 auto IsBindCapturingThis =
99 callExpr(
100 callee(functionDecl(matchers::matchesAnyListedName(BindFunctions))
101 .bind("callee")),
102 hasAnyArgument(cxxThisExpr()))
103 .bind("bind");
104
105 auto IsInitWithLambdaOrBind =
106 anyOf(IsLambdaCapturingThis, IsBindCapturingThis,
107 cxxConstructExpr(hasArgument(
108 0, anyOf(IsLambdaCapturingThis, IsBindCapturingThis))));
109
110 Finder->addMatcher(
111 cxxRecordDecl(
112 anyOf(has(cxxConstructorDecl(
113 unless(isCopyConstructor()), unless(isMoveConstructor()),
114 hasAnyConstructorInitializer(cxxCtorInitializer(
115 isMemberInitializer(), forField(IsStdFunctionField),
116 withInitializer(IsInitWithLambdaOrBind))))),
117 has(fieldDecl(IsStdFunctionField,
118 hasInClassInitializer(IsInitWithLambdaOrBind)))),
119 unless(correctHandleCaptureThisLambda())),
120 this);
121}
123 const MatchFinder::MatchResult &Result) {
124 if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda")) {
125 diag(Lambda->getBeginLoc(),
126 "'this' captured by a lambda and stored in a class member variable; "
127 "disable implicit class copying/moving to prevent potential "
128 "use-after-free");
129 } else if (const auto *Bind = Result.Nodes.getNodeAs<CallExpr>("bind")) {
130 const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>("callee");
131 assert(Callee);
132 diag(Bind->getBeginLoc(),
133 "'this' captured by a '%0' call and stored in a class member "
134 "variable; disable implicit class copying/moving to prevent potential "
135 "use-after-free")
136 << Callee->getQualifiedNameAsString();
137 }
138
139 const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");
140 assert(Field);
141
142 diag(Field->getLocation(),
143 "class member of type '%0' that stores captured 'this'",
144 DiagnosticIDs::Note)
145 << Field->getType().getAsString();
146}
147
148} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
constexpr const char * DefaultFunctionWrapperTypes
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap