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