1 //===--- SpecialMemberFunctionsCheck.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 "SpecialMemberFunctionsCheck.h"
10
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "llvm/ADT/DenseMapInfo.h"
14 #include "llvm/ADT/StringExtras.h"
15
16 #define DEBUG_TYPE "clang-tidy"
17
18 using namespace clang::ast_matchers;
19
20 namespace clang {
21 namespace tidy {
22 namespace cppcoreguidelines {
23
SpecialMemberFunctionsCheck(StringRef Name,ClangTidyContext * Context)24 SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck(
25 StringRef Name, ClangTidyContext *Context)
26 : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get(
27 "AllowMissingMoveFunctions", false)),
28 AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)),
29 AllowMissingMoveFunctionsWhenCopyIsDeleted(
30 Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {}
31
storeOptions(ClangTidyOptions::OptionMap & Opts)32 void SpecialMemberFunctionsCheck::storeOptions(
33 ClangTidyOptions::OptionMap &Opts) {
34 Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions);
35 Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor);
36 Options.store(Opts, "AllowMissingMoveFunctionsWhenCopyIsDeleted",
37 AllowMissingMoveFunctionsWhenCopyIsDeleted);
38 }
39
registerMatchers(MatchFinder * Finder)40 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) {
41 Finder->addMatcher(
42 cxxRecordDecl(
43 eachOf(has(cxxDestructorDecl().bind("dtor")),
44 has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")),
45 has(cxxMethodDecl(isCopyAssignmentOperator())
46 .bind("copy-assign")),
47 has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")),
48 has(cxxMethodDecl(isMoveAssignmentOperator())
49 .bind("move-assign"))))
50 .bind("class-def"),
51 this);
52 }
53
54 static llvm::StringRef
toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)55 toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) {
56 switch (K) {
57 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor:
58 return "a destructor";
59 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
60 DefaultDestructor:
61 return "a default destructor";
62 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
63 NonDefaultDestructor:
64 return "a non-default destructor";
65 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor:
66 return "a copy constructor";
67 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment:
68 return "a copy assignment operator";
69 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor:
70 return "a move constructor";
71 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment:
72 return "a move assignment operator";
73 }
74 llvm_unreachable("Unhandled SpecialMemberFunctionKind");
75 }
76
77 static std::string
join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS,llvm::StringRef AndOr)78 join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS,
79 llvm::StringRef AndOr) {
80
81 assert(!SMFS.empty() &&
82 "List of defined or undefined members should never be empty.");
83 std::string Buffer;
84 llvm::raw_string_ostream Stream(Buffer);
85
86 Stream << toString(SMFS[0]);
87 size_t LastIndex = SMFS.size() - 1;
88 for (size_t I = 1; I < LastIndex; ++I) {
89 Stream << ", " << toString(SMFS[I]);
90 }
91 if (LastIndex != 0) {
92 Stream << AndOr << toString(SMFS[LastIndex]);
93 }
94 return Stream.str();
95 }
96
check(const MatchFinder::MatchResult & Result)97 void SpecialMemberFunctionsCheck::check(
98 const MatchFinder::MatchResult &Result) {
99 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("class-def");
100 if (!MatchedDecl)
101 return;
102
103 ClassDefId ID(MatchedDecl->getLocation(), std::string(MatchedDecl->getName()));
104
105 auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) {
106 llvm::SmallVectorImpl<SpecialMemberFunctionData> &Members =
107 ClassWithSpecialMembers[ID];
108 if (!llvm::is_contained(Members, Data))
109 Members.push_back(std::move(Data));
110 };
111
112 if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) {
113 StoreMember({Dtor->isDefaulted()
114 ? SpecialMemberFunctionKind::DefaultDestructor
115 : SpecialMemberFunctionKind::NonDefaultDestructor,
116 Dtor->isDeleted()});
117 }
118
119 std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>>
120 Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor},
121 {"copy-assign", SpecialMemberFunctionKind::CopyAssignment},
122 {"move-ctor", SpecialMemberFunctionKind::MoveConstructor},
123 {"move-assign", SpecialMemberFunctionKind::MoveAssignment}};
124
125 for (const auto &KV : Matchers)
126 if (const auto *MethodDecl =
127 Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) {
128 StoreMember({KV.second, MethodDecl->isDeleted()});
129 }
130 }
131
onEndOfTranslationUnit()132 void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() {
133 for (const auto &C : ClassWithSpecialMembers) {
134 checkForMissingMembers(C.first, C.second);
135 }
136 }
137
checkForMissingMembers(const ClassDefId & ID,llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers)138 void SpecialMemberFunctionsCheck::checkForMissingMembers(
139 const ClassDefId &ID,
140 llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) {
141 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers;
142
143 auto HasMember = [&](SpecialMemberFunctionKind Kind) {
144 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
145 return Data.FunctionKind == Kind;
146 });
147 };
148
149 auto IsDeleted = [&](SpecialMemberFunctionKind Kind) {
150 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
151 return Data.FunctionKind == Kind && Data.IsDeleted;
152 });
153 };
154
155 auto RequireMember = [&](SpecialMemberFunctionKind Kind) {
156 if (!HasMember(Kind))
157 MissingMembers.push_back(Kind);
158 };
159
160 bool RequireThree =
161 HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) ||
162 (!AllowSoleDefaultDtor &&
163 HasMember(SpecialMemberFunctionKind::DefaultDestructor)) ||
164 HasMember(SpecialMemberFunctionKind::CopyConstructor) ||
165 HasMember(SpecialMemberFunctionKind::CopyAssignment) ||
166 HasMember(SpecialMemberFunctionKind::MoveConstructor) ||
167 HasMember(SpecialMemberFunctionKind::MoveAssignment);
168
169 bool RequireFive = (!AllowMissingMoveFunctions && RequireThree &&
170 getLangOpts().CPlusPlus11) ||
171 HasMember(SpecialMemberFunctionKind::MoveConstructor) ||
172 HasMember(SpecialMemberFunctionKind::MoveAssignment);
173
174 if (RequireThree) {
175 if (!HasMember(SpecialMemberFunctionKind::DefaultDestructor) &&
176 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor))
177 MissingMembers.push_back(SpecialMemberFunctionKind::Destructor);
178
179 RequireMember(SpecialMemberFunctionKind::CopyConstructor);
180 RequireMember(SpecialMemberFunctionKind::CopyAssignment);
181 }
182
183 if (RequireFive &&
184 !(AllowMissingMoveFunctionsWhenCopyIsDeleted &&
185 (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) &&
186 IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) {
187 assert(RequireThree);
188 RequireMember(SpecialMemberFunctionKind::MoveConstructor);
189 RequireMember(SpecialMemberFunctionKind::MoveAssignment);
190 }
191
192 if (!MissingMembers.empty()) {
193 llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds;
194 llvm::transform(DefinedMembers, std::back_inserter(DefinedMemberKinds),
195 [](const auto &Data) { return Data.FunctionKind; });
196 diag(ID.first, "class '%0' defines %1 but does not define %2")
197 << ID.second << cppcoreguidelines::join(DefinedMemberKinds, " and ")
198 << cppcoreguidelines::join(MissingMembers, " or ");
199 }
200 }
201
202 } // namespace cppcoreguidelines
203 } // namespace tidy
204 } // namespace clang
205