arphaman created this revision. Herald added a subscriber: mgorny. The core engine of clang-rename will be used for local and global renames in the new refactoring engine (http://lists.llvm.org/pipermail/cfe-dev/2017-June/054286.html).
I haven't renamed the files/cleaned up the code in this patch, do you think I should? E.g. SymbolOccurrenceFinder instead of USRLocFinder. Repository: rL LLVM https://reviews.llvm.org/D34696 Files: include/clang/Tooling/Refactoring/USRFinder.h include/clang/Tooling/Refactoring/USRFindingAction.h include/clang/Tooling/Refactoring/USRLocFinder.h include/clang/module.modulemap lib/Tooling/Refactoring/CMakeLists.txt lib/Tooling/Refactoring/USRFinder.cpp lib/Tooling/Refactoring/USRFindingAction.cpp lib/Tooling/Refactoring/USRLocFinder.cpp tools/extra/clang-rename/CMakeLists.txt tools/extra/clang-rename/RenamingAction.cpp tools/extra/clang-rename/USRFinder.cpp tools/extra/clang-rename/USRFinder.h tools/extra/clang-rename/USRFindingAction.cpp tools/extra/clang-rename/USRFindingAction.h tools/extra/clang-rename/USRLocFinder.cpp tools/extra/clang-rename/USRLocFinder.h tools/extra/clang-rename/tool/ClangRename.cpp
Index: tools/extra/clang-rename/tool/ClangRename.cpp =================================================================== --- tools/extra/clang-rename/tool/ClangRename.cpp +++ tools/extra/clang-rename/tool/ClangRename.cpp @@ -14,7 +14,6 @@ //===----------------------------------------------------------------------===// #include "../RenamingAction.h" -#include "../USRFindingAction.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" @@ -26,6 +25,7 @@ #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/USRFindingAction.h" #include "clang/Tooling/ReplacementsYaml.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" @@ -160,7 +160,7 @@ auto Files = OP.getSourcePathList(); tooling::RefactoringTool Tool(OP.getCompilations(), Files); - rename::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force); + tooling::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames, Force); Tool.run(tooling::newFrontendActionFactory(&FindingAction).get()); const std::vector<std::vector<std::string>> &USRList = FindingAction.getUSRList(); Index: tools/extra/clang-rename/USRLocFinder.h =================================================================== --- tools/extra/clang-rename/USRLocFinder.h +++ /dev/null @@ -1,49 +0,0 @@ -//===--- tools/extra/clang-rename/USRLocFinder.h - Clang rename tool ------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// \brief Provides functionality for finding all instances of a USR in a given -/// AST. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H - -#include "clang/AST/AST.h" -#include "clang/Tooling/Core/Replacement.h" -#include "clang/Tooling/Refactoring/AtomicChange.h" -#include "llvm/ADT/StringRef.h" -#include <string> -#include <vector> - -namespace clang { -namespace rename { - -/// Create atomic changes for renaming all symbol references which are -/// identified by the USRs set to a given new name. -/// -/// \param USRs The set containing USRs of a particular old symbol. -/// \param NewName The new name to replace old symbol name. -/// \param TranslationUnitDecl The translation unit declaration. -/// -/// \return Atomic changes for renaming. -std::vector<tooling::AtomicChange> -createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, - llvm::StringRef NewName, Decl *TranslationUnitDecl); - -// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! -std::vector<SourceLocation> -getLocationsOfUSRs(const std::vector<std::string> &USRs, - llvm::StringRef PrevName, Decl *Decl); - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H Index: tools/extra/clang-rename/USRLocFinder.cpp =================================================================== --- tools/extra/clang-rename/USRLocFinder.cpp +++ /dev/null @@ -1,509 +0,0 @@ -//===--- tools/extra/clang-rename/USRLocFinder.cpp - Clang rename tool ----===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// \brief Mehtods 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 "USRLocFinder.h" -#include "USRFinder.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 "llvm/ADT/StringRef.h" -#include "llvm/Support/Casting.h" -#include <cstddef> -#include <set> -#include <string> -#include <vector> - -using namespace llvm; - -namespace clang { -namespace rename { - -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 clang::RecursiveASTVisitor<USRLocFindingASTVisitor> { -public: - explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs, - StringRef PrevName, - const ASTContext &Context) - : USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { - } - - // Declaration visitors: - - bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { - for (const auto *Initializer : ConstructorDecl->inits()) { - // Ignore implicit initializers. - if (!Initializer->isWritten()) - continue; - if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { - if (USRSet.find(getUSRForDecl(FieldDecl)) != USRSet.end()) - LocationsFound.push_back(Initializer->getSourceLocation()); - } - } - return true; - } - - bool VisitNamedDecl(const NamedDecl *Decl) { - if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) - checkAndAddLocation(Decl->getLocation()); - return true; - } - - // Expression visitors: - - bool VisitDeclRefExpr(const DeclRefExpr *Expr) { - const NamedDecl *Decl = Expr->getFoundDecl(); - - if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { - const SourceManager &Manager = Decl->getASTContext().getSourceManager(); - SourceLocation Location = Manager.getSpellingLoc(Expr->getLocation()); - checkAndAddLocation(Location); - } - - return true; - } - - bool VisitMemberExpr(const MemberExpr *Expr) { - const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); - if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { - const SourceManager &Manager = Decl->getASTContext().getSourceManager(); - SourceLocation Location = Manager.getSpellingLoc(Expr->getMemberLoc()); - checkAndAddLocation(Location); - } - return true; - } - - // Other visitors: - - bool VisitTypeLoc(const TypeLoc Loc) { - if (USRSet.find(getUSRForDecl(Loc.getType()->getAsCXXRecordDecl())) != - USRSet.end()) - checkAndAddLocation(Loc.getBeginLoc()); - if (const auto *TemplateTypeParm = - dyn_cast<TemplateTypeParmType>(Loc.getType())) { - if (USRSet.find(getUSRForDecl(TemplateTypeParm->getDecl())) != - USRSet.end()) - checkAndAddLocation(Loc.getBeginLoc()); - } - return true; - } - - // Non-visitors: - - // \brief Returns a list of unique locations. Duplicate or overlapping - // locations are erroneous and should be reported! - const std::vector<clang::SourceLocation> &getLocationsFound() const { - return LocationsFound; - } - - // Namespace traversal: - void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { - while (NameLoc) { - const NamespaceDecl *Decl = - NameLoc.getNestedNameSpecifier()->getAsNamespace(); - if (Decl && USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) - checkAndAddLocation(NameLoc.getLocalBeginLoc()); - NameLoc = NameLoc.getPrefix(); - } - } - -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<std::string> USRSet; - const std::string PrevName; - std::vector<clang::SourceLocation> 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<clang::ElaboratedTypeLoc>()) { - 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<int>) 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<TemplateSpecializationTypeLoc>() - .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<clang::ElaboratedTypeLoc>()) - 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<RenameLocFinder> { -public: - RenameLocFinder(llvm::ArrayRef<std::string> 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<UsingDecl>(Decl)) - return true; - - // DestructorDecl has been handled in Typeloc. - if (llvm::isa<CXXDestructorDecl>(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<NestedNameSpecifierLoc>()) { - VisitNestedNameSpecifierLocations(*NSL); - return true; - } - - if (const auto *TL = Parents[0].get<TypeLoc>()) - 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<TemplateSpecializationType>(Loc.getType())) { - TypeLoc TargetLoc = Loc; - if (!ParentTypeLoc.isNull()) { - if (llvm::isa<ElaboratedType>(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<int>" in "ns::Foo<int>& f();", we want the parent typeLoc - // (ElaboratedType) of the TemplateSpecializationType in order to - // catch the prefix qualifiers "ns::". - if (!ParentTypeLoc.isNull() && - llvm::isa<ElaboratedType>(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<RenameInfo> &getRenameInfos() const { return RenameInfos; } - - // Returns a list of using declarations which are needed to update. - const std::vector<const UsingDecl *> &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<typename T> struct S { - // T t; // <-- this T becomes a TypeLoc(int) with class - // // SubstTemplateTypeParm when S<int> 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 <typename ASTNodeType> - 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<Decl>().isBaseOf( - Parents[0].getNodeKind())) - return Parents[0].template get<Decl>(); - 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<TypeLoc>(); - } - - // 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<std::string> USRSet; - ASTContext &Context; - std::vector<RenameInfo> RenameInfos; - // Record all interested using declarations which contains the using-shadow - // declarations of the symbol declarations being renamed. - std::vector<const UsingDecl *> UsingDecls; -}; - -} // namespace - -std::vector<SourceLocation> -getLocationsOfUSRs(const std::vector<std::string> &USRs, StringRef PrevName, - Decl *Decl) { - USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); - Visitor.TraverseDecl(Decl); - NestedNameSpecifierLocFinder Finder(Decl->getASTContext()); - - for (const auto &Location : Finder.getNestedNameSpecifierLocations()) - Visitor.handleNestedNameSpecifierLoc(Location); - - return Visitor.getLocationsFound(); -} - -std::vector<tooling::AtomicChange> -createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, - llvm::StringRef NewName, Decl *TranslationUnitDecl) { - RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext()); - Finder.TraverseDecl(TranslationUnitDecl); - - const SourceManager &SM = - TranslationUnitDecl->getASTContext().getSourceManager(); - - std::vector<tooling::AtomicChange> 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<clang::TranslationUnitDecl>( - 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; -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/USRFindingAction.h =================================================================== --- tools/extra/clang-rename/USRFindingAction.h +++ /dev/null @@ -1,54 +0,0 @@ -//===--- tools/extra/clang-rename/USRFindingAction.h - Clang rename tool --===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// \brief Provides an action to find all relevant USRs at a point. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H - -#include "clang/Basic/LLVM.h" -#include "llvm/ADT/ArrayRef.h" - -#include <string> -#include <vector> - -namespace clang { -class ASTConsumer; -class CompilerInstance; -class NamedDecl; - -namespace rename { - -struct USRFindingAction { - USRFindingAction(ArrayRef<unsigned> SymbolOffsets, - ArrayRef<std::string> QualifiedNames, bool Force) - : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), - ErrorOccurred(false), Force(Force) {} - std::unique_ptr<ASTConsumer> newASTConsumer(); - - ArrayRef<std::string> getUSRSpellings() { return SpellingNames; } - ArrayRef<std::vector<std::string>> getUSRList() { return USRList; } - bool errorOccurred() { return ErrorOccurred; } - -private: - std::vector<unsigned> SymbolOffsets; - std::vector<std::string> QualifiedNames; - std::vector<std::string> SpellingNames; - std::vector<std::vector<std::string>> USRList; - bool ErrorOccurred; - bool Force; -}; - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H Index: tools/extra/clang-rename/USRFindingAction.cpp =================================================================== --- tools/extra/clang-rename/USRFindingAction.cpp +++ /dev/null @@ -1,236 +0,0 @@ -//===--- tools/extra/clang-rename/USRFindingAction.cpp - Clang rename tool ===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// \brief Provides an action to find USR for the symbol at <offset>, as well as -/// all additional USRs. -/// -//===----------------------------------------------------------------------===// - -#include "USRFindingAction.h" -#include "USRFinder.h" -#include "clang/AST/AST.h" -#include "clang/AST/ASTConsumer.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/Basic/FileManager.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/FrontendAction.h" -#include "clang/Lex/Lexer.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Tooling/CommonOptionsParser.h" -#include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/Tooling.h" - -#include <algorithm> -#include <set> -#include <string> -#include <vector> - -using namespace llvm; - -namespace clang { -namespace rename { - -namespace { -// \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to -// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given -// Decl refers to class and adds USRs of all overridden methods if Decl refers -// to virtual method. -class AdditionalUSRFinder : public RecursiveASTVisitor<AdditionalUSRFinder> { -public: - AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) - : FoundDecl(FoundDecl), Context(Context) {} - - std::vector<std::string> Find() { - // Fill OverriddenMethods and PartialSpecs storages. - TraverseDecl(Context.getTranslationUnitDecl()); - if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FoundDecl)) { - addUSRsOfOverridenFunctions(MethodDecl); - for (const auto &OverriddenMethod : OverriddenMethods) { - if (checkIfOverriddenFunctionAscends(OverriddenMethod)) - USRSet.insert(getUSRForDecl(OverriddenMethod)); - } - } else if (const auto *RecordDecl = dyn_cast<CXXRecordDecl>(FoundDecl)) { - handleCXXRecordDecl(RecordDecl); - } else if (const auto *TemplateDecl = - dyn_cast<ClassTemplateDecl>(FoundDecl)) { - handleClassTemplateDecl(TemplateDecl); - } else { - USRSet.insert(getUSRForDecl(FoundDecl)); - } - return std::vector<std::string>(USRSet.begin(), USRSet.end()); - } - - bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { - if (MethodDecl->isVirtual()) - OverriddenMethods.push_back(MethodDecl); - return true; - } - - bool VisitClassTemplatePartialSpecializationDecl( - const ClassTemplatePartialSpecializationDecl *PartialSpec) { - PartialSpecs.push_back(PartialSpec); - return true; - } - -private: - void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { - RecordDecl = RecordDecl->getDefinition(); - if (const auto *ClassTemplateSpecDecl = - dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl)) - handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate()); - addUSRsOfCtorDtors(RecordDecl); - } - - void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) { - for (const auto *Specialization : TemplateDecl->specializations()) - addUSRsOfCtorDtors(Specialization); - - for (const auto *PartialSpec : PartialSpecs) { - if (PartialSpec->getSpecializedTemplate() == TemplateDecl) - addUSRsOfCtorDtors(PartialSpec); - } - addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl()); - } - - void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) { - RecordDecl = RecordDecl->getDefinition(); - - // Skip if the CXXRecordDecl doesn't have definition. - if (!RecordDecl) - return; - - for (const auto *CtorDecl : RecordDecl->ctors()) - USRSet.insert(getUSRForDecl(CtorDecl)); - - USRSet.insert(getUSRForDecl(RecordDecl->getDestructor())); - USRSet.insert(getUSRForDecl(RecordDecl)); - } - - void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) { - USRSet.insert(getUSRForDecl(MethodDecl)); - // Recursively visit each OverridenMethod. - for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) - addUSRsOfOverridenFunctions(OverriddenMethod); - } - - bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { - for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { - if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) - return true; - return checkIfOverriddenFunctionAscends(OverriddenMethod); - } - return false; - } - - const Decl *FoundDecl; - ASTContext &Context; - std::set<std::string> USRSet; - std::vector<const CXXMethodDecl *> OverriddenMethods; - std::vector<const ClassTemplatePartialSpecializationDecl *> PartialSpecs; -}; -} // namespace - -class NamedDeclFindingConsumer : public ASTConsumer { -public: - NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets, - ArrayRef<std::string> QualifiedNames, - std::vector<std::string> &SpellingNames, - std::vector<std::vector<std::string>> &USRList, - bool Force, bool &ErrorOccurred) - : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), - SpellingNames(SpellingNames), USRList(USRList), Force(Force), - ErrorOccurred(ErrorOccurred) {} - -private: - bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr, - unsigned SymbolOffset, const std::string &QualifiedName) { - DiagnosticsEngine &Engine = Context.getDiagnostics(); - const FileID MainFileID = SourceMgr.getMainFileID(); - - if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) { - ErrorOccurred = true; - unsigned InvalidOffset = Engine.getCustomDiagID( - DiagnosticsEngine::Error, - "SourceLocation in file %0 at offset %1 is invalid"); - Engine.Report(SourceLocation(), InvalidOffset) - << SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset; - return false; - } - - const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID) - .getLocWithOffset(SymbolOffset); - const NamedDecl *FoundDecl = QualifiedName.empty() - ? getNamedDeclAt(Context, Point) - : getNamedDeclFor(Context, QualifiedName); - - if (FoundDecl == nullptr) { - if (QualifiedName.empty()) { - FullSourceLoc FullLoc(Point, SourceMgr); - unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID( - DiagnosticsEngine::Error, - "clang-rename could not find symbol (offset %0)"); - Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset; - ErrorOccurred = true; - return false; - } - - if (Force) - return true; - - unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( - DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); - Engine.Report(CouldNotFindSymbolNamed) << QualifiedName; - ErrorOccurred = true; - return false; - } - - // If FoundDecl is a constructor or destructor, we want to instead take - // the Decl of the corresponding class. - if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl)) - FoundDecl = CtorDecl->getParent(); - else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl)) - FoundDecl = DtorDecl->getParent(); - - SpellingNames.push_back(FoundDecl->getNameAsString()); - AdditionalUSRFinder Finder(FoundDecl, Context); - USRList.push_back(Finder.Find()); - return true; - } - - void HandleTranslationUnit(ASTContext &Context) override { - const SourceManager &SourceMgr = Context.getSourceManager(); - for (unsigned Offset : SymbolOffsets) { - if (!FindSymbol(Context, SourceMgr, Offset, "")) - return; - } - for (const std::string &QualifiedName : QualifiedNames) { - if (!FindSymbol(Context, SourceMgr, 0, QualifiedName)) - return; - } - } - - ArrayRef<unsigned> SymbolOffsets; - ArrayRef<std::string> QualifiedNames; - std::vector<std::string> &SpellingNames; - std::vector<std::vector<std::string>> &USRList; - bool Force; - bool &ErrorOccurred; -}; - -std::unique_ptr<ASTConsumer> USRFindingAction::newASTConsumer() { - return llvm::make_unique<NamedDeclFindingConsumer>( - SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force, - ErrorOccurred); -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/USRFinder.h =================================================================== --- tools/extra/clang-rename/USRFinder.h +++ /dev/null @@ -1,84 +0,0 @@ -//===--- tools/extra/clang-rename/USRFinder.h - Clang rename tool ---------===// -// -// 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 determining the USR of a symbol at a location in source -/// code. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H - -#include "clang/AST/AST.h" -#include "clang/AST/ASTContext.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include <string> -#include <vector> - -using namespace llvm; -using namespace clang::ast_matchers; - -namespace clang { - -class ASTContext; -class Decl; -class SourceLocation; -class NamedDecl; - -namespace rename { - -// Given an AST context and a point, returns a NamedDecl identifying the symbol -// at the point. Returns null if nothing is found at the point. -const NamedDecl *getNamedDeclAt(const ASTContext &Context, - const SourceLocation Point); - -// Given an AST context and a fully qualified name, returns a NamedDecl -// identifying the symbol with a matching name. Returns null if nothing is -// found for the name. -const NamedDecl *getNamedDeclFor(const ASTContext &Context, - const std::string &Name); - -// Converts a Decl into a USR. -std::string getUSRForDecl(const Decl *Decl); - -// FIXME: Implement RecursiveASTVisitor<T>::VisitNestedNameSpecifier instead. -class NestedNameSpecifierLocFinder : public MatchFinder::MatchCallback { -public: - explicit NestedNameSpecifierLocFinder(ASTContext &Context) - : Context(Context) {} - - std::vector<NestedNameSpecifierLoc> getNestedNameSpecifierLocations() { - addMatchers(); - Finder.matchAST(Context); - return Locations; - } - -private: - void addMatchers() { - const auto NestedNameSpecifierLocMatcher = - nestedNameSpecifierLoc().bind("nestedNameSpecifierLoc"); - Finder.addMatcher(NestedNameSpecifierLocMatcher, this); - } - - void run(const MatchFinder::MatchResult &Result) override { - const auto *NNS = Result.Nodes.getNodeAs<NestedNameSpecifierLoc>( - "nestedNameSpecifierLoc"); - Locations.push_back(*NNS); - } - - ASTContext &Context; - std::vector<NestedNameSpecifierLoc> Locations; - MatchFinder Finder; -}; - -} // namespace rename -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H Index: tools/extra/clang-rename/USRFinder.cpp =================================================================== --- tools/extra/clang-rename/USRFinder.cpp +++ /dev/null @@ -1,213 +0,0 @@ -//===--- tools/extra/clang-rename/USRFinder.cpp - Clang rename tool -------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -/// -/// \file Implements a recursive AST visitor that finds the USR of a symbol at a -/// point. -/// -//===----------------------------------------------------------------------===// - -#include "USRFinder.h" -#include "clang/AST/AST.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/Index/USRGeneration.h" -#include "clang/Lex/Lexer.h" -#include "llvm/ADT/SmallVector.h" - -using namespace llvm; - -namespace clang { -namespace rename { - -// NamedDeclFindingASTVisitor recursively visits each AST node to find the -// symbol underneath the cursor. -// FIXME: move to separate .h/.cc file if this gets too large. -namespace { -class NamedDeclFindingASTVisitor - : public clang::RecursiveASTVisitor<NamedDeclFindingASTVisitor> { -public: - // \brief Finds the NamedDecl at a point in the source. - // \param Point the location in the source to search for the NamedDecl. - explicit NamedDeclFindingASTVisitor(const SourceLocation Point, - const ASTContext &Context) - : Result(nullptr), Point(Point), Context(Context) {} - - // \brief Finds the NamedDecl for a name in the source. - // \param Name the fully qualified name. - explicit NamedDeclFindingASTVisitor(const std::string &Name, - const ASTContext &Context) - : Result(nullptr), Name(Name), Context(Context) {} - - // Declaration visitors: - - // \brief Checks if the point falls within the NameDecl. This covers every - // declaration of a named entity that we may come across. Usually, just - // checking if the point lies within the length of the name of the declaration - // and the start location is sufficient. - bool VisitNamedDecl(const NamedDecl *Decl) { - return dyn_cast<CXXConversionDecl>(Decl) - ? true - : setResult(Decl, Decl->getLocation(), - Decl->getNameAsString().length()); - } - - // Expression visitors: - - bool VisitDeclRefExpr(const DeclRefExpr *Expr) { - const NamedDecl *Decl = Expr->getFoundDecl(); - return setResult(Decl, Expr->getLocation(), - Decl->getNameAsString().length()); - } - - bool VisitMemberExpr(const MemberExpr *Expr) { - const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); - return setResult(Decl, Expr->getMemberLoc(), - Decl->getNameAsString().length()); - } - - // Other visitors: - - bool VisitTypeLoc(const TypeLoc Loc) { - const SourceLocation TypeBeginLoc = Loc.getBeginLoc(); - const SourceLocation TypeEndLoc = Lexer::getLocForEndOfToken( - TypeBeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); - if (const auto *TemplateTypeParm = - dyn_cast<TemplateTypeParmType>(Loc.getType())) - return setResult(TemplateTypeParm->getDecl(), TypeBeginLoc, TypeEndLoc); - if (const auto *TemplateSpecType = - dyn_cast<TemplateSpecializationType>(Loc.getType())) { - return setResult(TemplateSpecType->getTemplateName().getAsTemplateDecl(), - TypeBeginLoc, TypeEndLoc); - } - return setResult(Loc.getType()->getAsCXXRecordDecl(), TypeBeginLoc, - TypeEndLoc); - } - - bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { - for (const auto *Initializer : ConstructorDecl->inits()) { - // Ignore implicit initializers. - if (!Initializer->isWritten()) - continue; - if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { - const SourceLocation InitBeginLoc = Initializer->getSourceLocation(), - InitEndLoc = Lexer::getLocForEndOfToken( - InitBeginLoc, 0, Context.getSourceManager(), - Context.getLangOpts()); - if (!setResult(FieldDecl, InitBeginLoc, InitEndLoc)) - return false; - } - } - return true; - } - - // Other: - - const NamedDecl *getNamedDecl() { return Result; } - - // \brief Determines if a namespace qualifier contains the point. - // \returns false on success and sets Result. - void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { - while (NameLoc) { - const NamespaceDecl *Decl = - NameLoc.getNestedNameSpecifier()->getAsNamespace(); - setResult(Decl, NameLoc.getLocalBeginLoc(), NameLoc.getLocalEndLoc()); - NameLoc = NameLoc.getPrefix(); - } - } - -private: - // \brief Sets Result to Decl if the Point is within Start and End. - // \returns false on success. - bool setResult(const NamedDecl *Decl, SourceLocation Start, - SourceLocation End) { - if (!Decl) - return true; - if (Name.empty()) { - // Offset is used to find the declaration. - if (!Start.isValid() || !Start.isFileID() || !End.isValid() || - !End.isFileID() || !isPointWithin(Start, End)) - return true; - } else { - // Fully qualified name is used to find the declaration. - if (Name != Decl->getQualifiedNameAsString() && - Name != "::" + Decl->getQualifiedNameAsString()) - return true; - } - Result = Decl; - return false; - } - - // \brief Sets Result to Decl if Point is within Loc and Loc + Offset. - // \returns false on success. - bool setResult(const NamedDecl *Decl, SourceLocation Loc, unsigned Offset) { - // FIXME: Add test for Offset == 0. Add test for Offset - 1 (vs -2 etc). - return Offset == 0 || - setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); - } - - // \brief Determines if the Point is within Start and End. - bool isPointWithin(const SourceLocation Start, const SourceLocation End) { - // FIXME: Add tests for Point == End. - return Point == Start || Point == End || - (Context.getSourceManager().isBeforeInTranslationUnit(Start, - Point) && - Context.getSourceManager().isBeforeInTranslationUnit(Point, End)); - } - - const NamedDecl *Result; - const SourceLocation Point; // The location to find the NamedDecl. - const std::string Name; - const ASTContext &Context; -}; -} // namespace - -const NamedDecl *getNamedDeclAt(const ASTContext &Context, - const SourceLocation Point) { - const SourceManager &SM = Context.getSourceManager(); - NamedDeclFindingASTVisitor Visitor(Point, Context); - - // Try to be clever about pruning down the number of top-level declarations we - // see. If both start and end is either before or after the point we're - // looking for the point cannot be inside of this decl. Don't even look at it. - for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { - SourceLocation StartLoc = CurrDecl->getLocStart(); - SourceLocation EndLoc = CurrDecl->getLocEnd(); - if (StartLoc.isValid() && EndLoc.isValid() && - SM.isBeforeInTranslationUnit(StartLoc, Point) != - SM.isBeforeInTranslationUnit(EndLoc, Point)) - Visitor.TraverseDecl(CurrDecl); - } - - NestedNameSpecifierLocFinder Finder(const_cast<ASTContext &>(Context)); - for (const auto &Location : Finder.getNestedNameSpecifierLocations()) - Visitor.handleNestedNameSpecifierLoc(Location); - - return Visitor.getNamedDecl(); -} - -const NamedDecl *getNamedDeclFor(const ASTContext &Context, - const std::string &Name) { - NamedDeclFindingASTVisitor Visitor(Name, Context); - Visitor.TraverseDecl(Context.getTranslationUnitDecl()); - - return Visitor.getNamedDecl(); -} - -std::string getUSRForDecl(const Decl *Decl) { - llvm::SmallVector<char, 128> Buff; - - // FIXME: Add test for the nullptr case. - if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) - return ""; - - return std::string(Buff.data(), Buff.size()); -} - -} // namespace rename -} // namespace clang Index: tools/extra/clang-rename/RenamingAction.cpp =================================================================== --- tools/extra/clang-rename/RenamingAction.cpp +++ tools/extra/clang-rename/RenamingAction.cpp @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// #include "RenamingAction.h" -#include "USRLocFinder.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/FileManager.h" @@ -23,6 +22,7 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/USRLocFinder.h" #include "clang/Tooling/Tooling.h" #include <string> #include <vector> @@ -55,8 +55,8 @@ std::vector<SourceLocation> RenamingCandidates; std::vector<SourceLocation> NewCandidates; - NewCandidates = - getLocationsOfUSRs(USRs, PrevName, Context.getTranslationUnitDecl()); + NewCandidates = tooling::getLocationsOfUSRs( + USRs, PrevName, Context.getTranslationUnitDecl()); RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), NewCandidates.end()); @@ -101,7 +101,7 @@ for (unsigned I = 0; I < NewNames.size(); ++I) { // FIXME: Apply AtomicChanges directly once the refactoring APIs are // ready. - auto AtomicChanges = createRenameAtomicChanges( + auto AtomicChanges = tooling::createRenameAtomicChanges( USRList[I], NewNames[I], Context.getTranslationUnitDecl()); for (const auto AtomicChange : AtomicChanges) { for (const auto &Replace : AtomicChange.getReplacements()) { Index: tools/extra/clang-rename/CMakeLists.txt =================================================================== --- tools/extra/clang-rename/CMakeLists.txt +++ tools/extra/clang-rename/CMakeLists.txt @@ -1,9 +1,6 @@ set(LLVM_LINK_COMPONENTS support) add_clang_library(clangRename - USRFinder.cpp - USRFindingAction.cpp - USRLocFinder.cpp RenamingAction.cpp LINK_LIBS Index: lib/Tooling/Refactoring/USRLocFinder.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/USRLocFinder.cpp @@ -0,0 +1,509 @@ +//===--- 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 Mehtods 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/USRLocFinder.h" +#include "clang/Tooling/Refactoring/USRFinder.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 "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <cstddef> +#include <set> +#include <string> +#include <vector> + +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 clang::RecursiveASTVisitor<USRLocFindingASTVisitor> { +public: + explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs, + StringRef PrevName, + const ASTContext &Context) + : USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { + } + + // Declaration visitors: + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + if (USRSet.find(getUSRForDecl(FieldDecl)) != USRSet.end()) + LocationsFound.push_back(Initializer->getSourceLocation()); + } + } + return true; + } + + bool VisitNamedDecl(const NamedDecl *Decl) { + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(Decl->getLocation()); + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getLocation()); + checkAndAddLocation(Location); + } + + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getMemberLoc()); + checkAndAddLocation(Location); + } + return true; + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + if (USRSet.find(getUSRForDecl(Loc.getType()->getAsCXXRecordDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + if (const auto *TemplateTypeParm = + dyn_cast<TemplateTypeParmType>(Loc.getType())) { + if (USRSet.find(getUSRForDecl(TemplateTypeParm->getDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + } + return true; + } + + // Non-visitors: + + // \brief Returns a list of unique locations. Duplicate or overlapping + // locations are erroneous and should be reported! + const std::vector<clang::SourceLocation> &getLocationsFound() const { + return LocationsFound; + } + + // Namespace traversal: + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +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<std::string> USRSet; + const std::string PrevName; + std::vector<clang::SourceLocation> 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<clang::ElaboratedTypeLoc>()) { + 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<int>) 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<TemplateSpecializationTypeLoc>() + .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<clang::ElaboratedTypeLoc>()) + 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<RenameLocFinder> { +public: + RenameLocFinder(llvm::ArrayRef<std::string> 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<UsingDecl>(Decl)) + return true; + + // DestructorDecl has been handled in Typeloc. + if (llvm::isa<CXXDestructorDecl>(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<NestedNameSpecifierLoc>()) { + VisitNestedNameSpecifierLocations(*NSL); + return true; + } + + if (const auto *TL = Parents[0].get<TypeLoc>()) + 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<TemplateSpecializationType>(Loc.getType())) { + TypeLoc TargetLoc = Loc; + if (!ParentTypeLoc.isNull()) { + if (llvm::isa<ElaboratedType>(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<int>" in "ns::Foo<int>& f();", we want the parent typeLoc + // (ElaboratedType) of the TemplateSpecializationType in order to + // catch the prefix qualifiers "ns::". + if (!ParentTypeLoc.isNull() && + llvm::isa<ElaboratedType>(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<RenameInfo> &getRenameInfos() const { return RenameInfos; } + + // Returns a list of using declarations which are needed to update. + const std::vector<const UsingDecl *> &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<typename T> struct S { + // T t; // <-- this T becomes a TypeLoc(int) with class + // // SubstTemplateTypeParm when S<int> 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 <typename ASTNodeType> + 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<Decl>().isBaseOf( + Parents[0].getNodeKind())) + return Parents[0].template get<Decl>(); + 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<TypeLoc>(); + } + + // 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<std::string> USRSet; + ASTContext &Context; + std::vector<RenameInfo> RenameInfos; + // Record all interested using declarations which contains the using-shadow + // declarations of the symbol declarations being renamed. + std::vector<const UsingDecl *> UsingDecls; +}; + +} // namespace + +std::vector<SourceLocation> +getLocationsOfUSRs(const std::vector<std::string> &USRs, StringRef PrevName, + Decl *Decl) { + USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); + Visitor.TraverseDecl(Decl); + NestedNameSpecifierLocFinder Finder(Decl->getASTContext()); + + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Visitor.getLocationsFound(); +} + +std::vector<tooling::AtomicChange> +createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, + llvm::StringRef NewName, Decl *TranslationUnitDecl) { + RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext()); + Finder.TraverseDecl(TranslationUnitDecl); + + const SourceManager &SM = + TranslationUnitDecl->getASTContext().getSourceManager(); + + std::vector<tooling::AtomicChange> 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<clang::TranslationUnitDecl>( + 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 Index: lib/Tooling/Refactoring/USRFindingAction.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/USRFindingAction.cpp @@ -0,0 +1,236 @@ +//===--- USRFindingAction.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 Provides an action to find USR for the symbol at <offset>, as well as +/// all additional USRs. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/USRFindingAction.h" +#include "clang/Tooling/Refactoring/USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" + +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { +// \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to +// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given +// Decl refers to class and adds USRs of all overridden methods if Decl refers +// to virtual method. +class AdditionalUSRFinder : public RecursiveASTVisitor<AdditionalUSRFinder> { +public: + AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) + : FoundDecl(FoundDecl), Context(Context) {} + + std::vector<std::string> Find() { + // Fill OverriddenMethods and PartialSpecs storages. + TraverseDecl(Context.getTranslationUnitDecl()); + if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FoundDecl)) { + addUSRsOfOverridenFunctions(MethodDecl); + for (const auto &OverriddenMethod : OverriddenMethods) { + if (checkIfOverriddenFunctionAscends(OverriddenMethod)) + USRSet.insert(getUSRForDecl(OverriddenMethod)); + } + } else if (const auto *RecordDecl = dyn_cast<CXXRecordDecl>(FoundDecl)) { + handleCXXRecordDecl(RecordDecl); + } else if (const auto *TemplateDecl = + dyn_cast<ClassTemplateDecl>(FoundDecl)) { + handleClassTemplateDecl(TemplateDecl); + } else { + USRSet.insert(getUSRForDecl(FoundDecl)); + } + return std::vector<std::string>(USRSet.begin(), USRSet.end()); + } + + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isVirtual()) + OverriddenMethods.push_back(MethodDecl); + return true; + } + + bool VisitClassTemplatePartialSpecializationDecl( + const ClassTemplatePartialSpecializationDecl *PartialSpec) { + PartialSpecs.push_back(PartialSpec); + return true; + } + +private: + void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + if (const auto *ClassTemplateSpecDecl = + dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl)) + handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate()); + addUSRsOfCtorDtors(RecordDecl); + } + + void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) { + for (const auto *Specialization : TemplateDecl->specializations()) + addUSRsOfCtorDtors(Specialization); + + for (const auto *PartialSpec : PartialSpecs) { + if (PartialSpec->getSpecializedTemplate() == TemplateDecl) + addUSRsOfCtorDtors(PartialSpec); + } + addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl()); + } + + void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + + // Skip if the CXXRecordDecl doesn't have definition. + if (!RecordDecl) + return; + + for (const auto *CtorDecl : RecordDecl->ctors()) + USRSet.insert(getUSRForDecl(CtorDecl)); + + USRSet.insert(getUSRForDecl(RecordDecl->getDestructor())); + USRSet.insert(getUSRForDecl(RecordDecl)); + } + + void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) { + USRSet.insert(getUSRForDecl(MethodDecl)); + // Recursively visit each OverridenMethod. + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) + addUSRsOfOverridenFunctions(OverriddenMethod); + } + + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + return checkIfOverriddenFunctionAscends(OverriddenMethod); + } + return false; + } + + const Decl *FoundDecl; + ASTContext &Context; + std::set<std::string> USRSet; + std::vector<const CXXMethodDecl *> OverriddenMethods; + std::vector<const ClassTemplatePartialSpecializationDecl *> PartialSpecs; +}; +} // namespace + +class NamedDeclFindingConsumer : public ASTConsumer { +public: + NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets, + ArrayRef<std::string> QualifiedNames, + std::vector<std::string> &SpellingNames, + std::vector<std::vector<std::string>> &USRList, + bool Force, bool &ErrorOccurred) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + SpellingNames(SpellingNames), USRList(USRList), Force(Force), + ErrorOccurred(ErrorOccurred) {} + +private: + bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr, + unsigned SymbolOffset, const std::string &QualifiedName) { + DiagnosticsEngine &Engine = Context.getDiagnostics(); + const FileID MainFileID = SourceMgr.getMainFileID(); + + if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) { + ErrorOccurred = true; + unsigned InvalidOffset = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "SourceLocation in file %0 at offset %1 is invalid"); + Engine.Report(SourceLocation(), InvalidOffset) + << SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset; + return false; + } + + const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID) + .getLocWithOffset(SymbolOffset); + const NamedDecl *FoundDecl = QualifiedName.empty() + ? getNamedDeclAt(Context, Point) + : getNamedDeclFor(Context, QualifiedName); + + if (FoundDecl == nullptr) { + if (QualifiedName.empty()) { + FullSourceLoc FullLoc(Point, SourceMgr); + unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "clang-rename could not find symbol (offset %0)"); + Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset; + ErrorOccurred = true; + return false; + } + + if (Force) + return true; + + unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( + DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); + Engine.Report(CouldNotFindSymbolNamed) << QualifiedName; + ErrorOccurred = true; + return false; + } + + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + + SpellingNames.push_back(FoundDecl->getNameAsString()); + AdditionalUSRFinder Finder(FoundDecl, Context); + USRList.push_back(Finder.Find()); + return true; + } + + void HandleTranslationUnit(ASTContext &Context) override { + const SourceManager &SourceMgr = Context.getSourceManager(); + for (unsigned Offset : SymbolOffsets) { + if (!FindSymbol(Context, SourceMgr, Offset, "")) + return; + } + for (const std::string &QualifiedName : QualifiedNames) { + if (!FindSymbol(Context, SourceMgr, 0, QualifiedName)) + return; + } + } + + ArrayRef<unsigned> SymbolOffsets; + ArrayRef<std::string> QualifiedNames; + std::vector<std::string> &SpellingNames; + std::vector<std::vector<std::string>> &USRList; + bool Force; + bool &ErrorOccurred; +}; + +std::unique_ptr<ASTConsumer> USRFindingAction::newASTConsumer() { + return llvm::make_unique<NamedDeclFindingConsumer>( + SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force, + ErrorOccurred); +} + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/Refactoring/USRFinder.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/USRFinder.cpp @@ -0,0 +1,213 @@ +//===--- USRFinder.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 Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace tooling { + +// NamedDeclFindingASTVisitor recursively visits each AST node to find the +// symbol underneath the cursor. +// FIXME: move to separate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public clang::RecursiveASTVisitor<NamedDeclFindingASTVisitor> { +public: + // \brief Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclFindingASTVisitor(const SourceLocation Point, + const ASTContext &Context) + : Result(nullptr), Point(Point), Context(Context) {} + + // \brief Finds the NamedDecl for a name in the source. + // \param Name the fully qualified name. + explicit NamedDeclFindingASTVisitor(const std::string &Name, + const ASTContext &Context) + : Result(nullptr), Name(Name), Context(Context) {} + + // Declaration visitors: + + // \brief Checks if the point falls within the NameDecl. This covers every + // declaration of a named entity that we may come across. Usually, just + // checking if the point lies within the length of the name of the declaration + // and the start location is sufficient. + bool VisitNamedDecl(const NamedDecl *Decl) { + return dyn_cast<CXXConversionDecl>(Decl) + ? true + : setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + return setResult(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + return setResult(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + const SourceLocation TypeBeginLoc = Loc.getBeginLoc(); + const SourceLocation TypeEndLoc = Lexer::getLocForEndOfToken( + TypeBeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + if (const auto *TemplateTypeParm = + dyn_cast<TemplateTypeParmType>(Loc.getType())) + return setResult(TemplateTypeParm->getDecl(), TypeBeginLoc, TypeEndLoc); + if (const auto *TemplateSpecType = + dyn_cast<TemplateSpecializationType>(Loc.getType())) { + return setResult(TemplateSpecType->getTemplateName().getAsTemplateDecl(), + TypeBeginLoc, TypeEndLoc); + } + return setResult(Loc.getType()->getAsCXXRecordDecl(), TypeBeginLoc, + TypeEndLoc); + } + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + const SourceLocation InitBeginLoc = Initializer->getSourceLocation(), + InitEndLoc = Lexer::getLocForEndOfToken( + InitBeginLoc, 0, Context.getSourceManager(), + Context.getLangOpts()); + if (!setResult(FieldDecl, InitBeginLoc, InitEndLoc)) + return false; + } + } + return true; + } + + // Other: + + const NamedDecl *getNamedDecl() { return Result; } + + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + setResult(Decl, NameLoc.getLocalBeginLoc(), NameLoc.getLocalEndLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + // \brief Sets Result to Decl if the Point is within Start and End. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Start, + SourceLocation End) { + if (!Decl) + return true; + if (Name.empty()) { + // Offset is used to find the declaration. + if (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) + return true; + } else { + // Fully qualified name is used to find the declaration. + if (Name != Decl->getQualifiedNameAsString() && + Name != "::" + Decl->getQualifiedNameAsString()) + return true; + } + Result = Decl; + return false; + } + + // \brief Sets Result to Decl if Point is within Loc and Loc + Offset. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Loc, unsigned Offset) { + // FIXME: Add test for Offset == 0. Add test for Offset - 1 (vs -2 etc). + return Offset == 0 || + setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); + } + + // \brief Determines if the Point is within Start and End. + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + // FIXME: Add tests for Point == End. + return Point == Start || Point == End || + (Context.getSourceManager().isBeforeInTranslationUnit(Start, + Point) && + Context.getSourceManager().isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceLocation Point; // The location to find the NamedDecl. + const std::string Name; + const ASTContext &Context; +}; +} // namespace + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const SourceManager &SM = Context.getSourceManager(); + NamedDeclFindingASTVisitor Visitor(Point, Context); + + // Try to be clever about pruning down the number of top-level declarations we + // see. If both start and end is either before or after the point we're + // looking for the point cannot be inside of this decl. Don't even look at it. + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + SourceLocation StartLoc = CurrDecl->getLocStart(); + SourceLocation EndLoc = CurrDecl->getLocEnd(); + if (StartLoc.isValid() && EndLoc.isValid() && + SM.isBeforeInTranslationUnit(StartLoc, Point) != + SM.isBeforeInTranslationUnit(EndLoc, Point)) + Visitor.TraverseDecl(CurrDecl); + } + + NestedNameSpecifierLocFinder Finder(const_cast<ASTContext &>(Context)); + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Visitor.getNamedDecl(); +} + +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name) { + NamedDeclFindingASTVisitor Visitor(Name, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + + return Visitor.getNamedDecl(); +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector<char, 128> Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -5,6 +5,9 @@ add_clang_library(clangToolingRefactor AtomicChange.cpp + USRFinder.cpp + USRFindingAction.cpp + USRLocFinder.cpp LINK_LIBS clangBasic Index: include/clang/module.modulemap =================================================================== --- include/clang/module.modulemap +++ include/clang/module.modulemap @@ -133,9 +133,10 @@ module Clang_Tooling { requires cplusplus umbrella "Tooling" module * { export * } - // FIXME: Exclude this header to avoid pulling all of the AST matchers + // FIXME: Exclude these headers to avoid pulling all of the AST matchers // library into clang-format. Due to inline key functions in the headers, // importing the AST matchers library gives a link dependency on the AST // matchers (and thus the AST), which clang-format should not have. exclude header "Tooling/RefactoringCallbacks.h" + exclude header "Tooling/Refactoring/USRFinder.h" } Index: include/clang/Tooling/Refactoring/USRLocFinder.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/USRLocFinder.h @@ -0,0 +1,49 @@ +//===--- USRLocFinder.h - 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 Provides functionality for finding all instances of a USR in a given +/// AST. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_USR_LOC_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_USR_LOC_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/ADT/StringRef.h" +#include <string> +#include <vector> + +namespace clang { +namespace tooling { + +/// Create atomic changes for renaming all symbol references which are +/// identified by the USRs set to a given new name. +/// +/// \param USRs The set containing USRs of a particular old symbol. +/// \param NewName The new name to replace old symbol name. +/// \param TranslationUnitDecl The translation unit declaration. +/// +/// \return Atomic changes for renaming. +std::vector<tooling::AtomicChange> +createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, + llvm::StringRef NewName, Decl *TranslationUnitDecl); + +// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! +std::vector<SourceLocation> +getLocationsOfUSRs(const std::vector<std::string> &USRs, + llvm::StringRef PrevName, Decl *Decl); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_USR_LOC_FINDER_H Index: include/clang/Tooling/Refactoring/USRFindingAction.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/USRFindingAction.h @@ -0,0 +1,54 @@ +//===--- USRFindingAction.h - 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 Provides an action to find all relevant USRs at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_USR_FINDING_ACTION_H +#define LLVM_CLANG_TOOLING_REFACTOR_USR_FINDING_ACTION_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" + +#include <string> +#include <vector> + +namespace clang { +class ASTConsumer; +class CompilerInstance; +class NamedDecl; + +namespace tooling { + +struct USRFindingAction { + USRFindingAction(ArrayRef<unsigned> SymbolOffsets, + ArrayRef<std::string> QualifiedNames, bool Force) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + ErrorOccurred(false), Force(Force) {} + std::unique_ptr<ASTConsumer> newASTConsumer(); + + ArrayRef<std::string> getUSRSpellings() { return SpellingNames; } + ArrayRef<std::vector<std::string>> getUSRList() { return USRList; } + bool errorOccurred() { return ErrorOccurred; } + +private: + std::vector<unsigned> SymbolOffsets; + std::vector<std::string> QualifiedNames; + std::vector<std::string> SpellingNames; + std::vector<std::vector<std::string>> USRList; + bool ErrorOccurred; + bool Force; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_USR_FINDING_ACTION_H Index: include/clang/Tooling/Refactoring/USRFinder.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/USRFinder.h @@ -0,0 +1,84 @@ +//===--- USRFinder.h - 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 determining the USR of a symbol at a location in source +/// code. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H +#define LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include <string> +#include <vector> + +using namespace llvm; +using namespace clang::ast_matchers; + +namespace clang { + +class ASTContext; +class Decl; +class SourceLocation; +class NamedDecl; + +namespace tooling { + +// Given an AST context and a point, returns a NamedDecl identifying the symbol +// at the point. Returns null if nothing is found at the point. +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point); + +// Given an AST context and a fully qualified name, returns a NamedDecl +// identifying the symbol with a matching name. Returns null if nothing is +// found for the name. +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +// FIXME: Implement RecursiveASTVisitor<T>::VisitNestedNameSpecifier instead. +class NestedNameSpecifierLocFinder : public MatchFinder::MatchCallback { +public: + explicit NestedNameSpecifierLocFinder(ASTContext &Context) + : Context(Context) {} + + std::vector<NestedNameSpecifierLoc> getNestedNameSpecifierLocations() { + addMatchers(); + Finder.matchAST(Context); + return Locations; + } + +private: + void addMatchers() { + const auto NestedNameSpecifierLocMatcher = + nestedNameSpecifierLoc().bind("nestedNameSpecifierLoc"); + Finder.addMatcher(NestedNameSpecifierLocMatcher, this); + } + + void run(const MatchFinder::MatchResult &Result) override { + const auto *NNS = Result.Nodes.getNodeAs<NestedNameSpecifierLoc>( + "nestedNameSpecifierLoc"); + Locations.push_back(*NNS); + } + + ASTContext &Context; + std::vector<NestedNameSpecifierLoc> Locations; + MatchFinder Finder; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_USR_FINDER_H
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits