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