1 //===--- ReservedIdentifierCheck.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 "ReservedIdentifierCheck.h"
10 #include "../utils/Matchers.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Token.h"
21 using namespace clang::ast_matchers
;
23 namespace clang::tidy::bugprone
{
25 static const char DoubleUnderscoreTag
[] = "du";
26 static const char UnderscoreCapitalTag
[] = "uc";
27 static const char GlobalUnderscoreTag
[] = "global-under";
28 static const char NonReservedTag
[] = "non-reserved";
30 static const char Message
[] =
31 "declaration uses identifier '%0', which is %select{a reserved "
32 "identifier|not a reserved identifier|reserved in the global namespace}1";
34 static int getMessageSelectIndex(StringRef Tag
) {
35 if (Tag
== NonReservedTag
)
37 if (Tag
== GlobalUnderscoreTag
)
42 llvm::SmallVector
<llvm::Regex
>
43 ReservedIdentifierCheck::parseAllowedIdentifiers() const {
44 llvm::SmallVector
<llvm::Regex
> AllowedIdentifiers
;
45 AllowedIdentifiers
.reserve(AllowedIdentifiersRaw
.size());
47 for (const auto &Identifier
: AllowedIdentifiersRaw
) {
48 AllowedIdentifiers
.emplace_back(Identifier
.str());
49 if (!AllowedIdentifiers
.back().isValid()) {
50 configurationDiag("Invalid allowed identifier regex '%0'") << Identifier
;
51 AllowedIdentifiers
.pop_back();
55 return AllowedIdentifiers
;
58 ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name
,
59 ClangTidyContext
*Context
)
60 : RenamerClangTidyCheck(Name
, Context
),
61 Invert(Options
.get("Invert", false)),
62 AllowedIdentifiersRaw(utils::options::parseStringList(
63 Options
.get("AllowedIdentifiers", ""))),
64 AllowedIdentifiers(parseAllowedIdentifiers()) {}
66 void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
67 RenamerClangTidyCheck::storeOptions(Opts
);
68 Options
.store(Opts
, "Invert", Invert
);
69 Options
.store(Opts
, "AllowedIdentifiers",
70 utils::options::serializeStringList(AllowedIdentifiersRaw
));
73 static std::string
collapseConsecutive(StringRef Str
, char C
) {
75 std::unique_copy(Str
.begin(), Str
.end(), std::back_inserter(Result
),
76 [C
](char A
, char B
) { return A
== C
&& B
== C
; });
80 static bool hasReservedDoubleUnderscore(StringRef Name
,
81 const LangOptions
&LangOpts
) {
82 if (LangOpts
.CPlusPlus
)
83 return Name
.contains("__");
84 return Name
.starts_with("__");
87 static std::optional
<std::string
>
88 getDoubleUnderscoreFixup(StringRef Name
, const LangOptions
&LangOpts
) {
89 if (hasReservedDoubleUnderscore(Name
, LangOpts
))
90 return collapseConsecutive(Name
, '_');
94 static bool startsWithUnderscoreCapital(StringRef Name
) {
95 return Name
.size() >= 2 && Name
[0] == '_' && std::isupper(Name
[1]);
98 static std::optional
<std::string
> getUnderscoreCapitalFixup(StringRef Name
) {
99 if (startsWithUnderscoreCapital(Name
))
100 return std::string(Name
.drop_front(1));
104 static bool startsWithUnderscoreInGlobalNamespace(StringRef Name
,
105 bool IsInGlobalNamespace
,
107 return !IsMacro
&& IsInGlobalNamespace
&& Name
.starts_with("_");
110 static std::optional
<std::string
>
111 getUnderscoreGlobalNamespaceFixup(StringRef Name
, bool IsInGlobalNamespace
,
113 if (startsWithUnderscoreInGlobalNamespace(Name
, IsInGlobalNamespace
, IsMacro
))
114 return std::string(Name
.drop_front(1));
118 static std::string
getNonReservedFixup(std::string Name
) {
119 assert(!Name
.empty());
120 if (Name
[0] == '_' || std::isupper(Name
[0]))
121 Name
.insert(Name
.begin(), '_');
123 Name
.insert(Name
.begin(), 2, '_');
127 static std::optional
<RenamerClangTidyCheck::FailureInfo
>
128 getFailureInfoImpl(StringRef Name
, bool IsInGlobalNamespace
, bool IsMacro
,
129 const LangOptions
&LangOpts
, bool Invert
,
130 ArrayRef
<llvm::Regex
> AllowedIdentifiers
) {
131 assert(!Name
.empty());
133 if (llvm::any_of(AllowedIdentifiers
, [&](const llvm::Regex
&Regex
) {
134 return Regex
.match(Name
);
138 // TODO: Check for names identical to language keywords, and other names
139 // specifically reserved by language standards, e.g. C++ 'zombie names' and C
140 // future library directions
142 using FailureInfo
= RenamerClangTidyCheck::FailureInfo
;
144 std::optional
<FailureInfo
> Info
;
145 auto AppendFailure
= [&](StringRef Kind
, std::string
&&Fixup
) {
147 Info
= FailureInfo
{std::string(Kind
), std::move(Fixup
)};
149 Info
->KindName
+= Kind
;
150 Info
->Fixup
= std::move(Fixup
);
153 auto InProgressFixup
= [&] {
154 return llvm::transformOptional(
156 [](const FailureInfo
&Info
) { return StringRef(Info
.Fixup
); })
159 if (auto Fixup
= getDoubleUnderscoreFixup(InProgressFixup(), LangOpts
))
160 AppendFailure(DoubleUnderscoreTag
, std::move(*Fixup
));
161 if (auto Fixup
= getUnderscoreCapitalFixup(InProgressFixup()))
162 AppendFailure(UnderscoreCapitalTag
, std::move(*Fixup
));
163 if (auto Fixup
= getUnderscoreGlobalNamespaceFixup(
164 InProgressFixup(), IsInGlobalNamespace
, IsMacro
))
165 AppendFailure(GlobalUnderscoreTag
, std::move(*Fixup
));
169 if (!(hasReservedDoubleUnderscore(Name
, LangOpts
) ||
170 startsWithUnderscoreCapital(Name
) ||
171 startsWithUnderscoreInGlobalNamespace(Name
, IsInGlobalNamespace
,
173 return FailureInfo
{NonReservedTag
, getNonReservedFixup(std::string(Name
))};
177 std::optional
<RenamerClangTidyCheck::FailureInfo
>
178 ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl
*Decl
,
179 const SourceManager
&) const {
180 assert(Decl
&& Decl
->getIdentifier() && !Decl
->getName().empty() &&
181 "Decl must be an explicit identifier with a name.");
182 // Implicit identifiers cannot fail.
183 if (Decl
->isImplicit())
186 return getFailureInfoImpl(
187 Decl
->getName(), isa
<TranslationUnitDecl
>(Decl
->getDeclContext()),
188 /*IsMacro = */ false, getLangOpts(), Invert
, AllowedIdentifiers
);
191 std::optional
<RenamerClangTidyCheck::FailureInfo
>
192 ReservedIdentifierCheck::getMacroFailureInfo(const Token
&MacroNameTok
,
193 const SourceManager
&) const {
194 return getFailureInfoImpl(MacroNameTok
.getIdentifierInfo()->getName(), true,
195 /*IsMacro = */ true, getLangOpts(), Invert
,
199 RenamerClangTidyCheck::DiagInfo
200 ReservedIdentifierCheck::getDiagInfo(const NamingCheckId
&ID
,
201 const NamingCheckFailure
&Failure
) const {
202 return DiagInfo
{Message
, [&](DiagnosticBuilder
&Diag
) {
204 << getMessageSelectIndex(Failure
.Info
.KindName
);
208 } // namespace clang::tidy::bugprone