1 | //===--- ReservedIdentifierCheck.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 "ReservedIdentifierCheck.h" |
10 | #include "../utils/OptionsUtils.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/Lex/Token.h" |
13 | #include <algorithm> |
14 | #include <cctype> |
15 | #include <optional> |
16 | |
17 | // FixItHint |
18 | |
19 | using namespace clang::ast_matchers; |
20 | |
21 | namespace clang::tidy::bugprone { |
22 | |
23 | static const char DoubleUnderscoreTag[] = "du"; |
24 | static const char UnderscoreCapitalTag[] = "uc"; |
25 | static const char GlobalUnderscoreTag[] = "global-under"; |
26 | static const char NonReservedTag[] = "non-reserved"; |
27 | |
28 | static const char Message[] = |
29 | "declaration uses identifier '%0', which is %select{a reserved " |
30 | "identifier|not a reserved identifier|reserved in the global namespace}1"; |
31 | |
32 | static int getMessageSelectIndex(StringRef Tag) { |
33 | if (Tag == NonReservedTag) |
34 | return 1; |
35 | if (Tag == GlobalUnderscoreTag) |
36 | return 2; |
37 | return 0; |
38 | } |
39 | |
40 | llvm::SmallVector<llvm::Regex> |
41 | ReservedIdentifierCheck::parseAllowedIdentifiers() const { |
42 | llvm::SmallVector<llvm::Regex> AllowedIdentifiers; |
43 | AllowedIdentifiers.reserve(N: AllowedIdentifiersRaw.size()); |
44 | |
45 | for (const auto &Identifier : AllowedIdentifiersRaw) { |
46 | AllowedIdentifiers.emplace_back(Args: Identifier.str()); |
47 | if (!AllowedIdentifiers.back().isValid()) { |
48 | configurationDiag(Description: "Invalid allowed identifier regex '%0'") << Identifier; |
49 | AllowedIdentifiers.pop_back(); |
50 | } |
51 | } |
52 | |
53 | return AllowedIdentifiers; |
54 | } |
55 | |
56 | ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name, |
57 | ClangTidyContext *Context) |
58 | : RenamerClangTidyCheck(Name, Context), |
59 | Invert(Options.get(LocalName: "Invert", Default: false)), |
60 | AllowedIdentifiersRaw(utils::options::parseStringList( |
61 | Option: Options.get(LocalName: "AllowedIdentifiers", Default: ""))), |
62 | AllowedIdentifiers(parseAllowedIdentifiers()) {} |
63 | |
64 | void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
65 | RenamerClangTidyCheck::storeOptions(Opts); |
66 | Options.store(Options&: Opts, LocalName: "Invert", Value: Invert); |
67 | Options.store(Options&: Opts, LocalName: "AllowedIdentifiers", |
68 | Value: utils::options::serializeStringList(Strings: AllowedIdentifiersRaw)); |
69 | } |
70 | |
71 | static std::string collapseConsecutive(StringRef Str, char C) { |
72 | std::string Result; |
73 | std::unique_copy(first: Str.begin(), last: Str.end(), result: std::back_inserter(x&: Result), |
74 | binary_pred: [C](char A, char B) { return A == C && B == C; }); |
75 | return Result; |
76 | } |
77 | |
78 | static bool hasReservedDoubleUnderscore(StringRef Name, |
79 | const LangOptions &LangOpts) { |
80 | if (LangOpts.CPlusPlus) |
81 | return Name.contains(Other: "__"); |
82 | return Name.starts_with(Prefix: "__"); |
83 | } |
84 | |
85 | static std::optional<std::string> |
86 | getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) { |
87 | if (hasReservedDoubleUnderscore(Name, LangOpts)) |
88 | return collapseConsecutive(Str: Name, C: '_'); |
89 | return std::nullopt; |
90 | } |
91 | |
92 | static bool startsWithUnderscoreCapital(StringRef Name) { |
93 | return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]); |
94 | } |
95 | |
96 | static std::optional<std::string> getUnderscoreCapitalFixup(StringRef Name) { |
97 | if (startsWithUnderscoreCapital(Name)) |
98 | return std::string(Name.drop_front(N: 1)); |
99 | return std::nullopt; |
100 | } |
101 | |
102 | static bool startsWithUnderscoreInGlobalNamespace(StringRef Name, |
103 | bool IsInGlobalNamespace, |
104 | bool IsMacro) { |
105 | return !IsMacro && IsInGlobalNamespace && Name.starts_with(Prefix: "_"); |
106 | } |
107 | |
108 | static std::optional<std::string> |
109 | getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace, |
110 | bool IsMacro) { |
111 | if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace, IsMacro)) |
112 | return std::string(Name.drop_front(N: 1)); |
113 | return std::nullopt; |
114 | } |
115 | |
116 | static std::string getNonReservedFixup(std::string Name) { |
117 | assert(!Name.empty()); |
118 | if (Name[0] == '_' || std::isupper(Name[0])) |
119 | Name.insert(p: Name.begin(), c: '_'); |
120 | else |
121 | Name.insert(p: Name.begin(), n: 2, c: '_'); |
122 | return Name; |
123 | } |
124 | |
125 | static std::optional<RenamerClangTidyCheck::FailureInfo> |
126 | getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace, bool IsMacro, |
127 | const LangOptions &LangOpts, bool Invert, |
128 | ArrayRef<llvm::Regex> AllowedIdentifiers) { |
129 | assert(!Name.empty()); |
130 | |
131 | if (llvm::any_of(Range&: AllowedIdentifiers, P: [&](const llvm::Regex &Regex) { |
132 | return Regex.match(String: Name); |
133 | })) { |
134 | return std::nullopt; |
135 | } |
136 | // TODO: Check for names identical to language keywords, and other names |
137 | // specifically reserved by language standards, e.g. C++ 'zombie names' and C |
138 | // future library directions |
139 | |
140 | using FailureInfo = RenamerClangTidyCheck::FailureInfo; |
141 | if (!Invert) { |
142 | std::optional<FailureInfo> Info; |
143 | auto AppendFailure = [&](StringRef Kind, std::string &&Fixup) { |
144 | if (!Info) { |
145 | Info = FailureInfo{.KindName: std::string(Kind), .Fixup: std::move(Fixup)}; |
146 | } else { |
147 | Info->KindName += Kind; |
148 | Info->Fixup = std::move(Fixup); |
149 | } |
150 | }; |
151 | auto InProgressFixup = [&] { |
152 | return llvm::transformOptional( |
153 | O: Info, |
154 | F: [](const FailureInfo &Info) { return StringRef(Info.Fixup); }) |
155 | .value_or(u&: Name); |
156 | }; |
157 | if (auto Fixup = getDoubleUnderscoreFixup(Name: InProgressFixup(), LangOpts)) |
158 | AppendFailure(DoubleUnderscoreTag, std::move(*Fixup)); |
159 | if (auto Fixup = getUnderscoreCapitalFixup(Name: InProgressFixup())) |
160 | AppendFailure(UnderscoreCapitalTag, std::move(*Fixup)); |
161 | if (auto Fixup = getUnderscoreGlobalNamespaceFixup( |
162 | Name: InProgressFixup(), IsInGlobalNamespace, IsMacro)) |
163 | AppendFailure(GlobalUnderscoreTag, std::move(*Fixup)); |
164 | |
165 | return Info; |
166 | } |
167 | if (!(hasReservedDoubleUnderscore(Name, LangOpts) || |
168 | startsWithUnderscoreCapital(Name) || |
169 | startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace, |
170 | IsMacro))) |
171 | return FailureInfo{.KindName: NonReservedTag, .Fixup: getNonReservedFixup(Name: std::string(Name))}; |
172 | return std::nullopt; |
173 | } |
174 | |
175 | std::optional<RenamerClangTidyCheck::FailureInfo> |
176 | ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl, |
177 | const SourceManager &) const { |
178 | assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() && |
179 | "Decl must be an explicit identifier with a name."); |
180 | // Implicit identifiers cannot fail. |
181 | if (Decl->isImplicit()) |
182 | return std::nullopt; |
183 | |
184 | return getFailureInfoImpl( |
185 | Decl->getName(), isa<TranslationUnitDecl>(Decl->getDeclContext()), |
186 | /*IsMacro = */ false, getLangOpts(), Invert, AllowedIdentifiers); |
187 | } |
188 | |
189 | std::optional<RenamerClangTidyCheck::FailureInfo> |
190 | ReservedIdentifierCheck::getMacroFailureInfo(const Token &MacroNameTok, |
191 | const SourceManager &) const { |
192 | return getFailureInfoImpl(Name: MacroNameTok.getIdentifierInfo()->getName(), IsInGlobalNamespace: true, |
193 | /*IsMacro = */ true, LangOpts: getLangOpts(), Invert, |
194 | AllowedIdentifiers); |
195 | } |
196 | |
197 | RenamerClangTidyCheck::DiagInfo |
198 | ReservedIdentifierCheck::getDiagInfo(const NamingCheckId &ID, |
199 | const NamingCheckFailure &Failure) const { |
200 | return DiagInfo{.Text: Message, .ApplyArgs: [&](DiagnosticBuilder &Diag) { |
201 | Diag << ID.second |
202 | << getMessageSelectIndex(Tag: Failure.Info.KindName); |
203 | }}; |
204 | } |
205 | |
206 | } // namespace clang::tidy::bugprone |
207 |
Definitions
- DoubleUnderscoreTag
- UnderscoreCapitalTag
- GlobalUnderscoreTag
- NonReservedTag
- Message
- getMessageSelectIndex
- parseAllowedIdentifiers
- ReservedIdentifierCheck
- storeOptions
- collapseConsecutive
- hasReservedDoubleUnderscore
- getDoubleUnderscoreFixup
- startsWithUnderscoreCapital
- getUnderscoreCapitalFixup
- startsWithUnderscoreInGlobalNamespace
- getUnderscoreGlobalNamespaceFixup
- getNonReservedFixup
- getFailureInfoImpl
- getDeclFailureInfo
- getMacroFailureInfo
Learn to use CMake with our Intro Training
Find out more