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"
20 using namespace clang::ast_matchers
;
26 static const char DoubleUnderscoreTag
[] = "du";
27 static const char UnderscoreCapitalTag
[] = "uc";
28 static const char GlobalUnderscoreTag
[] = "global-under";
29 static const char NonReservedTag
[] = "non-reserved";
31 static const char Message
[] =
32 "declaration uses identifier '%0', which is %select{a reserved "
33 "identifier|not a reserved identifier|reserved in the global namespace}1";
35 static int getMessageSelectIndex(StringRef Tag
) {
36 if (Tag
== NonReservedTag
)
38 if (Tag
== GlobalUnderscoreTag
)
43 ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name
,
44 ClangTidyContext
*Context
)
45 : RenamerClangTidyCheck(Name
, Context
),
46 Invert(Options
.get("Invert", false)),
47 AllowedIdentifiers(utils::options::parseStringList(
48 Options
.get("AllowedIdentifiers", ""))) {}
50 void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
51 RenamerClangTidyCheck::storeOptions(Opts
);
52 Options
.store(Opts
, "Invert", Invert
);
53 Options
.store(Opts
, "AllowedIdentifiers",
54 utils::options::serializeStringList(AllowedIdentifiers
));
57 static std::string
collapseConsecutive(StringRef Str
, char C
) {
59 std::unique_copy(Str
.begin(), Str
.end(), std::back_inserter(Result
),
60 [C
](char A
, char B
) { return A
== C
&& B
== C
; });
64 static bool hasReservedDoubleUnderscore(StringRef Name
,
65 const LangOptions
&LangOpts
) {
66 if (LangOpts
.CPlusPlus
)
67 return Name
.contains("__");
68 return Name
.startswith("__");
71 static Optional
<std::string
>
72 getDoubleUnderscoreFixup(StringRef Name
, const LangOptions
&LangOpts
) {
73 if (hasReservedDoubleUnderscore(Name
, LangOpts
))
74 return collapseConsecutive(Name
, '_');
78 static bool startsWithUnderscoreCapital(StringRef Name
) {
79 return Name
.size() >= 2 && Name
[0] == '_' && std::isupper(Name
[1]);
82 static Optional
<std::string
> getUnderscoreCapitalFixup(StringRef Name
) {
83 if (startsWithUnderscoreCapital(Name
))
84 return std::string(Name
.drop_front(1));
88 static bool startsWithUnderscoreInGlobalNamespace(StringRef Name
,
89 bool IsInGlobalNamespace
) {
90 return IsInGlobalNamespace
&& Name
.size() >= 1 && Name
[0] == '_';
93 static Optional
<std::string
>
94 getUnderscoreGlobalNamespaceFixup(StringRef Name
, bool IsInGlobalNamespace
) {
95 if (startsWithUnderscoreInGlobalNamespace(Name
, IsInGlobalNamespace
))
96 return std::string(Name
.drop_front(1));
100 static std::string
getNonReservedFixup(std::string Name
) {
101 assert(!Name
.empty());
102 if (Name
[0] == '_' || std::isupper(Name
[0]))
103 Name
.insert(Name
.begin(), '_');
105 Name
.insert(Name
.begin(), 2, '_');
109 static Optional
<RenamerClangTidyCheck::FailureInfo
>
110 getFailureInfoImpl(StringRef Name
, bool IsInGlobalNamespace
,
111 const LangOptions
&LangOpts
, bool Invert
,
112 ArrayRef
<StringRef
> AllowedIdentifiers
) {
113 assert(!Name
.empty());
114 if (llvm::is_contained(AllowedIdentifiers
, Name
))
117 // TODO: Check for names identical to language keywords, and other names
118 // specifically reserved by language standards, e.g. C++ 'zombie names' and C
119 // future library directions
121 using FailureInfo
= RenamerClangTidyCheck::FailureInfo
;
123 Optional
<FailureInfo
> Info
;
124 auto AppendFailure
= [&](StringRef Kind
, std::string
&&Fixup
) {
126 Info
= FailureInfo
{std::string(Kind
), std::move(Fixup
)};
128 Info
->KindName
+= Kind
;
129 Info
->Fixup
= std::move(Fixup
);
132 auto InProgressFixup
= [&] {
135 [](const FailureInfo
&Info
) { return StringRef(Info
.Fixup
); })
138 if (auto Fixup
= getDoubleUnderscoreFixup(InProgressFixup(), LangOpts
))
139 AppendFailure(DoubleUnderscoreTag
, std::move(*Fixup
));
140 if (auto Fixup
= getUnderscoreCapitalFixup(InProgressFixup()))
141 AppendFailure(UnderscoreCapitalTag
, std::move(*Fixup
));
142 if (auto Fixup
= getUnderscoreGlobalNamespaceFixup(InProgressFixup(),
143 IsInGlobalNamespace
))
144 AppendFailure(GlobalUnderscoreTag
, std::move(*Fixup
));
148 if (!(hasReservedDoubleUnderscore(Name
, LangOpts
) ||
149 startsWithUnderscoreCapital(Name
) ||
150 startsWithUnderscoreInGlobalNamespace(Name
, IsInGlobalNamespace
)))
151 return FailureInfo
{NonReservedTag
, getNonReservedFixup(std::string(Name
))};
155 Optional
<RenamerClangTidyCheck::FailureInfo
>
156 ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl
*Decl
,
157 const SourceManager
&) const {
158 assert(Decl
&& Decl
->getIdentifier() && !Decl
->getName().empty() &&
159 !Decl
->isImplicit() &&
160 "Decl must be an explicit identifier with a name.");
161 return getFailureInfoImpl(Decl
->getName(),
162 isa
<TranslationUnitDecl
>(Decl
->getDeclContext()),
163 getLangOpts(), Invert
, AllowedIdentifiers
);
166 Optional
<RenamerClangTidyCheck::FailureInfo
>
167 ReservedIdentifierCheck::getMacroFailureInfo(const Token
&MacroNameTok
,
168 const SourceManager
&) const {
169 return getFailureInfoImpl(MacroNameTok
.getIdentifierInfo()->getName(), true,
170 getLangOpts(), Invert
, AllowedIdentifiers
);
173 RenamerClangTidyCheck::DiagInfo
174 ReservedIdentifierCheck::getDiagInfo(const NamingCheckId
&ID
,
175 const NamingCheckFailure
&Failure
) const {
176 return DiagInfo
{Message
, [&](DiagnosticBuilder
&Diag
) {
178 << getMessageSelectIndex(Failure
.Info
.KindName
);
182 } // namespace bugprone