1 //===--- CrtpConstructorAccessibilityCheck.cpp - clang-tidy ---------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "CrtpConstructorAccessibilityCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 using namespace clang::ast_matchers
;
15 namespace clang::tidy::bugprone
{
17 static bool hasPrivateConstructor(const CXXRecordDecl
*RD
) {
18 return llvm::any_of(RD
->ctors(), [](const CXXConstructorDecl
*Ctor
) {
19 return Ctor
->getAccess() == AS_private
;
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();
31 const auto *const TTPT
=
32 dyn_cast
<TemplateTypeParmType
>(FriendType
->getType());
34 return TTPT
&& TTPT
->getDecl() == Param
;
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();
46 return FriendType
->getType()->getAsCXXRecordDecl() == Derived
;
50 static const NamedDecl
*
51 getDerivedParameter(const ClassTemplateSpecializationDecl
*CRTP
,
52 const CXXRecordDecl
*Derived
) {
54 const bool AnyOf
= llvm::any_of(
55 CRTP
->getTemplateArgs().asArray(), [&](const TemplateArgument
&Arg
) {
57 return Arg
.getKind() == TemplateArgument::Type
&&
58 Arg
.getAsType()->getAsCXXRecordDecl() == Derived
;
61 return AnyOf
? CRTP
->getSpecializedTemplate()
62 ->getTemplateParameters()
67 static std::vector
<FixItHint
>
68 hintMakeCtorPrivate(const CXXConstructorDecl
*Ctor
,
69 const std::string
&OriginalAccess
) {
70 std::vector
<FixItHint
> Hints
;
72 Hints
.emplace_back(FixItHint::CreateInsertion(
73 Ctor
->getBeginLoc().getLocWithOffset(-1), "private:\n"));
75 const ASTContext
&ASTCtx
= Ctor
->getASTContext();
76 const SourceLocation CtorEndLoc
=
77 Ctor
->isExplicitlyDefaulted()
78 ? utils::lexer::findNextTerminator(Ctor
->getEndLoc(),
79 ASTCtx
.getSourceManager(),
82 Hints
.emplace_back(FixItHint::CreateInsertion(
83 CtorEndLoc
.getLocWithOffset(1), '\n' + OriginalAccess
+ ':' + '\n'));
88 void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder
*Finder
) {
90 classTemplateSpecializationDecl(
92 hasAnyTemplateArgument(refersToType(recordType(hasDeclaration(
94 isDerivedFrom(cxxRecordDecl(equalsBoundNode("crtp"))))
95 .bind("derived")))))),
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();
107 if (!CRTPDeclaration
->hasDefinition()) {
111 const auto *DerivedTemplateParameter
=
112 getDerivedParameter(CRTPInstantiation
, DerivedRecord
);
114 assert(DerivedTemplateParameter
&&
115 "No template parameter corresponds to the derived class of the CRTP.");
117 bool NeedsFriend
= !isDerivedParameterBefriended(CRTPDeclaration
,
118 DerivedTemplateParameter
) &&
119 !isDerivedClassBefriended(CRTPDeclaration
, DerivedRecord
);
121 const FixItHint HintFriend
= FixItHint::CreateInsertion(
122 CRTPDeclaration
->getBraceRange().getEnd(),
123 "friend " + DerivedTemplateParameter
->getNameAsString() + ';' + '\n');
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")
132 auto WithFriendHintIfNeeded
=
133 [&](const DiagnosticBuilder
&Diag
,
134 bool NeedsFriend
) -> const DiagnosticBuilder
& {
141 if (!CRTPDeclaration
->hasUserDeclaredConstructor()) {
142 const bool IsStruct
= CRTPDeclaration
->isStruct();
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")
150 << FixItHint::CreateInsertion(
151 CRTPDeclaration
->getBraceRange().getBegin().getLocWithOffset(
153 (IsStruct
? "\nprivate:\n" : "\n") +
154 CRTPDeclaration
->getNameAsString() + "() = default;\n" +
155 (IsStruct
? "public:\n" : "")),
159 for (auto &&Ctor
: CRTPDeclaration
->ctors()) {
160 if (Ctor
->getAccess() == AS_private
)
163 const bool IsPublic
= Ctor
->getAccess() == AS_public
;
164 const std::string Access
= IsPublic
? "public" : "protected";
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
),
177 bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(
178 const LangOptions
&LangOpts
) const {
179 return LangOpts
.CPlusPlus11
;
181 } // namespace clang::tidy::bugprone