| //===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SpecialMemberFunctionsCheck.h" |
| |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "llvm/ADT/DenseMapInfo.h" |
| #include "llvm/ADT/StringExtras.h" |
| |
| #define DEBUG_TYPE "clang-tidy" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace cppcoreguidelines { |
| |
| SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| AllowMissingMoveFunctions(Options.get("AllowMissingMoveFunctions", 0)), |
| AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", 0)) {} |
| |
| void SpecialMemberFunctionsCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions); |
| Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor); |
| } |
| |
| void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { |
| if (!getLangOpts().CPlusPlus) |
| return; |
| Finder->addMatcher( |
| cxxRecordDecl( |
| eachOf( |
| has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), |
| has(cxxConstructorDecl(isCopyConstructor(), unless(isImplicit())) |
| .bind("copy-ctor")), |
| has(cxxMethodDecl(isCopyAssignmentOperator(), |
| unless(isImplicit())) |
| .bind("copy-assign")), |
| has(cxxConstructorDecl(isMoveConstructor(), unless(isImplicit())) |
| .bind("move-ctor")), |
| has(cxxMethodDecl(isMoveAssignmentOperator(), |
| unless(isImplicit())) |
| .bind("move-assign")))) |
| .bind("class-def"), |
| this); |
| } |
| |
| static llvm::StringRef |
| toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) { |
| switch (K) { |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::Destructor: |
| return "a destructor"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: |
| DefaultDestructor: |
| return "a default destructor"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind:: |
| NonDefaultDestructor: |
| return "a non-default destructor"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyConstructor: |
| return "a copy constructor"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::CopyAssignment: |
| return "a copy assignment operator"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveConstructor: |
| return "a move constructor"; |
| case SpecialMemberFunctionsCheck::SpecialMemberFunctionKind::MoveAssignment: |
| return "a move assignment operator"; |
| } |
| llvm_unreachable("Unhandled SpecialMemberFunctionKind"); |
| } |
| |
| static std::string |
| join(ArrayRef<SpecialMemberFunctionsCheck::SpecialMemberFunctionKind> SMFS, |
| llvm::StringRef AndOr) { |
| |
| assert(!SMFS.empty() && |
| "List of defined or undefined members should never be empty."); |
| std::string Buffer; |
| llvm::raw_string_ostream Stream(Buffer); |
| |
| Stream << toString(SMFS[0]); |
| size_t LastIndex = SMFS.size() - 1; |
| for (size_t i = 1; i < LastIndex; ++i) { |
| Stream << ", " << toString(SMFS[i]); |
| } |
| if (LastIndex != 0) { |
| Stream << AndOr << toString(SMFS[LastIndex]); |
| } |
| return Stream.str(); |
| } |
| |
| void SpecialMemberFunctionsCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *MatchedDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("class-def"); |
| if (!MatchedDecl) |
| return; |
| |
| ClassDefId ID(MatchedDecl->getLocation(), MatchedDecl->getName()); |
| |
| auto StoreMember = [this, &ID](SpecialMemberFunctionKind Kind) { |
| llvm::SmallVectorImpl<SpecialMemberFunctionKind> &Members = |
| ClassWithSpecialMembers[ID]; |
| if (!llvm::is_contained(Members, Kind)) |
| Members.push_back(Kind); |
| }; |
| |
| if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) { |
| StoreMember(Dtor->isDefaulted() |
| ? SpecialMemberFunctionKind::DefaultDestructor |
| : SpecialMemberFunctionKind::NonDefaultDestructor); |
| } |
| |
| std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>> |
| Matchers = {{"copy-ctor", SpecialMemberFunctionKind::CopyConstructor}, |
| {"copy-assign", SpecialMemberFunctionKind::CopyAssignment}, |
| {"move-ctor", SpecialMemberFunctionKind::MoveConstructor}, |
| {"move-assign", SpecialMemberFunctionKind::MoveAssignment}}; |
| |
| for (const auto &KV : Matchers) |
| if (Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) { |
| StoreMember(KV.second); |
| } |
| } |
| |
| void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { |
| for (const auto &C : ClassWithSpecialMembers) { |
| checkForMissingMembers(C.first, C.second); |
| } |
| } |
| |
| void SpecialMemberFunctionsCheck::checkForMissingMembers( |
| const ClassDefId &ID, |
| llvm::ArrayRef<SpecialMemberFunctionKind> DefinedMembers) { |
| llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers; |
| |
| auto HasMember = [&](SpecialMemberFunctionKind Kind) { |
| return llvm::is_contained(DefinedMembers, Kind); |
| }; |
| |
| auto RequireMember = [&](SpecialMemberFunctionKind Kind) { |
| if (!HasMember(Kind)) |
| MissingMembers.push_back(Kind); |
| }; |
| |
| bool RequireThree = |
| HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || |
| (!AllowSoleDefaultDtor && |
| HasMember(SpecialMemberFunctionKind::DefaultDestructor)) || |
| HasMember(SpecialMemberFunctionKind::CopyConstructor) || |
| HasMember(SpecialMemberFunctionKind::CopyAssignment) || |
| HasMember(SpecialMemberFunctionKind::MoveConstructor) || |
| HasMember(SpecialMemberFunctionKind::MoveAssignment); |
| |
| bool RequireFive = (!AllowMissingMoveFunctions && RequireThree && |
| getLangOpts().CPlusPlus11) || |
| HasMember(SpecialMemberFunctionKind::MoveConstructor) || |
| HasMember(SpecialMemberFunctionKind::MoveAssignment); |
| |
| if (RequireThree) { |
| if (!HasMember(SpecialMemberFunctionKind::DefaultDestructor) && |
| !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) |
| MissingMembers.push_back(SpecialMemberFunctionKind::Destructor); |
| |
| RequireMember(SpecialMemberFunctionKind::CopyConstructor); |
| RequireMember(SpecialMemberFunctionKind::CopyAssignment); |
| } |
| |
| if (RequireFive) { |
| assert(RequireThree); |
| RequireMember(SpecialMemberFunctionKind::MoveConstructor); |
| RequireMember(SpecialMemberFunctionKind::MoveAssignment); |
| } |
| |
| if (!MissingMembers.empty()) |
| diag(ID.first, "class '%0' defines %1 but does not define %2") |
| << ID.second << cppcoreguidelines::join(DefinedMembers, " and ") |
| << cppcoreguidelines::join(MissingMembers, " or "); |
| } |
| |
| } // namespace cppcoreguidelines |
| } // namespace tidy |
| } // namespace clang |