clang-tools 22.0.0git
ReservedIdentifierCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
19using namespace clang::ast_matchers;
20
21namespace clang::tidy::bugprone {
22
23static const char DoubleUnderscoreTag[] = "du";
24static const char UnderscoreCapitalTag[] = "uc";
25static const char GlobalUnderscoreTag[] = "global-under";
26static const char NonReservedTag[] = "non-reserved";
27
28static 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
32static int getMessageSelectIndex(StringRef Tag) {
33 if (Tag == NonReservedTag)
34 return 1;
35 if (Tag == GlobalUnderscoreTag)
36 return 2;
37 return 0;
38}
39
40llvm::SmallVector<llvm::Regex>
41ReservedIdentifierCheck::parseAllowedIdentifiers() const {
42 llvm::SmallVector<llvm::Regex> AllowedIdentifiers;
43 AllowedIdentifiers.reserve(AllowedIdentifiersRaw.size());
44
45 for (const auto &Identifier : AllowedIdentifiersRaw) {
46 AllowedIdentifiers.emplace_back(Identifier.str());
47 if (!AllowedIdentifiers.back().isValid()) {
48 configurationDiag("Invalid allowed identifier regex '%0'") << Identifier;
49 AllowedIdentifiers.pop_back();
50 }
51 }
52
53 return AllowedIdentifiers;
54}
55
57 ClangTidyContext *Context)
58 : RenamerClangTidyCheck(Name, Context),
59 Invert(Options.get("Invert", false)),
60 AllowedIdentifiersRaw(utils::options::parseStringList(
61 Options.get("AllowedIdentifiers", ""))),
62 AllowedIdentifiers(parseAllowedIdentifiers()) {}
63
66 Options.store(Opts, "Invert", Invert);
67 Options.store(Opts, "AllowedIdentifiers",
68 utils::options::serializeStringList(AllowedIdentifiersRaw));
69}
70
71static std::string collapseConsecutive(StringRef Str, char C) {
72 std::string Result;
73 std::unique_copy(Str.begin(), Str.end(), std::back_inserter(Result),
74 [C](char A, char B) { return A == C && B == C; });
75 return Result;
76}
77
78static bool hasReservedDoubleUnderscore(StringRef Name,
79 const LangOptions &LangOpts) {
80 if (LangOpts.CPlusPlus)
81 return Name.contains("__");
82 return Name.starts_with("__");
83}
84
85static std::optional<std::string>
86getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
87 if (hasReservedDoubleUnderscore(Name, LangOpts))
88 return collapseConsecutive(Name, '_');
89 return std::nullopt;
90}
91
92static bool startsWithUnderscoreCapital(StringRef Name) {
93 return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
94}
95
96static std::optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {
98 return std::string(Name.drop_front(1));
99 return std::nullopt;
100}
101
102static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
103 bool IsInGlobalNamespace,
104 bool IsMacro) {
105 return !IsMacro && IsInGlobalNamespace && Name.starts_with("_");
106}
107
108static std::optional<std::string>
109getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace,
110 bool IsMacro) {
111 if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace, IsMacro))
112 return std::string(Name.drop_front(1));
113 return std::nullopt;
114}
115
116static std::string getNonReservedFixup(std::string Name) {
117 assert(!Name.empty());
118 if (Name[0] == '_' || std::isupper(Name[0]))
119 Name.insert(Name.begin(), '_');
120 else
121 Name.insert(Name.begin(), 2, '_');
122 return Name;
123}
124
125static std::optional<RenamerClangTidyCheck::FailureInfo>
126getFailureInfoImpl(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(AllowedIdentifiers, [&](const llvm::Regex &Regex) {
132 return Regex.match(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{std::string(Kind), 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 Info,
154 [](const FailureInfo &Info) { return StringRef(Info.Fixup); })
155 .value_or(Name);
156 };
157 if (auto Fixup = getDoubleUnderscoreFixup(InProgressFixup(), LangOpts))
158 AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));
159 if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))
160 AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));
161 if (auto Fixup = getUnderscoreGlobalNamespaceFixup(
162 InProgressFixup(), IsInGlobalNamespace, IsMacro))
163 AppendFailure(GlobalUnderscoreTag, std::move(*Fixup));
164
165 return Info;
166 }
167 if (!(hasReservedDoubleUnderscore(Name, LangOpts) ||
169 startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace,
170 IsMacro)))
171 return FailureInfo{NonReservedTag, getNonReservedFixup(std::string(Name))};
172 return std::nullopt;
173}
174
175std::optional<RenamerClangTidyCheck::FailureInfo>
176ReservedIdentifierCheck::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
189std::optional<RenamerClangTidyCheck::FailureInfo>
190ReservedIdentifierCheck::getMacroFailureInfo(const Token &MacroNameTok,
191 const SourceManager &) const {
192 return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,
193 /*IsMacro = */ true, getLangOpts(), Invert,
194 AllowedIdentifiers);
195}
196
197RenamerClangTidyCheck::DiagInfo
198ReservedIdentifierCheck::getDiagInfo(const NamingCheckId &ID,
199 const NamingCheckFailure &Failure) const {
200 return DiagInfo{Message, [&](DiagnosticBuilder &Diag) {
201 Diag << ID.second
202 << getMessageSelectIndex(Failure.Info.KindName);
203 }};
204}
205
206} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
RenamerClangTidyCheck(StringRef CheckName, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Derived classes that override this function should call this method from the overridden method.
ReservedIdentifierCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
static std::optional< std::string > getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts)
static const char DoubleUnderscoreTag[]
static const char GlobalUnderscoreTag[]
static std::optional< std::string > getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace, bool IsMacro)
static const char UnderscoreCapitalTag[]
static std::string getNonReservedFixup(std::string Name)
static const char NonReservedTag[]
static std::optional< RenamerClangTidyCheck::FailureInfo > getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace, bool IsMacro, const LangOptions &LangOpts, bool Invert, ArrayRef< llvm::Regex > AllowedIdentifiers)
static std::string collapseConsecutive(StringRef Str, char C)
static bool hasReservedDoubleUnderscore(StringRef Name, const LangOptions &LangOpts)
static bool startsWithUnderscoreCapital(StringRef Name)
static bool startsWithUnderscoreInGlobalNamespace(StringRef Name, bool IsInGlobalNamespace, bool IsMacro)
static std::optional< std::string > getUnderscoreCapitalFixup(StringRef Name)
static int getMessageSelectIndex(StringRef Tag)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap
Information describing a failed check.