182 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 "CrtpConstructorAccessibilityCheck.h"10#include "../utils/LexerUtils.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::bugprone {16 17static bool hasPrivateConstructor(const CXXRecordDecl *RD) {18 return llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) {19 return Ctor->getAccess() == AS_private;20 });21}22 23static bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP,24 const NamedDecl *Param) {25 return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) {26 const TypeSourceInfo *const FriendType = Friend->getFriendType();27 if (!FriendType) {28 return false;29 }30 31 const auto *const TTPT =32 dyn_cast<TemplateTypeParmType>(FriendType->getType());33 34 return TTPT && TTPT->getDecl() == Param;35 });36}37 38static bool isDerivedClassBefriended(const CXXRecordDecl *CRTP,39 const CXXRecordDecl *Derived) {40 return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) {41 const TypeSourceInfo *const FriendType = Friend->getFriendType();42 if (!FriendType) {43 return false;44 }45 46 return declaresSameEntity(FriendType->getType()->getAsCXXRecordDecl(),47 Derived);48 });49}50 51static const NamedDecl *52getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP,53 const CXXRecordDecl *Derived) {54 size_t Idx = 0;55 const bool AnyOf = llvm::any_of(56 CRTP->getTemplateArgs().asArray(), [&](const TemplateArgument &Arg) {57 ++Idx;58 return Arg.getKind() == TemplateArgument::Type &&59 declaresSameEntity(Arg.getAsType()->getAsCXXRecordDecl(),60 Derived);61 });62 63 return AnyOf ? CRTP->getSpecializedTemplate()64 ->getTemplateParameters()65 ->getParam(Idx - 1)66 : nullptr;67}68 69static std::vector<FixItHint>70hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,71 const std::string &OriginalAccess) {72 std::vector<FixItHint> Hints;73 74 Hints.emplace_back(FixItHint::CreateInsertion(75 Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n"));76 77 const ASTContext &ASTCtx = Ctor->getASTContext();78 const SourceLocation CtorEndLoc =79 Ctor->isExplicitlyDefaulted()80 ? utils::lexer::findNextTerminator(Ctor->getEndLoc(),81 ASTCtx.getSourceManager(),82 ASTCtx.getLangOpts())83 : Ctor->getEndLoc();84 Hints.emplace_back(FixItHint::CreateInsertion(85 CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n'));86 87 return Hints;88}89 90void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder *Finder) {91 Finder->addMatcher(92 classTemplateSpecializationDecl(93 decl().bind("crtp"),94 hasAnyTemplateArgument(refersToType(recordType(hasDeclaration(95 cxxRecordDecl(96 isDerivedFrom(cxxRecordDecl(equalsBoundNode("crtp"))))97 .bind("derived")))))),98 this);99}100 101void CrtpConstructorAccessibilityCheck::check(102 const MatchFinder::MatchResult &Result) {103 const auto *CRTPInstantiation =104 Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp");105 const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived");106 const CXXRecordDecl *CRTPDeclaration =107 CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();108 109 if (!CRTPDeclaration->hasDefinition()) {110 return;111 }112 113 const auto *DerivedTemplateParameter =114 getDerivedParameter(CRTPInstantiation, DerivedRecord);115 116 assert(DerivedTemplateParameter &&117 "No template parameter corresponds to the derived class of the CRTP.");118 119 const bool NeedsFriend =120 !isDerivedParameterBefriended(CRTPDeclaration,121 DerivedTemplateParameter) &&122 !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord);123 124 const FixItHint HintFriend = FixItHint::CreateInsertion(125 CRTPDeclaration->getBraceRange().getEnd(),126 "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n');127 128 if (hasPrivateConstructor(CRTPDeclaration) && NeedsFriend) {129 diag(CRTPDeclaration->getLocation(),130 "the CRTP cannot be constructed from the derived class; consider "131 "declaring the derived class as friend")132 << HintFriend;133 }134 135 auto WithFriendHintIfNeeded = [&](const DiagnosticBuilder &Diag,136 bool NeedsFriend) {137 if (NeedsFriend)138 Diag << HintFriend;139 };140 141 if (!CRTPDeclaration->hasUserDeclaredConstructor()) {142 const bool IsStruct = CRTPDeclaration->isStruct();143 144 WithFriendHintIfNeeded(145 diag(CRTPDeclaration->getLocation(),146 "the implicit default constructor of the CRTP is publicly "147 "accessible; consider making it private%select{| and declaring "148 "the derived class as friend}0")149 << NeedsFriend150 << FixItHint::CreateInsertion(151 CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(152 1),153 (IsStruct ? "\nprivate:\n" : "\n") +154 CRTPDeclaration->getNameAsString() + "() = default;\n" +155 (IsStruct ? "public:\n" : "")),156 NeedsFriend);157 }158 159 for (auto &&Ctor : CRTPDeclaration->ctors()) {160 if (Ctor->getAccess() == AS_private || Ctor->isDeleted())161 continue;162 163 const bool IsPublic = Ctor->getAccess() == AS_public;164 const std::string Access = IsPublic ? "public" : "protected";165 166 WithFriendHintIfNeeded(167 diag(Ctor->getLocation(),168 "%0 constructor allows the CRTP to be %select{inherited "169 "from|constructed}1 as a regular template class; consider making "170 "it private%select{| and declaring the derived class as friend}2")171 << Access << IsPublic << NeedsFriend172 << hintMakeCtorPrivate(Ctor, Access),173 NeedsFriend);174 }175}176 177bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(178 const LangOptions &LangOpts) const {179 return LangOpts.CPlusPlus11;180}181} // namespace clang::tidy::bugprone182