613 lines · cpp
1//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// Methods for finding all instances of a USR. Our strategy is very11/// simple; we just compare the USR at every relevant AST node with the one12/// provided.13///14//===----------------------------------------------------------------------===//15 16#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"17#include "clang/AST/ASTContext.h"18#include "clang/AST/ParentMapContext.h"19#include "clang/AST/RecursiveASTVisitor.h"20#include "clang/Basic/LLVM.h"21#include "clang/Basic/SourceLocation.h"22#include "clang/Basic/SourceManager.h"23#include "clang/Lex/Lexer.h"24#include "clang/Tooling/Refactoring/Lookup.h"25#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"26#include "clang/Tooling/Refactoring/Rename/SymbolName.h"27#include "clang/Tooling/Refactoring/Rename/USRFinder.h"28#include "llvm/ADT/StringRef.h"29#include "llvm/Support/Casting.h"30#include <cstddef>31#include <set>32#include <string>33#include <vector>34 35using namespace llvm;36 37namespace clang {38namespace tooling {39 40namespace {41 42// Returns true if the given Loc is valid for edit. We don't edit the43// SourceLocations that are valid or in temporary buffer.44bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) {45 if (Loc.isInvalid())46 return false;47 const clang::FullSourceLoc FullLoc(Loc, SM);48 auto FileIdAndOffset = FullLoc.getSpellingLoc().getDecomposedLoc();49 return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr;50}51 52// 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:57 explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs,58 StringRef PrevName,59 const ASTContext &Context)60 : RecursiveSymbolVisitor(Context.getSourceManager(),61 Context.getLangOpts()),62 USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) {63 }64 65 bool visitSymbolOccurrence(const NamedDecl *ND,66 ArrayRef<SourceRange> NameRanges) {67 if (USRSet.find(getUSRForDecl(ND)) != USRSet.end()) {68 assert(NameRanges.size() == 1 &&69 "Multiple name pieces are not supported yet!");70 SourceLocation Loc = NameRanges[0].getBegin();71 const SourceManager &SM = Context.getSourceManager();72 // TODO: Deal with macro occurrences correctly.73 if (Loc.isMacroID())74 Loc = SM.getSpellingLoc(Loc);75 checkAndAddLocation(Loc);76 }77 return true;78 }79 80 // Non-visitors:81 82 /// Returns a set of unique symbol occurrences. Duplicate or83 /// overlapping occurrences are erroneous and should be reported!84 SymbolOccurrences takeOccurrences() { return std::move(Occurrences); }85 86private:87 void checkAndAddLocation(SourceLocation Loc) {88 const SourceLocation BeginLoc = Loc;89 const SourceLocation EndLoc = Lexer::getLocForEndOfToken(90 BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts());91 StringRef TokenName =92 Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),93 Context.getSourceManager(), Context.getLangOpts());94 size_t Offset = TokenName.find(PrevName.getNamePieces()[0]);95 96 // The token of the source location we find actually has the old97 // name.98 if (Offset != StringRef::npos)99 Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol,100 BeginLoc.getLocWithOffset(Offset));101 }102 103 const std::set<std::string> USRSet;104 const SymbolName PrevName;105 SymbolOccurrences Occurrences;106 const ASTContext &Context;107};108 109SourceLocation StartLocationForType(TypeLoc TL) {110 if (auto QTL = TL.getAs<QualifiedTypeLoc>())111 TL = QTL.getUnqualifiedLoc();112 113 // For elaborated types (e.g. `struct a::A`) we want the portion after the114 // `struct`, including the namespace qualifier, `a::`.115 switch (TL.getTypeLocClass()) {116 case TypeLoc::Record:117 case TypeLoc::InjectedClassName:118 case TypeLoc::Enum: {119 auto TTL = TL.castAs<TagTypeLoc>();120 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())121 return QualifierLoc.getBeginLoc();122 return TTL.getNameLoc();123 }124 case TypeLoc::Typedef: {125 auto TTL = TL.castAs<TypedefTypeLoc>();126 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())127 return QualifierLoc.getBeginLoc();128 return TTL.getNameLoc();129 }130 case TypeLoc::UnresolvedUsing: {131 auto TTL = TL.castAs<UnresolvedUsingTypeLoc>();132 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())133 return QualifierLoc.getBeginLoc();134 return TTL.getNameLoc();135 }136 case TypeLoc::Using: {137 auto TTL = TL.castAs<UsingTypeLoc>();138 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())139 return QualifierLoc.getBeginLoc();140 return TTL.getNameLoc();141 }142 case TypeLoc::TemplateSpecialization: {143 auto TTL = TL.castAs<TemplateSpecializationTypeLoc>();144 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())145 return QualifierLoc.getBeginLoc();146 return TTL.getTemplateNameLoc();147 }148 case TypeLoc::DeducedTemplateSpecialization: {149 auto DTL = TL.castAs<clang::DeducedTemplateSpecializationTypeLoc>();150 if (NestedNameSpecifierLoc QualifierLoc = DTL.getQualifierLoc())151 return QualifierLoc.getBeginLoc();152 return DTL.getTemplateNameLoc();153 }154 case TypeLoc::DependentName: {155 auto TTL = TL.castAs<DependentNameTypeLoc>();156 if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())157 return QualifierLoc.getBeginLoc();158 return TTL.getNameLoc();159 }160 default:161 llvm_unreachable("unhandled TypeLoc class");162 }163}164 165SourceLocation EndLocationForType(TypeLoc TL) {166 if (auto QTL = TL.getAs<QualifiedTypeLoc>())167 TL = QTL.getUnqualifiedLoc();168 169 // The location for template specializations (e.g. Foo<int>) includes the170 // templated types in its location range. We want to restrict this to just171 // before the `<` character.172 if (auto TTL = TL.getAs<TemplateSpecializationTypeLoc>())173 return TTL.getLAngleLoc().getLocWithOffset(-1);174 return TL.getEndLoc();175}176 177NestedNameSpecifier GetNestedNameForType(TypeLoc TL) {178 if (auto QTL = TL.getAs<QualifiedTypeLoc>())179 TL = QTL.getUnqualifiedLoc();180 return TL.getPrefix().getNestedNameSpecifier();181}182 183// Find all locations identified by the given USRs for rename.184//185// This class will traverse the AST and find every AST node whose USR is in the186// given USRs' set.187class RenameLocFinder : public RecursiveASTVisitor<RenameLocFinder> {188public:189 RenameLocFinder(llvm::ArrayRef<std::string> USRs, ASTContext &Context)190 : USRSet(USRs.begin(), USRs.end()), Context(Context) {}191 192 // A structure records all information of a symbol reference being renamed.193 // We try to add as few prefix qualifiers as possible.194 struct RenameInfo {195 // The begin location of a symbol being renamed.196 SourceLocation Begin;197 // The end location of a symbol being renamed.198 SourceLocation End;199 // The declaration of a symbol being renamed (can be nullptr).200 const NamedDecl *FromDecl;201 // The declaration in which the nested name is contained (can be nullptr).202 const Decl *Context;203 // The nested name being replaced.204 NestedNameSpecifier Specifier;205 // Determine whether the prefix qualifiers of the NewName should be ignored.206 // Normally, we set it to true for the symbol declaration and definition to207 // avoid adding prefix qualifiers.208 // For example, if it is true and NewName is "a::b::foo", then the symbol209 // occurrence which the RenameInfo points to will be renamed to "foo".210 bool IgnorePrefixQualifiers;211 };212 213 bool VisitNamedDecl(const NamedDecl *Decl) {214 // UsingDecl has been handled in other place.215 if (llvm::isa<UsingDecl>(Decl))216 return true;217 218 // DestructorDecl has been handled in Typeloc.219 if (llvm::isa<CXXDestructorDecl>(Decl))220 return true;221 222 if (Decl->isImplicit())223 return true;224 225 if (isInUSRSet(Decl)) {226 // For the case of renaming an alias template, we actually rename the227 // underlying alias declaration of the template.228 if (const auto* TAT = dyn_cast<TypeAliasTemplateDecl>(Decl))229 Decl = TAT->getTemplatedDecl();230 231 auto StartLoc = Decl->getLocation();232 auto EndLoc = StartLoc;233 if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {234 RenameInfo Info = {StartLoc,235 EndLoc,236 /*FromDecl=*/nullptr,237 /*Context=*/nullptr,238 /*Specifier=*/std::nullopt,239 /*IgnorePrefixQualifiers=*/true};240 RenameInfos.push_back(Info);241 }242 }243 return true;244 }245 246 bool VisitMemberExpr(const MemberExpr *Expr) {247 const NamedDecl *Decl = Expr->getFoundDecl();248 auto StartLoc = Expr->getMemberLoc();249 auto EndLoc = Expr->getMemberLoc();250 if (isInUSRSet(Decl)) {251 RenameInfos.push_back({StartLoc, EndLoc,252 /*FromDecl=*/nullptr,253 /*Context=*/nullptr,254 /*Specifier=*/std::nullopt,255 /*IgnorePrefixQualifiers=*/true});256 }257 return true;258 }259 260 bool VisitDesignatedInitExpr(const DesignatedInitExpr *E) {261 for (const DesignatedInitExpr::Designator &D : E->designators()) {262 if (D.isFieldDesignator()) {263 if (const FieldDecl *Decl = D.getFieldDecl()) {264 if (isInUSRSet(Decl)) {265 auto StartLoc = D.getFieldLoc();266 auto EndLoc = D.getFieldLoc();267 RenameInfos.push_back({StartLoc, EndLoc,268 /*FromDecl=*/nullptr,269 /*Context=*/nullptr,270 /*Specifier=*/std::nullopt,271 /*IgnorePrefixQualifiers=*/true});272 }273 }274 }275 }276 return true;277 }278 279 bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) {280 // Fix the constructor initializer when renaming class members.281 for (const auto *Initializer : CD->inits()) {282 // Ignore implicit initializers.283 if (!Initializer->isWritten())284 continue;285 286 if (const FieldDecl *FD = Initializer->getMember()) {287 if (isInUSRSet(FD)) {288 auto Loc = Initializer->getSourceLocation();289 RenameInfos.push_back({Loc, Loc,290 /*FromDecl=*/nullptr,291 /*Context=*/nullptr,292 /*Specifier=*/std::nullopt,293 /*IgnorePrefixQualifiers=*/true});294 }295 }296 }297 return true;298 }299 300 bool VisitDeclRefExpr(const DeclRefExpr *Expr) {301 const NamedDecl *Decl = Expr->getFoundDecl();302 // Get the underlying declaration of the shadow declaration introduced by a303 // using declaration.304 if (auto *UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) {305 Decl = UsingShadow->getTargetDecl();306 }307 308 auto StartLoc = Expr->getBeginLoc();309 // For template function call expressions like `foo<int>()`, we want to310 // restrict the end of location to just before the `<` character.311 SourceLocation EndLoc = Expr->hasExplicitTemplateArgs()312 ? Expr->getLAngleLoc().getLocWithOffset(-1)313 : Expr->getEndLoc();314 315 if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Decl)) {316 if (isInUSRSet(MD)) {317 // Handle renaming static template class methods, we only rename the318 // name without prefix qualifiers and restrict the source range to the319 // name.320 RenameInfos.push_back({EndLoc, EndLoc,321 /*FromDecl=*/nullptr,322 /*Context=*/nullptr,323 /*Specifier=*/std::nullopt,324 /*IgnorePrefixQualifiers=*/true});325 return true;326 }327 }328 329 // In case of renaming an enum declaration, we have to explicitly handle330 // unscoped enum constants referenced in expressions (e.g.331 // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped332 // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by333 // TypeLoc.334 if (const auto *T = llvm::dyn_cast<EnumConstantDecl>(Decl)) {335 // FIXME: Handle the enum constant without prefix qualifiers (`a = Green`)336 // when renaming an unscoped enum declaration with a new namespace.337 if (!Expr->hasQualifier())338 return true;339 340 if (const auto *ED =341 llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) {342 if (ED->isScoped())343 return true;344 Decl = ED;345 }346 // The current fix would qualify "ns1::ns2::Green" as347 // "ns1::ns2::Color::Green".348 //349 // Get the EndLoc of the replacement by moving 1 character backward (350 // to exclude the last '::').351 //352 // ns1::ns2::Green;353 // ^ ^^354 // BeginLoc |EndLoc of the qualifier355 // new EndLoc356 EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1);357 assert(EndLoc.isValid() &&358 "The enum constant should have prefix qualifers.");359 }360 if (isInUSRSet(Decl) &&361 IsValidEditLoc(Context.getSourceManager(), StartLoc)) {362 RenameInfo Info = {StartLoc,363 EndLoc,364 Decl,365 getClosestAncestorDecl(*Expr),366 Expr->getQualifier(),367 /*IgnorePrefixQualifiers=*/false};368 RenameInfos.push_back(Info);369 }370 371 return true;372 }373 374 bool VisitUsingDecl(const UsingDecl *Using) {375 for (const auto *UsingShadow : Using->shadows()) {376 if (isInUSRSet(UsingShadow->getTargetDecl())) {377 UsingDecls.push_back(Using);378 break;379 }380 }381 return true;382 }383 384 bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) {385 TypeLoc TL = NestedLoc.getAsTypeLoc();386 if (!TL)387 return true;388 389 if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(TL)) {390 if (isInUSRSet(TargetDecl)) {391 RenameInfo Info = {NestedLoc.getBeginLoc(),392 EndLocationForType(TL),393 TargetDecl,394 getClosestAncestorDecl(NestedLoc),395 /*Specifier=*/std::nullopt,396 /*IgnorePrefixQualifiers=*/false};397 RenameInfos.push_back(Info);398 }399 }400 return true;401 }402 403 bool VisitTypeLoc(TypeLoc Loc) {404 auto Parents = Context.getParents(Loc);405 TypeLoc ParentTypeLoc;406 if (!Parents.empty()) {407 // Handle cases of nested name specificier locations.408 //409 // The VisitNestedNameSpecifierLoc interface is not impelmented in410 // RecursiveASTVisitor, we have to handle it explicitly.411 if (const auto *NSL = Parents[0].get<NestedNameSpecifierLoc>()) {412 VisitNestedNameSpecifierLocations(*NSL);413 return true;414 }415 416 if (const auto *TL = Parents[0].get<TypeLoc>())417 ParentTypeLoc = *TL;418 }419 420 // Handle the outermost TypeLoc which is directly linked to the interesting421 // declaration and don't handle nested name specifier locations.422 if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) {423 if (isInUSRSet(TargetDecl)) {424 // Only handle the outermost typeLoc.425 //426 // For a type like "a::Foo", there will be two typeLocs for it.427 // One ElaboratedType, the other is RecordType:428 //429 // ElaboratedType 0x33b9390 'a::Foo' sugar430 // `-RecordType 0x338fef0 'class a::Foo'431 // `-CXXRecord 0x338fe58 'Foo'432 //433 // Skip if this is an inner typeLoc.434 if (!ParentTypeLoc.isNull() &&435 isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc)))436 return true;437 438 auto StartLoc = StartLocationForType(Loc);439 auto EndLoc = EndLocationForType(Loc);440 if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {441 RenameInfo Info = {StartLoc,442 EndLoc,443 TargetDecl,444 getClosestAncestorDecl(Loc),445 GetNestedNameForType(Loc),446 /*IgnorePrefixQualifiers=*/false};447 RenameInfos.push_back(Info);448 }449 return true;450 }451 }452 453 // Handle specific template class specialiation cases.454 if (const auto *TemplateSpecType =455 dyn_cast<TemplateSpecializationType>(Loc.getType())) {456 if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) {457 auto StartLoc = StartLocationForType(Loc);458 auto EndLoc = EndLocationForType(Loc);459 if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {460 RenameInfo Info = {461 StartLoc,462 EndLoc,463 TemplateSpecType->getTemplateName().getAsTemplateDecl(),464 getClosestAncestorDecl(DynTypedNode::create(Loc)),465 GetNestedNameForType(Loc),466 /*IgnorePrefixQualifiers=*/false};467 RenameInfos.push_back(Info);468 }469 }470 }471 return true;472 }473 474 // Returns a list of RenameInfo.475 const std::vector<RenameInfo> &getRenameInfos() const { return RenameInfos; }476 477 // Returns a list of using declarations which are needed to update.478 const std::vector<const UsingDecl *> &getUsingDecls() const {479 return UsingDecls;480 }481 482private:483 // Get the supported declaration from a given typeLoc. If the declaration type484 // is not supported, returns nullptr.485 const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) {486 if (const auto* TT = Loc.getType()->getAs<clang::TypedefType>())487 return TT->getDecl();488 return Loc.getType()->getAsTagDecl();489 }490 491 // Get the closest ancester which is a declaration of a given AST node.492 template <typename ASTNodeType>493 const Decl *getClosestAncestorDecl(const ASTNodeType &Node) {494 auto Parents = Context.getParents(Node);495 // FIXME: figure out how to handle it when there are multiple parents.496 if (Parents.size() != 1)497 return nullptr;498 if (ASTNodeKind::getFromNodeKind<Decl>().isBaseOf(Parents[0].getNodeKind()))499 return Parents[0].template get<Decl>();500 return getClosestAncestorDecl(Parents[0]);501 }502 503 // Get the parent typeLoc of a given typeLoc. If there is no such parent,504 // return nullptr.505 const TypeLoc *getParentTypeLoc(TypeLoc Loc) const {506 auto Parents = Context.getParents(Loc);507 // FIXME: figure out how to handle it when there are multiple parents.508 if (Parents.size() != 1)509 return nullptr;510 return Parents[0].get<TypeLoc>();511 }512 513 // Check whether the USR of a given Decl is in the USRSet.514 bool isInUSRSet(const Decl *Decl) const {515 auto USR = getUSRForDecl(Decl);516 if (USR.empty())517 return false;518 return llvm::is_contained(USRSet, USR);519 }520 521 const std::set<std::string> USRSet;522 ASTContext &Context;523 std::vector<RenameInfo> RenameInfos;524 // Record all interested using declarations which contains the using-shadow525 // declarations of the symbol declarations being renamed.526 std::vector<const UsingDecl *> UsingDecls;527};528 529} // namespace530 531SymbolOccurrences getOccurrencesOfUSRs(ArrayRef<std::string> USRs,532 StringRef PrevName, Decl *Decl) {533 USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext());534 Visitor.TraverseDecl(Decl);535 return Visitor.takeOccurrences();536}537 538std::vector<tooling::AtomicChange>539createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,540 llvm::StringRef NewName, Decl *TranslationUnitDecl) {541 RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext());542 Finder.TraverseDecl(TranslationUnitDecl);543 544 const SourceManager &SM =545 TranslationUnitDecl->getASTContext().getSourceManager();546 547 std::vector<tooling::AtomicChange> AtomicChanges;548 auto Replace = [&](SourceLocation Start, SourceLocation End,549 llvm::StringRef Text) {550 tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start);551 llvm::Error Err = ReplaceChange.replace(552 SM, CharSourceRange::getTokenRange(Start, End), Text);553 if (Err) {554 llvm::errs() << "Failed to add replacement to AtomicChange: "555 << llvm::toString(std::move(Err)) << "\n";556 return;557 }558 AtomicChanges.push_back(std::move(ReplaceChange));559 };560 561 for (const auto &RenameInfo : Finder.getRenameInfos()) {562 std::string ReplacedName = NewName.str();563 if (RenameInfo.IgnorePrefixQualifiers) {564 // Get the name without prefix qualifiers from NewName.565 size_t LastColonPos = NewName.find_last_of(':');566 if (LastColonPos != std::string::npos)567 ReplacedName = std::string(NewName.substr(LastColonPos + 1));568 } else {569 if (RenameInfo.FromDecl && RenameInfo.Context) {570 if (!llvm::isa<clang::TranslationUnitDecl>(571 RenameInfo.Context->getDeclContext())) {572 ReplacedName = tooling::replaceNestedName(573 RenameInfo.Specifier, RenameInfo.Begin,574 RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl,575 NewName.starts_with("::") ? NewName.str()576 : ("::" + NewName).str());577 } else {578 // This fixes the case where type `T` is a parameter inside a function579 // type (e.g. `std::function<void(T)>`) and the DeclContext of `T`580 // becomes the translation unit. As a workaround, we simply use581 // fully-qualified name here for all references whose `DeclContext` is582 // the translation unit and ignore the possible existence of583 // using-decls (in the global scope) that can shorten the replaced584 // name.585 llvm::StringRef ActualName = Lexer::getSourceText(586 CharSourceRange::getTokenRange(587 SourceRange(RenameInfo.Begin, RenameInfo.End)),588 SM, TranslationUnitDecl->getASTContext().getLangOpts());589 // Add the leading "::" back if the name written in the code contains590 // it.591 if (ActualName.starts_with("::") && !NewName.starts_with("::")) {592 ReplacedName = "::" + NewName.str();593 }594 }595 }596 // If the NewName contains leading "::", add it back.597 if (NewName.starts_with("::") && NewName.substr(2) == ReplacedName)598 ReplacedName = NewName.str();599 }600 Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName);601 }602 603 // Hanlde using declarations explicitly as "using a::Foo" don't trigger604 // typeLoc for "a::Foo".605 for (const auto *Using : Finder.getUsingDecls())606 Replace(Using->getBeginLoc(), Using->getEndLoc(), "using " + NewName.str());607 608 return AtomicChanges;609}610 611} // end namespace tooling612} // end namespace clang613