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
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 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
65constexpr const char *DefaultFunctionWrapperTypes =
66 "::std::function;::std::move_only_function;::boost::function";
67constexpr const char *DefaultBindFunctions =
68 "::std::bind;::boost::bind;::std::bind_front;::std::bind_back;"
69 "::boost::compat::bind_front;::boost::compat::bind_back";
70
71CapturingThisInMemberVariableCheck::CapturingThisInMemberVariableCheck(
72 StringRef Name, ClangTidyContext *Context)
73 : ClangTidyCheck(Name, Context),
74 FunctionWrapperTypes(utils::options::parseStringList(
75 Option: Options.get(LocalName: "FunctionWrapperTypes", Default: DefaultFunctionWrapperTypes))),
76 BindFunctions(utils::options::parseStringList(
77 Option: Options.get(LocalName: "BindFunctions", Default: DefaultBindFunctions))) {}
78void CapturingThisInMemberVariableCheck::storeOptions(
79 ClangTidyOptions::OptionMap &Opts) {
80 Options.store(Options&: Opts, LocalName: "FunctionWrapperTypes",
81 Value: utils::options::serializeStringList(Strings: FunctionWrapperTypes));
82 Options.store(Options&: Opts, LocalName: "BindFunctions",
83 Value: utils::options::serializeStringList(Strings: BindFunctions));
84}
85
86void CapturingThisInMemberVariableCheck::registerMatchers(MatchFinder *Finder) {
87 auto IsStdFunctionField =
88 fieldDecl(hasType(InnerMatcher: cxxRecordDecl(
89 matchers::matchesAnyListedName(NameList: FunctionWrapperTypes))))
90 .bind(ID: "field");
91 auto CaptureThis = lambdaCapture(anyOf(
92 // [this]
93 capturesThis(),
94 // [self = this]
95 capturesVar(InnerMatcher: varDecl(hasInitializer(InnerMatcher: cxxThisExpr())))));
96 auto IsLambdaCapturingThis =
97 lambdaExpr(hasAnyCapture(InnerMatcher: CaptureThis)).bind(ID: "lambda");
98
99 auto IsBindCapturingThis =
100 callExpr(
101 callee(InnerMatcher: functionDecl(matchers::matchesAnyListedName(NameList: BindFunctions))
102 .bind(ID: "callee")),
103 hasAnyArgument(InnerMatcher: cxxThisExpr()))
104 .bind(ID: "bind");
105
106 auto IsInitWithLambdaOrBind =
107 anyOf(IsLambdaCapturingThis, IsBindCapturingThis,
108 cxxConstructExpr(hasArgument(
109 N: 0, InnerMatcher: anyOf(IsLambdaCapturingThis, IsBindCapturingThis))));
110
111 Finder->addMatcher(
112 NodeMatch: cxxRecordDecl(
113 anyOf(has(cxxConstructorDecl(
114 unless(isCopyConstructor()), unless(isMoveConstructor()),
115 hasAnyConstructorInitializer(InnerMatcher: cxxCtorInitializer(
116 isMemberInitializer(), forField(InnerMatcher: IsStdFunctionField),
117 withInitializer(InnerMatcher: IsInitWithLambdaOrBind))))),
118 has(fieldDecl(IsStdFunctionField,
119 hasInClassInitializer(InnerMatcher: IsInitWithLambdaOrBind)))),
120 unless(correctHandleCaptureThisLambda())),
121 Action: this);
122}
123void CapturingThisInMemberVariableCheck::check(
124 const MatchFinder::MatchResult &Result) {
125 if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>(ID: "lambda")) {
126 diag(Loc: Lambda->getBeginLoc(),
127 Description: "'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>(ID: "bind")) {
131 const auto *Callee = Result.Nodes.getNodeAs<FunctionDecl>(ID: "callee");
132 assert(Callee);
133 diag(Loc: Bind->getBeginLoc(),
134 Description: "'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
140 const auto *Field = Result.Nodes.getNodeAs<FieldDecl>(ID: "field");
141 assert(Field);
142
143 diag(Field->getLocation(),
144 "class member of type '%0' that stores captured 'this'",
145 DiagnosticIDs::Note)
146 << Field->getType().getAsString();
147}
148
149} // namespace clang::tidy::bugprone
150

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.cpp