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)),
29 AllowImplicitlyDeletedCopyOrMove(
30 Options
.get("AllowImplicitlyDeletedCopyOrMove", false)) {}
32 void SpecialMemberFunctionsCheck::storeOptions(
33 ClangTidyOptions::OptionMap
&Opts
) {
34 Options
.store(Opts
, "AllowMissingMoveFunctions", AllowMissingMoveFunctions
);
35 Options
.store(Opts
, "AllowSoleDefaultDtor", AllowSoleDefaultDtor
);
36 Options
.store(Opts
, "AllowMissingMoveFunctionsWhenCopyIsDeleted",
37 AllowMissingMoveFunctionsWhenCopyIsDeleted
);
38 Options
.store(Opts
, "AllowImplicitlyDeletedCopyOrMove",
39 AllowImplicitlyDeletedCopyOrMove
);
42 std::optional
<TraversalKind
>
43 SpecialMemberFunctionsCheck::getCheckTraversalKind() const {
44 return AllowImplicitlyDeletedCopyOrMove
? TK_AsIs
45 : TK_IgnoreUnlessSpelledInSource
;
48 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder
*Finder
) {
49 auto IsNotImplicitOrDeleted
= anyOf(unless(isImplicit()), isDeleted());
54 eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")),
55 has(cxxConstructorDecl(isCopyConstructor(),
56 IsNotImplicitOrDeleted
)
58 has(cxxMethodDecl(isCopyAssignmentOperator(),
59 IsNotImplicitOrDeleted
)
60 .bind("copy-assign")),
61 has(cxxConstructorDecl(isMoveConstructor(),
62 IsNotImplicitOrDeleted
)
64 has(cxxMethodDecl(isMoveAssignmentOperator(),
65 IsNotImplicitOrDeleted
)
66 .bind("move-assign"))))
71 static llvm::StringRef
72 toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K
) {
74 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor
:
75 return "a destructor";
76 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
78 return "a default destructor";
79 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
81 return "a non-default destructor";
82 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor
:
83 return "a copy constructor";
84 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment
:
85 return "a copy assignment operator";
86 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor
:
87 return "a move constructor";
88 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment
:
89 return "a move assignment operator";
91 llvm_unreachable("Unhandled SpecialMemberFunctionKind");
95 join(ArrayRef
<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind
> SMFS
,
96 llvm::StringRef AndOr
) {
98 assert(!SMFS
.empty() &&
99 "List of defined or undefined members should never be empty.");
101 llvm::raw_string_ostream
Stream(Buffer
);
103 Stream
<< toString(SMFS
[0]);
104 size_t LastIndex
= SMFS
.size() - 1;
105 for (size_t I
= 1; I
< LastIndex
; ++I
) {
106 Stream
<< ", " << toString(SMFS
[I
]);
108 if (LastIndex
!= 0) {
109 Stream
<< AndOr
<< toString(SMFS
[LastIndex
]);
114 void SpecialMemberFunctionsCheck::check(
115 const MatchFinder::MatchResult
&Result
) {
116 const auto *MatchedDecl
= Result
.Nodes
.getNodeAs
<CXXRecordDecl
>("class-def");
120 ClassDefId
ID(MatchedDecl
->getLocation(), std::string(MatchedDecl
->getName()));
122 auto StoreMember
= [this, &ID
](SpecialMemberFunctionData Data
) {
123 llvm::SmallVectorImpl
<SpecialMemberFunctionData
> &Members
=
124 ClassWithSpecialMembers
[ID
];
125 if (!llvm::is_contained(Members
, Data
))
126 Members
.push_back(std::move(Data
));
129 if (const auto *Dtor
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("dtor")) {
130 SpecialMemberFunctionKind DestructorType
=
131 SpecialMemberFunctionKind::Destructor
;
132 if (Dtor
->isDefined()) {
133 DestructorType
= Dtor
->getDefinition()->isDefaulted()
134 ? SpecialMemberFunctionKind::DefaultDestructor
135 : SpecialMemberFunctionKind::NonDefaultDestructor
;
137 StoreMember({DestructorType
, Dtor
->isDeleted()});
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
}};
146 for (const auto &KV
: Matchers
)
147 if (const auto *MethodDecl
=
148 Result
.Nodes
.getNodeAs
<CXXMethodDecl
>(KV
.first
)) {
150 {KV
.second
, MethodDecl
->isDeleted(), MethodDecl
->isImplicit()});
154 void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() {
155 for (const auto &C
: ClassWithSpecialMembers
) {
156 checkForMissingMembers(C
.first
, C
.second
);
160 void SpecialMemberFunctionsCheck::checkForMissingMembers(
161 const ClassDefId
&ID
,
162 llvm::ArrayRef
<SpecialMemberFunctionData
> DefinedMembers
) {
163 llvm::SmallVector
<SpecialMemberFunctionKind
, 5> MissingMembers
;
165 auto HasMember
= [&](SpecialMemberFunctionKind Kind
) {
166 return llvm::any_of(DefinedMembers
, [Kind
](const auto &Data
) {
167 return Data
.FunctionKind
== Kind
&& !Data
.IsImplicit
;
171 auto HasImplicitDeletedMember
= [&](SpecialMemberFunctionKind Kind
) {
172 return llvm::any_of(DefinedMembers
, [Kind
](const auto &Data
) {
173 return Data
.FunctionKind
== Kind
&& Data
.IsImplicit
&& Data
.IsDeleted
;
177 auto IsDeleted
= [&](SpecialMemberFunctionKind Kind
) {
178 return llvm::any_of(DefinedMembers
, [Kind
](const auto &Data
) {
179 return Data
.FunctionKind
== Kind
&& Data
.IsDeleted
;
183 auto RequireMembers
= [&](SpecialMemberFunctionKind Kind1
,
184 SpecialMemberFunctionKind Kind2
) {
185 if (AllowImplicitlyDeletedCopyOrMove
&& HasImplicitDeletedMember(Kind1
) &&
186 HasImplicitDeletedMember(Kind2
))
189 if (!HasMember(Kind1
))
190 MissingMembers
.push_back(Kind1
);
192 if (!HasMember(Kind2
))
193 MissingMembers
.push_back(Kind2
);
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
);
206 bool RequireFive
= (!AllowMissingMoveFunctions
&& RequireThree
&&
207 getLangOpts().CPlusPlus11
) ||
208 HasMember(SpecialMemberFunctionKind::MoveConstructor
) ||
209 HasMember(SpecialMemberFunctionKind::MoveAssignment
);
212 if (!HasMember(SpecialMemberFunctionKind::Destructor
) &&
213 !HasMember(SpecialMemberFunctionKind::DefaultDestructor
) &&
214 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor
))
215 MissingMembers
.push_back(SpecialMemberFunctionKind::Destructor
);
217 RequireMembers(SpecialMemberFunctionKind::CopyConstructor
,
218 SpecialMemberFunctionKind::CopyAssignment
);
222 !(AllowMissingMoveFunctionsWhenCopyIsDeleted
&&
223 (IsDeleted(SpecialMemberFunctionKind::CopyConstructor
) &&
224 IsDeleted(SpecialMemberFunctionKind::CopyAssignment
)))) {
225 assert(RequireThree
);
226 RequireMembers(SpecialMemberFunctionKind::MoveConstructor
,
227 SpecialMemberFunctionKind::MoveAssignment
);
230 if (!MissingMembers
.empty()) {
231 llvm::SmallVector
<SpecialMemberFunctionKind
, 5> DefinedMemberKinds
;
232 for (const auto &Data
: DefinedMembers
) {
233 if (!Data
.IsImplicit
)
234 DefinedMemberKinds
.push_back(Data
.FunctionKind
);
236 diag(ID
.first
, "class '%0' defines %1 but does not define %2")
237 << ID
.second
<< cppcoreguidelines::join(DefinedMemberKinds
, " and ")
238 << cppcoreguidelines::join(MissingMembers
, " or ");
242 } // namespace clang::tidy::cppcoreguidelines