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