1 //===--- CrtpConstructorAccessibilityCheck.cpp - clang-tidy ---------------===// 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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "CrtpConstructorAccessibilityCheck.h" 10 #include "../utils/LexerUtils.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 13 using namespace clang::ast_matchers; 14 15 namespace clang::tidy::bugprone { 16 17 static bool hasPrivateConstructor(const CXXRecordDecl *RD) { 18 return llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) { 19 return Ctor->getAccess() == AS_private; 20 }); 21 } 22 23 static 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 38 static 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 FriendType->getType()->getAsCXXRecordDecl() == Derived; 47 }); 48 } 49 50 static const NamedDecl * 51 getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP, 52 const CXXRecordDecl *Derived) { 53 size_t Idx = 0; 54 const bool AnyOf = llvm::any_of( 55 CRTP->getTemplateArgs().asArray(), [&](const TemplateArgument &Arg) { 56 ++Idx; 57 return Arg.getKind() == TemplateArgument::Type && 58 Arg.getAsType()->getAsCXXRecordDecl() == Derived; 59 }); 60 61 return AnyOf ? CRTP->getSpecializedTemplate() 62 ->getTemplateParameters() 63 ->getParam(Idx - 1) 64 : nullptr; 65 } 66 67 static std::vector<FixItHint> 68 hintMakeCtorPrivate(const CXXConstructorDecl *Ctor, 69 const std::string &OriginalAccess) { 70 std::vector<FixItHint> Hints; 71 72 Hints.emplace_back(FixItHint::CreateInsertion( 73 Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n")); 74 75 const ASTContext &ASTCtx = Ctor->getASTContext(); 76 const SourceLocation CtorEndLoc = 77 Ctor->isExplicitlyDefaulted() 78 ? utils::lexer::findNextTerminator(Ctor->getEndLoc(), 79 ASTCtx.getSourceManager(), 80 ASTCtx.getLangOpts()) 81 : Ctor->getEndLoc(); 82 Hints.emplace_back(FixItHint::CreateInsertion( 83 CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n')); 84 85 return Hints; 86 } 87 88 void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder *Finder) { 89 Finder->addMatcher( 90 classTemplateSpecializationDecl( 91 decl().bind("crtp"), 92 hasAnyTemplateArgument(refersToType(recordType(hasDeclaration( 93 cxxRecordDecl( 94 isDerivedFrom(cxxRecordDecl(equalsBoundNode("crtp")))) 95 .bind("derived")))))), 96 this); 97 } 98 99 void CrtpConstructorAccessibilityCheck::check( 100 const MatchFinder::MatchResult &Result) { 101 const auto *CRTPInstantiation = 102 Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp"); 103 const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived"); 104 const CXXRecordDecl *CRTPDeclaration = 105 CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl(); 106 107 if (!CRTPDeclaration->hasDefinition()) { 108 return; 109 } 110 111 const auto *DerivedTemplateParameter = 112 getDerivedParameter(CRTPInstantiation, DerivedRecord); 113 114 assert(DerivedTemplateParameter && 115 "No template parameter corresponds to the derived class of the CRTP."); 116 117 bool NeedsFriend = !isDerivedParameterBefriended(CRTPDeclaration, 118 DerivedTemplateParameter) && 119 !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord); 120 121 const FixItHint HintFriend = FixItHint::CreateInsertion( 122 CRTPDeclaration->getBraceRange().getEnd(), 123 "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n'); 124 125 if (hasPrivateConstructor(CRTPDeclaration) && NeedsFriend) { 126 diag(CRTPDeclaration->getLocation(), 127 "the CRTP cannot be constructed from the derived class; consider " 128 "declaring the derived class as friend") 129 << HintFriend; 130 } 131 132 auto WithFriendHintIfNeeded = 133 [&](const DiagnosticBuilder &Diag, 134 bool NeedsFriend) -> const DiagnosticBuilder & { 135 if (NeedsFriend) 136 Diag << HintFriend; 137 138 return Diag; 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 << NeedsFriend 150 << 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) 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 << NeedsFriend 172 << hintMakeCtorPrivate(Ctor, Access), 173 NeedsFriend); 174 } 175 } 176 177 bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported( 178 const LangOptions &LangOpts) const { 179 return LangOpts.CPlusPlus11; 180 } 181 } // namespace clang::tidy::bugprone 182