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
13using namespace clang::ast_matchers;
14
15namespace clang::tidy::bugprone {
16
17static bool hasPrivateConstructor(const CXXRecordDecl *RD) {
18 return llvm::any_of(Range: RD->ctors(), P: [](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(Range: CRTP->friends(), P: [&](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>(Val: 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(Range: CRTP->friends(), P: [&](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
50static const NamedDecl *
51getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP,
52 const CXXRecordDecl *Derived) {
53 size_t Idx = 0;
54 const bool AnyOf = llvm::any_of(
55 Range: CRTP->getTemplateArgs().asArray(), P: [&](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: Idx - 1)
64 : nullptr;
65}
66
67static std::vector<FixItHint>
68hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,
69 const std::string &OriginalAccess) {
70 std::vector<FixItHint> Hints;
71
72 Hints.emplace_back(args: FixItHint::CreateInsertion(
73 InsertionLoc: Ctor->getBeginLoc().getLocWithOffset(Offset: -1), Code: "private:\n"));
74
75 const ASTContext &ASTCtx = Ctor->getASTContext();
76 const SourceLocation CtorEndLoc =
77 Ctor->isExplicitlyDefaulted()
78 ? utils::lexer::findNextTerminator(Start: Ctor->getEndLoc(),
79 SM: ASTCtx.getSourceManager(),
80 LangOpts: ASTCtx.getLangOpts())
81 : Ctor->getEndLoc();
82 Hints.emplace_back(args: FixItHint::CreateInsertion(
83 InsertionLoc: CtorEndLoc.getLocWithOffset(Offset: 1), Code: '\n' + OriginalAccess + ':' + '\n'));
84
85 return Hints;
86}
87
88void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder *Finder) {
89 Finder->addMatcher(
90 NodeMatch: classTemplateSpecializationDecl(
91 decl().bind(ID: "crtp"),
92 hasAnyTemplateArgument(InnerMatcher: refersToType(InnerMatcher: recordType(hasDeclaration(
93 InnerMatcher: cxxRecordDecl(
94 isDerivedFrom(Base: cxxRecordDecl(equalsBoundNode(ID: "crtp"))))
95 .bind(ID: "derived")))))),
96 Action: this);
97}
98
99void CrtpConstructorAccessibilityCheck::check(
100 const MatchFinder::MatchResult &Result) {
101 const auto *CRTPInstantiation =
102 Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID: "crtp");
103 const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "derived");
104 const CXXRecordDecl *CRTPDeclaration =
105 CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();
106
107 if (!CRTPDeclaration->hasDefinition()) {
108 return;
109 }
110
111 const auto *DerivedTemplateParameter =
112 getDerivedParameter(CRTP: CRTPInstantiation, Derived: DerivedRecord);
113
114 assert(DerivedTemplateParameter &&
115 "No template parameter corresponds to the derived class of the CRTP.");
116
117 bool NeedsFriend = !isDerivedParameterBefriended(CRTP: CRTPDeclaration,
118 Param: DerivedTemplateParameter) &&
119 !isDerivedClassBefriended(CRTP: CRTPDeclaration, Derived: DerivedRecord);
120
121 const FixItHint HintFriend = FixItHint::CreateInsertion(
122 InsertionLoc: CRTPDeclaration->getBraceRange().getEnd(),
123 Code: "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n');
124
125 if (hasPrivateConstructor(RD: CRTPDeclaration) && NeedsFriend) {
126 diag(Loc: CRTPDeclaration->getLocation(),
127 Description: "the CRTP cannot be constructed from the derived class; consider "
128 "declaring the derived class as friend")
129 << HintFriend;
130 }
131
132 auto WithFriendHintIfNeeded = [&](const DiagnosticBuilder &Diag,
133 bool NeedsFriend) {
134 if (NeedsFriend)
135 Diag << HintFriend;
136 };
137
138 if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
139 const bool IsStruct = CRTPDeclaration->isStruct();
140
141 WithFriendHintIfNeeded(
142 diag(Loc: CRTPDeclaration->getLocation(),
143 Description: "the implicit default constructor of the CRTP is publicly "
144 "accessible; consider making it private%select{| and declaring "
145 "the derived class as friend}0")
146 << NeedsFriend
147 << FixItHint::CreateInsertion(
148 InsertionLoc: CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(
149 Offset: 1),
150 Code: (IsStruct ? "\nprivate:\n" : "\n") +
151 CRTPDeclaration->getNameAsString() + "() = default;\n" +
152 (IsStruct ? "public:\n" : "")),
153 NeedsFriend);
154 }
155
156 for (auto &&Ctor : CRTPDeclaration->ctors()) {
157 if (Ctor->getAccess() == AS_private || Ctor->isDeleted())
158 continue;
159
160 const bool IsPublic = Ctor->getAccess() == AS_public;
161 const std::string Access = IsPublic ? "public" : "protected";
162
163 WithFriendHintIfNeeded(
164 diag(Loc: Ctor->getLocation(),
165 Description: "%0 constructor allows the CRTP to be %select{inherited "
166 "from|constructed}1 as a regular template class; consider making "
167 "it private%select{| and declaring the derived class as friend}2")
168 << Access << IsPublic << NeedsFriend
169 << hintMakeCtorPrivate(Ctor, OriginalAccess: Access),
170 NeedsFriend);
171 }
172}
173
174bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(
175 const LangOptions &LangOpts) const {
176 return LangOpts.CPlusPlus11;
177}
178} // namespace clang::tidy::bugprone
179

source code of clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp