[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / bugprone / CrtpConstructorAccessibilityCheck.cpp
blob8eaf54fe0088a4d27d2153427ab24b6110eb1a90
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 //===----------------------------------------------------------------------===//
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;
20 });
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;
31 const auto *const TTPT =
32 dyn_cast<TemplateTypeParmType>(FriendType->getType());
34 return TTPT && TTPT->getDecl() == Param;
35 });
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;
46 return FriendType->getType()->getAsCXXRecordDecl() == Derived;
47 });
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 });
61 return AnyOf ? CRTP->getSpecializedTemplate()
62 ->getTemplateParameters()
63 ->getParam(Idx - 1)
64 : nullptr;
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(),
80 ASTCtx.getLangOpts())
81 : Ctor->getEndLoc();
82 Hints.emplace_back(FixItHint::CreateInsertion(
83 CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n'));
85 return Hints;
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);
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()) {
108 return;
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")
129 << HintFriend;
132 auto WithFriendHintIfNeeded =
133 [&](const DiagnosticBuilder &Diag,
134 bool NeedsFriend) -> const DiagnosticBuilder & {
135 if (NeedsFriend)
136 Diag << HintFriend;
138 return Diag;
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")
149 << NeedsFriend
150 << FixItHint::CreateInsertion(
151 CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(
153 (IsStruct ? "\nprivate:\n" : "\n") +
154 CRTPDeclaration->getNameAsString() + "() = default;\n" +
155 (IsStruct ? "public:\n" : "")),
156 NeedsFriend);
159 for (auto &&Ctor : CRTPDeclaration->ctors()) {
160 if (Ctor->getAccess() == AS_private)
161 continue;
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),
173 NeedsFriend);
177 bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(
178 const LangOptions &LangOpts) const {
179 return LangOpts.CPlusPlus11;
181 } // namespace clang::tidy::bugprone