diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h @@ -9,8 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H -#include "../ClangTidyCheck.h" - +#include "../utils/RenamerClangTidyCheck.h" namespace clang { class MacroInfo; @@ -31,17 +30,12 @@ /// different rules for different kind of identifier. In general, the /// rules are falling back to a more generic rule if the specific case is not /// configured. -class IdentifierNamingCheck : public ClangTidyCheck { +class IdentifierNamingCheck final : public RenamerClangTidyCheck { public: IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context); ~IdentifierNamingCheck(); void storeOptions(ClangTidyOptions::OptionMap &Opts) override; - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, - Preprocessor *ModuleExpanderPP) override; - void onEndOfTranslationUnit() override; enum CaseType { CT_AnyCase = 0, @@ -65,66 +59,18 @@ std::string Suffix; }; - /// This enum will be used in %select of the diagnostic message. - /// Each value below IgnoreFailureThreshold should have an error message. - enum class ShouldFixStatus { - ShouldFix, - ConflictsWithKeyword, /// The fixup will conflict with a language keyword, - /// so we can't fix it automatically. - ConflictsWithMacroDefinition, /// The fixup will conflict with a macro - /// definition, so we can't fix it - /// automatically. - - /// Values pass this threshold will be ignored completely - /// i.e no message, no fixup. - IgnoreFailureThreshold, - - InsideMacro, /// If the identifier was used or declared within a macro we - /// won't offer a fixup for safety reasons. - }; - - /// Holds an identifier name check failure, tracking the kind of the - /// identifier, its possible fixup and the starting locations of all the - /// identifier usages. - struct NamingCheckFailure { - std::string KindName; - std::string Fixup; - - /// Whether the failure should be fixed or not. - /// - /// ie: if the identifier was used or declared within a macro we won't offer - /// a fixup for safety reasons. - bool ShouldFix() const { return FixStatus == ShouldFixStatus::ShouldFix; } - - bool ShouldNotify() const { - return FixStatus < ShouldFixStatus::IgnoreFailureThreshold; - } - - ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix; - - /// A set of all the identifier usages starting SourceLocation, in - /// their encoded form. - llvm::DenseSet RawUsageLocs; - - NamingCheckFailure() = default; - }; - - typedef std::pair NamingCheckId; - - typedef llvm::DenseMap - NamingCheckFailureMap; - - /// Check Macros for style violations. - void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok, - const MacroInfo *MI); - - /// Add a usage of a macro if it already has a violation. - void expandMacro(const Token &MacroNameTok, const MacroInfo *MI); - private: + llvm::Optional + GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const override; + llvm::Optional + GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const override; + DiagInfo GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const override; + std::vector> NamingStyles; bool IgnoreFailedSplit; - NamingCheckFailureMap NamingCheckFailures; }; } // namespace readability diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -22,44 +22,6 @@ using namespace clang::ast_matchers; -namespace llvm { -/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps -template <> -struct DenseMapInfo< - clang::tidy::readability::IdentifierNamingCheck::NamingCheckId> { - using NamingCheckId = - clang::tidy::readability::IdentifierNamingCheck::NamingCheckId; - - static inline NamingCheckId getEmptyKey() { - return NamingCheckId( - clang::SourceLocation::getFromRawEncoding(static_cast(-1)), - "EMPTY"); - } - - static inline NamingCheckId getTombstoneKey() { - return NamingCheckId( - clang::SourceLocation::getFromRawEncoding(static_cast(-2)), - "TOMBSTONE"); - } - - static unsigned getHashValue(NamingCheckId Val) { - assert(Val != getEmptyKey() && "Cannot hash the empty key!"); - assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!"); - - std::hash SecondHash; - return Val.first.getRawEncoding() + SecondHash(Val.second); - } - - static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) { - if (RHS == getEmptyKey()) - return LHS == getEmptyKey(); - if (RHS == getTombstoneKey()) - return LHS == getTombstoneKey(); - return LHS == RHS; - } -}; -} // namespace llvm - namespace clang { namespace tidy { namespace readability { @@ -164,7 +126,7 @@ IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) { + : RenamerClangTidyCheck(Name, Context) { auto const fromString = [](StringRef Str) { return llvm::StringSwitch>(Str) .Case("aNy_CasE", CT_AnyCase) @@ -233,23 +195,6 @@ Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit); } -void IdentifierNamingCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(namedDecl().bind("decl"), this); - Finder->addMatcher(usingDecl().bind("using"), this); - Finder->addMatcher(declRefExpr().bind("declRef"), this); - Finder->addMatcher(cxxConstructorDecl().bind("classRef"), this); - Finder->addMatcher(cxxDestructorDecl().bind("classRef"), this); - Finder->addMatcher(typeLoc().bind("typeLoc"), this); - Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); -} - -void IdentifierNamingCheck::registerPPCallbacks( - const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { - ModuleExpanderPP->addPPCallbacks( - std::make_unique(ModuleExpanderPP, - this)); -} - static bool matchesStyle(StringRef Name, IdentifierNamingCheck::NamingStyle Style) { static llvm::Regex Matchers[] = { @@ -667,239 +612,47 @@ return SK_Invalid; } -static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures, - const IdentifierNamingCheck::NamingCheckId &Decl, - SourceRange Range, SourceManager *SourceMgr = nullptr) { - // Do nothing if the provided range is invalid. - if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid()) - return; - - // If we have a source manager, use it to convert to the spelling location for - // performing the fix. This is necessary because macros can map the same - // spelling location to different source locations, and we only want to fix - // the token once, before it is expanded by the macro. - SourceLocation FixLocation = Range.getBegin(); - if (SourceMgr) - FixLocation = SourceMgr->getSpellingLoc(FixLocation); - if (FixLocation.isInvalid()) - return; - - // Try to insert the identifier location in the Usages map, and bail out if it - // is already in there - auto &Failure = Failures[Decl]; - if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second) - return; - - if (!Failure.ShouldFix()) - return; - - if (!utils::rangeCanBeFixed(Range, SourceMgr)) - Failure.FixStatus = IdentifierNamingCheck::ShouldFixStatus::InsideMacro; -} - -/// Convenience method when the usage to be added is a NamedDecl -static void addUsage(IdentifierNamingCheck::NamingCheckFailureMap &Failures, - const NamedDecl *Decl, SourceRange Range, - SourceManager *SourceMgr = nullptr) { - return addUsage(Failures, - IdentifierNamingCheck::NamingCheckId(Decl->getLocation(), - Decl->getNameAsString()), - Range, SourceMgr); -} - -void IdentifierNamingCheck::check(const MatchFinder::MatchResult &Result) { - if (const auto *Decl = - Result.Nodes.getNodeAs("classRef")) { - if (Decl->isImplicit()) - return; - - addUsage(NamingCheckFailures, Decl->getParent(), - Decl->getNameInfo().getSourceRange()); - - for (const auto *Init : Decl->inits()) { - if (!Init->isWritten() || Init->isInClassMemberInitializer()) - continue; - if (const auto *FD = Init->getAnyMember()) - addUsage(NamingCheckFailures, FD, - SourceRange(Init->getMemberLocation())); - // Note: delegating constructors and base class initializers are handled - // via the "typeLoc" matcher. - } - return; - } - - if (const auto *Decl = - Result.Nodes.getNodeAs("classRef")) { - if (Decl->isImplicit()) - return; - - SourceRange Range = Decl->getNameInfo().getSourceRange(); - if (Range.getBegin().isInvalid()) - return; - // The first token that will be found is the ~ (or the equivalent trigraph), - // we want instead to replace the next token, that will be the identifier. - Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd()); - - addUsage(NamingCheckFailures, Decl->getParent(), Range); - return; - } - - if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { - NamedDecl *Decl = nullptr; - if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } else if (const auto &Ref = Loc->getAs()) { - Decl = Ref.getDecl(); - } - - if (Decl) { - addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); - return; - } - - if (const auto &Ref = Loc->getAs()) { - const auto *Decl = - Ref.getTypePtr()->getTemplateName().getAsTemplateDecl(); - - SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc()); - if (const auto *ClassDecl = dyn_cast(Decl)) { - if (const auto *TemplDecl = ClassDecl->getTemplatedDecl()) - addUsage(NamingCheckFailures, TemplDecl, Range); - return; - } - } - - if (const auto &Ref = - Loc->getAs()) { - if (const auto *Decl = Ref.getTypePtr()->getAsTagDecl()) - addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); - return; - } - } - - if (const auto *Loc = - Result.Nodes.getNodeAs("nestedNameLoc")) { - if (NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { - if (NamespaceDecl *Decl = Spec->getAsNamespace()) { - addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange()); - return; - } - } - } +llvm::Optional +IdentifierNamingCheck::GetDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const { + StyleKind SK = findStyleKind(Decl, NamingStyles); + if (SK == SK_Invalid) + return None; - if (const auto *Decl = Result.Nodes.getNodeAs("using")) { - for (const auto *Shadow : Decl->shadows()) { - addUsage(NamingCheckFailures, Shadow->getTargetDecl(), - Decl->getNameInfo().getSourceRange()); - } - return; - } - - if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { - SourceRange Range = DeclRef->getNameInfo().getSourceRange(); - addUsage(NamingCheckFailures, DeclRef->getDecl(), Range, - Result.SourceManager); - return; - } + if (!NamingStyles[SK]) + return None; - if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { - if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) - return; - - // Fix type aliases in value declarations - if (const auto *Value = Result.Nodes.getNodeAs("decl")) { - if (const auto *TypePtr = Value->getType().getTypePtrOrNull()) { - if (const auto *Typedef = TypePtr->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - } - } - - // Fix type aliases in function declarations - if (const auto *Value = Result.Nodes.getNodeAs("decl")) { - if (const auto *Typedef = - Value->getReturnType().getTypePtr()->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - for (unsigned i = 0; i < Value->getNumParams(); ++i) { - if (const auto *Typedef = Value->parameters()[i] - ->getType() - .getTypePtr() - ->getAs()) { - addUsage(NamingCheckFailures, Typedef->getDecl(), - Value->getSourceRange()); - } - } - } + const NamingStyle &Style = *NamingStyles[SK]; + StringRef Name = Decl->getName(); + if (matchesStyle(Name, Style)) + return None; - // Ignore ClassTemplateSpecializationDecl which are creating duplicate - // replacements with CXXRecordDecl - if (isa(Decl)) - return; - - StyleKind SK = findStyleKind(Decl, NamingStyles); - if (SK == SK_Invalid) - return; - - if (!NamingStyles[SK]) - return; - - const NamingStyle &Style = *NamingStyles[SK]; - StringRef Name = Decl->getName(); - if (matchesStyle(Name, Style)) - return; - - std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); - std::replace(KindName.begin(), KindName.end(), '_', ' '); - - std::string Fixup = fixupWithStyle(Name, Style); - if (StringRef(Fixup).equals(Name)) { - if (!IgnoreFailedSplit) { - LLVM_DEBUG(llvm::dbgs() - << Decl->getBeginLoc().printToString(*Result.SourceManager) - << llvm::format(": unable to split words for %s '%s'\n", - KindName.c_str(), Name.str().c_str())); - } - } else { - NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId( - Decl->getLocation(), Decl->getNameAsString())]; - SourceRange Range = - DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) - .getSourceRange(); - - const IdentifierTable &Idents = Decl->getASTContext().Idents; - auto CheckNewIdentifier = Idents.find(Fixup); - if (CheckNewIdentifier != Idents.end()) { - const IdentifierInfo *Ident = CheckNewIdentifier->second; - if (Ident->isKeyword(getLangOpts())) - Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword; - else if (Ident->hasMacroDefinition()) - Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition; - } + std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); - Failure.Fixup = std::move(Fixup); - Failure.KindName = std::move(KindName); - addUsage(NamingCheckFailures, Decl, Range); + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + LLVM_DEBUG(llvm::dbgs() + << Decl->getBeginLoc().printToString(SM) + << llvm::format(": unable to split words for %s '%s'\n", + KindName.c_str(), Name.str().c_str())); } + return None; } + return FailureInfo{std::move(KindName), std::move(Fixup)}; } -void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr, - const Token &MacroNameTok, - const MacroInfo *MI) { +llvm::Optional +IdentifierNamingCheck::GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const { if (!NamingStyles[SK_MacroDefinition]) - return; + return None; StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); const NamingStyle &Style = *NamingStyles[SK_MacroDefinition]; if (matchesStyle(Name, Style)) - return; + return None; std::string KindName = fixupWithCase(StyleNames[SK_MacroDefinition], CT_LowerCase); @@ -909,74 +662,22 @@ if (StringRef(Fixup).equals(Name)) { if (!IgnoreFailedSplit) { LLVM_DEBUG(llvm::dbgs() - << MacroNameTok.getLocation().printToString(SourceMgr) + << MacroNameTok.getLocation().printToString(SM) << llvm::format(": unable to split words for %s '%s'\n", KindName.c_str(), Name.str().c_str())); } - } else { - NamingCheckId ID(MI->getDefinitionLoc(), Name); - NamingCheckFailure &Failure = NamingCheckFailures[ID]; - SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); - - Failure.Fixup = std::move(Fixup); - Failure.KindName = std::move(KindName); - addUsage(NamingCheckFailures, ID, Range); + return None; } + return FailureInfo{std::move(KindName), std::move(Fixup)}; } -void IdentifierNamingCheck::expandMacro(const Token &MacroNameTok, - const MacroInfo *MI) { - StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); - NamingCheckId ID(MI->getDefinitionLoc(), Name); - - auto Failure = NamingCheckFailures.find(ID); - if (Failure == NamingCheckFailures.end()) - return; - - SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); - addUsage(NamingCheckFailures, ID, Range); -} - -void IdentifierNamingCheck::onEndOfTranslationUnit() { - for (const auto &Pair : NamingCheckFailures) { - const NamingCheckId &Decl = Pair.first; - const NamingCheckFailure &Failure = Pair.second; - - if (Failure.KindName.empty()) - continue; - - if (Failure.ShouldNotify()) { - auto Diag = - diag(Decl.first, - "invalid case style for %0 '%1'%select{|" // Case 0 is empty on - // purpose, because we - // intent to provide a - // fix - "; cannot be fixed because '%3' would conflict with a keyword|" - "; cannot be fixed because '%3' would conflict with a macro " - "definition}2") - << Failure.KindName << Decl.second - << static_cast(Failure.FixStatus) << Failure.Fixup; - - if (Failure.ShouldFix()) { - for (const auto &Loc : Failure.RawUsageLocs) { - // We assume that the identifier name is made of one token only. This - // is always the case as we ignore usages in macros that could build - // identifier names by combining multiple tokens. - // - // For destructors, we already take care of it by remembering the - // location of the start of the identifier and not the start of the - // tilde. - // - // Other multi-token identifiers, such as operators are not checked at - // all. - Diag << FixItHint::CreateReplacement( - SourceRange(SourceLocation::getFromRawEncoding(Loc)), - Failure.Fixup); - } - } - } - } +RenamerClangTidyCheck::DiagInfo +IdentifierNamingCheck::GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const { + return DiagInfo{"invalid case style for %0 '%1'", + [&](DiagnosticBuilder &diag) { + diag << Failure.Info.KindName << ID.second; + }}; } } // namespace readability diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -13,6 +13,7 @@ LexerUtils.cpp NamespaceAliaser.cpp OptionsUtils.cpp + RenamerClangTidyCheck.cpp TransformerClangTidyCheck.cpp TypeTraits.cpp UsingInserter.cpp diff --git a/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h @@ -0,0 +1,145 @@ +//===--- RenamderClangTidyCheck.h - clang-tidy ------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Optional.h" +#include +#include + +namespace clang { + +class MacroInfo; + +namespace tidy { + +/// Base class for clang-tidy checks that want to flag declarations and/or +/// macros for renaming based on customizable criteria. +class RenamerClangTidyCheck : public ClangTidyCheck { +public: + RenamerClangTidyCheck(StringRef CheckName, ClangTidyContext *Context); + ~RenamerClangTidyCheck(); + + /// Derived classes should not implement any matching logic themselves; this + /// class will do the matching and call the derived class' + /// GetDeclFailureInfo() and GetMacroFailureInfo() for determining whether a + /// given identifier passes or fails the check. + void registerMatchers(ast_matchers::MatchFinder *Finder) override final; + void + check(const ast_matchers::MatchFinder::MatchResult &Result) override final; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override final; + void onEndOfTranslationUnit() override final; + + /// This enum will be used in %select of the diagnostic message. + /// Each value below IgnoreFailureThreshold should have an error message. + enum class ShouldFixStatus { + ShouldFix, + ConflictsWithKeyword, /// The fixup will conflict with a language keyword, + /// so we can't fix it automatically. + ConflictsWithMacroDefinition, /// The fixup will conflict with a macro + /// definition, so we can't fix it + /// automatically. + + /// Values pass this threshold will be ignored completely + /// i.e no message, no fixup. + IgnoreFailureThreshold, + + InsideMacro, /// If the identifier was used or declared within a macro we + /// won't offer a fixup for safety reasons. + }; + + /// Information describing a failed check + struct FailureInfo { + std::string KindName; // Tag or misc info to be used as derived classes need + std::string Fixup; // The name that will be proposed as a fix-it hint + }; + + /// Holds an identifier name check failure, tracking the kind of the + /// identifier, its possible fixup and the starting locations of all the + /// identifier usages. + struct NamingCheckFailure { + FailureInfo Info; + + /// Whether the failure should be fixed or not. + /// + /// e.g.: if the identifier was used or declared within a macro we won't + /// offer a fixup for safety reasons. + bool ShouldFix() const { + return FixStatus == ShouldFixStatus::ShouldFix && !Info.Fixup.empty(); + } + + bool ShouldNotify() const { + return FixStatus < ShouldFixStatus::IgnoreFailureThreshold; + } + + ShouldFixStatus FixStatus = ShouldFixStatus::ShouldFix; + + /// A set of all the identifier usages starting SourceLocation, in + /// their encoded form. + llvm::DenseSet RawUsageLocs; + + NamingCheckFailure() = default; + }; + + typedef std::pair NamingCheckId; + + typedef llvm::DenseMap + NamingCheckFailureMap; + + /// Check Macros for style violations. + void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok, + const MacroInfo *MI); + + /// Add a usage of a macro if it already has a violation. + void expandMacro(const Token &MacroNameTok, const MacroInfo *MI); + +protected: + /// Overridden by derived classes, returns information about if and how a Decl + /// failed the check. A 'None' result means the Decl did not fail the check. + virtual llvm::Optional + GetDeclFailureInfo(const NamedDecl *Decl, const SourceManager &SM) const = 0; + + /// Overridden by derived classes, returns information about if and how a + /// macro failed the check. A 'None' result means the macro did not fail the + /// check. + virtual llvm::Optional + GetMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const = 0; + + /// Represents customized diagnostic text and how arguments should be applied. + /// Example usage: + /// + /// return DiagInfo{"my %1 very %2 special %3 text", + /// [=](DiagnosticBuilder &diag) { + /// diag << arg1 << arg2 << arg3; + /// }}; + struct DiagInfo { + std::string Text; + llvm::unique_function ApplyArgs; + }; + + /// Overridden by derived classes, returns a description of the diagnostic + /// that should be emitted for the given failure. The base class will then + /// further customize the diagnostic by adding info about whether the fix-it + /// can be automatically applied or not. + virtual DiagInfo GetDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const = 0; + +private: + NamingCheckFailureMap NamingCheckFailures; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_RENAMERCLANGTIDYCHECK_H diff --git a/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp @@ -0,0 +1,406 @@ +//===--- RenamerClangTidyCheck.cpp - clang-tidy ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "RenamerClangTidyCheck.h" +#include "ASTUtils.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Format.h" + +#define DEBUG_TYPE "clang-tidy" + +using namespace clang::ast_matchers; + +namespace llvm { + +/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps +template <> +struct DenseMapInfo { + using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId; + + static inline NamingCheckId getEmptyKey() { + return NamingCheckId( + clang::SourceLocation::getFromRawEncoding(static_cast(-1)), + "EMPTY"); + } + + static inline NamingCheckId getTombstoneKey() { + return NamingCheckId( + clang::SourceLocation::getFromRawEncoding(static_cast(-2)), + "TOMBSTONE"); + } + + static unsigned getHashValue(NamingCheckId Val) { + assert(Val != getEmptyKey() && "Cannot hash the empty key!"); + assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!"); + + std::hash SecondHash; + return Val.first.getRawEncoding() + SecondHash(Val.second); + } + + static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) { + if (RHS == getEmptyKey()) + return LHS == getEmptyKey(); + if (RHS == getTombstoneKey()) + return LHS == getTombstoneKey(); + return LHS == RHS; + } +}; + +} // namespace llvm + +namespace clang { +namespace tidy { + +namespace { + +/// Callback supplies macros to RenamerClangTidyCheck::checkMacro +class RenamerClangTidyCheckPPCallbacks : public PPCallbacks { +public: + RenamerClangTidyCheckPPCallbacks(Preprocessor *PP, + RenamerClangTidyCheck *Check) + : PP(PP), Check(Check) {} + + /// MacroDefined calls checkMacro for macros in the main file + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + Check->checkMacro(PP->getSourceManager(), MacroNameTok, MD->getMacroInfo()); + } + + /// MacroExpands calls expandMacro for macros in the main file + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange /*Range*/, + const MacroArgs * /*Args*/) override { + Check->expandMacro(MacroNameTok, MD.getMacroInfo()); + } + +private: + Preprocessor *PP; + RenamerClangTidyCheck *Check; +}; + +} // namespace + +RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName, + ClangTidyContext *Context) + : ClangTidyCheck(CheckName, Context) {} +RenamerClangTidyCheck::~RenamerClangTidyCheck() = default; + +void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(namedDecl().bind("decl"), this); + Finder->addMatcher(usingDecl().bind("using"), this); + Finder->addMatcher(declRefExpr().bind("declRef"), this); + Finder->addMatcher(cxxConstructorDecl().bind("classRef"), this); + Finder->addMatcher(cxxDestructorDecl().bind("classRef"), this); + Finder->addMatcher(typeLoc().bind("typeLoc"), this); + Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); +} + +void RenamerClangTidyCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + ModuleExpanderPP->addPPCallbacks( + std::make_unique(ModuleExpanderPP, + this)); +} + +static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures, + const RenamerClangTidyCheck::NamingCheckId &Decl, + SourceRange Range, SourceManager *SourceMgr = nullptr) { + // Do nothing if the provided range is invalid. + if (Range.getBegin().isInvalid() || Range.getEnd().isInvalid()) + return; + + // If we have a source manager, use it to convert to the spelling location for + // performing the fix. This is necessary because macros can map the same + // spelling location to different source locations, and we only want to fix + // the token once, before it is expanded by the macro. + SourceLocation FixLocation = Range.getBegin(); + if (SourceMgr) + FixLocation = SourceMgr->getSpellingLoc(FixLocation); + if (FixLocation.isInvalid()) + return; + + // Try to insert the identifier location in the Usages map, and bail out if it + // is already in there + RenamerClangTidyCheck::NamingCheckFailure &Failure = Failures[Decl]; + if (!Failure.RawUsageLocs.insert(FixLocation.getRawEncoding()).second) + return; + + if (!Failure.ShouldFix()) + return; + + if (!utils::rangeCanBeFixed(Range, SourceMgr)) + Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; +} + +/// Convenience method when the usage to be added is a NamedDecl +static void addUsage(RenamerClangTidyCheck::NamingCheckFailureMap &Failures, + const NamedDecl *Decl, SourceRange Range, + SourceManager *SourceMgr = nullptr) { + return addUsage(Failures, + RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(), + Decl->getNameAsString()), + Range, SourceMgr); +} + +void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + addUsage(NamingCheckFailures, Decl->getParent(), + Decl->getNameInfo().getSourceRange()); + + for (const auto *Init : Decl->inits()) { + if (!Init->isWritten() || Init->isInClassMemberInitializer()) + continue; + if (const FieldDecl *FD = Init->getAnyMember()) + addUsage(NamingCheckFailures, FD, + SourceRange(Init->getMemberLocation())); + // Note: delegating constructors and base class initializers are handled + // via the "typeLoc" matcher. + } + return; + } + + if (const auto *Decl = + Result.Nodes.getNodeAs("classRef")) { + if (Decl->isImplicit()) + return; + + SourceRange Range = Decl->getNameInfo().getSourceRange(); + if (Range.getBegin().isInvalid()) + return; + // The first token that will be found is the ~ (or the equivalent trigraph), + // we want instead to replace the next token, that will be the identifier. + Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd()); + + addUsage(NamingCheckFailures, Decl->getParent(), Range); + return; + } + + if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { + NamedDecl *Decl = nullptr; + if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } else if (const auto &Ref = Loc->getAs()) { + Decl = Ref.getDecl(); + } // further TypeLocs handled below + + if (Decl) { + addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); + return; + } + + if (const auto &Ref = Loc->getAs()) { + const TemplateDecl *Decl = + Ref.getTypePtr()->getTemplateName().getAsTemplateDecl(); + + SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc()); + if (const auto *ClassDecl = dyn_cast(Decl)) { + if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl()) + addUsage(NamingCheckFailures, TemplDecl, Range); + return; + } + } + + if (const auto &Ref = + Loc->getAs()) { + if (const TagDecl *Decl = Ref.getTypePtr()->getAsTagDecl()) + addUsage(NamingCheckFailures, Decl, Loc->getSourceRange()); + return; + } + } + + if (const auto *Loc = + Result.Nodes.getNodeAs("nestedNameLoc")) { + if (const NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { + if (const NamespaceDecl *Decl = Spec->getAsNamespace()) { + addUsage(NamingCheckFailures, Decl, Loc->getLocalSourceRange()); + return; + } + } + } + + if (const auto *Decl = Result.Nodes.getNodeAs("using")) { + for (const auto *Shadow : Decl->shadows()) { + addUsage(NamingCheckFailures, Shadow->getTargetDecl(), + Decl->getNameInfo().getSourceRange()); + } + return; + } + + if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { + SourceRange Range = DeclRef->getNameInfo().getSourceRange(); + addUsage(NamingCheckFailures, DeclRef->getDecl(), Range, + Result.SourceManager); + return; + } + + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) + return; + + // Fix type aliases in value declarations. + if (const auto *Value = Result.Nodes.getNodeAs("decl")) { + if (const Type *TypePtr = Value->getType().getTypePtrOrNull()) { + if (const auto *Typedef = TypePtr->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + } + } + + // Fix type aliases in function declarations. + if (const auto *Value = Result.Nodes.getNodeAs("decl")) { + if (const auto *Typedef = + Value->getReturnType().getTypePtr()->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + for (unsigned i = 0; i < Value->getNumParams(); ++i) { + if (const TypedefType *Typedef = Value->parameters()[i] + ->getType() + .getTypePtr() + ->getAs()) { + addUsage(NamingCheckFailures, Typedef->getDecl(), + Value->getSourceRange()); + } + } + } + + // Ignore ClassTemplateSpecializationDecl which are creating duplicate + // replacements with CXXRecordDecl. + if (isa(Decl)) + return; + + Optional MaybeFailure = + GetDeclFailureInfo(Decl, *Result.SourceManager); + if (!MaybeFailure) + return; + FailureInfo &Info = *MaybeFailure; + NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId( + Decl->getLocation(), Decl->getNameAsString())]; + SourceRange Range = + DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) + .getSourceRange(); + + const IdentifierTable &Idents = Decl->getASTContext().Idents; + auto CheckNewIdentifier = Idents.find(Info.Fixup); + if (CheckNewIdentifier != Idents.end()) { + const IdentifierInfo *Ident = CheckNewIdentifier->second; + if (Ident->isKeyword(getLangOpts())) + Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword; + else if (Ident->hasMacroDefinition()) + Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition; + } + + Failure.Info = std::move(Info); + addUsage(NamingCheckFailures, Decl, Range); + } +} + +void RenamerClangTidyCheck::checkMacro(SourceManager &SourceMgr, + const Token &MacroNameTok, + const MacroInfo *MI) { + Optional MaybeFailure = + GetMacroFailureInfo(MacroNameTok, SourceMgr); + if (!MaybeFailure) + return; + FailureInfo &Info = *MaybeFailure; + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingCheckId ID(MI->getDefinitionLoc(), Name); + NamingCheckFailure &Failure = NamingCheckFailures[ID]; + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + + Failure.Info = std::move(Info); + addUsage(NamingCheckFailures, ID, Range); +} + +void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok, + const MacroInfo *MI) { + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingCheckId ID(MI->getDefinitionLoc(), Name); + + auto Failure = NamingCheckFailures.find(ID); + if (Failure == NamingCheckFailures.end()) + return; + + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + addUsage(NamingCheckFailures, ID, Range); +} + +static std::string +getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus, + const std::string &Fixup) { + if (Fixup.empty()) + return "; cannot be fixed automatically"; + if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix) + return {}; + if (FixStatus >= + RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold) + return {}; + if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword) + return "; cannot be fixed because '" + Fixup + + "' would conflict with a keyword"; + if (FixStatus == + RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition) + return "; cannot be fixed because '" + Fixup + + "' would conflict with a macro definition"; + + llvm_unreachable("invalid ShouldFixStatus"); +} + +void RenamerClangTidyCheck::onEndOfTranslationUnit() { + for (const auto &Pair : NamingCheckFailures) { + const NamingCheckId &Decl = Pair.first; + const NamingCheckFailure &Failure = Pair.second; + + if (Failure.Info.KindName.empty()) + continue; + + if (Failure.ShouldNotify()) { + auto DiagInfo = GetDiagInfo(Decl, Failure); + auto Diag = diag(Decl.first, + DiagInfo.Text + getDiagnosticSuffix(Failure.FixStatus, + Failure.Info.Fixup)); + DiagInfo.ApplyArgs(Diag); + + if (Failure.ShouldFix()) { + for (const auto &Loc : Failure.RawUsageLocs) { + // We assume that the identifier name is made of one token only. This + // is always the case as we ignore usages in macros that could build + // identifier names by combining multiple tokens. + // + // For destructors, we already take care of it by remembering the + // location of the start of the identifier and not the start of the + // tilde. + // + // Other multi-token identifiers, such as operators are not checked at + // all. + Diag << FixItHint::CreateReplacement( + SourceRange(SourceLocation::getFromRawEncoding(Loc)), + Failure.Info.Fixup); + } + } + } + } +} + +} // namespace tidy +} // namespace clang