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/StringExtras.h"
14
15#define DEBUG_TYPE "clang-tidy"
16
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::cppcoreguidelines {
20
21namespace {
22AST_MATCHER(CXXRecordDecl, isInMacro) {
23 return Node.getBeginLoc().isMacroID() && Node.getEndLoc().isMacroID();
24}
25} // namespace
26
27SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck(
28 StringRef Name, ClangTidyContext *Context)
29 : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get(
30 LocalName: "AllowMissingMoveFunctions", Default: false)),
31 AllowSoleDefaultDtor(Options.get(LocalName: "AllowSoleDefaultDtor", Default: false)),
32 AllowMissingMoveFunctionsWhenCopyIsDeleted(
33 Options.get(LocalName: "AllowMissingMoveFunctionsWhenCopyIsDeleted", Default: false)),
34 AllowImplicitlyDeletedCopyOrMove(
35 Options.get(LocalName: "AllowImplicitlyDeletedCopyOrMove", Default: false)),
36 IgnoreMacros(Options.get(LocalName: "IgnoreMacros", Default: true)) {}
37
38void SpecialMemberFunctionsCheck::storeOptions(
39 ClangTidyOptions::OptionMap &Opts) {
40 Options.store(Options&: Opts, LocalName: "AllowMissingMoveFunctions", Value: AllowMissingMoveFunctions);
41 Options.store(Options&: Opts, LocalName: "AllowSoleDefaultDtor", Value: AllowSoleDefaultDtor);
42 Options.store(Options&: Opts, LocalName: "AllowMissingMoveFunctionsWhenCopyIsDeleted",
43 Value: AllowMissingMoveFunctionsWhenCopyIsDeleted);
44 Options.store(Options&: Opts, LocalName: "AllowImplicitlyDeletedCopyOrMove",
45 Value: AllowImplicitlyDeletedCopyOrMove);
46 Options.store(Options&: Opts, LocalName: "IgnoreMacros", Value: IgnoreMacros);
47}
48
49std::optional<TraversalKind>
50SpecialMemberFunctionsCheck::getCheckTraversalKind() const {
51 return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs
52 : TK_IgnoreUnlessSpelledInSource;
53}
54
55void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) {
56 const auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted());
57 const ast_matchers::internal::Matcher<CXXRecordDecl> Anything = anything();
58
59 Finder->addMatcher(
60 NodeMatch: cxxRecordDecl(
61 unless(isImplicit()), IgnoreMacros ? unless(isInMacro()) : Anything,
62 eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind(ID: "dtor")),
63 has(cxxConstructorDecl(isCopyConstructor(),
64 IsNotImplicitOrDeleted)
65 .bind(ID: "copy-ctor")),
66 has(cxxMethodDecl(isCopyAssignmentOperator(),
67 IsNotImplicitOrDeleted)
68 .bind(ID: "copy-assign")),
69 has(cxxConstructorDecl(isMoveConstructor(),
70 IsNotImplicitOrDeleted)
71 .bind(ID: "move-ctor")),
72 has(cxxMethodDecl(isMoveAssignmentOperator(),
73 IsNotImplicitOrDeleted)
74 .bind(ID: "move-assign"))))
75 .bind(ID: "class-def"),
76 Action: this);
77}
78
79static llvm::StringRef
80toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) {
81 switch (K) {
82 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor:
83 return "a destructor";
84 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
85 DefaultDestructor:
86 return "a default destructor";
87 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
88 NonDefaultDestructor:
89 return "a non-default destructor";
90 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor:
91 return "a copy constructor";
92 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment:
93 return "a copy assignment operator";
94 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor:
95 return "a move constructor";
96 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment:
97 return "a move assignment operator";
98 }
99 llvm_unreachable("Unhandled SpecialMemberFunctionKind");
100}
101
102static std::string
103join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS,
104 llvm::StringRef AndOr) {
105
106 assert(!SMFS.empty() &&
107 "List of defined or undefined members should never be empty.");
108 std::string Buffer;
109 llvm::raw_string_ostream Stream(Buffer);
110
111 Stream << toString(K: SMFS[0]);
112 size_t LastIndex = SMFS.size() - 1;
113 for (size_t I = 1; I < LastIndex; ++I) {
114 Stream << ", " << toString(K: SMFS[I]);
115 }
116 if (LastIndex != 0) {
117 Stream << AndOr << toString(K: SMFS[LastIndex]);
118 }
119 return Stream.str();
120}
121
122void SpecialMemberFunctionsCheck::check(
123 const MatchFinder::MatchResult &Result) {
124 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "class-def");
125 if (!MatchedDecl)
126 return;
127
128 ClassDefId ID(MatchedDecl->getLocation(),
129 std::string(MatchedDecl->getName()));
130
131 auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) {
132 llvm::SmallVectorImpl<SpecialMemberFunctionData> &Members =
133 ClassWithSpecialMembers[ID];
134 if (!llvm::is_contained(Range&: Members, Element: Data))
135 Members.push_back(Elt: std::move(Data));
136 };
137
138 if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "dtor")) {
139 SpecialMemberFunctionKind DestructorType =
140 SpecialMemberFunctionKind::Destructor;
141 if (Dtor->isDefined()) {
142 DestructorType = Dtor->getDefinition()->isDefaulted()
143 ? SpecialMemberFunctionKind::DefaultDestructor
144 : SpecialMemberFunctionKind::NonDefaultDestructor;
145 }
146 StoreMember({.FunctionKind: DestructorType, .IsDeleted: Dtor->isDeleted()});
147 }
148
149 std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>>
150 Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor},
151 {"copy-assign", SpecialMemberFunctionKind::CopyAssignment},
152 {"move-ctor", SpecialMemberFunctionKind::MoveConstructor},
153 {"move-assign", SpecialMemberFunctionKind::MoveAssignment}};
154
155 for (const auto &KV : Matchers)
156 if (const auto *MethodDecl =
157 Result.Nodes.getNodeAs<CXXMethodDecl>(ID: KV.first)) {
158 StoreMember(
159 {.FunctionKind: KV.second, .IsDeleted: MethodDecl->isDeleted(), .IsImplicit: MethodDecl->isImplicit()});
160 }
161}
162
163void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() {
164 for (const auto &C : ClassWithSpecialMembers) {
165 checkForMissingMembers(ID: C.first, DefinedMembers: C.second);
166 }
167}
168
169void SpecialMemberFunctionsCheck::checkForMissingMembers(
170 const ClassDefId &ID,
171 llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) {
172 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers;
173
174 auto HasMember = [&](SpecialMemberFunctionKind Kind) {
175 return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) {
176 return Data.FunctionKind == Kind && !Data.IsImplicit;
177 });
178 };
179
180 auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) {
181 return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) {
182 return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted;
183 });
184 };
185
186 auto IsDeleted = [&](SpecialMemberFunctionKind Kind) {
187 return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) {
188 return Data.FunctionKind == Kind && Data.IsDeleted;
189 });
190 };
191
192 auto RequireMembers = [&](SpecialMemberFunctionKind Kind1,
193 SpecialMemberFunctionKind Kind2) {
194 if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) &&
195 HasImplicitDeletedMember(Kind2))
196 return;
197
198 if (!HasMember(Kind1))
199 MissingMembers.push_back(Elt: Kind1);
200
201 if (!HasMember(Kind2))
202 MissingMembers.push_back(Elt: Kind2);
203 };
204
205 bool RequireThree =
206 HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) ||
207 (!AllowSoleDefaultDtor &&
208 (HasMember(SpecialMemberFunctionKind::Destructor) ||
209 HasMember(SpecialMemberFunctionKind::DefaultDestructor))) ||
210 HasMember(SpecialMemberFunctionKind::CopyConstructor) ||
211 HasMember(SpecialMemberFunctionKind::CopyAssignment) ||
212 HasMember(SpecialMemberFunctionKind::MoveConstructor) ||
213 HasMember(SpecialMemberFunctionKind::MoveAssignment);
214
215 bool RequireFive = (!AllowMissingMoveFunctions && RequireThree &&
216 getLangOpts().CPlusPlus11) ||
217 HasMember(SpecialMemberFunctionKind::MoveConstructor) ||
218 HasMember(SpecialMemberFunctionKind::MoveAssignment);
219
220 if (RequireThree) {
221 if (!HasMember(SpecialMemberFunctionKind::Destructor) &&
222 !HasMember(SpecialMemberFunctionKind::DefaultDestructor) &&
223 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor))
224 MissingMembers.push_back(Elt: SpecialMemberFunctionKind::Destructor);
225
226 RequireMembers(SpecialMemberFunctionKind::CopyConstructor,
227 SpecialMemberFunctionKind::CopyAssignment);
228 }
229
230 if (RequireFive &&
231 !(AllowMissingMoveFunctionsWhenCopyIsDeleted &&
232 (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) &&
233 IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) {
234 assert(RequireThree);
235 RequireMembers(SpecialMemberFunctionKind::MoveConstructor,
236 SpecialMemberFunctionKind::MoveAssignment);
237 }
238
239 if (!MissingMembers.empty()) {
240 llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds;
241 for (const auto &Data : DefinedMembers) {
242 if (!Data.IsImplicit)
243 DefinedMemberKinds.push_back(Elt: Data.FunctionKind);
244 }
245 diag(Loc: ID.first, Description: "class '%0' defines %1 but does not define %2")
246 << ID.second << cppcoreguidelines::join(SMFS: DefinedMemberKinds, AndOr: " and ")
247 << cppcoreguidelines::join(SMFS: MissingMembers, AndOr: " or ");
248 }
249}
250
251} // namespace clang::tidy::cppcoreguidelines
252

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp