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