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 
18 // FixItHint
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang {
23 namespace tidy {
24 namespace bugprone {
25 
26 static const char DoubleUnderscoreTag[] = "du";
27 static const char UnderscoreCapitalTag[] = "uc";
28 static const char GlobalUnderscoreTag[] = "global-under";
29 static const char NonReservedTag[] = "non-reserved";
30 
31 static const char Message[] =
32     "declaration uses identifier '%0', which is %select{a reserved "
33     "identifier|not a reserved identifier|reserved in the global namespace}1";
34 
getMessageSelectIndex(StringRef Tag)35 static int getMessageSelectIndex(StringRef Tag) {
36   if (Tag == NonReservedTag)
37     return 1;
38   if (Tag == GlobalUnderscoreTag)
39     return 2;
40   return 0;
41 }
42 
ReservedIdentifierCheck(StringRef Name,ClangTidyContext * Context)43 ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name,
44                                                  ClangTidyContext *Context)
45     : RenamerClangTidyCheck(Name, Context),
46       Invert(Options.get("Invert", false)),
47       AllowedIdentifiers(utils::options::parseStringList(
48           Options.get("AllowedIdentifiers", ""))) {}
49 
storeOptions(ClangTidyOptions::OptionMap & Opts)50 void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
51   RenamerClangTidyCheck::storeOptions(Opts);
52   Options.store(Opts, "Invert", Invert);
53   Options.store(Opts, "AllowedIdentifiers",
54                 utils::options::serializeStringList(AllowedIdentifiers));
55 }
56 
collapseConsecutive(StringRef Str,char C)57 static std::string collapseConsecutive(StringRef Str, char C) {
58   std::string Result;
59   std::unique_copy(Str.begin(), Str.end(), std::back_inserter(Result),
60                    [C](char A, char B) { return A == C && B == C; });
61   return Result;
62 }
63 
hasReservedDoubleUnderscore(StringRef Name,const LangOptions & LangOpts)64 static bool hasReservedDoubleUnderscore(StringRef Name,
65                                         const LangOptions &LangOpts) {
66   if (LangOpts.CPlusPlus)
67     return Name.find("__") != StringRef::npos;
68   return Name.startswith("__");
69 }
70 
71 static Optional<std::string>
getDoubleUnderscoreFixup(StringRef Name,const LangOptions & LangOpts)72 getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
73   if (hasReservedDoubleUnderscore(Name, LangOpts))
74     return collapseConsecutive(Name, '_');
75   return None;
76 }
77 
startsWithUnderscoreCapital(StringRef Name)78 static bool startsWithUnderscoreCapital(StringRef Name) {
79   return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
80 }
81 
getUnderscoreCapitalFixup(StringRef Name)82 static Optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {
83   if (startsWithUnderscoreCapital(Name))
84     return std::string(Name.drop_front(1));
85   return None;
86 }
87 
startsWithUnderscoreInGlobalNamespace(StringRef Name,bool IsInGlobalNamespace)88 static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
89                                                   bool IsInGlobalNamespace) {
90   return IsInGlobalNamespace && Name.size() >= 1 && Name[0] == '_';
91 }
92 
93 static Optional<std::string>
getUnderscoreGlobalNamespaceFixup(StringRef Name,bool IsInGlobalNamespace)94 getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace) {
95   if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace))
96     return std::string(Name.drop_front(1));
97   return None;
98 }
99 
getNonReservedFixup(std::string Name)100 static std::string getNonReservedFixup(std::string Name) {
101   assert(!Name.empty());
102   if (Name[0] == '_' || std::isupper(Name[0]))
103     Name.insert(Name.begin(), '_');
104   else
105     Name.insert(Name.begin(), 2, '_');
106   return Name;
107 }
108 
109 static Optional<RenamerClangTidyCheck::FailureInfo>
getFailureInfoImpl(StringRef Name,bool IsInGlobalNamespace,const LangOptions & LangOpts,bool Invert,ArrayRef<std::string> AllowedIdentifiers)110 getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace,
111                    const LangOptions &LangOpts, bool Invert,
112                    ArrayRef<std::string> AllowedIdentifiers) {
113   assert(!Name.empty());
114   if (llvm::is_contained(AllowedIdentifiers, Name))
115     return None;
116 
117   // TODO: Check for names identical to language keywords, and other names
118   // specifically reserved by language standards, e.g. C++ 'zombie names' and C
119   // future library directions
120 
121   using FailureInfo = RenamerClangTidyCheck::FailureInfo;
122   if (!Invert) {
123     Optional<FailureInfo> Info;
124     auto AppendFailure = [&](StringRef Kind, std::string &&Fixup) {
125       if (!Info) {
126         Info = FailureInfo{std::string(Kind), std::move(Fixup)};
127       } else {
128         Info->KindName += Kind;
129         Info->Fixup = std::move(Fixup);
130       }
131     };
132     auto InProgressFixup = [&] {
133       return Info
134           .map([](const FailureInfo &Info) { return StringRef(Info.Fixup); })
135           .getValueOr(Name);
136     };
137     if (auto Fixup = getDoubleUnderscoreFixup(InProgressFixup(), LangOpts))
138       AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));
139     if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))
140       AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));
141     if (auto Fixup = getUnderscoreGlobalNamespaceFixup(InProgressFixup(),
142                                                        IsInGlobalNamespace))
143       AppendFailure(GlobalUnderscoreTag, std::move(*Fixup));
144 
145     return Info;
146   }
147   if (!(hasReservedDoubleUnderscore(Name, LangOpts) ||
148         startsWithUnderscoreCapital(Name) ||
149         startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace)))
150     return FailureInfo{NonReservedTag, getNonReservedFixup(std::string(Name))};
151   return None;
152 }
153 
154 Optional<RenamerClangTidyCheck::FailureInfo>
GetDeclFailureInfo(const NamedDecl * Decl,const SourceManager &) const155 ReservedIdentifierCheck::GetDeclFailureInfo(const NamedDecl *Decl,
156                                             const SourceManager &) const {
157   assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&
158          !Decl->isImplicit() &&
159          "Decl must be an explicit identifier with a name.");
160   return getFailureInfoImpl(Decl->getName(),
161                             isa<TranslationUnitDecl>(Decl->getDeclContext()),
162                             getLangOpts(), Invert, AllowedIdentifiers);
163 }
164 
165 Optional<RenamerClangTidyCheck::FailureInfo>
GetMacroFailureInfo(const Token & MacroNameTok,const SourceManager &) const166 ReservedIdentifierCheck::GetMacroFailureInfo(const Token &MacroNameTok,
167                                              const SourceManager &) const {
168   return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,
169                             getLangOpts(), Invert, AllowedIdentifiers);
170 }
171 
172 RenamerClangTidyCheck::DiagInfo
GetDiagInfo(const NamingCheckId & ID,const NamingCheckFailure & Failure) const173 ReservedIdentifierCheck::GetDiagInfo(const NamingCheckId &ID,
174                                      const NamingCheckFailure &Failure) const {
175   return DiagInfo{Message, [&](DiagnosticBuilder &diag) {
176                     diag << ID.second
177                          << getMessageSelectIndex(Failure.Info.KindName);
178                   }};
179 }
180 
181 } // namespace bugprone
182 } // namespace tidy
183 } // namespace clang
184