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