Hi klimek,

This is the first patch for the clang rename tool. In order to reduce the 
amount of code in this patch, a fair amount of code has been removed from 
USRFinder.cpp and USRLocFinder.cpp, at the request of Manuel. This reduces the 
functionality of the tool to just renaming variables, and wont rename them in 
all contexts. The remaining code will be added when it is found to be 
appropriate (it does in fact exist; I have it in a tar in my home directory).

It builds fine, and I can run the program just fine on my home laptop as well 
as my work computer.

http://reviews.llvm.org/D4739

Files:
  CMakeLists.txt
  Makefile
  clang-rename/CMakeLists.txt
  clang-rename/ClangRename.cpp
  clang-rename/Makefile
  clang-rename/USRFinder.cpp
  clang-rename/USRFinder.h
  clang-rename/USRLocFinder.cpp
  clang-rename/USRLocFinder.h
  test/CMakeLists.txt
  test/clang-rename/VarTest.cpp
  test/clang-rename/rename_test.py
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,397 @@
+//===--- 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/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 <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+
+static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
+
+// Hack to allow us to use "-" as a filename in a command line source location.
+static cl::opt<std::string> STDINLocation(":", cl::Prefix, cl::ReallyHidden);
+
+static cl::OptionCategory ClangRenameCategory("Clang-rename options");
+
+static cl::opt<bool> Inplace("o", cl::desc("Overwrite edited <file>s."),
+                             cl::cat(ClangRenameCategory));
+static cl::opt<bool> PrintLocations(
+    "pl", cl::desc("Print the locations affected by renaming to stderr."),
+    cl::cat(ClangRenameCategory));
+static cl::opt<bool> PrintName(
+    "pn",
+    cl::desc("Print the found symbol's name prior to renaming to stderr."),
+    cl::cat(ClangRenameCategory));
+static cl::list<std::string>
+    IncludePaths("I", cl::desc("Add a directory to the header search path."),
+                 cl::Prefix, cl::cat(ClangRenameCategory));
+static cl::list<std::string> MacroDefs("D", cl::desc("Defines a macro."),
+                                       cl::Prefix,
+                                       cl::cat(ClangRenameCategory));
+static cl::opt<std::string>
+    NewName("new-name", cl::desc("The new name to change the symbol to."),
+            cl::cat(ClangRenameCategory));
+static cl::opt<std::string> SymbolLocation(cl::Positional,
+                                           cl::desc("<file>:<line>:<column>"),
+                                           cl::cat(ClangRenameCategory));
+static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
+                                       cl::cat(ClangRenameCategory));
+
+namespace clang {
+namespace rename {
+
+static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source,
+                                 SourceManager &Sources, FileManager &Files) {
+  const FileEntry *Entry = Files.getVirtualFile(
+      (FileName == "-" ? "<stdin>" : FileName), Source->getBufferSize(), 0);
+  Sources.overrideFileContents(Entry, Source, true);
+  return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
+}
+
+// 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<std::string> getClassEquivUSRs(const CXXRecordDecl *Decl) {
+  std::vector<std::string> 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;
+}
+
+static std::vector<SourceLocation>
+fixOperatorLocs(const std::string &OpSpelling,
+                const std::vector<SourceLocation> &OldLocs,
+                const SourceManager &SourceMgr, const LangOptions &LangOpts) {
+  std::vector<SourceLocation> Candidates;
+  for (auto Loc : OldLocs) {
+    // If the current location poihnts 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<char, 32> 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() : Failed(false) {}
+
+  void HandleTranslationUnit(ASTContext &Context) override {
+    const auto &SourceMgr = Context.getSourceManager();
+    const auto &LangOpts = Context.getLangOpts();
+
+    if (PrevName.empty()) {
+      // Retrieve the previous name and USRs.
+      const auto ParsedSymLoc =
+          ParsedSourceLocation::FromString(SymbolLocation);
+      // The source file we are searching will always be the mail source file.
+      const auto Point = SourceMgr.translateLineCol(
+          SourceMgr.getMainFileID(), ParsedSymLoc.Line, ParsedSymLoc.Column);
+      const NamedDecl *TargetDecl = getNamedDeclAt(Context, Point);
+      if (TargetDecl == nullptr) {
+        errs() << "clang-rename: no symbol found at " << SymbolLocation << "\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<CXXConstructorDecl>(TargetDecl))
+        TargetDecl = CtorDecl->getParent();
+      else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(TargetDecl))
+        TargetDecl = DtorDecl->getParent();
+
+      if (const auto *Record = dyn_cast<CXXRecordDecl>(TargetDecl)) {
+        auto ExtraUSRs = getClassEquivUSRs(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<SourceLocation> RenamingCandidates;
+    std::vector<SourceLocation> 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";
+        Rewrite->ReplaceText(Loc, PrevNameLen, NewName);
+      }
+    else
+      for (const auto &Loc : RenamingCandidates)
+        Rewrite->ReplaceText(Loc, PrevNameLen, NewName);
+
+    // ...and we're done!
+  }
+
+  void setRewriter(Rewriter *NewRewriter) { Rewrite = NewRewriter; }
+
+  bool hasFailed() { return Failed; }
+
+private:
+  bool Failed;
+  Rewriter *Rewrite;
+  std::string PrevName;
+  std::vector<std::string> USRs;
+};
+
+static bool Rename() {
+  CompilerInstance Compiler;
+
+  auto *Invocation = new CompilerInvocation;
+  Compiler.setInvocation(Invocation);
+
+  // Make sure we're parsing the right language.
+  Compiler.getLangOpts().CPlusPlus11 = true;
+  Compiler.getLangOpts().GNUKeywords = true;
+  Compiler.getLangOpts().NoBuiltin = false;
+  Invocation->setLangDefaults(Compiler.getLangOpts(), IK_CXX,
+                              LangStandard::lang_gnucxx11);
+
+  Compiler.createDiagnostics(
+      new TextDiagnosticPrinter(errs(), new DiagnosticOptions));
+  std::shared_ptr<TargetOptions> TargetOpts(new TargetOptions());
+  TargetOpts->Triple = sys::getDefaultTargetTriple();
+  auto *Info =
+      TargetInfo::CreateTargetInfo(Compiler.getDiagnostics(), TargetOpts);
+  Compiler.setTarget(Info);
+
+  // Add the extra header search paths.
+  auto &HeaderSearchOpts = Compiler.getHeaderSearchOpts();
+  for (auto &Path : IncludePaths)
+    HeaderSearchOpts.AddPath(Path, frontend::Angled, false, false);
+
+  // Load and rename each of the files.
+  RenamingASTConsumer RenamingConsumer;
+  //  Compiler.setASTConsumer(&RenamingConsumer);
+  for (const auto &FileName : FileNames) {
+    Compiler.createFileManager();
+    auto &Files = Compiler.getFileManager();
+    Compiler.createSourceManager(Files);
+    auto &SourceMgr = Compiler.getSourceManager();
+    // Add the macro definitions.
+    for (auto &Macro : MacroDefs)
+      Compiler.getPreprocessorOpts().addMacroDef(Macro);
+    Compiler.createPreprocessor(TU_Complete);
+
+    // Make sure the preprocessor has builtins defined.
+    auto &PP = Compiler.getPreprocessor();
+    PP.getBuiltinInfo().InitializeBuiltins(PP.getIdentifierTable(),
+                                           PP.getLangOpts());
+
+    // Fetch the file content.
+    ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
+        MemoryBuffer::getFileOrSTDIN(FileName == "<stdin>" ? "-" : FileName);
+    if (auto EC = CodeOrErr.getError()) {
+      errs() << "clang-rename: " << EC.message() << "\n";
+      return true;
+    }
+    std::unique_ptr<MemoryBuffer> Code = std::move(CodeOrErr.get());
+    if (Code->getBufferSize() == 0) {
+      errs() << "clang-rename: " << FileName << " is empty, skipping.\n";
+      continue;
+    }
+    auto ID = createInMemoryFile(FileName, Code.get(), SourceMgr, Files);
+
+    Rewriter Rewrite(SourceMgr, Compiler.getLangOpts());
+    RenamingConsumer.setRewriter(&Rewrite);
+    Compiler.createASTContext();
+    SourceMgr.setMainFileID(ID);
+    Compiler.getDiagnosticClient().BeginSourceFile(Compiler.getLangOpts(),
+                                                   &Compiler.getPreprocessor());
+    ParseAST(Compiler.getPreprocessor(), &RenamingConsumer,
+             Compiler.getASTContext());
+    Compiler.getDiagnosticClient().EndSourceFile();
+
+    if (RenamingConsumer.hasFailed())
+      return true;
+
+    // Push rewrites to disk/stdout.
+    if (Inplace && FileName != "<stdin>")
+      Rewrite.overwriteChangedFiles();
+    else
+      Rewrite.getEditBuffer(ID).write(outs());
+
+    // Reset the Compiler
+    Compiler.resetAndLeakFileManager();
+    Compiler.resetAndLeakSourceManager();
+    Compiler.resetAndLeakPreprocessor();
+    Compiler.resetAndLeakASTContext();
+  }
+  return false;
+}
+
+} // namespace rename
+} // namespace clang;
+
+#define CLANG_RENAME_VERSION "0.0.1"
+
+static void PrintVersion() {
+  outs() << "clang-rename version " << CLANG_RENAME_VERSION << "\n";
+}
+
+int main(int argc, char **argv) {
+  // Hide unrelated options.
+  StringMap<cl::Option *> Options;
+  cl::getRegisteredOptions(Options);
+  for (StringMap<cl::Option *>::iterator I = Options.begin(), E = Options.end();
+       I != E; ++I)
+    if (I->second->Category != &ClangRenameCategory && I->first() != "help" &&
+        I->first() != "version")
+      I->second->setHiddenFlag(cl::ReallyHidden);
+
+  cl::SetVersionPrinter(PrintVersion);
+  cl::ParseCommandLineOptions(argc, argv,
+                              "A tool to rename symbols in C/C++ code.\n\n\
+clang-rename renames all instances of the symbol found at \n\
+<file>:<line>:<column>. If <file> is '<stdin>' or '-', input is taken from\n\
+standard input and written to standard output. If -o is specified along with\n\
+<file>s, the files are edited in-place. Otherwise, the results are written to\n\
+standard output.\n");
+
+  if (Help) {
+    cl::PrintHelpMessage();
+    exit(0);
+  }
+
+  // Check the arguments for correctness.
+
+  auto SymLocation = clang::ParsedSourceLocation::FromString(SymbolLocation);
+  if (SymLocation.FileName.empty()) {
+    if (!STDINLocation.empty()) {
+      SymLocation =
+          clang::ParsedSourceLocation::FromString("-:" + STDINLocation);
+    } else {
+      if (SymbolLocation.empty())
+        llvm::errs() << "clang-rename: no symbol location provided.\n\n";
+      else
+        llvm::errs() << "clang-rename: invalid symbol location: "
+                     << SymbolLocation << "\n\n";
+      cl::PrintHelpMessage();
+      exit(1);
+    }
+  }
+  if (NewName.empty()) {
+    errs() << "clang-rename: no new name provided.\n\n";
+    cl::PrintHelpMessage();
+    exit(1);
+  }
+
+  if (SymLocation.FileName == "<stdin>" && FileNames.empty() && Inplace)
+    errs() << "clang-rename: note, -i flag is useless when only taking "
+           << "input from stdin.\n";
+
+  FileNames.insert(FileNames.begin(), SymLocation.FileName);
+
+  // Run the tool.
+  exit(clang::rename::Rename());
+}
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.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<NamedDeclFindingASTVisitor> {
+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<FunctionDecl>(Decl);
+    if (FuncDecl && (FuncDecl->isOverloadedOperator() ||
+                      isa<CXXConversionDecl>(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<char, 32> 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<CXXOperatorCallExpr>(Expr) && (Decl->isOverloadedOperator() ||
+                                              isa<CXXConversionDecl>(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<MemberExpr>(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<char, 32> 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<char, 32> 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<CXXOperatorCallExpr>(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<FunctionDecl>(Decl)) {
+      if (FuncDecl->isOverloadedOperator() && !isa<CallExpr>(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<char, 128> Buff;
+
+  if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff))
+    return "";
+
+  return std::string(Buff.data(), Buff.size());
+}
+
+} // namespace clang
+} // namespace rename
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 <string>
+
+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/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<USRLocFindingASTVisitor> {
+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<clang::SourceLocation> &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<clang::SourceLocation> LocationsFound;
+};
+} // namespace
+
+std::vector<SourceLocation> getLocationsOfUSR(const std::string USR,
+                                              Decl *Decl) {
+  USRLocFindingASTVisitor visitor(USR);
+
+  visitor.TraverseDecl(Decl);
+  return visitor.getLocationsFound();
+}
+
+} // namespace rename
+} // namespace clang
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 <string>
+#include <vector>
+
+namespace clang {
+
+class Decl;
+class SourceLocation;
+
+namespace rename {
+
+std::vector<SourceLocation> getLocationsOfUSR(const std::string usr,
+                                              Decl *decl);
+}
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H
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)
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to