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