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 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 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 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 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 64 static bool hasReservedDoubleUnderscore(StringRef Name, 65 const LangOptions &LangOpts) { 66 if (LangOpts.CPlusPlus) 67 return Name.contains("__"); 68 return Name.startswith("__"); 69 } 70 71 static Optional<std::string> 72 getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) { 73 if (hasReservedDoubleUnderscore(Name, LangOpts)) 74 return collapseConsecutive(Name, '_'); 75 return None; 76 } 77 78 static bool startsWithUnderscoreCapital(StringRef Name) { 79 return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]); 80 } 81 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 88 static bool startsWithUnderscoreInGlobalNamespace(StringRef Name, 89 bool IsInGlobalNamespace) { 90 return IsInGlobalNamespace && Name.size() >= 1 && Name[0] == '_'; 91 } 92 93 static Optional<std::string> 94 getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace) { 95 if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace)) 96 return std::string(Name.drop_front(1)); 97 return None; 98 } 99 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> 110 getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace, 111 const LangOptions &LangOpts, bool Invert, 112 ArrayRef<StringRef> 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 .value_or(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> 155 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> 166 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 173 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