| 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/StringExtras.h" |
| 14 | |
| 15 | #define DEBUG_TYPE "clang-tidy" |
| 16 | |
| 17 | using namespace clang::ast_matchers; |
| 18 | |
| 19 | namespace clang::tidy::cppcoreguidelines { |
| 20 | |
| 21 | SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( |
| 22 | StringRef Name, ClangTidyContext *Context) |
| 23 | : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get( |
| 24 | LocalName: "AllowMissingMoveFunctions" , Default: false)), |
| 25 | AllowSoleDefaultDtor(Options.get(LocalName: "AllowSoleDefaultDtor" , Default: false)), |
| 26 | AllowMissingMoveFunctionsWhenCopyIsDeleted( |
| 27 | Options.get(LocalName: "AllowMissingMoveFunctionsWhenCopyIsDeleted" , Default: false)), |
| 28 | AllowImplicitlyDeletedCopyOrMove( |
| 29 | Options.get(LocalName: "AllowImplicitlyDeletedCopyOrMove" , Default: false)) {} |
| 30 | |
| 31 | void SpecialMemberFunctionsCheck::storeOptions( |
| 32 | ClangTidyOptions::OptionMap &Opts) { |
| 33 | Options.store(Options&: Opts, LocalName: "AllowMissingMoveFunctions" , Value: AllowMissingMoveFunctions); |
| 34 | Options.store(Options&: Opts, LocalName: "AllowSoleDefaultDtor" , Value: AllowSoleDefaultDtor); |
| 35 | Options.store(Options&: Opts, LocalName: "AllowMissingMoveFunctionsWhenCopyIsDeleted" , |
| 36 | Value: AllowMissingMoveFunctionsWhenCopyIsDeleted); |
| 37 | Options.store(Options&: Opts, LocalName: "AllowImplicitlyDeletedCopyOrMove" , |
| 38 | Value: AllowImplicitlyDeletedCopyOrMove); |
| 39 | } |
| 40 | |
| 41 | std::optional<TraversalKind> |
| 42 | SpecialMemberFunctionsCheck::getCheckTraversalKind() const { |
| 43 | return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs |
| 44 | : TK_IgnoreUnlessSpelledInSource; |
| 45 | } |
| 46 | |
| 47 | void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { |
| 48 | auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted()); |
| 49 | |
| 50 | Finder->addMatcher( |
| 51 | NodeMatch: cxxRecordDecl( |
| 52 | unless(isImplicit()), |
| 53 | eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind(ID: "dtor" )), |
| 54 | has(cxxConstructorDecl(isCopyConstructor(), |
| 55 | IsNotImplicitOrDeleted) |
| 56 | .bind(ID: "copy-ctor" )), |
| 57 | has(cxxMethodDecl(isCopyAssignmentOperator(), |
| 58 | IsNotImplicitOrDeleted) |
| 59 | .bind(ID: "copy-assign" )), |
| 60 | has(cxxConstructorDecl(isMoveConstructor(), |
| 61 | IsNotImplicitOrDeleted) |
| 62 | .bind(ID: "move-ctor" )), |
| 63 | has(cxxMethodDecl(isMoveAssignmentOperator(), |
| 64 | IsNotImplicitOrDeleted) |
| 65 | .bind(ID: "move-assign" )))) |
| 66 | .bind(ID: "class-def" ), |
| 67 | Action: this); |
| 68 | } |
| 69 | |
| 70 | static llvm::StringRef |
| 71 | toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) { |
| 72 | switch (K) { |
| 73 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor: |
| 74 | return "a destructor" ; |
| 75 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: |
| 76 | DefaultDestructor: |
| 77 | return "a default destructor" ; |
| 78 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: |
| 79 | NonDefaultDestructor: |
| 80 | return "a non-default destructor" ; |
| 81 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor: |
| 82 | return "a copy constructor" ; |
| 83 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment: |
| 84 | return "a copy assignment operator" ; |
| 85 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor: |
| 86 | return "a move constructor" ; |
| 87 | case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment: |
| 88 | return "a move assignment operator" ; |
| 89 | } |
| 90 | llvm_unreachable("Unhandled SpecialMemberFunctionKind" ); |
| 91 | } |
| 92 | |
| 93 | static std::string |
| 94 | join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS, |
| 95 | llvm::StringRef AndOr) { |
| 96 | |
| 97 | assert(!SMFS.empty() && |
| 98 | "List of defined or undefined members should never be empty." ); |
| 99 | std::string Buffer; |
| 100 | llvm::raw_string_ostream Stream(Buffer); |
| 101 | |
| 102 | Stream << toString(K: SMFS[0]); |
| 103 | size_t LastIndex = SMFS.size() - 1; |
| 104 | for (size_t I = 1; I < LastIndex; ++I) { |
| 105 | Stream << ", " << toString(K: SMFS[I]); |
| 106 | } |
| 107 | if (LastIndex != 0) { |
| 108 | Stream << AndOr << toString(K: SMFS[LastIndex]); |
| 109 | } |
| 110 | return Stream.str(); |
| 111 | } |
| 112 | |
| 113 | void SpecialMemberFunctionsCheck::check( |
| 114 | const MatchFinder::MatchResult &Result) { |
| 115 | const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "class-def" ); |
| 116 | if (!MatchedDecl) |
| 117 | return; |
| 118 | |
| 119 | ClassDefId ID(MatchedDecl->getLocation(), |
| 120 | std::string(MatchedDecl->getName())); |
| 121 | |
| 122 | auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) { |
| 123 | llvm::SmallVectorImpl<SpecialMemberFunctionData> &Members = |
| 124 | ClassWithSpecialMembers[ID]; |
| 125 | if (!llvm::is_contained(Range&: Members, Element: Data)) |
| 126 | Members.push_back(Elt: std::move(Data)); |
| 127 | }; |
| 128 | |
| 129 | if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "dtor" )) { |
| 130 | SpecialMemberFunctionKind DestructorType = |
| 131 | SpecialMemberFunctionKind::Destructor; |
| 132 | if (Dtor->isDefined()) { |
| 133 | DestructorType = Dtor->getDefinition()->isDefaulted() |
| 134 | ? SpecialMemberFunctionKind::DefaultDestructor |
| 135 | : SpecialMemberFunctionKind::NonDefaultDestructor; |
| 136 | } |
| 137 | StoreMember({DestructorType, Dtor->isDeleted()}); |
| 138 | } |
| 139 | |
| 140 | std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>> |
| 141 | Matchers = {{"copy-ctor" , SpecialMemberFunctionKind::CopyConstructor}, |
| 142 | {"copy-assign" , SpecialMemberFunctionKind::CopyAssignment}, |
| 143 | {"move-ctor" , SpecialMemberFunctionKind::MoveConstructor}, |
| 144 | {"move-assign" , SpecialMemberFunctionKind::MoveAssignment}}; |
| 145 | |
| 146 | for (const auto &KV : Matchers) |
| 147 | if (const auto *MethodDecl = |
| 148 | Result.Nodes.getNodeAs<CXXMethodDecl>(ID: KV.first)) { |
| 149 | StoreMember( |
| 150 | {KV.second, MethodDecl->isDeleted(), MethodDecl->isImplicit()}); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { |
| 155 | for (const auto &C : ClassWithSpecialMembers) { |
| 156 | checkForMissingMembers(ID: C.first, DefinedMembers: C.second); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | void SpecialMemberFunctionsCheck::checkForMissingMembers( |
| 161 | const ClassDefId &ID, |
| 162 | llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) { |
| 163 | llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers; |
| 164 | |
| 165 | auto HasMember = [&](SpecialMemberFunctionKind Kind) { |
| 166 | return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) { |
| 167 | return Data.FunctionKind == Kind && !Data.IsImplicit; |
| 168 | }); |
| 169 | }; |
| 170 | |
| 171 | auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) { |
| 172 | return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) { |
| 173 | return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted; |
| 174 | }); |
| 175 | }; |
| 176 | |
| 177 | auto IsDeleted = [&](SpecialMemberFunctionKind Kind) { |
| 178 | return llvm::any_of(Range&: DefinedMembers, P: [Kind](const auto &Data) { |
| 179 | return Data.FunctionKind == Kind && Data.IsDeleted; |
| 180 | }); |
| 181 | }; |
| 182 | |
| 183 | auto RequireMembers = [&](SpecialMemberFunctionKind Kind1, |
| 184 | SpecialMemberFunctionKind Kind2) { |
| 185 | if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) && |
| 186 | HasImplicitDeletedMember(Kind2)) |
| 187 | return; |
| 188 | |
| 189 | if (!HasMember(Kind1)) |
| 190 | MissingMembers.push_back(Elt: Kind1); |
| 191 | |
| 192 | if (!HasMember(Kind2)) |
| 193 | MissingMembers.push_back(Elt: Kind2); |
| 194 | }; |
| 195 | |
| 196 | bool RequireThree = |
| 197 | HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || |
| 198 | (!AllowSoleDefaultDtor && |
| 199 | (HasMember(SpecialMemberFunctionKind::Destructor) || |
| 200 | HasMember(SpecialMemberFunctionKind::DefaultDestructor))) || |
| 201 | HasMember(SpecialMemberFunctionKind::CopyConstructor) || |
| 202 | HasMember(SpecialMemberFunctionKind::CopyAssignment) || |
| 203 | HasMember(SpecialMemberFunctionKind::MoveConstructor) || |
| 204 | HasMember(SpecialMemberFunctionKind::MoveAssignment); |
| 205 | |
| 206 | bool RequireFive = (!AllowMissingMoveFunctions && RequireThree && |
| 207 | getLangOpts().CPlusPlus11) || |
| 208 | HasMember(SpecialMemberFunctionKind::MoveConstructor) || |
| 209 | HasMember(SpecialMemberFunctionKind::MoveAssignment); |
| 210 | |
| 211 | if (RequireThree) { |
| 212 | if (!HasMember(SpecialMemberFunctionKind::Destructor) && |
| 213 | !HasMember(SpecialMemberFunctionKind::DefaultDestructor) && |
| 214 | !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) |
| 215 | MissingMembers.push_back(Elt: SpecialMemberFunctionKind::Destructor); |
| 216 | |
| 217 | RequireMembers(SpecialMemberFunctionKind::CopyConstructor, |
| 218 | SpecialMemberFunctionKind::CopyAssignment); |
| 219 | } |
| 220 | |
| 221 | if (RequireFive && |
| 222 | !(AllowMissingMoveFunctionsWhenCopyIsDeleted && |
| 223 | (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) && |
| 224 | IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) { |
| 225 | assert(RequireThree); |
| 226 | RequireMembers(SpecialMemberFunctionKind::MoveConstructor, |
| 227 | SpecialMemberFunctionKind::MoveAssignment); |
| 228 | } |
| 229 | |
| 230 | if (!MissingMembers.empty()) { |
| 231 | llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds; |
| 232 | for (const auto &Data : DefinedMembers) { |
| 233 | if (!Data.IsImplicit) |
| 234 | DefinedMemberKinds.push_back(Elt: Data.FunctionKind); |
| 235 | } |
| 236 | diag(Loc: ID.first, Description: "class '%0' defines %1 but does not define %2" ) |
| 237 | << ID.second << cppcoreguidelines::join(SMFS: DefinedMemberKinds, AndOr: " and " ) |
| 238 | << cppcoreguidelines::join(SMFS: MissingMembers, AndOr: " or " ); |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | } // namespace clang::tidy::cppcoreguidelines |
| 243 | |