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