1 //===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "SpecialMemberFunctionsCheck.h" 11 12 #include "clang/AST/ASTContext.h" 13 #include "clang/ASTMatchers/ASTMatchFinder.h" 14 #include "llvm/ADT/DenseMapInfo.h" 15 #include "llvm/ADT/StringExtras.h" 16 17 #define DEBUG_TYPE "clang-tidy" 18 19 using namespace clang::ast_matchers; 20 21 namespace clang { 22 namespace tidy { 23 namespace cppcoreguidelines { 24 25 SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( 26 StringRef Name, ClangTidyContext *Context) 27 : ClangTidyCheck(Name, Context), 28 AllowMissingMoveFunctions(Options.get("AllowMissingMoveFunctions", 0)), 29 AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", 0)) {} 30 31 void SpecialMemberFunctionsCheck::storeOptions( 32 ClangTidyOptions::OptionMap &Opts) { 33 Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions); 34 Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor); 35 } 36 37 void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { 38 if (!getLangOpts().CPlusPlus) 39 return; 40 Finder->addMatcher( 41 cxxRecordDecl( 42 eachOf( 43 has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), 44 has(cxxConstructorDecl(isCopyConstructor(), unless(isImplicit())) 45 .bind("copy-ctor")), 46 has(cxxMethodDecl(isCopyAssignmentOperator(), 47 unless(isImplicit())) 48 .bind("copy-assign")), 49 has(cxxConstructorDecl(isMoveConstructor(), unless(isImplicit())) 50 .bind("move-ctor")), 51 has(cxxMethodDecl(isMoveAssignmentOperator(), 52 unless(isImplicit())) 53 .bind("move-assign")))) 54 .bind("class-def"), 55 this); 56 } 57 58 static llvm::StringRef 59 toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) { 60 switch (K) { 61 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor: 62 return "a destructor"; 63 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: 64 DefaultDestructor: 65 return "a default destructor"; 66 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: 67 NonDefaultDestructor: 68 return "a non-default destructor"; 69 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor: 70 return "a copy constructor"; 71 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment: 72 return "a copy assignment operator"; 73 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor: 74 return "a move constructor"; 75 case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment: 76 return "a move assignment operator"; 77 } 78 llvm_unreachable("Unhandled SpecialMemberFunctionKind"); 79 } 80 81 static std::string 82 join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS, 83 llvm::StringRef AndOr) { 84 85 assert(!SMFS.empty() && 86 "List of defined or undefined members should never be empty."); 87 std::string Buffer; 88 llvm::raw_string_ostream Stream(Buffer); 89 90 Stream << toString(SMFS[0]); 91 size_t LastIndex = SMFS.size() - 1; 92 for (size_t i = 1; i < LastIndex; ++i) { 93 Stream << ", " << toString(SMFS[i]); 94 } 95 if (LastIndex != 0) { 96 Stream << AndOr << toString(SMFS[LastIndex]); 97 } 98 return Stream.str(); 99 } 100 101 void SpecialMemberFunctionsCheck::check( 102 const MatchFinder::MatchResult &Result) { 103 const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("class-def"); 104 if (!MatchedDecl) 105 return; 106 107 ClassDefId ID(MatchedDecl->getLocation(), MatchedDecl->getName()); 108 109 auto StoreMember = [this, &ID](SpecialMemberFunctionKind Kind) { 110 llvm::SmallVectorImpl<SpecialMemberFunctionKind> &Members = 111 ClassWithSpecialMembers[ID]; 112 if (!llvm::is_contained(Members, Kind)) 113 Members.push_back(Kind); 114 }; 115 116 if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) { 117 StoreMember(Dtor->isDefaulted() 118 ? SpecialMemberFunctionKind::DefaultDestructor 119 : SpecialMemberFunctionKind::NonDefaultDestructor); 120 } 121 122 std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>> 123 Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor}, 124 {"copy-assign", SpecialMemberFunctionKind::CopyAssignment}, 125 {"move-ctor", SpecialMemberFunctionKind::MoveConstructor}, 126 {"move-assign", SpecialMemberFunctionKind::MoveAssignment}}; 127 128 for (const auto &KV : Matchers) 129 if (Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) { 130 StoreMember(KV.second); 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<SpecialMemberFunctionKind> DefinedMembers) { 143 llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers; 144 145 auto HasMember = [&](SpecialMemberFunctionKind Kind) { 146 return llvm::is_contained(DefinedMembers, Kind); 147 }; 148 149 auto RequireMember = [&](SpecialMemberFunctionKind Kind) { 150 if (!HasMember(Kind)) 151 MissingMembers.push_back(Kind); 152 }; 153 154 bool RequireThree = 155 HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || 156 (!AllowSoleDefaultDtor && 157 HasMember(SpecialMemberFunctionKind::DefaultDestructor)) || 158 HasMember(SpecialMemberFunctionKind::CopyConstructor) || 159 HasMember(SpecialMemberFunctionKind::CopyAssignment) || 160 HasMember(SpecialMemberFunctionKind::MoveConstructor) || 161 HasMember(SpecialMemberFunctionKind::MoveAssignment); 162 163 bool RequireFive = (!AllowMissingMoveFunctions && RequireThree && 164 getLangOpts().CPlusPlus11) || 165 HasMember(SpecialMemberFunctionKind::MoveConstructor) || 166 HasMember(SpecialMemberFunctionKind::MoveAssignment); 167 168 if (RequireThree) { 169 if (!HasMember(SpecialMemberFunctionKind::DefaultDestructor) && 170 !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) 171 MissingMembers.push_back(SpecialMemberFunctionKind::Destructor); 172 173 RequireMember(SpecialMemberFunctionKind::CopyConstructor); 174 RequireMember(SpecialMemberFunctionKind::CopyAssignment); 175 } 176 177 if (RequireFive) { 178 assert(RequireThree); 179 RequireMember(SpecialMemberFunctionKind::MoveConstructor); 180 RequireMember(SpecialMemberFunctionKind::MoveAssignment); 181 } 182 183 if (!MissingMembers.empty()) 184 diag(ID.first, "class '%0' defines %1 but does not define %2") 185 << ID.second << join(DefinedMembers, " and ") 186 << join(MissingMembers, " or "); 187 } 188 189 } // namespace cppcoreguidelines 190 } // namespace tidy 191 } // namespace clang 192