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
21using namespace clang::ast_matchers;
22
23namespace clang::tidy::bugprone {
24
25static const char DoubleUnderscoreTag[] = "du";
26static const char UnderscoreCapitalTag[] = "uc";
27static const char GlobalUnderscoreTag[] = "global-under";
28static const char NonReservedTag[] = "non-reserved";
29
30static 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
34static int getMessageSelectIndex(StringRef Tag) {
35 if (Tag == NonReservedTag)
36 return 1;
37 if (Tag == GlobalUnderscoreTag)
38 return 2;
39 return 0;
40}
41
42llvm::SmallVector<llvm::Regex>
43ReservedIdentifierCheck::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
58ReservedIdentifierCheck::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
66void 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
73static 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
80static 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
87static std::optional<std::string>
88getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
89 if (hasReservedDoubleUnderscore(Name, LangOpts))
90 return collapseConsecutive(Str: Name, C: '_');
91 return std::nullopt;
92}
93
94static bool startsWithUnderscoreCapital(StringRef Name) {
95 return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
96}
97
98static 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
104static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
105 bool IsInGlobalNamespace,
106 bool IsMacro) {
107 return !IsMacro && IsInGlobalNamespace && Name.starts_with(Prefix: "_");
108}
109
110static std::optional<std::string>
111getUnderscoreGlobalNamespaceFixup(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
118static 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
127static std::optional<RenamerClangTidyCheck::FailureInfo>
128getFailureInfoImpl(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
177std::optional<RenamerClangTidyCheck::FailureInfo>
178ReservedIdentifierCheck::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
188std::optional<RenamerClangTidyCheck::FailureInfo>
189ReservedIdentifierCheck::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
196RenamerClangTidyCheck::DiagInfo
197ReservedIdentifierCheck::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

source code of clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp