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