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 "AllowMissingMoveFunctions", false)), 26 AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)), 27 AllowMissingMoveFunctionsWhenCopyIsDeleted( 28 Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {} 29 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); 36 } 37 38 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { 39 Finder->addMatcher( 40 cxxRecordDecl( 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")))) 48 .bind("class-def"), 49 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(SMFS[0]); 85 size_t LastIndex = SMFS.size() - 1; 86 for (size_t I = 1; I < LastIndex; ++I) { 87 Stream << ", " << toString(SMFS[I]); 88 } 89 if (LastIndex != 0) { 90 Stream << AndOr << toString(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>("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(Members, Data)) 107 Members.push_back(std::move(Data)); 108 }; 109 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; 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>(KV.first)) { 130 StoreMember({KV.second, MethodDecl->isDeleted()}); 131 } 132 } 133 134 void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { 135 for (const auto &C : ClassWithSpecialMembers) { 136 checkForMissingMembers(C.first, 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(DefinedMembers, [Kind](const auto &Data) { 147 return Data.FunctionKind == Kind; 148 }); 149 }; 150 151 auto IsDeleted = [&](SpecialMemberFunctionKind Kind) { 152 return llvm::any_of(DefinedMembers, [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(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(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(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 "); 203 } 204 } 205 206 } // namespace clang::tidy::cppcoreguidelines 207