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