clang-tools 22.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
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
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
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 bool NeedsFriend = !isDerivedParameterBefriended(CRTPDeclaration,
120 DerivedTemplateParameter) &&
121 !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord);
122
123 const FixItHint HintFriend = FixItHint::CreateInsertion(
124 CRTPDeclaration->getBraceRange().getEnd(),
125 "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n');
126
127 if (hasPrivateConstructor(CRTPDeclaration) && NeedsFriend) {
128 diag(CRTPDeclaration->getLocation(),
129 "the CRTP cannot be constructed from the derived class; consider "
130 "declaring the derived class as friend")
131 << HintFriend;
132 }
133
134 auto WithFriendHintIfNeeded = [&](const DiagnosticBuilder &Diag,
135 bool NeedsFriend) {
136 if (NeedsFriend)
137 Diag << HintFriend;
138 };
139
140 if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
141 const bool IsStruct = CRTPDeclaration->isStruct();
142
143 WithFriendHintIfNeeded(
144 diag(CRTPDeclaration->getLocation(),
145 "the implicit default constructor of the CRTP is publicly "
146 "accessible; consider making it private%select{| and declaring "
147 "the derived class as friend}0")
148 << NeedsFriend
149 << FixItHint::CreateInsertion(
150 CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(
151 1),
152 (IsStruct ? "\nprivate:\n" : "\n") +
153 CRTPDeclaration->getNameAsString() + "() = default;\n" +
154 (IsStruct ? "public:\n" : "")),
155 NeedsFriend);
156 }
157
158 for (auto &&Ctor : CRTPDeclaration->ctors()) {
159 if (Ctor->getAccess() == AS_private || Ctor->isDeleted())
160 continue;
161
162 const bool IsPublic = Ctor->getAccess() == AS_public;
163 const std::string Access = IsPublic ? "public" : "protected";
164
165 WithFriendHintIfNeeded(
166 diag(Ctor->getLocation(),
167 "%0 constructor allows the CRTP to be %select{inherited "
168 "from|constructed}1 as a regular template class; consider making "
169 "it private%select{| and declaring the derived class as friend}2")
170 << Access << IsPublic << NeedsFriend
171 << hintMakeCtorPrivate(Ctor, Access),
172 NeedsFriend);
173 }
174}
175
177 const LangOptions &LangOpts) const {
178 return LangOpts.CPlusPlus11;
179}
180} // 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)