Path: blob/main/contrib/llvm-project/clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp
35295 views
//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// Methods for finding all instances of a USR. Our strategy is very10/// simple; we just compare the USR at every relevant AST node with the one11/// provided.12///13//===----------------------------------------------------------------------===//1415#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"16#include "clang/AST/ASTContext.h"17#include "clang/AST/ParentMapContext.h"18#include "clang/AST/RecursiveASTVisitor.h"19#include "clang/Basic/LLVM.h"20#include "clang/Basic/SourceLocation.h"21#include "clang/Basic/SourceManager.h"22#include "clang/Lex/Lexer.h"23#include "clang/Tooling/Refactoring/Lookup.h"24#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"25#include "clang/Tooling/Refactoring/Rename/SymbolName.h"26#include "clang/Tooling/Refactoring/Rename/USRFinder.h"27#include "llvm/ADT/StringRef.h"28#include "llvm/Support/Casting.h"29#include <cstddef>30#include <set>31#include <string>32#include <vector>3334using namespace llvm;3536namespace clang {37namespace tooling {3839namespace {4041// Returns true if the given Loc is valid for edit. We don't edit the42// SourceLocations that are valid or in temporary buffer.43bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) {44if (Loc.isInvalid())45return false;46const clang::FullSourceLoc FullLoc(Loc, SM);47std::pair<clang::FileID, unsigned> FileIdAndOffset =48FullLoc.getSpellingLoc().getDecomposedLoc();49return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr;50}5152// This visitor recursively searches for all instances of a USR in a53// translation unit and stores them for later usage.54class USRLocFindingASTVisitor55: public RecursiveSymbolVisitor<USRLocFindingASTVisitor> {56public:57explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs,58StringRef PrevName,59const ASTContext &Context)60: RecursiveSymbolVisitor(Context.getSourceManager(),61Context.getLangOpts()),62USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) {63}6465bool visitSymbolOccurrence(const NamedDecl *ND,66ArrayRef<SourceRange> NameRanges) {67if (USRSet.find(getUSRForDecl(ND)) != USRSet.end()) {68assert(NameRanges.size() == 1 &&69"Multiple name pieces are not supported yet!");70SourceLocation Loc = NameRanges[0].getBegin();71const SourceManager &SM = Context.getSourceManager();72// TODO: Deal with macro occurrences correctly.73if (Loc.isMacroID())74Loc = SM.getSpellingLoc(Loc);75checkAndAddLocation(Loc);76}77return true;78}7980// Non-visitors:8182/// Returns a set of unique symbol occurrences. Duplicate or83/// overlapping occurrences are erroneous and should be reported!84SymbolOccurrences takeOccurrences() { return std::move(Occurrences); }8586private:87void checkAndAddLocation(SourceLocation Loc) {88const SourceLocation BeginLoc = Loc;89const SourceLocation EndLoc = Lexer::getLocForEndOfToken(90BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts());91StringRef TokenName =92Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),93Context.getSourceManager(), Context.getLangOpts());94size_t Offset = TokenName.find(PrevName.getNamePieces()[0]);9596// The token of the source location we find actually has the old97// name.98if (Offset != StringRef::npos)99Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol,100BeginLoc.getLocWithOffset(Offset));101}102103const std::set<std::string> USRSet;104const SymbolName PrevName;105SymbolOccurrences Occurrences;106const ASTContext &Context;107};108109SourceLocation StartLocationForType(TypeLoc TL) {110// For elaborated types (e.g. `struct a::A`) we want the portion after the111// `struct` but including the namespace qualifier, `a::`.112if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>()) {113NestedNameSpecifierLoc NestedNameSpecifier =114ElaboratedTypeLoc.getQualifierLoc();115if (NestedNameSpecifier.getNestedNameSpecifier())116return NestedNameSpecifier.getBeginLoc();117TL = TL.getNextTypeLoc();118}119return TL.getBeginLoc();120}121122SourceLocation EndLocationForType(TypeLoc TL) {123// Dig past any namespace or keyword qualifications.124while (TL.getTypeLocClass() == TypeLoc::Elaborated ||125TL.getTypeLocClass() == TypeLoc::Qualified)126TL = TL.getNextTypeLoc();127128// The location for template specializations (e.g. Foo<int>) includes the129// templated types in its location range. We want to restrict this to just130// before the `<` character.131if (TL.getTypeLocClass() == TypeLoc::TemplateSpecialization) {132return TL.castAs<TemplateSpecializationTypeLoc>()133.getLAngleLoc()134.getLocWithOffset(-1);135}136return TL.getEndLoc();137}138139NestedNameSpecifier *GetNestedNameForType(TypeLoc TL) {140// Dig past any keyword qualifications.141while (TL.getTypeLocClass() == TypeLoc::Qualified)142TL = TL.getNextTypeLoc();143144// For elaborated types (e.g. `struct a::A`) we want the portion after the145// `struct` but including the namespace qualifier, `a::`.146if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>())147return ElaboratedTypeLoc.getQualifierLoc().getNestedNameSpecifier();148return nullptr;149}150151// Find all locations identified by the given USRs for rename.152//153// This class will traverse the AST and find every AST node whose USR is in the154// given USRs' set.155class RenameLocFinder : public RecursiveASTVisitor<RenameLocFinder> {156public:157RenameLocFinder(llvm::ArrayRef<std::string> USRs, ASTContext &Context)158: USRSet(USRs.begin(), USRs.end()), Context(Context) {}159160// A structure records all information of a symbol reference being renamed.161// We try to add as few prefix qualifiers as possible.162struct RenameInfo {163// The begin location of a symbol being renamed.164SourceLocation Begin;165// The end location of a symbol being renamed.166SourceLocation End;167// The declaration of a symbol being renamed (can be nullptr).168const NamedDecl *FromDecl;169// The declaration in which the nested name is contained (can be nullptr).170const Decl *Context;171// The nested name being replaced (can be nullptr).172const NestedNameSpecifier *Specifier;173// Determine whether the prefix qualifiers of the NewName should be ignored.174// Normally, we set it to true for the symbol declaration and definition to175// avoid adding prefix qualifiers.176// For example, if it is true and NewName is "a::b::foo", then the symbol177// occurrence which the RenameInfo points to will be renamed to "foo".178bool IgnorePrefixQualifers;179};180181bool VisitNamedDecl(const NamedDecl *Decl) {182// UsingDecl has been handled in other place.183if (llvm::isa<UsingDecl>(Decl))184return true;185186// DestructorDecl has been handled in Typeloc.187if (llvm::isa<CXXDestructorDecl>(Decl))188return true;189190if (Decl->isImplicit())191return true;192193if (isInUSRSet(Decl)) {194// For the case of renaming an alias template, we actually rename the195// underlying alias declaration of the template.196if (const auto* TAT = dyn_cast<TypeAliasTemplateDecl>(Decl))197Decl = TAT->getTemplatedDecl();198199auto StartLoc = Decl->getLocation();200auto EndLoc = StartLoc;201if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {202RenameInfo Info = {StartLoc,203EndLoc,204/*FromDecl=*/nullptr,205/*Context=*/nullptr,206/*Specifier=*/nullptr,207/*IgnorePrefixQualifers=*/true};208RenameInfos.push_back(Info);209}210}211return true;212}213214bool VisitMemberExpr(const MemberExpr *Expr) {215const NamedDecl *Decl = Expr->getFoundDecl();216auto StartLoc = Expr->getMemberLoc();217auto EndLoc = Expr->getMemberLoc();218if (isInUSRSet(Decl)) {219RenameInfos.push_back({StartLoc, EndLoc,220/*FromDecl=*/nullptr,221/*Context=*/nullptr,222/*Specifier=*/nullptr,223/*IgnorePrefixQualifiers=*/true});224}225return true;226}227228bool VisitDesignatedInitExpr(const DesignatedInitExpr *E) {229for (const DesignatedInitExpr::Designator &D : E->designators()) {230if (D.isFieldDesignator()) {231if (const FieldDecl *Decl = D.getFieldDecl()) {232if (isInUSRSet(Decl)) {233auto StartLoc = D.getFieldLoc();234auto EndLoc = D.getFieldLoc();235RenameInfos.push_back({StartLoc, EndLoc,236/*FromDecl=*/nullptr,237/*Context=*/nullptr,238/*Specifier=*/nullptr,239/*IgnorePrefixQualifiers=*/true});240}241}242}243}244return true;245}246247bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) {248// Fix the constructor initializer when renaming class members.249for (const auto *Initializer : CD->inits()) {250// Ignore implicit initializers.251if (!Initializer->isWritten())252continue;253254if (const FieldDecl *FD = Initializer->getMember()) {255if (isInUSRSet(FD)) {256auto Loc = Initializer->getSourceLocation();257RenameInfos.push_back({Loc, Loc,258/*FromDecl=*/nullptr,259/*Context=*/nullptr,260/*Specifier=*/nullptr,261/*IgnorePrefixQualifiers=*/true});262}263}264}265return true;266}267268bool VisitDeclRefExpr(const DeclRefExpr *Expr) {269const NamedDecl *Decl = Expr->getFoundDecl();270// Get the underlying declaration of the shadow declaration introduced by a271// using declaration.272if (auto *UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) {273Decl = UsingShadow->getTargetDecl();274}275276auto StartLoc = Expr->getBeginLoc();277// For template function call expressions like `foo<int>()`, we want to278// restrict the end of location to just before the `<` character.279SourceLocation EndLoc = Expr->hasExplicitTemplateArgs()280? Expr->getLAngleLoc().getLocWithOffset(-1)281: Expr->getEndLoc();282283if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Decl)) {284if (isInUSRSet(MD)) {285// Handle renaming static template class methods, we only rename the286// name without prefix qualifiers and restrict the source range to the287// name.288RenameInfos.push_back({EndLoc, EndLoc,289/*FromDecl=*/nullptr,290/*Context=*/nullptr,291/*Specifier=*/nullptr,292/*IgnorePrefixQualifiers=*/true});293return true;294}295}296297// In case of renaming an enum declaration, we have to explicitly handle298// unscoped enum constants referenced in expressions (e.g.299// "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped300// enum decl "ns1::ns2::Color") as these enum constants cannot be caught by301// TypeLoc.302if (const auto *T = llvm::dyn_cast<EnumConstantDecl>(Decl)) {303// FIXME: Handle the enum constant without prefix qualifiers (`a = Green`)304// when renaming an unscoped enum declaration with a new namespace.305if (!Expr->hasQualifier())306return true;307308if (const auto *ED =309llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) {310if (ED->isScoped())311return true;312Decl = ED;313}314// The current fix would qualify "ns1::ns2::Green" as315// "ns1::ns2::Color::Green".316//317// Get the EndLoc of the replacement by moving 1 character backward (318// to exclude the last '::').319//320// ns1::ns2::Green;321// ^ ^^322// BeginLoc |EndLoc of the qualifier323// new EndLoc324EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1);325assert(EndLoc.isValid() &&326"The enum constant should have prefix qualifers.");327}328if (isInUSRSet(Decl) &&329IsValidEditLoc(Context.getSourceManager(), StartLoc)) {330RenameInfo Info = {StartLoc,331EndLoc,332Decl,333getClosestAncestorDecl(*Expr),334Expr->getQualifier(),335/*IgnorePrefixQualifers=*/false};336RenameInfos.push_back(Info);337}338339return true;340}341342bool VisitUsingDecl(const UsingDecl *Using) {343for (const auto *UsingShadow : Using->shadows()) {344if (isInUSRSet(UsingShadow->getTargetDecl())) {345UsingDecls.push_back(Using);346break;347}348}349return true;350}351352bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) {353if (!NestedLoc.getNestedNameSpecifier()->getAsType())354return true;355356if (const auto *TargetDecl =357getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) {358if (isInUSRSet(TargetDecl)) {359RenameInfo Info = {NestedLoc.getBeginLoc(),360EndLocationForType(NestedLoc.getTypeLoc()),361TargetDecl,362getClosestAncestorDecl(NestedLoc),363NestedLoc.getNestedNameSpecifier()->getPrefix(),364/*IgnorePrefixQualifers=*/false};365RenameInfos.push_back(Info);366}367}368return true;369}370371bool VisitTypeLoc(TypeLoc Loc) {372auto Parents = Context.getParents(Loc);373TypeLoc ParentTypeLoc;374if (!Parents.empty()) {375// Handle cases of nested name specificier locations.376//377// The VisitNestedNameSpecifierLoc interface is not impelmented in378// RecursiveASTVisitor, we have to handle it explicitly.379if (const auto *NSL = Parents[0].get<NestedNameSpecifierLoc>()) {380VisitNestedNameSpecifierLocations(*NSL);381return true;382}383384if (const auto *TL = Parents[0].get<TypeLoc>())385ParentTypeLoc = *TL;386}387388// Handle the outermost TypeLoc which is directly linked to the interesting389// declaration and don't handle nested name specifier locations.390if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) {391if (isInUSRSet(TargetDecl)) {392// Only handle the outermost typeLoc.393//394// For a type like "a::Foo", there will be two typeLocs for it.395// One ElaboratedType, the other is RecordType:396//397// ElaboratedType 0x33b9390 'a::Foo' sugar398// `-RecordType 0x338fef0 'class a::Foo'399// `-CXXRecord 0x338fe58 'Foo'400//401// Skip if this is an inner typeLoc.402if (!ParentTypeLoc.isNull() &&403isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc)))404return true;405406auto StartLoc = StartLocationForType(Loc);407auto EndLoc = EndLocationForType(Loc);408if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {409RenameInfo Info = {StartLoc,410EndLoc,411TargetDecl,412getClosestAncestorDecl(Loc),413GetNestedNameForType(Loc),414/*IgnorePrefixQualifers=*/false};415RenameInfos.push_back(Info);416}417return true;418}419}420421// Handle specific template class specialiation cases.422if (const auto *TemplateSpecType =423dyn_cast<TemplateSpecializationType>(Loc.getType())) {424TypeLoc TargetLoc = Loc;425if (!ParentTypeLoc.isNull()) {426if (llvm::isa<ElaboratedType>(ParentTypeLoc.getType()))427TargetLoc = ParentTypeLoc;428}429430if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) {431TypeLoc TargetLoc = Loc;432// FIXME: Find a better way to handle this case.433// For the qualified template class specification type like434// "ns::Foo<int>" in "ns::Foo<int>& f();", we want the parent typeLoc435// (ElaboratedType) of the TemplateSpecializationType in order to436// catch the prefix qualifiers "ns::".437if (!ParentTypeLoc.isNull() &&438llvm::isa<ElaboratedType>(ParentTypeLoc.getType()))439TargetLoc = ParentTypeLoc;440441auto StartLoc = StartLocationForType(TargetLoc);442auto EndLoc = EndLocationForType(TargetLoc);443if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {444RenameInfo Info = {445StartLoc,446EndLoc,447TemplateSpecType->getTemplateName().getAsTemplateDecl(),448getClosestAncestorDecl(DynTypedNode::create(TargetLoc)),449GetNestedNameForType(TargetLoc),450/*IgnorePrefixQualifers=*/false};451RenameInfos.push_back(Info);452}453}454}455return true;456}457458// Returns a list of RenameInfo.459const std::vector<RenameInfo> &getRenameInfos() const { return RenameInfos; }460461// Returns a list of using declarations which are needed to update.462const std::vector<const UsingDecl *> &getUsingDecls() const {463return UsingDecls;464}465466private:467// Get the supported declaration from a given typeLoc. If the declaration type468// is not supported, returns nullptr.469const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) {470if (const auto* TT = Loc.getType()->getAs<clang::TypedefType>())471return TT->getDecl();472if (const auto *RD = Loc.getType()->getAsCXXRecordDecl())473return RD;474if (const auto *ED =475llvm::dyn_cast_or_null<EnumDecl>(Loc.getType()->getAsTagDecl()))476return ED;477return nullptr;478}479480// Get the closest ancester which is a declaration of a given AST node.481template <typename ASTNodeType>482const Decl *getClosestAncestorDecl(const ASTNodeType &Node) {483auto Parents = Context.getParents(Node);484// FIXME: figure out how to handle it when there are multiple parents.485if (Parents.size() != 1)486return nullptr;487if (ASTNodeKind::getFromNodeKind<Decl>().isBaseOf(Parents[0].getNodeKind()))488return Parents[0].template get<Decl>();489return getClosestAncestorDecl(Parents[0]);490}491492// Get the parent typeLoc of a given typeLoc. If there is no such parent,493// return nullptr.494const TypeLoc *getParentTypeLoc(TypeLoc Loc) const {495auto Parents = Context.getParents(Loc);496// FIXME: figure out how to handle it when there are multiple parents.497if (Parents.size() != 1)498return nullptr;499return Parents[0].get<TypeLoc>();500}501502// Check whether the USR of a given Decl is in the USRSet.503bool isInUSRSet(const Decl *Decl) const {504auto USR = getUSRForDecl(Decl);505if (USR.empty())506return false;507return llvm::is_contained(USRSet, USR);508}509510const std::set<std::string> USRSet;511ASTContext &Context;512std::vector<RenameInfo> RenameInfos;513// Record all interested using declarations which contains the using-shadow514// declarations of the symbol declarations being renamed.515std::vector<const UsingDecl *> UsingDecls;516};517518} // namespace519520SymbolOccurrences getOccurrencesOfUSRs(ArrayRef<std::string> USRs,521StringRef PrevName, Decl *Decl) {522USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext());523Visitor.TraverseDecl(Decl);524return Visitor.takeOccurrences();525}526527std::vector<tooling::AtomicChange>528createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,529llvm::StringRef NewName, Decl *TranslationUnitDecl) {530RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext());531Finder.TraverseDecl(TranslationUnitDecl);532533const SourceManager &SM =534TranslationUnitDecl->getASTContext().getSourceManager();535536std::vector<tooling::AtomicChange> AtomicChanges;537auto Replace = [&](SourceLocation Start, SourceLocation End,538llvm::StringRef Text) {539tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start);540llvm::Error Err = ReplaceChange.replace(541SM, CharSourceRange::getTokenRange(Start, End), Text);542if (Err) {543llvm::errs() << "Failed to add replacement to AtomicChange: "544<< llvm::toString(std::move(Err)) << "\n";545return;546}547AtomicChanges.push_back(std::move(ReplaceChange));548};549550for (const auto &RenameInfo : Finder.getRenameInfos()) {551std::string ReplacedName = NewName.str();552if (RenameInfo.IgnorePrefixQualifers) {553// Get the name without prefix qualifiers from NewName.554size_t LastColonPos = NewName.find_last_of(':');555if (LastColonPos != std::string::npos)556ReplacedName = std::string(NewName.substr(LastColonPos + 1));557} else {558if (RenameInfo.FromDecl && RenameInfo.Context) {559if (!llvm::isa<clang::TranslationUnitDecl>(560RenameInfo.Context->getDeclContext())) {561ReplacedName = tooling::replaceNestedName(562RenameInfo.Specifier, RenameInfo.Begin,563RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl,564NewName.starts_with("::") ? NewName.str()565: ("::" + NewName).str());566} else {567// This fixes the case where type `T` is a parameter inside a function568// type (e.g. `std::function<void(T)>`) and the DeclContext of `T`569// becomes the translation unit. As a workaround, we simply use570// fully-qualified name here for all references whose `DeclContext` is571// the translation unit and ignore the possible existence of572// using-decls (in the global scope) that can shorten the replaced573// name.574llvm::StringRef ActualName = Lexer::getSourceText(575CharSourceRange::getTokenRange(576SourceRange(RenameInfo.Begin, RenameInfo.End)),577SM, TranslationUnitDecl->getASTContext().getLangOpts());578// Add the leading "::" back if the name written in the code contains579// it.580if (ActualName.starts_with("::") && !NewName.starts_with("::")) {581ReplacedName = "::" + NewName.str();582}583}584}585// If the NewName contains leading "::", add it back.586if (NewName.starts_with("::") && NewName.substr(2) == ReplacedName)587ReplacedName = NewName.str();588}589Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName);590}591592// Hanlde using declarations explicitly as "using a::Foo" don't trigger593// typeLoc for "a::Foo".594for (const auto *Using : Finder.getUsingDecls())595Replace(Using->getBeginLoc(), Using->getEndLoc(), "using " + NewName.str());596597return AtomicChanges;598}599600} // end namespace tooling601} // end namespace clang602603604