blob: 47a84c225711e52d8d4bc8f50ba0da7002842c6b [file] [log] [blame]
Logan Smith42a03552020-01-17 08:43:20 -05001//===--- 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"
Nathan James860aefd2020-06-29 16:05:51 +010014#include "clang/Lex/Token.h"
Logan Smith42a03552020-01-17 08:43:20 -050015#include <algorithm>
Aaron Ballman7f4e7442020-01-17 10:23:45 -050016#include <cctype>
Logan Smith42a03552020-01-17 08:43:20 -050017
Nathan Jamesd1d51802020-02-19 23:19:09 +000018// FixItHint
19
Logan Smith42a03552020-01-17 08:43:20 -050020using namespace clang::ast_matchers;
21
22namespace clang {
23namespace tidy {
24namespace bugprone {
25
26static const char DoubleUnderscoreTag[] = "du";
27static const char UnderscoreCapitalTag[] = "uc";
28static const char GlobalUnderscoreTag[] = "global-under";
29static const char NonReservedTag[] = "non-reserved";
30
31static 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
35static int getMessageSelectIndex(StringRef Tag) {
36 if (Tag == NonReservedTag)
37 return 1;
38 if (Tag == GlobalUnderscoreTag)
39 return 2;
40 return 0;
41}
42
43ReservedIdentifierCheck::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
50void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Nathan James82ddae02020-05-08 19:30:18 +010051 RenamerClangTidyCheck::storeOptions(Opts);
Logan Smith42a03552020-01-17 08:43:20 -050052 Options.store(Opts, "Invert", Invert);
53 Options.store(Opts, "AllowedIdentifiers",
54 utils::options::serializeStringList(AllowedIdentifiers));
55}
56
57static 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
64static bool hasReservedDoubleUnderscore(StringRef Name,
65 const LangOptions &LangOpts) {
66 if (LangOpts.CPlusPlus)
67 return Name.find("__") != StringRef::npos;
68 return Name.startswith("__");
69}
70
71static Optional<std::string>
72getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
73 if (hasReservedDoubleUnderscore(Name, LangOpts))
74 return collapseConsecutive(Name, '_');
75 return None;
76}
77
78static bool startsWithUnderscoreCapital(StringRef Name) {
79 return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
80}
81
82static Optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {
83 if (startsWithUnderscoreCapital(Name))
84 return std::string(Name.drop_front(1));
85 return None;
86}
87
88static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
89 bool IsInGlobalNamespace) {
90 return IsInGlobalNamespace && Name.size() >= 1 && Name[0] == '_';
91}
92
93static Optional<std::string>
94getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace) {
95 if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace))
96 return std::string(Name.drop_front(1));
97 return None;
98}
99
100static 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
109static Optional<RenamerClangTidyCheck::FailureInfo>
110getFailureInfoImpl(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) {
Benjamin Krameradcd0262020-01-28 20:23:46 +0100126 Info = FailureInfo{std::string(Kind), std::move(Fixup)};
Logan Smith42a03552020-01-17 08:43:20 -0500127 } 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))
Aaron Ballmanbcda8772020-01-17 09:49:32 -0500138 AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));
Logan Smith42a03552020-01-17 08:43:20 -0500139 if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))
Aaron Ballmanbcda8772020-01-17 09:49:32 -0500140 AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));
Logan Smith42a03552020-01-17 08:43:20 -0500141 if (auto Fixup = getUnderscoreGlobalNamespaceFixup(InProgressFixup(),
142 IsInGlobalNamespace))
Aaron Ballmanbcda8772020-01-17 09:49:32 -0500143 AppendFailure(GlobalUnderscoreTag, std::move(*Fixup));
Logan Smith42a03552020-01-17 08:43:20 -0500144
145 return Info;
146 }
147 if (!(hasReservedDoubleUnderscore(Name, LangOpts) ||
148 startsWithUnderscoreCapital(Name) ||
149 startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace)))
Benjamin Krameradcd0262020-01-28 20:23:46 +0100150 return FailureInfo{NonReservedTag, getNonReservedFixup(std::string(Name))};
Logan Smith42a03552020-01-17 08:43:20 -0500151 return None;
152}
153
154Optional<RenamerClangTidyCheck::FailureInfo>
155ReservedIdentifierCheck::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
165Optional<RenamerClangTidyCheck::FailureInfo>
166ReservedIdentifierCheck::GetMacroFailureInfo(const Token &MacroNameTok,
167 const SourceManager &) const {
168 return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,
169 getLangOpts(), Invert, AllowedIdentifiers);
170}
171
172RenamerClangTidyCheck::DiagInfo
173ReservedIdentifierCheck::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 }};
Simon Pilgrim201c646b2020-01-22 11:07:27 +0000179}
Logan Smith42a03552020-01-17 08:43:20 -0500180
181} // namespace bugprone
182} // namespace tidy
183} // namespace clang