xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp (revision 57d109c4c93c8cf2d7ff9e5877a48152ed7a617a)
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 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::bugprone {
16 
17 static bool hasPrivateConstructor(const CXXRecordDecl *RD) {
18   return llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) {
19     return Ctor->getAccess() == AS_private;
20   });
21 }
22 
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;
29     }
30 
31     const auto *const TTPT =
32         dyn_cast<TemplateTypeParmType>(FriendType->getType());
33 
34     return TTPT && TTPT->getDecl() == Param;
35   });
36 }
37 
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;
44     }
45 
46     return FriendType->getType()->getAsCXXRecordDecl() == Derived;
47   });
48 }
49 
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       });
60 
61   return AnyOf ? CRTP->getSpecializedTemplate()
62                      ->getTemplateParameters()
63                      ->getParam(Idx - 1)
64                : nullptr;
65 }
66 
67 static std::vector<FixItHint>
68 hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,
69                     const std::string &OriginalAccess) {
70   std::vector<FixItHint> Hints;
71 
72   Hints.emplace_back(FixItHint::CreateInsertion(
73       Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n"));
74 
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'));
84 
85   return Hints;
86 }
87 
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);
97 }
98 
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();
106 
107   if (!CRTPDeclaration->hasDefinition()) {
108     return;
109   }
110 
111   const auto *DerivedTemplateParameter =
112       getDerivedParameter(CRTPInstantiation, DerivedRecord);
113 
114   assert(DerivedTemplateParameter &&
115          "No template parameter corresponds to the derived class of the CRTP.");
116 
117   bool NeedsFriend = !isDerivedParameterBefriended(CRTPDeclaration,
118                                                    DerivedTemplateParameter) &&
119                      !isDerivedClassBefriended(CRTPDeclaration, DerivedRecord);
120 
121   const FixItHint HintFriend = FixItHint::CreateInsertion(
122       CRTPDeclaration->getBraceRange().getEnd(),
123       "friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n');
124 
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;
130   }
131 
132   auto WithFriendHintIfNeeded =
133       [&](const DiagnosticBuilder &Diag,
134           bool NeedsFriend) -> const DiagnosticBuilder & {
135     if (NeedsFriend)
136       Diag << HintFriend;
137 
138     return Diag;
139   };
140 
141   if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
142     const bool IsStruct = CRTPDeclaration->isStruct();
143 
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(
152                        1),
153                    (IsStruct ? "\nprivate:\n" : "\n") +
154                        CRTPDeclaration->getNameAsString() + "() = default;\n" +
155                        (IsStruct ? "public:\n" : "")),
156         NeedsFriend);
157   }
158 
159   for (auto &&Ctor : CRTPDeclaration->ctors()) {
160     if (Ctor->getAccess() == AS_private)
161       continue;
162 
163     const bool IsPublic = Ctor->getAccess() == AS_public;
164     const std::string Access = IsPublic ? "public" : "protected";
165 
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);
174   }
175 }
176 
177 bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(
178     const LangOptions &LangOpts) const {
179   return LangOpts.CPlusPlus11;
180 }
181 } // namespace clang::tidy::bugprone
182