//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===// // // 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 // //===----------------------------------------------------------------------===// /// /// \file /// Methods for finding all instances of a USR. Our strategy is very /// simple; we just compare the USR at every relevant AST node with the one /// provided. /// //===----------------------------------------------------------------------===// #include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Core/Lookup.h" #include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h" #include "clang/Tooling/Refactoring/Rename/SymbolName.h" #include "clang/Tooling/Refactoring/Rename/USRFinder.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include #include #include #include using namespace llvm; namespace clang { namespace tooling { namespace { // Returns true if the given Loc is valid for edit. We don't edit the // SourceLocations that are valid or in temporary buffer. bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) { if (Loc.isInvalid()) return false; const clang::FullSourceLoc FullLoc(Loc, SM); std::pair FileIdAndOffset = FullLoc.getSpellingLoc().getDecomposedLoc(); return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr; } // This visitor recursively searches for all instances of a USR in a // translation unit and stores them for later usage. class USRLocFindingASTVisitor : public RecursiveSymbolVisitor { public: explicit USRLocFindingASTVisitor(const std::vector &USRs, StringRef PrevName, const ASTContext &Context) : RecursiveSymbolVisitor(Context.getSourceManager(), Context.getLangOpts()), USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { } bool visitSymbolOccurrence(const NamedDecl *ND, ArrayRef NameRanges) { if (USRSet.find(getUSRForDecl(ND)) != USRSet.end()) { assert(NameRanges.size() == 1 && "Multiple name pieces are not supported yet!"); SourceLocation Loc = NameRanges[0].getBegin(); const SourceManager &SM = Context.getSourceManager(); // TODO: Deal with macro occurrences correctly. if (Loc.isMacroID()) Loc = SM.getSpellingLoc(Loc); checkAndAddLocation(Loc); } return true; } // Non-visitors: /// Returns a set of unique symbol occurrences. Duplicate or /// overlapping occurrences are erroneous and should be reported! SymbolOccurrences takeOccurrences() { return std::move(Occurrences); } private: void checkAndAddLocation(SourceLocation Loc) { const SourceLocation BeginLoc = Loc; const SourceLocation EndLoc = Lexer::getLocForEndOfToken( BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); StringRef TokenName = Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), Context.getSourceManager(), Context.getLangOpts()); size_t Offset = TokenName.find(PrevName.getNamePieces()[0]); // The token of the source location we find actually has the old // name. if (Offset != StringRef::npos) Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol, BeginLoc.getLocWithOffset(Offset)); } const std::set USRSet; const SymbolName PrevName; SymbolOccurrences Occurrences; const ASTContext &Context; }; SourceLocation StartLocationForType(TypeLoc TL) { // For elaborated types (e.g. `struct a::A`) we want the portion after the // `struct` but including the namespace qualifier, `a::`. if (auto ElaboratedTypeLoc = TL.getAs()) { NestedNameSpecifierLoc NestedNameSpecifier = ElaboratedTypeLoc.getQualifierLoc(); if (NestedNameSpecifier.getNestedNameSpecifier()) return NestedNameSpecifier.getBeginLoc(); TL = TL.getNextTypeLoc(); } return TL.getBeginLoc(); } SourceLocation EndLocationForType(TypeLoc TL) { // Dig past any namespace or keyword qualifications. while (TL.getTypeLocClass() == TypeLoc::Elaborated || TL.getTypeLocClass() == TypeLoc::Qualified) TL = TL.getNextTypeLoc(); // The location for template specializations (e.g. Foo) includes the // templated types in its location range. We want to restrict this to just // before the `<` character. if (TL.getTypeLocClass() == TypeLoc::TemplateSpecialization) { return TL.castAs() .getLAngleLoc() .getLocWithOffset(-1); } return TL.getEndLoc(); } NestedNameSpecifier *GetNestedNameForType(TypeLoc TL) { // Dig past any keyword qualifications. while (TL.getTypeLocClass() == TypeLoc::Qualified) TL = TL.getNextTypeLoc(); // For elaborated types (e.g. `struct a::A`) we want the portion after the // `struct` but including the namespace qualifier, `a::`. if (auto ElaboratedTypeLoc = TL.getAs()) return ElaboratedTypeLoc.getQualifierLoc().getNestedNameSpecifier(); return nullptr; } // Find all locations identified by the given USRs for rename. // // This class will traverse the AST and find every AST node whose USR is in the // given USRs' set. class RenameLocFinder : public RecursiveASTVisitor { public: RenameLocFinder(llvm::ArrayRef USRs, ASTContext &Context) : USRSet(USRs.begin(), USRs.end()), Context(Context) {} // A structure records all information of a symbol reference being renamed. // We try to add as few prefix qualifiers as possible. struct RenameInfo { // The begin location of a symbol being renamed. SourceLocation Begin; // The end location of a symbol being renamed. SourceLocation End; // The declaration of a symbol being renamed (can be nullptr). const NamedDecl *FromDecl; // The declaration in which the nested name is contained (can be nullptr). const Decl *Context; // The nested name being replaced (can be nullptr). const NestedNameSpecifier *Specifier; // Determine whether the prefix qualifiers of the NewName should be ignored. // Normally, we set it to true for the symbol declaration and definition to // avoid adding prefix qualifiers. // For example, if it is true and NewName is "a::b::foo", then the symbol // occurrence which the RenameInfo points to will be renamed to "foo". bool IgnorePrefixQualifers; }; bool VisitNamedDecl(const NamedDecl *Decl) { // UsingDecl has been handled in other place. if (llvm::isa(Decl)) return true; // DestructorDecl has been handled in Typeloc. if (llvm::isa(Decl)) return true; if (Decl->isImplicit()) return true; if (isInUSRSet(Decl)) { // For the case of renaming an alias template, we actually rename the // underlying alias declaration of the template. if (const auto* TAT = dyn_cast(Decl)) Decl = TAT->getTemplatedDecl(); auto StartLoc = Decl->getLocation(); auto EndLoc = StartLoc; if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { RenameInfo Info = {StartLoc, EndLoc, /*FromDecl=*/nullptr, /*Context=*/nullptr, /*Specifier=*/nullptr, /*IgnorePrefixQualifers=*/true}; RenameInfos.push_back(Info); } } return true; } bool VisitMemberExpr(const MemberExpr *Expr) { const NamedDecl *Decl = Expr->getFoundDecl(); auto StartLoc = Expr->getMemberLoc(); auto EndLoc = Expr->getMemberLoc(); if (isInUSRSet(Decl)) { RenameInfos.push_back({StartLoc, EndLoc, /*FromDecl=*/nullptr, /*Context=*/nullptr, /*Specifier=*/nullptr, /*IgnorePrefixQualifiers=*/true}); } return true; } bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) { // Fix the constructor initializer when renaming class members. for (const auto *Initializer : CD->inits()) { // Ignore implicit initializers. if (!Initializer->isWritten()) continue; if (const FieldDecl *FD = Initializer->getMember()) { if (isInUSRSet(FD)) { auto Loc = Initializer->getSourceLocation(); RenameInfos.push_back({Loc, Loc, /*FromDecl=*/nullptr, /*Context=*/nullptr, /*Specifier=*/nullptr, /*IgnorePrefixQualifiers=*/true}); } } } return true; } bool VisitDeclRefExpr(const DeclRefExpr *Expr) { const NamedDecl *Decl = Expr->getFoundDecl(); // Get the underlying declaration of the shadow declaration introduced by a // using declaration. if (auto *UsingShadow = llvm::dyn_cast(Decl)) { Decl = UsingShadow->getTargetDecl(); } auto StartLoc = Expr->getBeginLoc(); // For template function call expressions like `foo()`, we want to // restrict the end of location to just before the `<` character. SourceLocation EndLoc = Expr->hasExplicitTemplateArgs() ? Expr->getLAngleLoc().getLocWithOffset(-1) : Expr->getEndLoc(); if (const auto *MD = llvm::dyn_cast(Decl)) { if (isInUSRSet(MD)) { // Handle renaming static template class methods, we only rename the // name without prefix qualifiers and restrict the source range to the // name. RenameInfos.push_back({EndLoc, EndLoc, /*FromDecl=*/nullptr, /*Context=*/nullptr, /*Specifier=*/nullptr, /*IgnorePrefixQualifiers=*/true}); return true; } } // In case of renaming an enum declaration, we have to explicitly handle // unscoped enum constants referenced in expressions (e.g. // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by // TypeLoc. if (const auto *T = llvm::dyn_cast(Decl)) { // FIXME: Handle the enum constant without prefix qualifiers (`a = Green`) // when renaming an unscoped enum declaration with a new namespace. if (!Expr->hasQualifier()) return true; if (const auto *ED = llvm::dyn_cast_or_null(getClosestAncestorDecl(*T))) { if (ED->isScoped()) return true; Decl = ED; } // The current fix would qualify "ns1::ns2::Green" as // "ns1::ns2::Color::Green". // // Get the EndLoc of the replacement by moving 1 character backward ( // to exclude the last '::'). // // ns1::ns2::Green; // ^ ^^ // BeginLoc |EndLoc of the qualifier // new EndLoc EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1); assert(EndLoc.isValid() && "The enum constant should have prefix qualifers."); } if (isInUSRSet(Decl) && IsValidEditLoc(Context.getSourceManager(), StartLoc)) { RenameInfo Info = {StartLoc, EndLoc, Decl, getClosestAncestorDecl(*Expr), Expr->getQualifier(), /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } return true; } bool VisitUsingDecl(const UsingDecl *Using) { for (const auto *UsingShadow : Using->shadows()) { if (isInUSRSet(UsingShadow->getTargetDecl())) { UsingDecls.push_back(Using); break; } } return true; } bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) { if (!NestedLoc.getNestedNameSpecifier()->getAsType()) return true; if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) { if (isInUSRSet(TargetDecl)) { RenameInfo Info = {NestedLoc.getBeginLoc(), EndLocationForType(NestedLoc.getTypeLoc()), TargetDecl, getClosestAncestorDecl(NestedLoc), NestedLoc.getNestedNameSpecifier()->getPrefix(), /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } } return true; } bool VisitTypeLoc(TypeLoc Loc) { auto Parents = Context.getParents(Loc); TypeLoc ParentTypeLoc; if (!Parents.empty()) { // Handle cases of nested name specificier locations. // // The VisitNestedNameSpecifierLoc interface is not impelmented in // RecursiveASTVisitor, we have to handle it explicitly. if (const auto *NSL = Parents[0].get()) { VisitNestedNameSpecifierLocations(*NSL); return true; } if (const auto *TL = Parents[0].get()) ParentTypeLoc = *TL; } // Handle the outermost TypeLoc which is directly linked to the interesting // declaration and don't handle nested name specifier locations. if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) { if (isInUSRSet(TargetDecl)) { // Only handle the outermost typeLoc. // // For a type like "a::Foo", there will be two typeLocs for it. // One ElaboratedType, the other is RecordType: // // ElaboratedType 0x33b9390 'a::Foo' sugar // `-RecordType 0x338fef0 'class a::Foo' // `-CXXRecord 0x338fe58 'Foo' // // Skip if this is an inner typeLoc. if (!ParentTypeLoc.isNull() && isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc))) return true; auto StartLoc = StartLocationForType(Loc); auto EndLoc = EndLocationForType(Loc); if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { RenameInfo Info = {StartLoc, EndLoc, TargetDecl, getClosestAncestorDecl(Loc), GetNestedNameForType(Loc), /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } return true; } } // Handle specific template class specialiation cases. if (const auto *TemplateSpecType = dyn_cast(Loc.getType())) { TypeLoc TargetLoc = Loc; if (!ParentTypeLoc.isNull()) { if (llvm::isa(ParentTypeLoc.getType())) TargetLoc = ParentTypeLoc; } if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) { TypeLoc TargetLoc = Loc; // FIXME: Find a better way to handle this case. // For the qualified template class specification type like // "ns::Foo" in "ns::Foo& f();", we want the parent typeLoc // (ElaboratedType) of the TemplateSpecializationType in order to // catch the prefix qualifiers "ns::". if (!ParentTypeLoc.isNull() && llvm::isa(ParentTypeLoc.getType())) TargetLoc = ParentTypeLoc; auto StartLoc = StartLocationForType(TargetLoc); auto EndLoc = EndLocationForType(TargetLoc); if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { RenameInfo Info = { StartLoc, EndLoc, TemplateSpecType->getTemplateName().getAsTemplateDecl(), getClosestAncestorDecl( ast_type_traits::DynTypedNode::create(TargetLoc)), GetNestedNameForType(TargetLoc), /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } } } return true; } // Returns a list of RenameInfo. const std::vector &getRenameInfos() const { return RenameInfos; } // Returns a list of using declarations which are needed to update. const std::vector &getUsingDecls() const { return UsingDecls; } private: // Get the supported declaration from a given typeLoc. If the declaration type // is not supported, returns nullptr. const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) { if (const auto* TT = Loc.getType()->getAs()) return TT->getDecl(); if (const auto *RD = Loc.getType()->getAsCXXRecordDecl()) return RD; if (const auto *ED = llvm::dyn_cast_or_null(Loc.getType()->getAsTagDecl())) return ED; return nullptr; } // Get the closest ancester which is a declaration of a given AST node. template const Decl *getClosestAncestorDecl(const ASTNodeType &Node) { auto Parents = Context.getParents(Node); // FIXME: figure out how to handle it when there are multiple parents. if (Parents.size() != 1) return nullptr; if (ast_type_traits::ASTNodeKind::getFromNodeKind().isBaseOf( Parents[0].getNodeKind())) return Parents[0].template get(); return getClosestAncestorDecl(Parents[0]); } // Get the parent typeLoc of a given typeLoc. If there is no such parent, // return nullptr. const TypeLoc *getParentTypeLoc(TypeLoc Loc) const { auto Parents = Context.getParents(Loc); // FIXME: figure out how to handle it when there are multiple parents. if (Parents.size() != 1) return nullptr; return Parents[0].get(); } // Check whether the USR of a given Decl is in the USRSet. bool isInUSRSet(const Decl *Decl) const { auto USR = getUSRForDecl(Decl); if (USR.empty()) return false; return llvm::is_contained(USRSet, USR); } const std::set USRSet; ASTContext &Context; std::vector RenameInfos; // Record all interested using declarations which contains the using-shadow // declarations of the symbol declarations being renamed. std::vector UsingDecls; }; } // namespace SymbolOccurrences getOccurrencesOfUSRs(ArrayRef USRs, StringRef PrevName, Decl *Decl) { USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); Visitor.TraverseDecl(Decl); return Visitor.takeOccurrences(); } std::vector createRenameAtomicChanges(llvm::ArrayRef USRs, llvm::StringRef NewName, Decl *TranslationUnitDecl) { RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext()); Finder.TraverseDecl(TranslationUnitDecl); const SourceManager &SM = TranslationUnitDecl->getASTContext().getSourceManager(); std::vector AtomicChanges; auto Replace = [&](SourceLocation Start, SourceLocation End, llvm::StringRef Text) { tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start); llvm::Error Err = ReplaceChange.replace( SM, CharSourceRange::getTokenRange(Start, End), Text); if (Err) { llvm::errs() << "Failed to add replacement to AtomicChange: " << llvm::toString(std::move(Err)) << "\n"; return; } AtomicChanges.push_back(std::move(ReplaceChange)); }; for (const auto &RenameInfo : Finder.getRenameInfos()) { std::string ReplacedName = NewName.str(); if (RenameInfo.IgnorePrefixQualifers) { // Get the name without prefix qualifiers from NewName. size_t LastColonPos = NewName.find_last_of(':'); if (LastColonPos != std::string::npos) ReplacedName = NewName.substr(LastColonPos + 1); } else { if (RenameInfo.FromDecl && RenameInfo.Context) { if (!llvm::isa( RenameInfo.Context->getDeclContext())) { ReplacedName = tooling::replaceNestedName( RenameInfo.Specifier, RenameInfo.Begin, RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl, NewName.startswith("::") ? NewName.str() : ("::" + NewName).str()); } else { // This fixes the case where type `T` is a parameter inside a function // type (e.g. `std::function`) and the DeclContext of `T` // becomes the translation unit. As a workaround, we simply use // fully-qualified name here for all references whose `DeclContext` is // the translation unit and ignore the possible existence of // using-decls (in the global scope) that can shorten the replaced // name. llvm::StringRef ActualName = Lexer::getSourceText( CharSourceRange::getTokenRange( SourceRange(RenameInfo.Begin, RenameInfo.End)), SM, TranslationUnitDecl->getASTContext().getLangOpts()); // Add the leading "::" back if the name written in the code contains // it. if (ActualName.startswith("::") && !NewName.startswith("::")) { ReplacedName = "::" + NewName.str(); } } } // If the NewName contains leading "::", add it back. if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) ReplacedName = NewName.str(); } Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName); } // Hanlde using declarations explicitly as "using a::Foo" don't trigger // typeLoc for "a::Foo". for (const auto *Using : Finder.getUsingDecls()) Replace(Using->getBeginLoc(), Using->getEndLoc(), "using " + NewName.str()); return AtomicChanges; } } // end namespace tooling } // end namespace clang