| //===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #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", false)), |
| AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)), |
| AllowMissingMoveFunctionsWhenCopyIsDeleted( |
| Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {} |
| |
| void SpecialMemberFunctionsCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions); |
| Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor); |
| Options.store(Opts, "AllowMissingMoveFunctionsWhenCopyIsDeleted", |
| AllowMissingMoveFunctionsWhenCopyIsDeleted); |
| } |
| |
| void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { |
| Finder->addMatcher( |
| cxxRecordDecl( |
| eachOf(has(cxxDestructorDecl().bind("dtor")), |
| has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")), |
| has(cxxMethodDecl(isCopyAssignmentOperator()) |
| .bind("copy-assign")), |
| has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")), |
| has(cxxMethodDecl(isMoveAssignmentOperator()) |
| .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(), std::string(MatchedDecl->getName())); |
| |
| auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) { |
| llvm::SmallVectorImpl<SpecialMemberFunctionData> &Members = |
| ClassWithSpecialMembers[ID]; |
| if (!llvm::is_contained(Members, Data)) |
| Members.push_back(std::move(Data)); |
| }; |
| |
| if (const auto *Dtor = Result.Nodes.getNodeAs<CXXMethodDecl>("dtor")) { |
| StoreMember({Dtor->isDefaulted() |
| ? SpecialMemberFunctionKind::DefaultDestructor |
| : SpecialMemberFunctionKind::NonDefaultDestructor, |
| Dtor->isDeleted()}); |
| } |
| |
| 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 (const auto *MethodDecl = |
| Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) { |
| StoreMember({KV.second, MethodDecl->isDeleted()}); |
| } |
| } |
| |
| void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { |
| for (const auto &C : ClassWithSpecialMembers) { |
| checkForMissingMembers(C.first, C.second); |
| } |
| } |
| |
| void SpecialMemberFunctionsCheck::checkForMissingMembers( |
| const ClassDefId &ID, |
| llvm::ArrayRef<SpecialMemberFunctionData> DefinedMembers) { |
| llvm::SmallVector<SpecialMemberFunctionKind, 5> MissingMembers; |
| |
| auto HasMember = [&](SpecialMemberFunctionKind Kind) { |
| return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { |
| return Data.FunctionKind == Kind; |
| }); |
| }; |
| |
| auto IsDeleted = [&](SpecialMemberFunctionKind Kind) { |
| return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { |
| return Data.FunctionKind == Kind && Data.IsDeleted; |
| }); |
| }; |
| |
| 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 && |
| !(AllowMissingMoveFunctionsWhenCopyIsDeleted && |
| (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) && |
| IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) { |
| assert(RequireThree); |
| RequireMember(SpecialMemberFunctionKind::MoveConstructor); |
| RequireMember(SpecialMemberFunctionKind::MoveAssignment); |
| } |
| |
| if (!MissingMembers.empty()) { |
| llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds; |
| llvm::transform(DefinedMembers, std::back_inserter(DefinedMemberKinds), |
| [](const auto &Data) { return Data.FunctionKind; }); |
| diag(ID.first, "class '%0' defines %1 but does not define %2") |
| << ID.second << cppcoreguidelines::join(DefinedMemberKinds, " and ") |
| << cppcoreguidelines::join(MissingMembers, " or "); |
| } |
| } |
| |
| } // namespace cppcoreguidelines |
| } // namespace tidy |
| } // namespace clang |