//===--- 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/Basic/CharInfo.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/PointerIntPair.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(DenseMapInfo::getEmptyKey(), "EMPTY"); } static inline NamingCheckId getTombstoneKey() { return NamingCheckId(DenseMapInfo::getTombstoneKey(), "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 DenseMapInfo::getHashValue(Val.first) + 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 { if (MD->getMacroInfo()->isBuiltinMacro()) return; if (PP->getSourceManager().isWrittenInBuiltinFile( MacroNameTok.getLocation())) return; if (PP->getSourceManager().isWrittenInCommandLineFile( MacroNameTok.getLocation())) return; 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), AggressiveDependentMemberLookup( Options.getLocalOrGlobal("AggressiveDependentMemberLookup", false)) {} RenamerClangTidyCheck::~RenamerClangTidyCheck() = default; void RenamerClangTidyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "AggressiveDependentMemberLookup", AggressiveDependentMemberLookup); } 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(unless(isImplicit())).bind("classRef"), this); Finder->addMatcher(cxxDestructorDecl(unless(isImplicit())).bind("classRef"), this); Finder->addMatcher(typeLoc().bind("typeLoc"), this); Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this); auto MemberRestrictions = unless(forFunction(anyOf(isDefaulted(), isImplicit()))); Finder->addMatcher(memberExpr(MemberRestrictions).bind("memberExpr"), this); Finder->addMatcher( cxxDependentScopeMemberExpr(MemberRestrictions).bind("depMemberExpr"), this); } void RenamerClangTidyCheck::registerPPCallbacks( const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { ModuleExpanderPP->addPPCallbacks( std::make_unique(ModuleExpanderPP, this)); } /// Returns the function that \p Method is overridding. If There are none or /// multiple overrides it returns nullptr. If the overridden function itself is /// overridding then it will recurse up to find the first decl of the function. static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) { if (Method->size_overridden_methods() != 1) return nullptr; while (true) { Method = *Method->begin_overridden_methods(); assert(Method && "Overridden method shouldn't be null"); unsigned NumOverrides = Method->size_overridden_methods(); if (NumOverrides == 0) return Method; if (NumOverrides > 1) return nullptr; } } void RenamerClangTidyCheck::addUsage( const RenamerClangTidyCheck::NamingCheckId &Decl, SourceRange Range, SourceManager *SourceMgr) { // Do nothing if the provided range is invalid. if (Range.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 = NamingCheckFailures[Decl]; if (!Failure.RawUsageLocs.insert(FixLocation).second) return; if (!Failure.ShouldFix()) return; if (SourceMgr && SourceMgr->isWrittenInScratchSpace(FixLocation)) Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; if (!utils::rangeCanBeFixed(Range, SourceMgr)) Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; } void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, SourceRange Range, SourceManager *SourceMgr) { if (const auto *Method = dyn_cast(Decl)) { if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) Decl = Overridden; } Decl = cast(Decl->getCanonicalDecl()); return addUsage(RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(), Decl->getNameAsString()), Range, SourceMgr); } const NamedDecl *findDecl(const RecordDecl &RecDecl, StringRef DeclName) { for (const Decl *D : RecDecl.decls()) { if (const auto *ND = dyn_cast(D)) { if (ND->getDeclName().isIdentifier() && ND->getName().equals(DeclName)) return ND; } } return nullptr; } namespace { class NameLookup { llvm::PointerIntPair Data; public: explicit NameLookup(const NamedDecl *ND) : Data(ND, false) {} explicit NameLookup(llvm::NoneType) : Data(nullptr, true) {} explicit NameLookup(std::nullptr_t) : Data(nullptr, false) {} NameLookup() : NameLookup(nullptr) {} bool hasMultipleResolutions() const { return Data.getInt(); } const NamedDecl *getDecl() const { assert(!hasMultipleResolutions() && "Found multiple decls"); return Data.getPointer(); } operator bool() const { return !hasMultipleResolutions(); } const NamedDecl *operator*() const { return getDecl(); } }; } // namespace /// Returns a decl matching the \p DeclName in \p Parent or one of its base /// classes. If \p AggressiveTemplateLookup is `true` then it will check /// template dependent base classes as well. /// If a matching decl is found in multiple base classes then it will return a /// flag indicating the multiple resolutions. NameLookup findDeclInBases(const CXXRecordDecl &Parent, StringRef DeclName, bool AggressiveTemplateLookup) { if (!Parent.hasDefinition()) return NameLookup(nullptr); if (const NamedDecl *InClassRef = findDecl(Parent, DeclName)) return NameLookup(InClassRef); const NamedDecl *Found = nullptr; for (CXXBaseSpecifier Base : Parent.bases()) { const auto *Record = Base.getType()->getAsCXXRecordDecl(); if (!Record && AggressiveTemplateLookup) { if (const auto *TST = Base.getType()->getAs()) { if (const auto *TD = llvm::dyn_cast_or_null( TST->getTemplateName().getAsTemplateDecl())) Record = TD->getTemplatedDecl(); } } if (!Record) continue; if (auto Search = findDeclInBases(*Record, DeclName, AggressiveTemplateLookup)) { if (*Search) { if (Found) return NameLookup( llvm::None); // Multiple decls found in different base classes. Found = *Search; continue; } } else return NameLookup(llvm::None); // Propagate multiple resolution back up. } return NameLookup(Found); // If nullptr, decl wasnt found. } void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *Decl = Result.Nodes.getNodeAs("classRef")) { addUsage(Decl->getParent(), Decl->getNameInfo().getSourceRange(), Result.SourceManager); for (const auto *Init : Decl->inits()) { if (!Init->isWritten() || Init->isInClassMemberInitializer()) continue; if (const FieldDecl *FD = Init->getAnyMember()) addUsage(FD, SourceRange(Init->getMemberLocation()), Result.SourceManager); // Note: delegating constructors and base class initializers are handled // via the "typeLoc" matcher. } return; } if (const auto *Decl = Result.Nodes.getNodeAs("classRef")) { 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(Decl->getParent(), Range, Result.SourceManager); return; } if (const auto *Loc = Result.Nodes.getNodeAs("typeLoc")) { UnqualTypeLoc Unqual = Loc->getUnqualifiedLoc(); NamedDecl *Decl = nullptr; if (const auto &Ref = Unqual.getAs()) Decl = Ref.getDecl(); else if (const auto &Ref = Unqual.getAs()) Decl = Ref.getDecl(); else if (const auto &Ref = Unqual.getAs()) Decl = Ref.getDecl(); else if (const auto &Ref = Unqual.getAs()) Decl = Ref.getDecl(); // further TypeLocs handled below if (Decl) { addUsage(Decl, Loc->getSourceRange(), Result.SourceManager); 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(TemplDecl, Range, Result.SourceManager); return; } } if (const auto &Ref = Loc->getAs()) { if (const TagDecl *Decl = Ref.getTypePtr()->getAsTagDecl()) addUsage(Decl, Loc->getSourceRange(), Result.SourceManager); return; } } if (const auto *Loc = Result.Nodes.getNodeAs("nestedNameLoc")) { if (const NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) { if (const NamespaceDecl *Decl = Spec->getAsNamespace()) { addUsage(Decl, Loc->getLocalSourceRange(), Result.SourceManager); return; } } } if (const auto *Decl = Result.Nodes.getNodeAs("using")) { for (const auto *Shadow : Decl->shadows()) addUsage(Shadow->getTargetDecl(), Decl->getNameInfo().getSourceRange(), Result.SourceManager); return; } if (const auto *DeclRef = Result.Nodes.getNodeAs("declRef")) { SourceRange Range = DeclRef->getNameInfo().getSourceRange(); addUsage(DeclRef->getDecl(), Range, Result.SourceManager); return; } if (const auto *MemberRef = Result.Nodes.getNodeAs("memberExpr")) { SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange(); addUsage(MemberRef->getMemberDecl(), Range, Result.SourceManager); return; } if (const auto *DepMemberRef = Result.Nodes.getNodeAs( "depMemberExpr")) { QualType BaseType = DepMemberRef->isArrow() ? DepMemberRef->getBaseType()->getPointeeType() : DepMemberRef->getBaseType(); if (BaseType.isNull()) return; const CXXRecordDecl *Base = BaseType.getTypePtr()->getAsCXXRecordDecl(); if (!Base) return; DeclarationName DeclName = DepMemberRef->getMemberNameInfo().getName(); if (!DeclName.isIdentifier()) return; StringRef DependentName = DeclName.getAsIdentifierInfo()->getName(); if (NameLookup Resolved = findDeclInBases( *Base, DependentName, AggressiveDependentMemberLookup)) { if (*Resolved) addUsage(*Resolved, DepMemberRef->getMemberNameInfo().getSourceRange(), Result.SourceManager); } return; } if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { // Fix using namespace declarations. if (const auto *UsingNS = dyn_cast(Decl)) addUsage(UsingNS->getNominatedNamespaceAsWritten(), UsingNS->getIdentLocation(), Result.SourceManager); if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit()) return; const auto *Canonical = cast(Decl->getCanonicalDecl()); if (Canonical != Decl) { addUsage(Canonical, Decl->getLocation(), Result.SourceManager); 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(Typedef->getDecl(), Value->getSourceRange(), Result.SourceManager); } } // Fix type aliases in function declarations. if (const auto *Value = Result.Nodes.getNodeAs("decl")) { if (const auto *Typedef = Value->getReturnType().getTypePtr()->getAs()) addUsage(Typedef->getDecl(), Value->getSourceRange(), Result.SourceManager); for (const ParmVarDecl *Param : Value->parameters()) { if (const TypedefType *Typedef = Param->getType().getTypePtr()->getAs()) addUsage(Typedef->getDecl(), Value->getSourceRange(), Result.SourceManager); } } // Fix overridden methods if (const auto *Method = Result.Nodes.getNodeAs("decl")) { if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) { addUsage(Overridden, Method->getLocation()); return; // Don't try to add the actual decl as a Failure. } } // 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; } else if (!isValidIdentifier(Info.Fixup)) { Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier; } Failure.Info = std::move(Info); addUsage(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(), std::string(Name)); NamingCheckFailure &Failure = NamingCheckFailures[ID]; SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); Failure.Info = std::move(Info); addUsage(ID, Range); } void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok, const MacroInfo *MI) { StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); NamingCheckId ID(MI->getDefinitionLoc(), std::string(Name)); auto Failure = NamingCheckFailures.find(ID); if (Failure == NamingCheckFailures.end()) return; SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); addUsage(ID, Range); } static std::string getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus, const std::string &Fixup) { if (Fixup.empty() || FixStatus == RenamerClangTidyCheck::ShouldFixStatus::FixInvalidIdentifier) 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(Loc), Failure.Info.Fixup); } } } } } } // namespace tidy } // namespace clang