207 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "ReservedIdentifierCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/Lex/Token.h"13#include <algorithm>14#include <cctype>15#include <optional>16 17// FixItHint18 19using namespace clang::ast_matchers;20 21namespace clang::tidy::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 40llvm::SmallVector<llvm::Regex>41ReservedIdentifierCheck::parseAllowedIdentifiers() const {42 llvm::SmallVector<llvm::Regex> AllowedIdentifiers;43 AllowedIdentifiers.reserve(AllowedIdentifiersRaw.size());44 45 for (const auto &Identifier : AllowedIdentifiersRaw) {46 AllowedIdentifiers.emplace_back(Identifier.str());47 if (!AllowedIdentifiers.back().isValid()) {48 configurationDiag("Invalid allowed identifier regex '%0'") << Identifier;49 AllowedIdentifiers.pop_back();50 }51 }52 53 return AllowedIdentifiers;54}55 56ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name,57 ClangTidyContext *Context)58 : RenamerClangTidyCheck(Name, Context),59 Invert(Options.get("Invert", false)),60 AllowedIdentifiersRaw(utils::options::parseStringList(61 Options.get("AllowedIdentifiers", ""))),62 AllowedIdentifiers(parseAllowedIdentifiers()) {}63 64void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {65 RenamerClangTidyCheck::storeOptions(Opts);66 Options.store(Opts, "Invert", Invert);67 Options.store(Opts, "AllowedIdentifiers",68 utils::options::serializeStringList(AllowedIdentifiersRaw));69}70 71static std::string collapseConsecutive(StringRef Str, char C) {72 std::string Result;73 std::unique_copy(Str.begin(), Str.end(), std::back_inserter(Result),74 [C](char A, char B) { return A == C && B == C; });75 return Result;76}77 78static bool hasReservedDoubleUnderscore(StringRef Name,79 const LangOptions &LangOpts) {80 if (LangOpts.CPlusPlus)81 return Name.contains("__");82 return Name.starts_with("__");83}84 85static std::optional<std::string>86getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {87 if (hasReservedDoubleUnderscore(Name, LangOpts))88 return collapseConsecutive(Name, '_');89 return std::nullopt;90}91 92static bool startsWithUnderscoreCapital(StringRef Name) {93 return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);94}95 96static std::optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {97 if (startsWithUnderscoreCapital(Name))98 return std::string(Name.drop_front(1));99 return std::nullopt;100}101 102static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,103 bool IsInGlobalNamespace,104 bool IsMacro) {105 return !IsMacro && IsInGlobalNamespace && Name.starts_with("_");106}107 108static std::optional<std::string>109getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace,110 bool IsMacro) {111 if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace, IsMacro))112 return std::string(Name.drop_front(1));113 return std::nullopt;114}115 116static std::string getNonReservedFixup(std::string Name) {117 assert(!Name.empty());118 if (Name[0] == '_' || std::isupper(Name[0]))119 Name.insert(Name.begin(), '_');120 else121 Name.insert(Name.begin(), 2, '_');122 return Name;123}124 125static std::optional<RenamerClangTidyCheck::FailureInfo>126getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace, bool IsMacro,127 const LangOptions &LangOpts, bool Invert,128 ArrayRef<llvm::Regex> AllowedIdentifiers) {129 assert(!Name.empty());130 131 if (llvm::any_of(AllowedIdentifiers, [&](const llvm::Regex &Regex) {132 return Regex.match(Name);133 })) {134 return std::nullopt;135 }136 // TODO: Check for names identical to language keywords, and other names137 // specifically reserved by language standards, e.g. C++ 'zombie names' and C138 // future library directions139 140 using FailureInfo = RenamerClangTidyCheck::FailureInfo;141 if (!Invert) {142 std::optional<FailureInfo> Info;143 auto AppendFailure = [&](StringRef Kind, std::string &&Fixup) {144 if (!Info) {145 Info = FailureInfo{std::string(Kind), std::move(Fixup)};146 } else {147 Info->KindName += Kind;148 Info->Fixup = std::move(Fixup);149 }150 };151 auto InProgressFixup = [&] {152 return llvm::transformOptional(153 Info,154 [](const FailureInfo &Info) { return StringRef(Info.Fixup); })155 .value_or(Name);156 };157 if (auto Fixup = getDoubleUnderscoreFixup(InProgressFixup(), LangOpts))158 AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));159 if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))160 AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));161 if (auto Fixup = getUnderscoreGlobalNamespaceFixup(162 InProgressFixup(), IsInGlobalNamespace, IsMacro))163 AppendFailure(GlobalUnderscoreTag, std::move(*Fixup));164 165 return Info;166 }167 if (!(hasReservedDoubleUnderscore(Name, LangOpts) ||168 startsWithUnderscoreCapital(Name) ||169 startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace,170 IsMacro)))171 return FailureInfo{NonReservedTag, getNonReservedFixup(std::string(Name))};172 return std::nullopt;173}174 175std::optional<RenamerClangTidyCheck::FailureInfo>176ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl,177 const SourceManager &) const {178 assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&179 "Decl must be an explicit identifier with a name.");180 // Implicit identifiers cannot fail.181 if (Decl->isImplicit())182 return std::nullopt;183 184 return getFailureInfoImpl(185 Decl->getName(), isa<TranslationUnitDecl>(Decl->getDeclContext()),186 /*IsMacro = */ false, getLangOpts(), Invert, AllowedIdentifiers);187}188 189std::optional<RenamerClangTidyCheck::FailureInfo>190ReservedIdentifierCheck::getMacroFailureInfo(const Token &MacroNameTok,191 const SourceManager &) const {192 return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,193 /*IsMacro = */ true, getLangOpts(), Invert,194 AllowedIdentifiers);195}196 197RenamerClangTidyCheck::DiagInfo198ReservedIdentifierCheck::getDiagInfo(const NamingCheckId &ID,199 const NamingCheckFailure &Failure) const {200 return DiagInfo{Message, [&](DiagnosticBuilder &Diag) {201 Diag << ID.second202 << getMessageSelectIndex(Failure.Info.KindName);203 }};204}205 206} // namespace clang::tidy::bugprone207