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,168 @@ +//===--- 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 + +using namespace llvm; + +cl::OptionCategory ClangRenameCategory("Clang-rename options"); + +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("o", 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)); + +#define CLANG_RENAME_VERSION "0.0.1" + +static void PrintVersion() { + outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n"; +} + +using namespace clang; + +template +inline std::unique_ptr +newFrontendActionFactory(ActionT *Action) { + // Renaming CreateASTConsumer to createASTConsumer doesn't work, so we have to + // create our own custom factory. + struct SimpleFactory : public tooling::FrontendActionFactory { + SimpleFactory(ActionT *Action) : Action(Action) { + } + + FrontendAction *create() override { + return new ConsumerFactoryAdaptor(Action); + } + + private: + struct ConsumerFactoryAdaptor : public ASTFrontendAction { + ConsumerFactoryAdaptor(ActionT *Action) : Action(Action) { + } + + ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + StringRef File) override { + return Action->CreateASTConsumer(CI, File); + } + + private: + ActionT *Action; + }; + ActionT *Action; + }; + + return std::unique_ptr(new SimpleFactory(Action)); +} + +int main(int argc, const char **argv) { + cl::SetVersionPrinter(PrintVersion); + tooling::CommonOptionsParser OP(argc, argv, ClangRenameCategory, + "A tool to rename symbols in C/C++ code.\n\n\ +clang-rename renames every occurrence of the symbol found at in\n\ +. If is '-', input is taken from stdin and written to\n\ +stdout. If -o is specified along with multiple files, the files are edited\n\ +in-place. Otherwise, the results are written to stdout.\n"); + + // 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(newFrontendActionFactory(&USRAction).get()); + + if (USRAction.PrevName.empty()) + exit(1); + + const auto &USRs = USRAction.USRs; + const auto &PrevName = USRAction.PrevName; + if (PrintName) + errs() << "clang-rename: found name: " << PrevName; + + // Perform the renaming. + rename::RenamingAction RenameAction(PrevName, USRs, Tool.getReplacements()); + auto Factory = 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,45 @@ +//===--- 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 ASTFrontendAction { +public: + RenamingAction(const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces) + : PrevName(PrevName), USRs(USRs), Replaces(Replaces) { + } + + ASTConsumer *CreateASTConsumer(CompilerInstance &, StringRef) override; + +private: + const std::string &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; +}; + +} +} + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H Index: clang-rename/RenamingAction.cpp =================================================================== --- /dev/null +++ clang-rename/RenamingAction.cpp @@ -0,0 +1,94 @@ +//===--- 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; + +extern cl::OptionCategory ClangRenameCategory; + +static cl::opt PrintLocations( + "pl", cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameCategory)); + +extern cl::opt NewName; + +namespace clang { +namespace rename { + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer(const std::string &PrevName, + const std::vector &USRs, + tooling::Replacements &Replaces) + : PrevName(PrevName), USRs(USRs), Replaces(Replaces) { + } + + 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 &PrevName; + const std::vector &USRs; + tooling::Replacements &Replaces; +}; + +ASTConsumer *RenamingAction::CreateASTConsumer(CompilerInstance &CI, + StringRef File) { + return new RenamingASTConsumer(PrevName, USRs, Replaces); +} + +} +} 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,279 @@ +//===--- 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: + // |context| is the |ASTContext| used to look up locations, and is not owned + // by us. |point| is the location to search for the USR. |result| is where we + // store the decl found at the point. + explicit NamedDeclFindingASTVisitor(const SourceManager &SourceMgr, + const LangOptions &LangOpts, + const SourceLocation Point, + const clang::NamedDecl **Result) + : Result(nullptr), SourceMgr(SourceMgr), LangOpts(LangOpts), + Point(Point) { + } + + // Declaration visitors: + + // 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. With overloaded operators or + // conversion, however, we need to adjust the range to include the actual + // operator as well as the "operator" keyword. + bool VisitNamedDecl(const NamedDecl *Decl) { + const auto *FuncDecl = dyn_cast(Decl); + if (FuncDecl && (FuncDecl->isOverloadedOperator() || + isa(FuncDecl))) { + // Since overloaded operator declarations consist of multiple tokens, we + // need to handle them specially so that whatever comes after the + // "operator" keyword is included in the range. Thus, for operators, we + // make the range that from the first 'o' to the end of the actual + // operator. + // + // getEndLoc will return the location of the first character of the last + // token in the name of the declaration. For example: + // operator += (); + // ^ + // Thus, to get the end location, we offset the operator's location by the + // length of the operator's spelling. + SmallVector Buffer; + auto OpLoc = FuncDecl->getNameInfo().getEndLoc(); + if (OpLoc.isValid() && OpLoc.isFileID()) { + auto OpName = Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts); + // FIXME: something with warnings here. + return setResult(FuncDecl, FuncDecl->getLocation(), + OpLoc.getLocWithOffset(OpName.size() - 1)); + } + // We couldn't find the end of the operator, so just range over the + // keyword "operator". + return setResult(FuncDecl, FuncDecl->getLocation(), strlen("operator")); + } + return setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + // Determines if a CallExpr is an overloaded operator or conversion call, and + // if it is, checks if the point falls inside of the expression. Most call + // expressions are handled correctly via DeclRefExpr. For explicit overloaded + // operator and operator conversion calls we need to fix the range we consider + // within the name. + // + // Note that explicit operator calls are ones that begin with the keyword + // "operator", for example: + // foo.operator +=(bar); + // is an explicit operator call, while + // foo += bar; + // is an implicit call. + bool VisitCallExpr(const CallExpr *Expr) { + if (const auto *Decl = Expr->getDirectCallee()) { + if (!isa(Expr) && (Decl->isOverloadedOperator() || + isa(Decl))) { + // If we got here, then we found an explicit operator call or + // conversion. We fix the range of such calls from the leading 'o' to + // the end of the actual operator. + const auto *Callee = Expr->getCallee(); + const auto *MemExpr = dyn_cast(Callee); + // If this is a member expression, the start location should be after + // the '.' or '->'. + auto LocStart = + ((MemExpr) ? MemExpr->getMemberLoc() : Expr->getLocStart()); + // getLocEnd returns the start of the last token in the callee + // expression. Thus, for 'operator +=', it will return the location of + // the '+'. + auto OpLoc = Callee->getLocEnd(); + if (OpLoc.isValid() && OpLoc.isFileID()) { + SmallVector Buffer; + auto OpName = Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts); + return setResult(Decl, LocStart, + OpLoc.getLocWithOffset(OpName.size() - 1)); + } + // If we couldn't get the location of the end of the operator, we just + // check in the range of the keyword "operator". + return setResult(Decl, LocStart, strlen("operator")); + } + } + return true; + } + + bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *Expr) { + // CXXOperatorCallExprs are overloaded operator calls that are of implicit + // form and do not include ".operator" or "->operator" (see VisitCallExpr + // for an example). + if (const auto *Decl = Expr->getDirectCallee()) { + SmallVector Buffer; + auto OpLoc = Expr->getOperatorLoc(); + // Get the spelling of the operator so we know its length. + auto TokenSpelling = + Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts); + return setResult(Decl, OpLoc, TokenSpelling.size()); + } + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + // Check the namespace specifier first. + if (!checkNestedNameSpecifierLoc(Expr->getQualifierLoc())) + return false; + + if (isa(Expr)) + return true; + + const auto *Decl = Expr->getFoundDecl(); + // DeclRefExpr includes overloaded operators. We want to handle those + // seperately, as determining the length of their spelling can be + // problematic. For example, one can call an overloaded addition operator by + // either foo + bar or foo.operator+(bar). + if (const auto *FuncDecl= dyn_cast(Decl)) { + if (FuncDecl->isOverloadedOperator() && !isa(Expr)) { + // If we got here, we are attempting to not call, but pass an overloaded + // operator method somewhere. We can only do this with the "operator" + // keyword. + auto Op = FuncDecl->getOverloadedOperator(); + // Ignore operators we don't want to check because their renaming + // requires changing syntax. + if (Op == OO_None || Op == OO_Call || Op == OO_Subscript) + return true; + auto LocStart = Expr->getLocStart(); + auto OpLoc = Expr->getLocEnd(); + if (OpLoc.isValid() && OpLoc.isFileID()) { + auto OpName = clang::getOperatorSpelling(Op); + auto OpLocEnd = OpLoc.getLocWithOffset(strlen(OpName) - 1); + return setResult(Decl, LocStart, OpLocEnd); + } + // If we couldn't get the range of the operator, get the range of the + // operator keyword. + return setResult(Decl, LocStart, strlen("operator")); + } + } + 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()); + } + + const NamedDecl *getNamedDecl() { + return Result; + } + +private: + // Check a namespace qualifier |name_loc|. If it contains |point_|, set + // |result_| and return false. + 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; + } + + // Sets |result_| if the |point_| is within |start| and |end|. Returns false + // if |result_| was set in order to terminate the traversal early. If |start| + // or |end| are not valid file locations |result_| will not be set. + 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; + } + + // Sets |result_| if the |point_| is within |start| and the |offset|. Returns + // false if |result_| was set in order to terminate the traversal early. If + // |start| is not a valid file location, |result_| will not be set. + bool setResult(const NamedDecl *Decl, SourceLocation Loc, + unsigned Offset) { + return Offset == 0 || + setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); + } + + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + return Point == Start || Point == End || + (SourceMgr.isBeforeInTranslationUnit(Start, Point) && + SourceMgr.isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceManager &SourceMgr; + const LangOptions &LangOpts; + 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 &LangOpts = Context.getLangOpts(); + const auto SearchFile = SourceMgr.getFilename(Point); + const NamedDecl *Result = nullptr; + + NamedDeclFindingASTVisitor Visitor(SourceMgr, LangOpts, Point, &Result); + + // 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); + if (FileName == SearchFile) { + Visitor.TraverseDecl(CurrDecl); + Result = Visitor.getNamedDecl(); + if (Result != nullptr) + break; + } + } + + return Result; +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + 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,41 @@ +//===--- 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 : public ASTFrontendAction { + USRFindingAction(unsigned Offset) : SymbolOffset(Offset) { + } + ASTConsumer *CreateASTConsumer(CompilerInstance &, StringRef) override; + + std::string PrevName; + std::vector USRs; +private: + unsigned SymbolOffset; +}; + +} +} + +#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,117 @@ +//===--- 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)); + *PrevName = FoundDecl->getNameAsString(); + } + + unsigned SymbolOffset; + std::string *PrevName; + std::vector *USRs; +}; + +ASTConsumer *USRFindingAction::CreateASTConsumer(CompilerInstance &CI, + StringRef File) { + auto *Consumer = new NamedDeclFindingConsumer; + PrevName = ""; + Consumer->SymbolOffset = SymbolOffset; + Consumer->USRs = &USRs; + Consumer->PrevName = &PrevName; + return 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,102 @@ +//===--- 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 { +// 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: + + // 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,22 @@ +// RUN: cat %s > %t.cpp +// RUN: clang-rename -offset=126 -new-name=hector %t.cpp -- | FileCheck %s < %t.cpp +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; +} 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,115 @@ +#include "USRFindingAction.h" +#include "gtest/gtest.h" +#include "clang/Tooling/Tooling.h" +#include +#include +#include +#include + +namespace clang { +namespace rename { +namespace test { + +template +inline std::unique_ptr +newFrontendActionFactory(ActionT *Action) { + // Renaming CreateASTConsumer to createASTConsumer doesn't work, so we have to + // create our own custom factory. + struct SimpleFactory : public tooling::FrontendActionFactory { + SimpleFactory(ActionT *Action) : Action(Action) { + } + + FrontendAction *create() override { + return new ConsumerFactoryAdaptor(Action); + } + + private: + struct ConsumerFactoryAdaptor : public ASTFrontendAction { + ConsumerFactoryAdaptor(ActionT *Action) : Action(Action) { + } + + ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + StringRef File) override { + return Action->CreateASTConsumer(CI, File); + } + + private: + ActionT *Action; + }; + ActionT *Action; + }; + + return std::unique_ptr(new SimpleFactory(Action)); +} + +struct OffsetGroup { + unsigned offset; + unsigned group; +}; + +static void testOffsetGroups(const char *Code, + const std::vector Groups) { + std::set AllUSRs; + std::map USRMap; + unsigned NumGroups = 0; + + for (const auto &Group : Groups) { + USRFindingAction Action(Group.offset); + auto Factory = newFrontendActionFactory(&Action); + EXPECT_TRUE(tooling::runToolOnCode(Factory->create(), Code)); + EXPECT_EQ(1u, Action.USRs.size()); + if (USRMap.find(Group.group) == USRMap.end()) { + NumGroups++; + USRMap[Group.group] = Action.USRs[0]; + AllUSRs.insert(Action.USRs[0]); + } else + EXPECT_EQ(USRMap[Group.group], Action.USRs[0]); + } + + EXPECT_EQ(NumGroups, AllUSRs.size()); +} + + +TEST(USRLocFinding, FindsVarUSR) { + const char VarTest[] = "\ +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\ +}"; + std::vector VarTestOffsets = { + { 19, 0 }, + { 63, 0 }, + { 205, 0 }, + { 223, 0 }, + { 30, 1 }, + { 45, 1 }, + { 129, 2 }, + { 148, 2 }, + { 172, 1 }, + { 187, 1 }, + { 242, 2 }, + }; + + testOffsetGroups(VarTest, VarTestOffsets); +} + + +} +} +}