xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp (revision b1bc1dbea6d0423813bb73d625c6eedc040007ed)
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 
getMessageSelectIndex(StringRef Tag)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>
parseAllowedIdentifiers() const43 ReservedIdentifierCheck::parseAllowedIdentifiers() const {
44   llvm::SmallVector<llvm::Regex> AllowedIdentifiers;
45   AllowedIdentifiers.reserve(AllowedIdentifiersRaw.size());
46 
47   for (const auto &Identifier : AllowedIdentifiersRaw) {
48     AllowedIdentifiers.emplace_back(Identifier.str());
49     if (!AllowedIdentifiers.back().isValid()) {
50       configurationDiag("Invalid allowed identifier regex '%0'") << Identifier;
51       AllowedIdentifiers.pop_back();
52     }
53   }
54 
55   return AllowedIdentifiers;
56 }
57 
ReservedIdentifierCheck(StringRef Name,ClangTidyContext * Context)58 ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name,
59                                                  ClangTidyContext *Context)
60     : RenamerClangTidyCheck(Name, Context),
61       Invert(Options.get("Invert", false)),
62       AllowedIdentifiersRaw(utils::options::parseStringList(
63           Options.get("AllowedIdentifiers", ""))),
64       AllowedIdentifiers(parseAllowedIdentifiers()) {}
65 
storeOptions(ClangTidyOptions::OptionMap & Opts)66 void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
67   RenamerClangTidyCheck::storeOptions(Opts);
68   Options.store(Opts, "Invert", Invert);
69   Options.store(Opts, "AllowedIdentifiers",
70                 utils::options::serializeStringList(AllowedIdentifiersRaw));
71 }
72 
collapseConsecutive(StringRef Str,char C)73 static std::string collapseConsecutive(StringRef Str, char C) {
74   std::string Result;
75   std::unique_copy(Str.begin(), Str.end(), std::back_inserter(Result),
76                    [C](char A, char B) { return A == C && B == C; });
77   return Result;
78 }
79 
hasReservedDoubleUnderscore(StringRef Name,const LangOptions & LangOpts)80 static bool hasReservedDoubleUnderscore(StringRef Name,
81                                         const LangOptions &LangOpts) {
82   if (LangOpts.CPlusPlus)
83     return Name.contains("__");
84   return Name.starts_with("__");
85 }
86 
87 static std::optional<std::string>
getDoubleUnderscoreFixup(StringRef Name,const LangOptions & LangOpts)88 getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
89   if (hasReservedDoubleUnderscore(Name, LangOpts))
90     return collapseConsecutive(Name, '_');
91   return std::nullopt;
92 }
93 
startsWithUnderscoreCapital(StringRef Name)94 static bool startsWithUnderscoreCapital(StringRef Name) {
95   return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
96 }
97 
getUnderscoreCapitalFixup(StringRef Name)98 static std::optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {
99   if (startsWithUnderscoreCapital(Name))
100     return std::string(Name.drop_front(1));
101   return std::nullopt;
102 }
103 
startsWithUnderscoreInGlobalNamespace(StringRef Name,bool IsInGlobalNamespace,bool IsMacro)104 static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
105                                                   bool IsInGlobalNamespace,
106                                                   bool IsMacro) {
107   return !IsMacro && IsInGlobalNamespace && Name.starts_with("_");
108 }
109 
110 static std::optional<std::string>
getUnderscoreGlobalNamespaceFixup(StringRef Name,bool IsInGlobalNamespace,bool IsMacro)111 getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace,
112                                   bool IsMacro) {
113   if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace, IsMacro))
114     return std::string(Name.drop_front(1));
115   return std::nullopt;
116 }
117 
getNonReservedFixup(std::string Name)118 static std::string getNonReservedFixup(std::string Name) {
119   assert(!Name.empty());
120   if (Name[0] == '_' || std::isupper(Name[0]))
121     Name.insert(Name.begin(), '_');
122   else
123     Name.insert(Name.begin(), 2, '_');
124   return Name;
125 }
126 
127 static std::optional<RenamerClangTidyCheck::FailureInfo>
getFailureInfoImpl(StringRef Name,bool IsInGlobalNamespace,bool IsMacro,const LangOptions & LangOpts,bool Invert,ArrayRef<llvm::Regex> AllowedIdentifiers)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(AllowedIdentifiers, [&](const llvm::Regex &Regex) {
134         return Regex.match(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{std::string(Kind), 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                  Info,
156                  [](const FailureInfo &Info) { return StringRef(Info.Fixup); })
157           .value_or(Name);
158     };
159     if (auto Fixup = getDoubleUnderscoreFixup(InProgressFixup(), LangOpts))
160       AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));
161     if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))
162       AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));
163     if (auto Fixup = getUnderscoreGlobalNamespaceFixup(
164             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{NonReservedTag, getNonReservedFixup(std::string(Name))};
174   return std::nullopt;
175 }
176 
177 std::optional<RenamerClangTidyCheck::FailureInfo>
getDeclFailureInfo(const NamedDecl * Decl,const SourceManager &) const178 ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl,
179                                             const SourceManager &) const {
180   assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&
181          "Decl must be an explicit identifier with a name.");
182   // Implicit identifiers cannot fail.
183   if (Decl->isImplicit())
184     return std::nullopt;
185 
186   return getFailureInfoImpl(
187       Decl->getName(), isa<TranslationUnitDecl>(Decl->getDeclContext()),
188       /*IsMacro = */ false, getLangOpts(), Invert, AllowedIdentifiers);
189 }
190 
191 std::optional<RenamerClangTidyCheck::FailureInfo>
getMacroFailureInfo(const Token & MacroNameTok,const SourceManager &) const192 ReservedIdentifierCheck::getMacroFailureInfo(const Token &MacroNameTok,
193                                              const SourceManager &) const {
194   return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,
195                             /*IsMacro = */ true, getLangOpts(), Invert,
196                             AllowedIdentifiers);
197 }
198 
199 RenamerClangTidyCheck::DiagInfo
getDiagInfo(const NamingCheckId & ID,const NamingCheckFailure & Failure) const200 ReservedIdentifierCheck::getDiagInfo(const NamingCheckId &ID,
201                                      const NamingCheckFailure &Failure) const {
202   return DiagInfo{Message, [&](DiagnosticBuilder &Diag) {
203                     Diag << ID.second
204                          << getMessageSelectIndex(Failure.Info.KindName);
205                   }};
206 }
207 
208 } // namespace clang::tidy::bugprone
209