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 { 21 namespace tidy { 22 namespace cppcoreguidelines { 23 24 SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( 25 StringRef Name, ClangTidyContext *Context) 26 : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get( 27 "AllowMissingMoveFunctions", false)), 28 AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)), 29 AllowMissingMoveFunctionsWhenCopyIsDeleted( 30 Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {} 31 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 } 39 40 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { 41 Finder->addMatcher( 42 cxxRecordDecl( 43 eachOf(has(cxxDestructorDecl().bind("dtor")), 44 has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")), 45 has(cxxMethodDecl(isCopyAssignmentOperator()) 46 .bind("copy-assign")), 47 has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")), 48 has(cxxMethodDecl(isMoveAssignmentOperator()) 49 .bind("move-assign")))) 50 .bind("class-def"), 51 this); 52 } 53 54 static llvm::StringRef 55 toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) { 56 switch (K) { 57 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor: 58 return "a destructor"; 59 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: 60 DefaultDestructor: 61 return "a default destructor"; 62 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: 63 NonDefaultDestructor: 64 return "a non-default destructor"; 65 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor: 66 return "a copy constructor"; 67 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment: 68 return "a copy assignment operator"; 69 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor: 70 return "a move constructor"; 71 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment: 72 return "a move assignment operator"; 73 } 74 llvm_unreachable("Unhandled SpecialMemberFunctionKind"); 75 } 76 77 static std::string 78 join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS, 79 llvm::StringRef AndOr) { 80 81 assert(!SMFS.empty() && 82 "List of defined or undefined members should never be empty."); 83 std::string Buffer; 84 llvm::raw_string_ostream Stream(Buffer); 85 86 Stream << toString(SMFS[0]); 87 size_t LastIndex = SMFS.size() - 1; 88 for (size_t I = 1; I < LastIndex; ++I) { 89 Stream << ", " << toString(SMFS[I]); 90 } 91 if (LastIndex != 0) { 92 Stream << AndOr << toString(SMFS[LastIndex]); 93 } 94 return Stream.str(); 95 } 96 97 void SpecialMemberFunctionsCheck::check( 98 const MatchFinder::MatchResult &Result) { 99 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("class-def"); 100 if (!MatchedDecl) 101 return; 102 103 ClassDefId ID(MatchedDecl->getLocation(), std::string(MatchedDecl->getName())); 104 105 auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) { 106 llvm::SmallVectorImpl<SpecialMemberFunctionData> &Members = 107 ClassWithSpecialMembers[ID]; 108 if (!llvm::is_contained(Members, Data)) 109 Members.push_back(std::move(Data)); 110 }; 111 112 if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) { 113 StoreMember({Dtor->isDefaulted() 114 ? SpecialMemberFunctionKind::DefaultDestructor 115 : SpecialMemberFunctionKind::NonDefaultDestructor, 116 Dtor->isDeleted()}); 117 } 118 119 std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>> 120 Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor}, 121 {"copy-assign", SpecialMemberFunctionKind::CopyAssignment}, 122 {"move-ctor", SpecialMemberFunctionKind::MoveConstructor}, 123 {"move-assign", SpecialMemberFunctionKind::MoveAssignment}}; 124 125 for (const auto &KV : Matchers) 126 if (const auto *MethodDecl = 127 Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) { 128 StoreMember({KV.second, MethodDecl->isDeleted()}); 129 } 130 } 131 132 void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { 133 for (const auto &C : ClassWithSpecialMembers) { 134 checkForMissingMembers(C.first, C.second); 135 } 136 } 137 138 void SpecialMemberFunctionsCheck::checkForMissingMembers( 139 const ClassDefId &ID, 140 llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) { 141 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers; 142 143 auto HasMember = [&](SpecialMemberFunctionKind Kind) { 144 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { 145 return Data.FunctionKind == Kind; 146 }); 147 }; 148 149 auto IsDeleted = [&](SpecialMemberFunctionKind Kind) { 150 return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { 151 return Data.FunctionKind == Kind && Data.IsDeleted; 152 }); 153 }; 154 155 auto RequireMember = [&](SpecialMemberFunctionKind Kind) { 156 if (!HasMember(Kind)) 157 MissingMembers.push_back(Kind); 158 }; 159 160 bool RequireThree = 161 HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || 162 (!AllowSoleDefaultDtor && 163 HasMember(SpecialMemberFunctionKind::DefaultDestructor)) || 164 HasMember(SpecialMemberFunctionKind::CopyConstructor) || 165 HasMember(SpecialMemberFunctionKind::CopyAssignment) || 166 HasMember(SpecialMemberFunctionKind::MoveConstructor) || 167 HasMember(SpecialMemberFunctionKind::MoveAssignment); 168 169 bool RequireFive = (!AllowMissingMoveFunctions && RequireThree && 170 getLangOpts().CPlusPlus11) || 171 HasMember(SpecialMemberFunctionKind::MoveConstructor) || 172 HasMember(SpecialMemberFunctionKind::MoveAssignment); 173 174 if (RequireThree) { 175 if (!HasMember(SpecialMemberFunctionKind::DefaultDestructor) && 176 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) 177 MissingMembers.push_back(SpecialMemberFunctionKind::Destructor); 178 179 RequireMember(SpecialMemberFunctionKind::CopyConstructor); 180 RequireMember(SpecialMemberFunctionKind::CopyAssignment); 181 } 182 183 if (RequireFive && 184 !(AllowMissingMoveFunctionsWhenCopyIsDeleted && 185 (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) && 186 IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) { 187 assert(RequireThree); 188 RequireMember(SpecialMemberFunctionKind::MoveConstructor); 189 RequireMember(SpecialMemberFunctionKind::MoveAssignment); 190 } 191 192 if (!MissingMembers.empty()) { 193 llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds; 194 llvm::transform(DefinedMembers, std::back_inserter(DefinedMemberKinds), 195 [](const auto &Data) { return Data.FunctionKind; }); 196 diag(ID.first, "class '%0' defines %1 but does not define %2") 197 << ID.second << cppcoreguidelines::join(DefinedMemberKinds, " and ") 198 << cppcoreguidelines::join(MissingMembers, " or "); 199 } 200 } 201 202 } // namespace cppcoreguidelines 203 } // namespace tidy 204 } // namespace clang 205