//===--- 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::tidy::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)), AllowImplicitlyDeletedCopyOrMove( Options.get("AllowImplicitlyDeletedCopyOrMove", false)) {} void SpecialMemberFunctionsCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions); Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor); Options.store(Opts, "AllowMissingMoveFunctionsWhenCopyIsDeleted", AllowMissingMoveFunctionsWhenCopyIsDeleted); Options.store(Opts, "AllowImplicitlyDeletedCopyOrMove", AllowImplicitlyDeletedCopyOrMove); } std::optional SpecialMemberFunctionsCheck::getCheckTraversalKind() const { return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs : TK_IgnoreUnlessSpelledInSource; } void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted()); Finder->addMatcher( cxxRecordDecl( unless(isImplicit()), eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), has(cxxConstructorDecl(isCopyConstructor(), IsNotImplicitOrDeleted) .bind("copy-ctor")), has(cxxMethodDecl(isCopyAssignmentOperator(), IsNotImplicitOrDeleted) .bind("copy-assign")), has(cxxConstructorDecl(isMoveConstructor(), IsNotImplicitOrDeleted) .bind("move-ctor")), has(cxxMethodDecl(isMoveAssignmentOperator(), IsNotImplicitOrDeleted) .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 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("class-def"); if (!MatchedDecl) return; ClassDefId ID(MatchedDecl->getLocation(), std::string(MatchedDecl->getName())); auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) { llvm::SmallVectorImpl &Members = ClassWithSpecialMembers[ID]; if (!llvm::is_contained(Members, Data)) Members.push_back(std::move(Data)); }; if (const auto *Dtor = Result.Nodes.getNodeAs("dtor")) { SpecialMemberFunctionKind DestructorType = SpecialMemberFunctionKind::Destructor; if (Dtor->isDefined()) { DestructorType = Dtor->getDefinition()->isDefaulted() ? SpecialMemberFunctionKind::DefaultDestructor : SpecialMemberFunctionKind::NonDefaultDestructor; } StoreMember({DestructorType, Dtor->isDeleted()}); } std::initializer_list> 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(KV.first)) { StoreMember( {KV.second, MethodDecl->isDeleted(), MethodDecl->isImplicit()}); } } void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { for (const auto &C : ClassWithSpecialMembers) { checkForMissingMembers(C.first, C.second); } } void SpecialMemberFunctionsCheck::checkForMissingMembers( const ClassDefId &ID, llvm::ArrayRef DefinedMembers) { llvm::SmallVector MissingMembers; auto HasMember = [&](SpecialMemberFunctionKind Kind) { return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { return Data.FunctionKind == Kind && !Data.IsImplicit; }); }; auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) { return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted; }); }; auto IsDeleted = [&](SpecialMemberFunctionKind Kind) { return llvm::any_of(DefinedMembers, [Kind](const auto &Data) { return Data.FunctionKind == Kind && Data.IsDeleted; }); }; auto RequireMembers = [&](SpecialMemberFunctionKind Kind1, SpecialMemberFunctionKind Kind2) { if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) && HasImplicitDeletedMember(Kind2)) return; if (!HasMember(Kind1)) MissingMembers.push_back(Kind1); if (!HasMember(Kind2)) MissingMembers.push_back(Kind2); }; bool RequireThree = HasMember(SpecialMemberFunctionKind::NonDefaultDestructor) || (!AllowSoleDefaultDtor && (HasMember(SpecialMemberFunctionKind::Destructor) || 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::Destructor) && !HasMember(SpecialMemberFunctionKind::DefaultDestructor) && !HasMember(SpecialMemberFunctionKind::NonDefaultDestructor)) MissingMembers.push_back(SpecialMemberFunctionKind::Destructor); RequireMembers(SpecialMemberFunctionKind::CopyConstructor, SpecialMemberFunctionKind::CopyAssignment); } if (RequireFive && !(AllowMissingMoveFunctionsWhenCopyIsDeleted && (IsDeleted(SpecialMemberFunctionKind::CopyConstructor) && IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) { assert(RequireThree); RequireMembers(SpecialMemberFunctionKind::MoveConstructor, SpecialMemberFunctionKind::MoveAssignment); } if (!MissingMembers.empty()) { llvm::SmallVector DefinedMemberKinds; for (const auto &Data : DefinedMembers) { if (!Data.IsImplicit) DefinedMemberKinds.push_back(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 clang::tidy::cppcoreguidelines