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