11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "llvm/ADT/StringExtras.h"
15#define DEBUG_TYPE "clang-tidy"
22AST_MATCHER(CXXRecordDecl, isInMacro) {
23 return Node.getBeginLoc().isMacroID() && Node.getEndLoc().isMacroID();
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)) {}
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);
49std::optional<TraversalKind>
51 return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs
52 : TK_IgnoreUnlessSpelledInSource;
56 const auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted());
57 const ast_matchers::internal::Matcher<CXXRecordDecl> Anything = anything();
61 unless(isImplicit()), IgnoreMacros ? unless(isInMacro()) : Anything,
62 eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind(
"dtor")),
63 has(cxxConstructorDecl(isCopyConstructor(),
64 IsNotImplicitOrDeleted)
66 has(cxxMethodDecl(isCopyAssignmentOperator(),
67 IsNotImplicitOrDeleted)
68 .bind(
"copy-assign")),
69 has(cxxConstructorDecl(isMoveConstructor(),
70 IsNotImplicitOrDeleted)
72 has(cxxMethodDecl(isMoveAssignmentOperator(),
73 IsNotImplicitOrDeleted)
74 .bind(
"move-assign"))))
83 return "a destructor";
84 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
86 return "a default destructor";
87 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::
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";
99 llvm_unreachable(
"Unhandled SpecialMemberFunctionKind");
103join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS,
104 llvm::StringRef AndOr) {
105 assert(!SMFS.empty() &&
106 "List of defined or undefined members should never be empty.");
108 llvm::raw_string_ostream Stream(Buffer);
111 const size_t LastIndex = SMFS.size() - 1;
112 for (
size_t I = 1; I < LastIndex; ++I)
113 Stream <<
", " <<
toString(SMFS[I]);
115 Stream << AndOr <<
toString(SMFS[LastIndex]);
120 const MatchFinder::MatchResult &Result) {
121 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>(
"class-def");
126 std::string(MatchedDecl->getName()));
130 ClassWithSpecialMembers[ID];
131 if (!llvm::is_contained(Members, Data))
132 Members.push_back(std::move(Data));
135 if (
const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>(
"dtor")) {
138 if (Dtor->isDefined()) {
139 DestructorType = Dtor->getDefinition()->isDefaulted()
143 StoreMember({DestructorType, Dtor->isDeleted()});
146 const std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>>
152 for (
const auto &KV : Matchers)
153 if (
const auto *MethodDecl =
154 Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) {
156 {KV.second, MethodDecl->isDeleted(), MethodDecl->isImplicit()});
161 for (
const auto &C : ClassWithSpecialMembers)
162 checkForMissingMembers(C.first, C.second);
165void SpecialMemberFunctionsCheck::checkForMissingMembers(
166 const ClassDefId &ID,
167 llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) {
168 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers;
170 auto HasMember = [&](SpecialMemberFunctionKind Kind) {
171 return llvm::any_of(DefinedMembers, [Kind](
const auto &Data) {
172 return Data.FunctionKind == Kind && !Data.IsImplicit;
176 auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) {
177 return llvm::any_of(DefinedMembers, [Kind](
const auto &Data) {
178 return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted;
183 return llvm::any_of(DefinedMembers, [Kind](
const auto &Data) {
184 return Data.FunctionKind == Kind && Data.IsDeleted;
190 if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) &&
191 HasImplicitDeletedMember(Kind2))
194 if (!HasMember(Kind1))
195 MissingMembers.push_back(Kind1);
197 if (!HasMember(Kind2))
198 MissingMembers.push_back(Kind2);
201 const bool RequireThree =
203 (!AllowSoleDefaultDtor &&
211 const bool RequireFive =
212 (!AllowMissingMoveFunctions && RequireThree &&
213 getLangOpts().CPlusPlus11) ||
228 !(AllowMissingMoveFunctionsWhenCopyIsDeleted &&
231 assert(RequireThree);
236 if (!MissingMembers.empty()) {
237 llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds;
238 for (
const auto &Data : DefinedMembers)
239 if (!Data.IsImplicit)
240 DefinedMemberKinds.push_back(Data.FunctionKind);
241 diag(ID.first,
"class '%0' defines %1 but does not define %2")
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void onEndOfTranslationUnit() override
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
std::pair< SourceLocation, std::string > ClassDefId
SpecialMemberFunctionKind
SpecialMemberFunctionsCheck(StringRef Name, ClangTidyContext *Context)
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
llvm::StringMap< ClangTidyValue > OptionMap