1 //===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "SpecialMemberFunctionsCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "llvm/ADT/DenseMapInfo.h"
14 #include "llvm/ADT/StringExtras.h"
16 #define DEBUG_TYPE "clang-tidy"
18 using namespace clang::ast_matchers
;
20 namespace clang::tidy::cppcoreguidelines
{
22 SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck(
23 StringRef Name
, ClangTidyContext
*Context
)
24 : ClangTidyCheck(Name
, Context
), AllowMissingMoveFunctions(Options
.get(
25 "AllowMissingMoveFunctions", false)),
26 AllowSoleDefaultDtor(Options
.get("AllowSoleDefaultDtor", false)),
27 AllowMissingMoveFunctionsWhenCopyIsDeleted(
28 Options
.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {}
30 void SpecialMemberFunctionsCheck::storeOptions(
31 ClangTidyOptions::OptionMap
&Opts
) {
32 Options
.store(Opts
, "AllowMissingMoveFunctions", AllowMissingMoveFunctions
);
33 Options
.store(Opts
, "AllowSoleDefaultDtor", AllowSoleDefaultDtor
);
34 Options
.store(Opts
, "AllowMissingMoveFunctionsWhenCopyIsDeleted",
35 AllowMissingMoveFunctionsWhenCopyIsDeleted
);
38 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder
*Finder
) {
41 eachOf(has(cxxDestructorDecl().bind("dtor")),
42 has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")),
43 has(cxxMethodDecl(isCopyAssignmentOperator())
44 .bind("copy-assign")),
45 has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")),
46 has(cxxMethodDecl(isMoveAssignmentOperator())
47 .bind("move-assign"))))
52 static llvm::StringRef
53 toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K
) {
55 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor
:
56 return "a destructor";
57 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
59 return "a default destructor";
60 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
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";
72 llvm_unreachable("Unhandled SpecialMemberFunctionKind");
76 join(ArrayRef
<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind
> SMFS
,
77 llvm::StringRef AndOr
) {
79 assert(!SMFS
.empty() &&
80 "List of defined or undefined members should never be empty.");
82 llvm::raw_string_ostream
Stream(Buffer
);
84 Stream
<< toString(SMFS
[0]);
85 size_t LastIndex
= SMFS
.size() - 1;
86 for (size_t I
= 1; I
< LastIndex
; ++I
) {
87 Stream
<< ", " << toString(SMFS
[I
]);
90 Stream
<< AndOr
<< toString(SMFS
[LastIndex
]);
95 void SpecialMemberFunctionsCheck::check(
96 const MatchFinder::MatchResult
&Result
) {
97 const auto *MatchedDecl
= Result
.Nodes
.getNodeAs
<CXXRecordDecl
>("class-def");
101 ClassDefId
ID(MatchedDecl
->getLocation(), std::string(MatchedDecl
->getName()));
103 auto StoreMember
= [this, &ID
](SpecialMemberFunctionData Data
) {
104 llvm::SmallVectorImpl
<SpecialMemberFunctionData
> &Members
=
105 ClassWithSpecialMembers
[ID
];
106 if (!llvm::is_contained(Members
, Data
))
107 Members
.push_back(std::move(Data
));
110 if (const auto *Dtor
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("dtor")) {
111 SpecialMemberFunctionKind DestructorType
=
112 SpecialMemberFunctionKind::Destructor
;
113 if (Dtor
->isDefined()) {
114 DestructorType
= Dtor
->getDefinition()->isDefaulted()
115 ? SpecialMemberFunctionKind::DefaultDestructor
116 : SpecialMemberFunctionKind::NonDefaultDestructor
;
118 StoreMember({DestructorType
, Dtor
->isDeleted()});
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
}};
127 for (const auto &KV
: Matchers
)
128 if (const auto *MethodDecl
=
129 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>(KV
.first
)) {
130 StoreMember({KV
.second
, MethodDecl
->isDeleted()});
134 void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() {
135 for (const auto &C
: ClassWithSpecialMembers
) {
136 checkForMissingMembers(C
.first
, C
.second
);
140 void SpecialMemberFunctionsCheck::checkForMissingMembers(
141 const ClassDefId
&ID
,
142 llvm::ArrayRef
<SpecialMemberFunctionData
> DefinedMembers
) {
143 llvm::SmallVector
<SpecialMemberFunctionKind
, 5> MissingMembers
;
145 auto HasMember
= [&](SpecialMemberFunctionKind Kind
) {
146 return llvm::any_of(DefinedMembers
, [Kind
](const auto &Data
) {
147 return Data
.FunctionKind
== Kind
;
151 auto IsDeleted
= [&](SpecialMemberFunctionKind Kind
) {
152 return llvm::any_of(DefinedMembers
, [Kind
](const auto &Data
) {
153 return Data
.FunctionKind
== Kind
&& Data
.IsDeleted
;
157 auto RequireMember
= [&](SpecialMemberFunctionKind Kind
) {
158 if (!HasMember(Kind
))
159 MissingMembers
.push_back(Kind
);
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
);
172 bool RequireFive
= (!AllowMissingMoveFunctions
&& RequireThree
&&
173 getLangOpts().CPlusPlus11
) ||
174 HasMember(SpecialMemberFunctionKind::MoveConstructor
) ||
175 HasMember(SpecialMemberFunctionKind::MoveAssignment
);
178 if (!HasMember(SpecialMemberFunctionKind::Destructor
) &&
179 !HasMember(SpecialMemberFunctionKind::DefaultDestructor
) &&
180 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor
))
181 MissingMembers
.push_back(SpecialMemberFunctionKind::Destructor
);
183 RequireMember(SpecialMemberFunctionKind::CopyConstructor
);
184 RequireMember(SpecialMemberFunctionKind::CopyAssignment
);
188 !(AllowMissingMoveFunctionsWhenCopyIsDeleted
&&
189 (IsDeleted(SpecialMemberFunctionKind::CopyConstructor
) &&
190 IsDeleted(SpecialMemberFunctionKind::CopyAssignment
)))) {
191 assert(RequireThree
);
192 RequireMember(SpecialMemberFunctionKind::MoveConstructor
);
193 RequireMember(SpecialMemberFunctionKind::MoveAssignment
);
196 if (!MissingMembers
.empty()) {
197 llvm::SmallVector
<SpecialMemberFunctionKind
, 5> DefinedMemberKinds
;
198 llvm::transform(DefinedMembers
, std::back_inserter(DefinedMemberKinds
),
199 [](const auto &Data
) { return Data
.FunctionKind
; });
200 diag(ID
.first
, "class '%0' defines %1 but does not define %2")
201 << ID
.second
<< cppcoreguidelines::join(DefinedMemberKinds
, " and ")
202 << cppcoreguidelines::join(MissingMembers
, " or ");
206 } // namespace clang::tidy::cppcoreguidelines