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,27 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_executable(clang-rename + ClangRename.cpp + USRFinder.cpp + USRLocFinder.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,336 @@ +//===--- 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 "USRFinder.h" +#include "USRLocFinder.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/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; + +static cl::OptionCategory ClangRenameCategory("Clang-rename options"); + +static cl::opt Inplace("o", cl::desc("Overwrite edited s."), + cl::cat(ClangRenameCategory)); +static cl::opt PrintLocations( + "pl", cl::desc("Print the locations affected by renaming to stderr."), + 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 + 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)); + +namespace clang { +namespace rename { + +// Checks if the string is a long-form operator call, and if it is, returns the +// short form of the call. For example, "operator+" would be converted to '+'. +// Returns an empty string if |call| is not a long-form operator call. Assumes +// the input was given by a Clang getNameAsString method. +static std::string getOpCallShortForm(const std::string &Call) { + const std::string Prefix = "operator"; + const auto PrefixLen = Prefix.length(); + if (Call.compare(0, PrefixLen, Prefix) || isalpha(Call[PrefixLen]) || + Call[PrefixLen] == '_') + return ""; + return Call.substr((Call[PrefixLen] == ' ') ? PrefixLen + 1 : PrefixLen); +} + +// 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; +} + +// Fix renaming locations for overloaded operator. Shifts the location from the +// ``operator'' keyword to the actual operator. If it can't find the operator, +// the location is discarded and the user is warned. +static std::vector +fixOperatorLocs(const std::string &OpSpelling, + const std::vector &OldLocs, + const SourceManager &SourceMgr, const LangOptions &LangOpts) { + std::vector Candidates; + for (auto Loc : OldLocs) { + // If the current location points to the "operator" keyword, we want to + // skip to the actual operator's spelling. We don't try to do this very; if + // the operator isn't the next token over, we give up on the location. + SmallVector Buffer; + auto TokSpelling = Lexer::getSpelling(Loc, Buffer, SourceMgr, LangOpts); + if (TokSpelling == "operator") { + auto LocInfo = SourceMgr.getDecomposedLoc( + Lexer::getLocForEndOfToken(Loc, 0, SourceMgr, LangOpts)); + auto File = SourceMgr.getBufferData(LocInfo.first); + Lexer Tokenizer(SourceMgr.getLocForStartOfFile(LocInfo.first), LangOpts, + File.begin(), File.data() + LocInfo.second, File.end()); + Token OpToken; + Tokenizer.LexFromRawLexer(OpToken); + auto OpLoc = OpToken.getLocation(); + auto CheckSpelling = + Lexer::getSpelling(OpLoc, Buffer, SourceMgr, LangOpts); + if (CheckSpelling != OpSpelling || !OpLoc.isValid()) { + FullSourceLoc FullLoc(Loc, SourceMgr); + errs() << "clang-rename: could not rename operator at " + << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << ", skipping.\n"; + continue; // Skip this location. + } + Loc = OpLoc; + } + Candidates.push_back(Loc); + } + return Candidates; +} + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer(bool *FailureCond, std::string &PrevName, + std::vector &USRs, + tooling::Replacements &Replaces) + : Failed(FailureCond), PrevName(PrevName), USRs(USRs), + Replaces(Replaces) { + } + + void HandleTranslationUnit(ASTContext &Context) override { + const auto &SourceMgr = Context.getSourceManager(); + const auto &LangOpts = Context.getLangOpts(); + + if (PrevName.empty()) { + // Retrieve the previous name and USRs. + // The source file we are searching will always be the main source file. + const auto Point = SourceMgr.getLocForStartOfFile( + SourceMgr.getMainFileID()).getLocWithOffset(SymbolOffset); + const NamedDecl *TargetDecl = getNamedDeclAt(Context, Point); + if (TargetDecl == nullptr) { + errs() << "clang-rename: no symbol found at given location\n"; + *Failed = true; + return; + } + + // If the decl is in any way related to a class, we want to make sure we + // search for the constructor and destructor as well as everything else. + if (const auto *CtorDecl = dyn_cast(TargetDecl)) + TargetDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(TargetDecl)) + TargetDecl = DtorDecl->getParent(); + + if (const auto *Record = dyn_cast(TargetDecl)) { + auto ExtraUSRs = getAllConstructorUSRs(Record); + USRs.insert(USRs.end(), ExtraUSRs.begin(), ExtraUSRs.end()); + } + + // Get the primary USR and previous symbol's name. + USRs.push_back(getUSRForDecl(TargetDecl)); + PrevName = TargetDecl->getNameAsString(); + if (PrintName) + errs() << "clang-rename: found symbol: " << PrevName << "\n"; + } + + // Now that we have the USR, find every affected location. + 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(); + } + + // If we're renaming an operator, we need to fix some of our source + // locations. + auto OpShortForm = getOpCallShortForm(PrevName); + if (!OpShortForm.empty()) + RenamingCandidates = + fixOperatorLocs(OpShortForm, RenamingCandidates, SourceMgr, LangOpts); + + 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: + bool *Failed; + std::string &PrevName; + std::vector &USRs; + tooling::Replacements &Replaces; + +}; + +} // namespace rename +} // namespace clang; + +#define CLANG_RENAME_VERSION "0.0.1" + +static void PrintVersion() { + outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n"; +} + +using namespace clang; + +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); + } + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + + class RenamingFrontendAction : public ASTFrontendAction { + public: + RenamingFrontendAction(tooling::Replacements &Replaces) + : FailureCond(false), Replaces(Replaces) { + } + + ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + StringRef File) override { + return new rename::RenamingASTConsumer(&FailureCond, PrevName, + USRs, Replaces); + } + + bool hasFailed() { return FailureCond; } + + private: + bool FailureCond; + std::string PrevName; + std::vector USRs; + tooling::Replacements &Replaces; + }; + + struct RenamingFactory : public tooling::FrontendActionFactory { + RenamingFactory(RenamingFrontendAction *Action) : Action(Action) { + } + + FrontendAction *create() override { + return new ConsumerFactoryAdaptor(Action); + } + private: + struct ConsumerFactoryAdaptor : public ASTFrontendAction { + ConsumerFactoryAdaptor(RenamingFrontendAction *Action) : Action(Action) { + } + + ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + StringRef File) override { + return Action->CreateASTConsumer(CI, File); + } + private: + RenamingFrontendAction *Action; + }; + RenamingFrontendAction *Action; + }; + + RenamingFrontendAction Action(Tool.getReplacements()); + std::unique_ptr Factory(new RenamingFactory(&Action)); + + if (Inplace) { + Tool.runAndSave(Factory.get()); + } else { + 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); + + tooling::applyAllReplacements(Tool.getReplacements(), Rewrite); + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + auto ID = Sources.translateFile(Entry); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + + exit(Action.hasFailed()); +} 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/USRFinder.h =================================================================== --- /dev/null +++ clang-rename/USRFinder.h @@ -0,0 +1,38 @@ +//===--- 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,273 @@ +//===--- 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) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Point(Point), Result(Result) { + } + + // 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()); + } + +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 SourceManager &SourceMgr; + const LangOptions &LangOpts; + const SourceLocation Point; // The location to find the NamedDecl. + const clang::NamedDecl **Result; // Pointer to store found NameDecl. +}; +} + +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); + 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/USRLocFinder.h =================================================================== --- /dev/null +++ clang-rename/USRLocFinder.h @@ -0,0 +1,34 @@ +//===--- 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 { + +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,107 @@ +//===--- 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) { + // Because we traverse the AST from top to bottom, it follows that the + // current locations must be further down (or right) than the previous one + // inspected. This has the effect of keeping LocationsFound sorted by + // line first column second with no effort of our own. + // This is correct as of 2014-06-27 + 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,21 @@ +// RUN: ./rename_test.py %s +namespace A { +int /*0*/foo; +} +int /*1*/foo; +int bar = /*1*/foo; +int /*3*/baz = A::/*0*/foo; +void fun1() { + struct { + int foo; + } b = { 100 }; + int /*2*/foo = 100; + /*3*/baz = /*2*/foo; + { + extern int /*1*/foo; + /*3*/baz = /*1*/foo; + /*1*/foo = A::/*0*/foo + /*3*/baz; + A::/*0*/foo = b.foo; + } + /*2*/foo = b.foo; +} Index: test/clang-rename/rename_test.py =================================================================== --- /dev/null +++ test/clang-rename/rename_test.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +##===- tools/extra/clang/clang-rename/rename_test.py ----------------------===## +## +## The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## +## +## \file +## \bried framework for testing renaming on a set of source files. Automatically +## finds and renames symbols part of the same group. +## +##===----------------------------------------------------------------------===## + +import re +import sets +import subprocess +import sys + +# We want to facilitate changing and adding renaming tests as much as possible, +# without having to change a hard-coded list of locations. To do this, we have a +# special framework designed specifically to test renaming. +# +# A symbol group is a list of identifiers for which the following two invariants +# hold: +# 1. All of the identifiers have the same USR. +# 2. The USR isn't shared with any other group. +# Thus, two symbol groups that share the same USR are two incomplete sub sets of +# the whole symbol group. +# +# Our renamer is succesful if the invariants hold before and after renaming. We +# do not extensively ensure that the invariants hold prior to renaming, however. +# +# To test this, we create sample C/C++ files in which we encode the symbol +# groups. To indicate in a C++ file that a symbol belongs to a certain group, we +# prepend the symbol with /*symbol group number*/. For example, if we declare an +# integer "foo" that belongs in symbol group three, the declaration would look +# like: +# int /*3*/foo; +# Using this schema, we can arbitrarily edit and extend the test files and the +# location of each identifier in a group will be found automatically. + +# Regex used to broadly capture a renaming location, which includes arithmetic +# operators so that we can capture them in overloaded operator declarations. +renaming_loc_regex = r"[^\s\(\);:,.\[\]]+" + +# Unique name to try renaming each symbol to. +new_name = "smooth_jazz" + +def file_lines(filename): + with open(filename) as f: + for i, l in enumerate(f): + pass + return i + 1 + +def get_group_locs(filename): + """Get the locations of every symbol group, placing in them in a map.""" + marker_regex = re.compile(r"/\*(\d+)\*/(" + renaming_loc_regex + ")") + + groups = {} + curr_line = 0 + with open(filename, 'r') as f: + for line in f: + curr_line += 1 + curr_column = 1 + # Find each symbol on the current line. + while True: + res = marker_regex.search(line) + if res == None: + break + gnum = res.group(1) + # Find the location of the symbol. + search_str = "/*" + gnum + "*/" + group_num = int(gnum) + pos = line.find(search_str) + len(search_str) + curr_column += pos + # Add the symbol to the group. + if not group_num in groups: + groups[group_num] = [ (filename, curr_line, curr_column) ] + else: + groups[group_num].append( (filename, curr_line, curr_column) ) + # Adjust the line so we get the correct next location. + line = line[pos:] + return groups + +def get_group_syms(group_num, code): + """Get all of the unique symbols following a marker deliminating a given + symbol group.""" + marker_regex = re.compile(r"/\*" + str(group_num) + r"\*/(" + + renaming_loc_regex + ")") + + symbols = sets.Set() + found = marker_regex.findall(code) + for sym in found: + symbols.add(sym) + + return symbols + +rel_args = sys.argv[1:] +if len(rel_args) < 1: + sys.stderr.write("rename-test.py: expected at least one file to rename\n") + sys.exit(1) + +# Get the symbol groups for every file. +sym_groups = {} +lines_per_file = {} +for arg in rel_args: + lines_per_file[arg] = file_lines(arg) + res = get_group_locs(arg) + for group, locs in res.iteritems(): + if not group in sym_groups: + sym_groups[group] = locs + else: + sym_groups[group].append(locs) + +# Try renaming on every location in every file. This is not designed to be +# speedy! +for group, locs in sym_groups.iteritems(): + for loc in locs: + filename = loc[0] + cl_files = "" + lines = [ lines_per_file[filename] ] + for file in rel_args: + if file != filename: + cl_files = cl_files + " " + file + lines.append(lines_per_file[file]) + cl = ("./clang-rename " + filename + ":" + str(loc[1]) + ":" + str(loc[2]) + + cl_files + ' -new-name=' + new_name) + p = subprocess.Popen(cl, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + if p_status != 0: + # clang-rename didn't work, so the test fails. + sys.stderr.write("rename-test.py: renaming failed\n") + sys.exit(1) + # Check the invariants on the code. + syms = get_group_syms(group, output) +# sys.stdout.write("\ +#Output: ------------------------------------------------------------------------") +# sys.stdout.write("\n" + output + "\n") + if len(syms) != 1: + if len(syms) == 0: + sys.stderr.write("rename-test.py: FAILURE: symbols dissappeared\n") + else: + sys.stderr.write("rename-test.py: FAILURE: found multiple symbols:\n") + for item in syms: + sys.stderr.write("rename-test.py:\t- " + item + "\n") + sys.exit(1) +sys.stdout.write("rename-test.py: SUCCESS\n") +sys.exit(0)