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