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