//===--- CrtpConstructorAccessibilityCheck.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 "CrtpConstructorAccessibilityCheck.h" #include "../utils/LexerUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang::tidy::bugprone { static bool hasPrivateConstructor(const CXXRecordDecl *RD) { return llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) { return Ctor->getAccess() == AS_private; }); } static bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP, const NamedDecl *Param) { return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) { const TypeSourceInfo *const FriendType = Friend->getFriendType(); if (!FriendType) { return false; } const auto *const TTPT = dyn_cast(FriendType->getType()); return TTPT && TTPT->getDecl() == Param; }); } static bool isDerivedClassBefriended(const CXXRecordDecl *CRTP, const CXXRecordDecl *Derived) { return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) { const TypeSourceInfo *const FriendType = Friend->getFriendType(); if (!FriendType) { return false; } return FriendType->getType()->getAsCXXRecordDecl() == Derived; }); } static const NamedDecl * getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP, const CXXRecordDecl *Derived) { size_t Idx = 0; const bool AnyOf = llvm::any_of( CRTP->getTemplateArgs().asArray(), [&](const TemplateArgument &Arg) { ++Idx; return Arg.getKind() == TemplateArgument::Type && Arg.getAsType()->getAsCXXRecordDecl() == Derived; }); return AnyOf ? CRTP->getSpecializedTemplate() ->getTemplateParameters() ->getParam(Idx - 1) : nullptr; } static std::vector hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, const std::string &OriginalAccess) { std::vector Hints; Hints.emplace_back(FixItHint::CreateInsertion( Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n")); const ASTContext &ASTCtx = Ctor->getASTContext(); const SourceLocation CtorEndLoc = Ctor->isExplicitlyDefaulted() ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), ASTCtx.getSourceManager(), ASTCtx.getLangOpts()) : Ctor->getEndLoc(); Hints.emplace_back(FixItHint::CreateInsertion( CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n')); return Hints; } void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( classTemplateSpecializationDecl( decl().bind("crtp"), hasAnyTemplateArgument(refersToType(recordType(hasDeclaration( cxxRecordDecl( isDerivedFrom(cxxRecordDecl(equalsBoundNode("crtp")))) .bind("derived")))))), this); } void CrtpConstructorAccessibilityCheck::check( const MatchFinder::MatchResult &Result) { const auto *CRTPInstantiation = Result.Nodes.getNodeAs("crtp"); const auto *DerivedRecord = Result.Nodes.getNodeAs("derived"); const CXXRecordDecl *CRTPDeclaration = CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl(); if (!CRTPDeclaration->hasDefinition()) { return; } const auto *DerivedTemplateParameter = getDerivedParameter(CRTPInstantiation, DerivedRecord); assert(DerivedTemplateParameter && "No template parameter corresponds to the derived class of the CRTP."); bool NeedsFriend = !isDerivedParameterBefriended(CRTPDeclaration, DerivedTemplateParameter) && !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord); const FixItHint HintFriend = FixItHint::CreateInsertion( CRTPDeclaration->getBraceRange().getEnd(), "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n'); if (hasPrivateConstructor(CRTPDeclaration) && NeedsFriend) { diag(CRTPDeclaration->getLocation(), "the CRTP cannot be constructed from the derived class; consider " "declaring the derived class as friend") << HintFriend; } auto WithFriendHintIfNeeded = [&](const DiagnosticBuilder &Diag, bool NeedsFriend) { if (NeedsFriend) Diag << HintFriend; }; if (!CRTPDeclaration->hasUserDeclaredConstructor()) { const bool IsStruct = CRTPDeclaration->isStruct(); WithFriendHintIfNeeded( diag(CRTPDeclaration->getLocation(), "the implicit default constructor of the CRTP is publicly " "accessible; consider making it private%select{| and declaring " "the derived class as friend}0") << NeedsFriend << FixItHint::CreateInsertion( CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset( 1), (IsStruct ? "\nprivate:\n" : "\n") + CRTPDeclaration->getNameAsString() + "() = default;\n" + (IsStruct ? "public:\n" : "")), NeedsFriend); } for (auto &&Ctor : CRTPDeclaration->ctors()) { if (Ctor->getAccess() == AS_private || Ctor->isDeleted()) continue; const bool IsPublic = Ctor->getAccess() == AS_public; const std::string Access = IsPublic ? "public" : "protected"; WithFriendHintIfNeeded( diag(Ctor->getLocation(), "%0 constructor allows the CRTP to be %select{inherited " "from|constructed}1 as a regular template class; consider making " "it private%select{| and declaring the derived class as friend}2") << Access << IsPublic << NeedsFriend << hintMakeCtorPrivate(Ctor, Access), NeedsFriend); } } bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported( const LangOptions &LangOpts) const { return LangOpts.CPlusPlus11; } } // namespace clang::tidy::bugprone