//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// /// /// \file /// \brief 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/USRFinder.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include #include #include #include using namespace llvm; namespace clang { namespace tooling { namespace { // \brief 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: // \brief Returns a list of unique locations. Duplicate or overlapping // locations are erroneous and should be reported! const std::vector &getLocationsFound() const { return LocationsFound; } 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); // The token of the source location we find actually has the old // name. if (Offset != StringRef::npos) LocationsFound.push_back(BeginLoc.getLocWithOffset(Offset)); } const std::set USRSet; const std::string PrevName; std::vector LocationsFound; 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.getLocStart(); } 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; }; // FIXME: Currently, prefix qualifiers will be added to the renamed symbol // definition (e.g. "class Foo {};" => "class b::Bar {};" when renaming // "a::Foo" to "b::Bar"). // For renaming declarations/definitions, prefix qualifiers should be filtered // out. 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)) { RenameInfo Info = {Decl->getLocation(), Decl->getLocation(), nullptr, nullptr, nullptr}; RenameInfos.push_back(Info); } return true; } bool VisitDeclRefExpr(const DeclRefExpr *Expr) { const NamedDecl *Decl = Expr->getFoundDecl(); if (isInUSRSet(Decl)) { RenameInfo Info = {Expr->getSourceRange().getBegin(), Expr->getSourceRange().getEnd(), Decl, getClosestAncestorDecl(*Expr), Expr->getQualifier()}; 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 (IsTypeAliasWhichWillBeRenamedElsewhere(NestedLoc.getTypeLoc())) 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()}; RenameInfos.push_back(Info); } } return true; } bool VisitTypeLoc(TypeLoc Loc) { if (IsTypeAliasWhichWillBeRenamedElsewhere(Loc)) return true; 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; RenameInfo Info = {StartLocationForType(Loc), EndLocationForType(Loc), TargetDecl, getClosestAncestorDecl(Loc), GetNestedNameForType(Loc)}; 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; RenameInfo Info = { StartLocationForType(TargetLoc), EndLocationForType(TargetLoc), TemplateSpecType->getTemplateName().getAsTemplateDecl(), getClosestAncestorDecl( ast_type_traits::DynTypedNode::create(TargetLoc)), GetNestedNameForType(TargetLoc)}; 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: // FIXME: This method may not be suitable for renaming other types like alias // types. Need to figure out a way to handle it. bool IsTypeAliasWhichWillBeRenamedElsewhere(TypeLoc TL) const { while (!TL.isNull()) { // SubstTemplateTypeParm is the TypeLocation class for a substituted type // inside a template expansion so we ignore these. For example: // // template struct S { // T t; // <-- this T becomes a TypeLoc(int) with class // // SubstTemplateTypeParm when S is instantiated // } if (TL.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm) return true; // Typedef is the TypeLocation class for a type which is a typedef to the // type we want to replace. We ignore the use of the typedef as we will // replace the definition of it. For example: // // typedef int T; // T a; // <--- This T is a TypeLoc(int) with class Typedef. if (TL.getTypeLocClass() == TypeLoc::Typedef) return true; TL = TL.getNextTypeLoc(); } return false; } // Get the supported declaration from a given typeLoc. If the declaration type // is not supported, returns nullptr. // // FIXME: support more types, e.g. enum, type alias. const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) { if (const auto *RD = Loc.getType()->getAsCXXRecordDecl()) return RD; 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 std::vector getLocationsOfUSRs(const std::vector &USRs, StringRef PrevName, Decl *Decl) { USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); Visitor.TraverseDecl(Decl); return Visitor.getLocationsFound(); } 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() << "Faile 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.FromDecl && RenameInfo.Context) { if (!llvm::isa( RenameInfo.Context->getDeclContext())) { ReplacedName = tooling::replaceNestedName( RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl, NewName.startswith("::") ? NewName.str() : ("::" + 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->getLocStart(), Using->getLocEnd(), "using " + NewName.str()); return AtomicChanges; } } // end namespace tooling } // end namespace clang