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