clang-tools 22.0.0git
SpecialMemberFunctionsCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
20
21namespace {
22AST_MATCHER(CXXRecordDecl, isInMacro) {
23 return Node.getBeginLoc().isMacroID() && Node.getEndLoc().isMacroID();
24}
25} // namespace
26
28 StringRef Name, ClangTidyContext *Context)
29 : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get(
30 "AllowMissingMoveFunctions", false)),
31 AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)),
32 AllowMissingMoveFunctionsWhenCopyIsDeleted(
33 Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)),
34 AllowImplicitlyDeletedCopyOrMove(
35 Options.get("AllowImplicitlyDeletedCopyOrMove", false)),
36 IgnoreMacros(Options.get("IgnoreMacros", true)) {}
37
40 Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions);
41 Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor);
42 Options.store(Opts, "AllowMissingMoveFunctionsWhenCopyIsDeleted",
43 AllowMissingMoveFunctionsWhenCopyIsDeleted);
44 Options.store(Opts, "AllowImplicitlyDeletedCopyOrMove",
45 AllowImplicitlyDeletedCopyOrMove);
46 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
47}
48
49std::optional<TraversalKind>
51 return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs
52 : TK_IgnoreUnlessSpelledInSource;
53}
54
56 const auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted());
57 const ast_matchers::internal::Matcher<CXXRecordDecl> Anything = anything();
58
59 Finder->addMatcher(
60 cxxRecordDecl(
61 unless(isImplicit()), IgnoreMacros ? unless(isInMacro()) : Anything,
62 eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")),
63 has(cxxConstructorDecl(isCopyConstructor(),
64 IsNotImplicitOrDeleted)
65 .bind("copy-ctor")),
66 has(cxxMethodDecl(isCopyAssignmentOperator(),
67 IsNotImplicitOrDeleted)
68 .bind("copy-assign")),
69 has(cxxConstructorDecl(isMoveConstructor(),
70 IsNotImplicitOrDeleted)
71 .bind("move-ctor")),
72 has(cxxMethodDecl(isMoveAssignmentOperator(),
73 IsNotImplicitOrDeleted)
74 .bind("move-assign"))))
75 .bind("class-def"),
76 this);
77}
78
79static llvm::StringRef
81 switch (K) {
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";
91 return "a copy constructor";
93 return "a copy assignment operator";
95 return "a move constructor";
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 assert(!SMFS.empty() &&
106 "List of defined or undefined members should never be empty.");
107 std::string Buffer;
108 llvm::raw_string_ostream Stream(Buffer);
109
110 Stream << toString(SMFS[0]);
111 const size_t LastIndex = SMFS.size() - 1;
112 for (size_t I = 1; I < LastIndex; ++I) {
113 Stream << ", " << toString(SMFS[I]);
114 }
115 if (LastIndex != 0) {
116 Stream << AndOr << toString(SMFS[LastIndex]);
117 }
118 return Stream.str();
119}
120
122 const MatchFinder::MatchResult &Result) {
123 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("class-def");
124 if (!MatchedDecl)
125 return;
126
127 ClassDefId ID(MatchedDecl->getLocation(),
128 std::string(MatchedDecl->getName()));
129
130 auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) {
132 ClassWithSpecialMembers[ID];
133 if (!llvm::is_contained(Members, Data))
134 Members.push_back(std::move(Data));
135 };
136
137 if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) {
138 SpecialMemberFunctionKind DestructorType =
140 if (Dtor->isDefined()) {
141 DestructorType = Dtor->getDefinition()->isDefaulted()
144 }
145 StoreMember({DestructorType, Dtor->isDeleted()});
146 }
147
148 const std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>>
149 Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor},
153
154 for (const auto &KV : Matchers)
155 if (const auto *MethodDecl =
156 Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) {
157 StoreMember(
158 {KV.second, MethodDecl->isDeleted(), MethodDecl->isImplicit()});
159 }
160}
161
163 for (const auto &C : ClassWithSpecialMembers) {
164 checkForMissingMembers(C.first, C.second);
165 }
166}
167
168void SpecialMemberFunctionsCheck::checkForMissingMembers(
169 const ClassDefId &ID,
170 llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) {
171 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers;
172
173 auto HasMember = [&](SpecialMemberFunctionKind Kind) {
174 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
175 return Data.FunctionKind == Kind && !Data.IsImplicit;
176 });
177 };
178
179 auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) {
180 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
181 return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted;
182 });
183 };
184
185 auto IsDeleted = [&](SpecialMemberFunctionKind Kind) {
186 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
187 return Data.FunctionKind == Kind && Data.IsDeleted;
188 });
189 };
190
191 auto RequireMembers = [&](SpecialMemberFunctionKind Kind1,
193 if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) &&
194 HasImplicitDeletedMember(Kind2))
195 return;
196
197 if (!HasMember(Kind1))
198 MissingMembers.push_back(Kind1);
199
200 if (!HasMember(Kind2))
201 MissingMembers.push_back(Kind2);
202 };
203
204 const bool RequireThree =
206 (!AllowSoleDefaultDtor &&
213
214 const bool RequireFive =
215 (!AllowMissingMoveFunctions && RequireThree &&
216 getLangOpts().CPlusPlus11) ||
219
220 if (RequireThree) {
224 MissingMembers.push_back(SpecialMemberFunctionKind::Destructor);
225
228 }
229
230 if (RequireFive &&
231 !(AllowMissingMoveFunctionsWhenCopyIsDeleted &&
234 assert(RequireThree);
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(Data.FunctionKind);
244 }
245 diag(ID.first, "class '%0' defines %1 but does not define %2")
246 << ID.second << cppcoreguidelines::join(DefinedMemberKinds, " and ")
247 << cppcoreguidelines::join(MissingMembers, " or ");
248 }
249}
250
251} // namespace clang::tidy::cppcoreguidelines
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
std::optional< TraversalKind > getCheckTraversalKind() const override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
llvm::StringMap< ClangTidyValue > OptionMap