Responded to comments and such

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,409 @@
+//===--- 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::opt<unsigned> SymbolOffset(
+    "offset",
+    cl::desc("Locates the symbol by offset as opposed to <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> getAllConstructorUSRs(
+    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;
+}
+
+// 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<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 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<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);
+      SourceLocation Point;
+      // The source file we are searching will always be the main source file.
+      if (ParsedSymLoc.FileName.empty()) {
+        Point = SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID());
+        Point = Point.getLocWithOffset(SymbolOffset);
+      } else {
+        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 = 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<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);
+  }
+
+  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 if (SymbolOffset == 0)
+        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