Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-modernize) +add_subdirectory(clang-rename) add_subdirectory(modularize) add_subdirectory(module-map-checker) add_subdirectory(remove-cstr-calls) Index: Makefile =================================================================== --- Makefile +++ Makefile @@ -13,8 +13,8 @@ PARALLEL_DIRS := remove-cstr-calls tool-template modularize \ module-map-checker pp-trace -DIRS := clang-apply-replacements clang-modernize clang-tidy clang-query \ - unittests +DIRS := clang-apply-replacements clang-modernize clang-rename clang-tidy \ + clang-query unittests include $(CLANG_LEVEL)/Makefile Index: clang-rename/CMakeLists.txt =================================================================== --- /dev/null +++ clang-rename/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_executable(clang-rename + ClangRename.cpp + USRFinder.cpp + USRFindingAction.cpp + USRLocFinder.cpp + RenamingAction.cpp + ) + +target_link_libraries(clang-rename + clangAnalysis + clangAST + clangBasic + clangDriver + clangEdit + clangFrontend + clangFrontendTool + clangIndex + clangLex + clangParse + clangRewrite + clangRewriteFrontend + clangSerialization + clangSema + clangTooling + ) + +install(TARGETS clang-rename RUNTIME DESTINATION bin) \ No newline at end of file Index: clang-rename/ClangRename.cpp =================================================================== --- /dev/null +++ clang-rename/ClangRename.cpp @@ -0,0 +1,151 @@ +//===--- tools/extra/clang-rename/ClangRename.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 This file implements a clang-rename tool that automatically finds and +/// renames symbols in C++ code. +/// +//===----------------------------------------------------------------------===// + +#include "USRFindingAction.h" +#include "RenamingAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Basic/TargetOptions.h" +#include "clang/Frontend/CommandLineSourceLoc.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/Lexer.h" +#include "clang/Parse/Parser.h" +#include "clang/Parse/ParseAST.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/Host.h" +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +cl::OptionCategory ClangRenameCategory("Clang-rename options"); + +static cl::opt +NewName( + "new-name", + cl::desc("The new name to change the symbol to."), + cl::cat(ClangRenameCategory)); +static cl::opt +SymbolOffset( + "offset", + cl::desc("Locates the symbol by offset as opposed to :."), + cl::cat(ClangRenameCategory)); +static cl::opt +Inplace( + "i", + cl::desc("Overwrite edited s."), + cl::cat(ClangRenameCategory)); +static cl::opt +PrintName( + "pn", + cl::desc("Print the found symbol's name prior to renaming to stderr."), + cl::cat(ClangRenameCategory)); +static cl::opt +PrintLocations( + "pl", + cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameCategory)); + +#define CLANG_RENAME_VERSION "0.0.1" + +static void PrintVersion() { + outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n"; +} + +using namespace clang; + +const char RenameUsage[] = "A tool to rename symbols in C/C++ code.\n\ +clang-rename renames every occurrence of a symbol found at in\n\ +. If -i is specified, the edited files are overwritten to disk.\n\ +Otherwise, the results are written to stdout.\n"; + +int main(int argc, const char **argv) { + cl::SetVersionPrinter(PrintVersion); + tooling::CommonOptionsParser OP(argc, argv, ClangRenameCategory, RenameUsage); + + // Check the arguments for correctness. + + if (NewName.empty()) { + errs() << "clang-rename: no new name provided.\n\n"; + cl::PrintHelpMessage(); + exit(1); + } + + // Get the USRs. + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + rename::USRFindingAction USRAction(SymbolOffset); + + // Find the USRs. + Tool.run(tooling::newFrontendActionFactory(&USRAction).get()); + const auto &USRs = USRAction.getUSRs(); + const auto &PrevName = USRAction.getUSRSpelling(); + + if (PrevName.empty()) + // An error should have already been printed. + exit(1); + + if (PrintName) + errs() << "clang-rename: found name: " << PrevName; + + // Perform the renaming. + rename::RenamingAction RenameAction(NewName, PrevName, USRs, + Tool.getReplacements(), PrintLocations); + auto Factory = tooling::newFrontendActionFactory(&RenameAction); + int res; + + if (Inplace) { + res = Tool.runAndSave(Factory.get()); + } else { + res = Tool.run(Factory.get()); + + // Write every file to stdout. Right now we just barf the files without any + // indication of which files start where, other than that we print the files + // in the same order we see them. + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts = + new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), + &*DiagOpts, &DiagnosticPrinter, false); + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + + Tool.applyAllReplacements(Rewrite); + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + auto ID = Sources.translateFile(Entry); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + + exit(res); +} Index: clang-rename/Makefile =================================================================== --- /dev/null +++ clang-rename/Makefile @@ -0,0 +1,16 @@ +##===- tools/extra/clang-rename/Makefile -------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +TOOLNAME = clang-rename +include $(CLANG_LEVEL)/../../Makefile.config + +DIRS = test + +include $(CLANG_LEVEL)/Makefile Index: clang-rename/RenamingAction.h =================================================================== --- /dev/null +++ clang-rename/RenamingAction.h @@ -0,0 +1,47 @@ +//===--- tools/extra/clang-rename/RenamingAction.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 rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; + +namespace rename { + +class RenamingAction { +public: + RenamingAction(const std::string &NewName, const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces, bool PrintLocations = false) + : NewName(NewName), PrevName(PrevName), USRs(USRs), Replaces(Replaces), + PrintLocations(PrintLocations) { + } + + std::unique_ptr newASTConsumer(); + +private: + const std::string &NewName, &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; + bool PrintLocations; +}; + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H_ Index: clang-rename/RenamingAction.cpp =================================================================== --- /dev/null +++ clang-rename/RenamingAction.cpp @@ -0,0 +1,90 @@ +//===--- tools/extra/clang-rename/RenamingAction.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 rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "RenamingAction.h" +#include "USRLocFinder.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer(const std::string &NewName, + const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces, + bool PrintLocations) + : NewName(NewName), PrevName(PrevName), USRs(USRs), Replaces(Replaces), + PrintLocations(PrintLocations) { + } + + void HandleTranslationUnit(ASTContext &Context) override { + const auto &SourceMgr = Context.getSourceManager(); + std::vector RenamingCandidates; + std::vector NewCandidates; + + for (const auto &USR : USRs) { + NewCandidates = getLocationsOfUSR(USR, Context.getTranslationUnitDecl()); + RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), + NewCandidates.end()); + NewCandidates.clear(); + } + + auto PrevNameLen = PrevName.length(); + if (PrintLocations) + for (const auto &Loc : RenamingCandidates) { + FullSourceLoc FullLoc(Loc, SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc) + << ":" << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << "\n"; + Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen, + NewName)); + } + else + for (const auto &Loc : RenamingCandidates) + Replaces.insert(tooling::Replacement(SourceMgr, Loc, PrevNameLen, + NewName)); + } + +private: + const std::string &NewName, &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; + bool PrintLocations; +}; + +std::unique_ptr RenamingAction::newASTConsumer() { + return llvm::make_unique(NewName, PrevName, USRs, + Replaces, PrintLocations); +} + +} +} Index: clang-rename/USRFinder.h =================================================================== --- /dev/null +++ clang-rename/USRFinder.h @@ -0,0 +1,39 @@ +//===--- 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 + +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); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H Index: clang-rename/USRFinder.cpp =================================================================== --- /dev/null +++ clang-rename/USRFinder.cpp @@ -0,0 +1,162 @@ +//===--- 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/Lex/Lexer.h" +#include "clang/Index/USRGeneration.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 seperate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public clang::RecursiveASTVisitor { +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 SourceManager &SourceMgr, + const SourceLocation Point) + : Result(nullptr), SourceMgr(SourceMgr), + Point(Point) { + } + + // 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 setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + // Check the namespace specifier first. + if (!checkNestedNameSpecifierLoc(Expr->getQualifierLoc())) + return false; + + const auto *Decl = Expr->getFoundDecl(); + return setResult(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const auto *Decl = Expr->getFoundDecl().getDecl(); + return setResult(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + // Other: + + const NamedDecl *getNamedDecl() { + return Result; + } + +private: + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + bool checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && !setResult(Decl, NameLoc.getLocalBeginLoc(), + Decl->getNameAsString().length())) + return false; + NameLoc = NameLoc.getPrefix(); + } + return true; + } + + // \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 (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) { + 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 || + (SourceMgr.isBeforeInTranslationUnit(Start, Point) && + SourceMgr.isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceManager &SourceMgr; + const SourceLocation Point; // The location to find the NamedDecl. +}; +} + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const auto &SourceMgr = Context.getSourceManager(); + const auto SearchFile = SourceMgr.getFilename(Point); + + NamedDeclFindingASTVisitor Visitor(SourceMgr, Point); + + // We only want to search the decls that exist in the same file as the point. + auto Decls = Context.getTranslationUnitDecl()->decls(); + for (auto &CurrDecl : Decls) { + const auto FileLoc = CurrDecl->getLocStart(); + const auto FileName = SourceMgr.getFilename(FileLoc); + // FIXME: Add test. + if (FileName == SearchFile) { + Visitor.TraverseDecl(CurrDecl); + if (const NamedDecl *Result = Visitor.getNamedDecl()) { + return Result; + } + } + } + + return nullptr; +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // namespace clang +} // namespace rename Index: clang-rename/USRFindingAction.h =================================================================== --- /dev/null +++ clang-rename/USRFindingAction.h @@ -0,0 +1,50 @@ +//===--- 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 relevent 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/Frontend/FrontendAction.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; +class NamedDecl; + +namespace rename { + +struct USRFindingAction { + USRFindingAction(unsigned Offset) : SymbolOffset(Offset) { + } + std::unique_ptr newASTConsumer(); + + // \brief get the spelling of the USR(s) as it would appear in source files. + const std::string &getUSRSpelling() { + return SpellingName; + } + + const std::vector &getUSRs() { + return USRs; + } + +private: + unsigned SymbolOffset; + std::string SpellingName; + std::vector USRs; +}; + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H_ Index: clang-rename/USRFindingAction.cpp =================================================================== --- /dev/null +++ clang-rename/USRFindingAction.cpp @@ -0,0 +1,118 @@ +//===--- 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 rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "USRFindingAction.h" +#include "USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +// Get the USRs for the constructors of the class. +static std::vector getAllConstructorUSRs( + const CXXRecordDecl *Decl) { + std::vector USRs; + + // We need to get the definition of the record (as opposed to any forward + // declarations) in order to find the constructor and destructor. + const auto *RecordDecl = Decl->getDefinition(); + + // Iterate over all the constructors and add their USRs. + for (const auto &CtorDecl : RecordDecl->ctors()) + USRs.push_back(getUSRForDecl(CtorDecl)); + + // Ignore destructors. GetLocationsOfUSR will find the declaration of and + // explicit calls to a destructor through TagTypeLoc (and it is better for the + // purpose of renaming). + // + // For example, in the following code segment, + // 1 class C { + // 2 ~C(); + // 3 }; + // At line 3, there is a NamedDecl starting from '~' and a TagTypeLoc starting + // from 'C'. + + return USRs; +} + +struct NamedDeclFindingConsumer : public ASTConsumer { + void HandleTranslationUnit(ASTContext &Context) override { + const auto &SourceMgr = Context.getSourceManager(); + // The file we look for the USR in will always be the main source file. + const auto Point = SourceMgr.getLocForStartOfFile( + SourceMgr.getMainFileID()).getLocWithOffset(SymbolOffset); + if (!Point.isValid()) + return; + const NamedDecl *FoundDecl = getNamedDeclAt(Context, Point); + if (FoundDecl == nullptr) { + FullSourceLoc FullLoc(Point, SourceMgr); + errs() << "clang-rename: could not find symbol at " + << SourceMgr.getFilename(Point) << ":" + << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << " (offset " << SymbolOffset + << ").\n"; + return; + } + + // If the decl is a constructor or destructor, we want to instead take the + // decl of the parent record. + if (const auto *CtorDecl = dyn_cast(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + + // If the decl is in any way relatedpp to a class, we want to make sure we + // search for the constructor and destructor as well as everything else. + if (const auto *Record = dyn_cast(FoundDecl)) + *USRs = getAllConstructorUSRs(Record); + + USRs->push_back(getUSRForDecl(FoundDecl)); + *SpellingName = FoundDecl->getNameAsString(); + } + + unsigned SymbolOffset; + std::string *SpellingName; + std::vector *USRs; +}; + +std::unique_ptr +USRFindingAction::newASTConsumer() { + std::unique_ptr Consumer( + new NamedDeclFindingConsumer); + SpellingName = ""; + Consumer->SymbolOffset = SymbolOffset; + Consumer->USRs = &USRs; + Consumer->SpellingName = &SpellingName; + return std::move(Consumer); +} + +} // namespace rename +} // namespace clang Index: clang-rename/USRLocFinder.h =================================================================== --- /dev/null +++ clang-rename/USRLocFinder.h @@ -0,0 +1,35 @@ +//===--- 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 +#include + +namespace clang { + +class Decl; +class SourceLocation; + +namespace rename { + +// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! +std::vector getLocationsOfUSR(const std::string usr, + Decl *decl); +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H Index: clang-rename/USRLocFinder.cpp =================================================================== --- /dev/null +++ clang-rename/USRLocFinder.cpp @@ -0,0 +1,103 @@ +//===--- 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/SourceLocation.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/ADT/SmallVector.h" + +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 { +public: + explicit USRLocFindingASTVisitor(const std::string USR) : USR(USR) { + } + + // Declaration visitors: + + bool VisitNamedDecl(const NamedDecl *Decl) { + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Decl->getLocation()); + } + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const auto *Decl = Expr->getFoundDecl(); + + checkNestedNameSpecifierLoc(Expr->getQualifierLoc()); + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Expr->getLocation()); + } + + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const auto *Decl = Expr->getFoundDecl().getDecl(); + if (getUSRForDecl(Decl) == USR) { + LocationsFound.push_back(Expr->getMemberLoc()); + } + return true; + } + + // Non-visitors: + + // \brief Returns a list of unique locations. Duplicate or overlapping + // locations are erroneous and should be reported! + const std::vector &getLocationsFound() const { + return LocationsFound; + } + +private: + // Namespace traversal: + void checkNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const auto *Decl = NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && getUSRForDecl(Decl) == USR) + LocationsFound.push_back(NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + + // All the locations of the USR were found. + const std::string USR; + std::vector LocationsFound; +}; +} // namespace + +std::vector getLocationsOfUSR(const std::string USR, + Decl *Decl) { + USRLocFindingASTVisitor visitor(USR); + + visitor.TraverseDecl(Decl); + return visitor.getLocationsFound(); +} + +} // namespace rename +} // namespace clang Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -37,6 +37,7 @@ # Individual tools we test. clang-apply-replacements clang-modernize + clang-rename clang-query clang-tidy modularize Index: test/clang-rename/VarTest.cpp =================================================================== --- /dev/null +++ test/clang-rename/VarTest.cpp @@ -0,0 +1,24 @@ +namespace A { +int foo; // CHECK: int hector; +} +int foo; // CHECK: int foo; +int bar = foo; // CHECK: bar = foo; +int baz = A::foo; // CHECK: baz = A::hector; +void fun1() { + struct { + int foo; // CHECK: int foo; + } b = { 100 }; + int foo = 100; // CHECK: int foo + baz = foo; // CHECK: baz = foo; + { + extern int foo; // CHECK: int foo; + baz = foo; // CHECK: baz = foo; + foo = A::foo + baz; // CHECK: foo = A::hector + baz; + A::foo = b.foo; // CHECK: A::hector = b.foo; + } + foo = b.foo; // CHECK: foo = b.foo; +} +// REQUIRES: shell +// RUN: cat %s > %t.cpp +// RUN: clang-rename -offset=$(grep -FUbo 'foo;' %t.cpp | head -1 | cut -d: -f1) -new-name=hector %t.cpp -i -- +// RUN: sed 's,//.*,,' %t.cpp | FileCheck %s Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -7,5 +7,6 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-modernize) +add_subdirectory(clang-rename) add_subdirectory(clang-query) add_subdirectory(clang-tidy) Index: unittests/clang-rename/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/clang-rename/CMakeLists.txt @@ -0,0 +1,33 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +get_filename_component(CLANG_RENAME_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-rename REALPATH) +include_directories( + ${CLANG_RENAME_SOURCE_DIR} + ) + +add_extra_unittest(ClangRenameTests + USRLocFindingTest.cpp + ${CLANG_RENAME_SOURCE_DIR}/USRFinder.cpp + ${CLANG_RENAME_SOURCE_DIR}/USRFindingAction.cpp + ) + +target_link_libraries(ClangRenameTests + clangAnalysis + clangAST + clangBasic + clangDriver + clangEdit + clangFrontend + clangFrontendTool + clangIndex + clangLex + clangParse + clangRewrite + clangRewriteFrontend + clangSerialization + clangSema + clangTooling + ) \ No newline at end of file Index: unittests/clang-rename/Makefile =================================================================== --- /dev/null +++ unittests/clang-rename/Makefile @@ -0,0 +1,24 @@ +##===- unittests/clang-rename/Makefile ---------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL = ../../../.. +include $(CLANG_LEVEL)/../../Makefile.config + +TESTNAME = ClangRenameTests +LINK_COMPONENTS := asmparser bitreader support MC MCParser option \ + TransformUtils +USEDLIBS = clangAnalysis.a clangAST.a clangBasic.a clangDriver.a clangEdit.a \ + clangFrontend.a clangFrontendTool.a clangIndex.a clangLex.a \ + clangParse.a clangRewrite.a clangRewriteFrontend.a \ + clangSerialization.a clangSema.a clangTooling.a + +include $(CLANG_LEVEL)/Makefile +MAKEFILE_UNITTEST_NO_INCLUDE_COMMON := 1 +CPP.Flags += -I(PROJ_SRC_DIR)/../../clang-rename +include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest Index: unittests/clang-rename/USRLocFindingTest.cpp =================================================================== --- /dev/null +++ unittests/clang-rename/USRLocFindingTest.cpp @@ -0,0 +1,77 @@ +#include "USRFindingAction.h" +#include "gtest/gtest.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include + +namespace clang { +namespace rename { +namespace test { + +// Determines if the symbol group invariants hold. To recap, those invariants +// are: +// (1) All symbols in the same symbol group share the same USR. +// (2) Two symbols from two different groups do not share the same USR. +static void testOffsetGroups(const char *Code, + const std::vector> Groups) { + std::set AllUSRs, CurrUSR; + + for (const auto &Group : Groups) { + // Groups the invariants do not hold then the value of USR is also invalid, + // but at that point the test has already failed and USR ceases to be + // useful. + std::string USR; + for (const auto &Offset : Group) { + USRFindingAction Action(Offset); + auto Factory = tooling::newFrontendActionFactory(&Action); + EXPECT_TRUE(tooling::runToolOnCode(Factory->create(), Code)); + const auto &USRs = Action.getUSRs(); + EXPECT_EQ(1u, USRs.size()); + USR = USRs[0]; + CurrUSR.insert(USR); + } + EXPECT_EQ(1u, CurrUSR.size()); + CurrUSR.clear(); + AllUSRs.insert(USR); + } + + EXPECT_EQ(Groups.size(), AllUSRs.size()); +} + + +TEST(USRLocFinding, FindsVarUSR) { + const char VarTest[] = "\n\ +namespace A {\n\ +int foo;\n\ +}\n\ +int foo;\n\ +int bar = foo;\n\ +int baz = A::foo;\n\ +void fun1() {\n\ + struct {\n\ + int foo;\n\ + } b = { 100 };\n\ + int foo = 100;\n\ + baz = foo;\n\ + {\n\ + extern int foo;\n\ + baz = foo;\n\ + foo = A::foo + baz;\n\ + A::foo = b.foo;\n\ + }\n\ + foo = b.foo;\n\ +}\n"; + std::vector> VarTestOffsets = { + { 19, 63, 205, 223 }, + { 30, 45, 172, 187 }, + { 129, 148, 242 }, + }; + + testOffsetGroups(VarTest, VarTestOffsets); +} + +} +} +}