[cfe-commits] r130306 - in /cfe/trunk: examples/CMakeLists.txt examples/Tooling/ examples/Tooling/CMakeLists.txt examples/Tooling/ClangCheck.cpp include/clang/Tooling/Tooling.h lib/Tooling/CMakeLists.txt lib/Tooling/JsonCompileCommandLineDatabase.cpp lib/Tooling/JsonCompileCommandLineDatabase.h lib/Tooling/Tooling.cpp unittests/CMakeLists.txt unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp unittests/Tooling/ToolingTest.cpp

Manuel Klimek klimek at google.com
Wed Apr 27 09:39:14 PDT 2011


Author: klimek
Date: Wed Apr 27 11:39:14 2011
New Revision: 130306

URL: http://llvm.org/viewvc/llvm-project?rev=130306&view=rev
Log:
This is the next step in building the standalone tools infrastructure:
This patch simplifies writing of standalone Clang tools. As an
example, we add clang-check, a tool that runs a syntax only frontend
action over a .cc file. When you integrate this into your favorite
editor, you get much faster feedback on your compilation errors, thus
reducing your feedback cycle especially when writing new code.

The tool depends on integration of an outstanding patch to CMake to
work which allows you to always have a current compile command
database in your cmake output directory when you set
CMAKE_EXPORT_COMPILE_COMMANDS.

Added:
    cfe/trunk/examples/Tooling/
    cfe/trunk/examples/Tooling/CMakeLists.txt
    cfe/trunk/examples/Tooling/ClangCheck.cpp
    cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.cpp
    cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.h
    cfe/trunk/unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp
Modified:
    cfe/trunk/examples/CMakeLists.txt
    cfe/trunk/include/clang/Tooling/Tooling.h
    cfe/trunk/lib/Tooling/CMakeLists.txt
    cfe/trunk/lib/Tooling/Tooling.cpp
    cfe/trunk/unittests/CMakeLists.txt
    cfe/trunk/unittests/Tooling/ToolingTest.cpp

Modified: cfe/trunk/examples/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/examples/CMakeLists.txt?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/examples/CMakeLists.txt (original)
+++ cfe/trunk/examples/CMakeLists.txt Wed Apr 27 11:39:14 2011
@@ -1,3 +1,3 @@
 add_subdirectory(clang-interpreter)
 add_subdirectory(PrintFunctionNames)
-
+add_subdirectory(Tooling)

Added: cfe/trunk/examples/Tooling/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/examples/Tooling/CMakeLists.txt?rev=130306&view=auto
==============================================================================
--- cfe/trunk/examples/Tooling/CMakeLists.txt (added)
+++ cfe/trunk/examples/Tooling/CMakeLists.txt Wed Apr 27 11:39:14 2011
@@ -0,0 +1,6 @@
+set(LLVM_USED_LIBS clangTooling clangBasic)
+
+add_clang_executable(clang-check
+  ClangCheck.cpp
+  )
+

Added: cfe/trunk/examples/Tooling/ClangCheck.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/examples/Tooling/ClangCheck.cpp?rev=130306&view=auto
==============================================================================
--- cfe/trunk/examples/Tooling/ClangCheck.cpp (added)
+++ cfe/trunk/examples/Tooling/ClangCheck.cpp Wed Apr 27 11:39:14 2011
@@ -0,0 +1,108 @@
+//===- examples/Tooling/ClangCheck.cpp - Clang check tool -----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements a clang-check tool that runs the
+//  clang::SyntaxOnlyAction over a number of translation units.
+//
+//  Usage:
+//  clang-check <cmake-output-dir> <file1> <file2> ...
+//
+//  Where <cmake-output-dir> is a CMake build directory in which a file named
+//  compile_commands.json exists (enable -DCMAKE_EXPORT_COMPILE_COMMANDS in
+//  CMake to get this output).
+//
+//  <file1> ... specify the paths of files in the CMake source tree. This  path
+//  is looked up in the compile command database. If the path of a file is
+//  absolute, it needs to point into CMake's source tree. If the path is
+//  relative, the current working directory needs to be in the CMake source
+//  tree and the file must be in a subdirectory of the current working
+//  directory. "./" prefixes in the relative files will be automatically
+//  removed, but the rest of a relative path must be a suffix of a path in
+//  the compile command line database.
+//
+//  For example, to use clang-check on all files in a subtree of the source
+//  tree, use:
+//  /path/to/cmake/sources $ find . -name '*.cpp' \
+//      |xargs clang-check /path/to/cmake/build
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/OwningPtr.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/system_error.h"
+
+/// \brief Returns the absolute path of 'File', by prepending it with
+/// 'BaseDirectory' if 'File' is not absolute. Otherwise returns 'File'.
+/// If 'File' starts with "./", the returned path will not contain the "./".
+/// Otherwise, the returned path will contain the literal path-concatenation of
+/// 'BaseDirectory' and 'File'.
+///
+/// \param File Either an absolute or relative path.
+/// \param BaseDirectory An absolute path.
+///
+/// FIXME: Put this somewhere where it is more generally available.
+static std::string GetAbsolutePath(
+    llvm::StringRef File, llvm::StringRef BaseDirectory) {
+  assert(llvm::sys::path::is_absolute(BaseDirectory));
+  if (llvm::sys::path::is_absolute(File)) {
+    return File;
+  }
+  llvm::StringRef RelativePath(File);
+  if (RelativePath.startswith("./")) {
+    RelativePath = RelativePath.substr(strlen("./"));
+  }
+  llvm::SmallString<1024> AbsolutePath(BaseDirectory);
+  llvm::sys::path::append(AbsolutePath, RelativePath);
+  return AbsolutePath.str();
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    llvm::outs() << "Usage: " << argv[0] << " <cmake-output-dir> "
+                 << "<file1> <file2> ...\n";
+    return 1;
+  }
+  // FIXME: We should pull how to find the database into the Tooling package.
+  llvm::OwningPtr<llvm::MemoryBuffer> JsonDatabase;
+  llvm::SmallString<1024> JsonDatabasePath(argv[1]);
+  llvm::sys::path::append(JsonDatabasePath, "compile_commands.json");
+  llvm::error_code Result =
+      llvm::MemoryBuffer::getFile(JsonDatabasePath, JsonDatabase);
+  if (Result != 0) {
+    llvm::outs() << "Error while opening JSON database: " << Result.message()
+                 << "\n";
+    return 1;
+  }
+  llvm::StringRef BaseDirectory(::getenv("PWD"));
+  for (int I = 2; I < argc; ++I) {
+    llvm::SmallString<1024> File(GetAbsolutePath(argv[I], BaseDirectory));
+    llvm::outs() << "Processing " << File << ".\n";
+    std::string ErrorMessage;
+    clang::tooling::CompileCommand LookupResult =
+        clang::tooling::FindCompileArgsInJsonDatabase(
+            File.str(), JsonDatabase->getBuffer(), ErrorMessage);
+    if (!LookupResult.CommandLine.empty()) {
+      if (!clang::tooling::RunToolWithFlags(
+               new clang::SyntaxOnlyAction,
+               LookupResult.CommandLine.size(),
+               clang::tooling::CommandLineToArgv(
+                   &LookupResult.CommandLine).data())) {
+        llvm::outs() << "Error while processing " << File << ".\n";
+      }
+    } else {
+      llvm::outs() << "Skipping " << File << ". Command line not found.\n";
+    }
+  }
+  return 0;
+}

Modified: cfe/trunk/include/clang/Tooling/Tooling.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Tooling.h?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/include/clang/Tooling/Tooling.h (original)
+++ cfe/trunk/include/clang/Tooling/Tooling.h Wed Apr 27 11:39:14 2011
@@ -16,6 +16,8 @@
 #define LLVM_CLANG_TOOLING_TOOLING_H
 
 #include "llvm/ADT/StringRef.h"
+#include <string>
+#include <vector>
 
 namespace clang {
 
@@ -26,12 +28,53 @@
 /// \brief Runs (and deletes) the tool on 'Code' with the -fsynatx-only flag.
 ///
 /// \param ToolAction The action to run over the code.
-//  \param Code C++ code.
+/// \param Code C++ code.
 ///
 /// \return - True if 'ToolAction' was successfully executed.
 bool RunSyntaxOnlyToolOnCode(
     clang::FrontendAction *ToolAction, llvm::StringRef Code);
 
+/// \brief Runs (and deletes) the tool with the given Clang flags.
+///
+/// \param ToolAction The action to run over the code.
+/// \param Argc The number of elements in Argv.
+/// \param Argv The command line arguments, including the path the binary
+/// was started with (Argv[0]).
+bool RunToolWithFlags(
+    clang::FrontendAction* ToolAction, int Argc, char *Argv[]);
+
+/// \brief Converts a vector<string> into a vector<char*> suitable to pass
+/// to main-style functions taking (int Argc, char *Argv[]).
+std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command);
+
+/// \brief Specifies the working directory and command of a compilation.
+struct CompileCommand {
+  /// \brief The working directory the command was executed from.
+  std::string Directory;
+
+  /// \brief The command line that was executed.
+  std::vector<std::string> CommandLine;
+};
+
+/// \brief Looks up the compile command for 'FileName' in 'JsonDatabase'.
+///
+/// \param FileName The path to an input file for which we want the compile
+/// command line. If the 'JsonDatabase' was created by CMake, this must be
+/// an absolute path inside the CMake source directory which does not have
+/// symlinks resolved.
+///
+/// \param JsonDatabase A JSON formatted list of compile commands. This lookup
+/// command supports only a subset of the JSON standard as written by CMake.
+///
+/// \param ErrorMessage If non-empty, an error occurred and 'ErrorMessage' will
+/// be set to contain the error message. In this case CompileCommand will
+/// contain an empty directory and command line.
+///
+/// \see JsonCompileCommandLineDatabase
+CompileCommand FindCompileArgsInJsonDatabase(
+    llvm::StringRef FileName, llvm::StringRef JsonDatabase,
+    std::string &ErrorMessage);
+
 } // end namespace tooling
 } // end namespace clang
 

Modified: cfe/trunk/lib/Tooling/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/CMakeLists.txt?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/CMakeLists.txt (original)
+++ cfe/trunk/lib/Tooling/CMakeLists.txt Wed Apr 27 11:39:14 2011
@@ -1,5 +1,6 @@
 SET(LLVM_USED_LIBS clangBasic clangFrontend clangAST)
 
 add_clang_library(clangTooling
+  JsonCompileCommandLineDatabase.cpp
   Tooling.cpp
   )

Added: cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.cpp?rev=130306&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.cpp (added)
+++ cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.cpp Wed Apr 27 11:39:14 2011
@@ -0,0 +1,214 @@
+//===--- JsonCompileCommandLineDatabase.cpp - Simple JSON database --------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements reading a compile command line database, as written
+//  out for example by CMake.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JsonCompileCommandLineDatabase.h"
+#include "llvm/ADT/Twine.h"
+
+namespace clang {
+namespace tooling {
+
+namespace {
+
+// A parser for JSON escaped strings of command line arguments with \-escaping
+// for quoted arguments (see the documentation of UnescapeJsonCommandLine(...)).
+class CommandLineArgumentParser {
+ public:
+  CommandLineArgumentParser(llvm::StringRef CommandLine)
+      : Input(CommandLine), Position(Input.begin()-1) {}
+
+  std::vector<std::string> Parse() {
+    bool HasMoreInput = true;
+    while (HasMoreInput && NextNonWhitespace()) {
+      std::string Argument;
+      HasMoreInput = ParseStringInto(Argument);
+      CommandLine.push_back(Argument);
+    }
+    return CommandLine;
+  }
+
+ private:
+  // All private methods return true if there is more input available.
+
+  bool ParseStringInto(std::string &String) {
+    do {
+      if (*Position == '"') {
+        if (!ParseQuotedStringInto(String)) return false;
+      } else {
+        if (!ParseFreeStringInto(String)) return false;
+      }
+    } while (*Position != ' ');
+    return true;
+  }
+
+  bool ParseQuotedStringInto(std::string &String) {
+    if (!Next()) return false;
+    while (*Position != '"') {
+      if (!SkipEscapeCharacter()) return false;
+      String.push_back(*Position);
+      if (!Next()) return false;
+    }
+    return Next();
+  }
+
+  bool ParseFreeStringInto(std::string &String) {
+    do {
+      if (!SkipEscapeCharacter()) return false;
+      String.push_back(*Position);
+      if (!Next()) return false;
+    } while (*Position != ' ' && *Position != '"');
+    return true;
+  }
+
+  bool SkipEscapeCharacter() {
+    if (*Position == '\\') {
+      return Next();
+    }
+    return true;
+  }
+
+  bool NextNonWhitespace() {
+    do {
+      if (!Next()) return false;
+    } while (*Position == ' ');
+    return true;
+  }
+
+  bool Next() {
+    ++Position;
+    if (Position == Input.end()) return false;
+    // Remove the JSON escaping first. This is done unconditionally.
+    if (*Position == '\\') ++Position;
+    return Position != Input.end();
+  }
+
+  const llvm::StringRef Input;
+  llvm::StringRef::iterator Position;
+  std::vector<std::string> CommandLine;
+};
+
+} // end namespace
+
+std::vector<std::string> UnescapeJsonCommandLine(
+    llvm::StringRef JsonEscapedCommandLine) {
+  CommandLineArgumentParser parser(JsonEscapedCommandLine);
+  return parser.Parse();
+}
+
+JsonCompileCommandLineParser::JsonCompileCommandLineParser(
+    const llvm::StringRef Input, CompileCommandHandler *CommandHandler)
+    : Input(Input), Position(Input.begin()-1), CommandHandler(CommandHandler) {}
+
+bool JsonCompileCommandLineParser::Parse() {
+  NextNonWhitespace();
+  return ParseTranslationUnits();
+}
+
+std::string JsonCompileCommandLineParser::GetErrorMessage() const {
+  return ErrorMessage;
+}
+
+bool JsonCompileCommandLineParser::ParseTranslationUnits() {
+  if (!ConsumeOrError('[', "at start of compile command file")) return false;
+  if (!ParseTranslationUnit(/*First=*/true)) return false;
+  while (Consume(',')) {
+    if (!ParseTranslationUnit(/*First=*/false)) return false;
+  }
+  if (!ConsumeOrError(']', "at end of array")) return false;
+  if (CommandHandler != NULL) {
+    CommandHandler->EndTranslationUnits();
+  }
+  return true;
+}
+
+bool JsonCompileCommandLineParser::ParseTranslationUnit(bool First) {
+  if (First) {
+    if (!Consume('{')) return true;
+  } else {
+    if (!ConsumeOrError('{', "at start of object")) return false;
+  }
+  if (!Consume('}')) {
+    if (!ParseObjectKeyValuePairs()) return false;
+    if (!ConsumeOrError('}', "at end of object")) return false;
+  }
+  if (CommandHandler != NULL) {
+    CommandHandler->EndTranslationUnit();
+  }
+  return true;
+}
+
+bool JsonCompileCommandLineParser::ParseObjectKeyValuePairs() {
+  do {
+    llvm::StringRef Key;
+    if (!ParseString(Key)) return false;
+    if (!ConsumeOrError(':', "between name and value")) return false;
+    llvm::StringRef Value;
+    if (!ParseString(Value)) return false;
+    if (CommandHandler != NULL) {
+      CommandHandler->HandleKeyValue(Key, Value);
+    }
+  } while (Consume(','));
+  return true;
+}
+
+bool JsonCompileCommandLineParser::ParseString(llvm::StringRef &String) {
+  if (!ConsumeOrError('"', "at start of string")) return false;
+  llvm::StringRef::iterator First = Position;
+  llvm::StringRef::iterator Last = Position;
+  while (!Consume('"')) {
+    Consume('\\');
+    ++Position;
+    // We need to store Position, as Consume will change Last before leaving
+    // the loop.
+    Last = Position;
+  }
+  String = llvm::StringRef(First, Last - First);
+  return true;
+}
+
+bool JsonCompileCommandLineParser::Consume(char C) {
+  if (Position == Input.end()) return false;
+  if (*Position != C) return false;
+  NextNonWhitespace();
+  return true;
+}
+
+bool JsonCompileCommandLineParser::ConsumeOrError(
+    char C, llvm::StringRef Message) {
+  if (!Consume(C)) {
+    SetExpectError(C, Message);
+    return false;
+  }
+  return true;
+}
+
+void JsonCompileCommandLineParser::SetExpectError(
+    char C, llvm::StringRef Message) {
+  ErrorMessage = (llvm::Twine("'") + llvm::StringRef(&C, 1) +
+                  "' expected " + Message + ".").str();
+}
+
+void JsonCompileCommandLineParser::NextNonWhitespace() {
+  do {
+    ++Position;
+  } while (IsWhitespace());
+}
+
+bool JsonCompileCommandLineParser::IsWhitespace() {
+  if (Position == Input.end()) return false;
+  return (*Position == ' ' || *Position == '\t' ||
+          *Position == '\n' || *Position == '\r');
+}
+
+} // end namespace tooling
+} // end namespace clang

Added: cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.h?rev=130306&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.h (added)
+++ cfe/trunk/lib/Tooling/JsonCompileCommandLineDatabase.h Wed Apr 27 11:39:14 2011
@@ -0,0 +1,107 @@
+//===--- JsonCompileCommandLineDatabase - Simple JSON database --*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements reading a compile command line database, as written
+//  out for example by CMake. It only supports the subset of the JSON standard
+//  that is needed to parse the CMake output.
+//  See http://www.json.org/ for the full standard.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_JSON_COMPILE_COMMAND_LINE_DATABASE_H
+#define LLVM_CLANG_TOOLING_JSON_COMPILE_COMMAND_LINE_DATABASE_H
+
+#include "llvm/ADT/StringRef.h"
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace tooling {
+
+/// \brief Converts a JSON escaped command line to a vector of arguments.
+///
+/// \param JsonEscapedCommandLine The escaped command line as a string. This
+/// is assumed to be escaped as a JSON string (e.g. " and \ are escaped).
+/// In addition, any arguments containing spaces are assumed to be \-escaped
+///
+/// For example, the input (|| denoting non C-escaped strings):
+///   |./call  a  \"b \\\" c \\\\ \"  d|
+/// would yield:
+///   [ |./call|, |a|, |b " c \ |, |d| ].
+std::vector<std::string> UnescapeJsonCommandLine(
+    llvm::StringRef JsonEscapedCommandLine);
+
+/// \brief Interface for users of the JsonCompileCommandLineParser.
+class CompileCommandHandler {
+ public:
+  virtual ~CompileCommandHandler() {};
+
+  /// \brief Called after all translation units are parsed.
+  virtual void EndTranslationUnits() {}
+
+  /// \brief Called at the end of a single translation unit.
+  virtual void EndTranslationUnit() {}
+
+  /// \brief Called for every (Key, Value) pair in a translation unit
+  /// description.
+  virtual void HandleKeyValue(llvm::StringRef Key, llvm::StringRef Value) {}
+};
+
+/// \brief A JSON parser that supports the subset of JSON needed to parse
+/// JSON compile command line databases as written out by CMake.
+///
+/// The supported subset describes a list of compile command lines for
+/// each processed translation unit. The translation units are stored in a
+/// JSON array, where each translation unit is described by a JSON object
+/// containing (Key, Value) pairs for the working directory the compile command
+/// line was executed from, the main C/C++ input file of the translation unit
+/// and the actual compile command line, for example:
+/// [
+///   {
+///     "file":"/file.cpp",
+///     "directory":"/",
+///     "command":"/cc /file.cpp"
+///   }
+/// ]
+class JsonCompileCommandLineParser {
+ public:
+  /// \brief Create a parser on 'Input', calling 'CommandHandler' to handle the
+  /// parsed constructs. 'CommandHandler' may be NULL in order to just check
+  /// the validity of 'Input'.
+  JsonCompileCommandLineParser(const llvm::StringRef Input,
+                               CompileCommandHandler *CommandHandler);
+
+  /// \brief Parses the specified input. Returns true if no parsing errors were
+  /// foudn.
+  bool Parse();
+
+  /// \brief Returns an error message if Parse() returned false previously.
+  std::string GetErrorMessage() const;
+
+ private:
+  bool ParseTranslationUnits();
+  bool ParseTranslationUnit(bool First);
+  bool ParseObjectKeyValuePairs();
+  bool ParseString(llvm::StringRef &String);
+  bool Consume(char C);
+  bool ConsumeOrError(char C, llvm::StringRef Message);
+  void NextNonWhitespace();
+  bool IsWhitespace();
+  void SetExpectError(char C, llvm::StringRef Message);
+
+  const llvm::StringRef Input;
+  llvm::StringRef::iterator Position;
+  std::string ErrorMessage;
+  CompileCommandHandler * const CommandHandler;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_JSON_COMPILE_COMMAND_LINE_DATABASE_H

Modified: cfe/trunk/lib/Tooling/Tooling.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Tooling.cpp?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/Tooling.cpp (original)
+++ cfe/trunk/lib/Tooling/Tooling.cpp Wed Apr 27 11:39:14 2011
@@ -29,14 +29,41 @@
 #include "clang/Frontend/FrontendAction.h"
 #include "clang/Frontend/FrontendDiagnostic.h"
 #include "clang/Frontend/TextDiagnosticPrinter.h"
-
-#include <string>
+#include "JsonCompileCommandLineDatabase.h"
 #include <map>
-#include <vector>
+#include <cstdio>
 
 namespace clang {
 namespace tooling {
 
+namespace {
+
+// Checks that the input conforms to the argv[] convention as in
+// main().  Namely:
+//   - it must contain at least a program path,
+//   - argv[0], ..., and argv[argc - 1] mustn't be NULL, and
+//   - argv[argc] must be NULL.
+void ValidateArgv(int argc, char* argv[]) {
+  if (argc < 1) {
+    fprintf(stderr, "ERROR: argc is %d.  It must be >= 1.\n", argc);
+    abort();
+  }
+
+  for (int i = 0; i < argc; ++i) {
+    if (argv[i] == NULL) {
+      fprintf(stderr, "ERROR: argv[%d] is NULL.\n", i);
+      abort();
+    }
+  }
+
+  if (argv[argc] != NULL) {
+    fprintf(stderr, "ERROR: argv[argc] isn't NULL.\n");
+    abort();
+  }
+}
+
+} // end namespace
+
 // FIXME: This file contains structural duplication with other parts of the
 // code that sets up a compiler to run tools on it, and we should refactor
 // it to be based on the same framework.
@@ -156,6 +183,25 @@
   return Result;
 }
 
+bool RunToolWithFlags(
+    clang::FrontendAction* ToolAction, int Args, char* Argv[]) {
+  ValidateArgv(Args, Argv);
+  const llvm::OwningPtr<clang::Diagnostic> Diagnostics(NewTextDiagnostics());
+  const llvm::OwningPtr<clang::driver::Driver> Driver(
+      NewDriver(Diagnostics.get(), Argv[0]));
+  const llvm::OwningPtr<clang::driver::Compilation> Compilation(
+      Driver->BuildCompilation(llvm::ArrayRef<const char*>(Argv, Args)));
+  const clang::driver::ArgStringList* const CC1Args = GetCC1Arguments(
+      Diagnostics.get(), Compilation.get());
+  if (CC1Args == NULL) {
+    return false;
+  }
+  llvm::OwningPtr<clang::CompilerInvocation> Invocation(
+      NewInvocation(Diagnostics.get(), *CC1Args));
+  return RunInvocation(Argv[0], Compilation.get(), Invocation.take(),
+                       *CC1Args, ToolAction);
+}
+
 /// \brief Runs 'ToolAction' on the code specified by 'FileContents'.
 ///
 /// \param FileContents A mapping from file name to source code. For each
@@ -213,6 +259,64 @@
       FileContents, ToolAction);
 }
 
+namespace {
+
+// A CompileCommandHandler implementation that finds compile commands for a
+// specific input file.
+//
+// FIXME: Implement early exit when JsonCompileCommandLineParser supports it.
+class FindHandler : public clang::tooling::CompileCommandHandler {
+ public:
+  explicit FindHandler(llvm::StringRef File)
+      : FileToMatch(File), FoundMatchingCommand(false) {};
+
+  virtual void EndTranslationUnits() {
+    if (!FoundMatchingCommand && ErrorMessage.empty()) {
+      ErrorMessage = "ERROR: No matching command found.";
+    }
+  }
+
+  virtual void EndTranslationUnit() {
+    if (File == FileToMatch) {
+      FoundMatchingCommand = true;
+      MatchingCommand.Directory = Directory;
+      MatchingCommand.CommandLine = UnescapeJsonCommandLine(Command);
+    }
+  }
+
+  virtual void HandleKeyValue(llvm::StringRef Key, llvm::StringRef Value) {
+    if (Key == "directory") { Directory = Value; }
+    else if (Key == "file") { File = Value; }
+    else if (Key == "command") { Command = Value; }
+    else {
+      ErrorMessage = (llvm::Twine("Unknown key: \"") + Key + "\"").str();
+    }
+  }
+
+  const llvm::StringRef FileToMatch;
+  bool FoundMatchingCommand;
+  CompileCommand MatchingCommand;
+  std::string ErrorMessage;
+
+  llvm::StringRef Directory;
+  llvm::StringRef File;
+  llvm::StringRef Command;
+};
+
+} // end namespace
+
+CompileCommand FindCompileArgsInJsonDatabase(
+    llvm::StringRef FileName, llvm::StringRef JsonDatabase,
+    std::string &ErrorMessage) {
+  FindHandler find_handler(FileName);
+  JsonCompileCommandLineParser parser(JsonDatabase, &find_handler);
+  if (!parser.Parse()) {
+    ErrorMessage = parser.GetErrorMessage();
+    return CompileCommand();
+  }
+  return find_handler.MatchingCommand;
+}
+
 } // end namespace tooling
 } // end namespace clang
 

Modified: cfe/trunk/unittests/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/CMakeLists.txt?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/unittests/CMakeLists.txt (original)
+++ cfe/trunk/unittests/CMakeLists.txt Wed Apr 27 11:39:14 2011
@@ -64,3 +64,8 @@
   Tooling/ToolingTest.cpp
   USED_LIBS gtest gtest_main clangTooling
  )
+
+add_clang_unittest(JsonCompileCommandLineDatabase
+  Tooling/JsonCompileCommandLineDatabaseTest.cpp
+  USED_LIBS gtest gtest_main clangTooling
+ )

Added: cfe/trunk/unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp?rev=130306&view=auto
==============================================================================
--- cfe/trunk/unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp (added)
+++ cfe/trunk/unittests/Tooling/JsonCompileCommandLineDatabaseTest.cpp Wed Apr 27 11:39:14 2011
@@ -0,0 +1,232 @@
+//===- unittest/Tooling/JsonCompileCommandLineDatabaseTest ----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../../lib/Tooling/JsonCompileCommandLineDatabase.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tooling {
+
+TEST(UnescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine("");
+  EXPECT_TRUE(Result.empty());
+}
+
+TEST(UnescapeJsonCommandLine, SplitsOnSpaces) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine("a b c");
+  ASSERT_EQ(3ul, Result.size());
+  EXPECT_EQ("a", Result[0]);
+  EXPECT_EQ("b", Result[1]);
+  EXPECT_EQ("c", Result[2]);
+}
+
+TEST(UnescapeJsonCommandLine, MungesMultipleSpaces) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine("   a   b   ");
+  ASSERT_EQ(2ul, Result.size());
+  EXPECT_EQ("a", Result[0]);
+  EXPECT_EQ("b", Result[1]);
+}
+
+TEST(UnescapeJsonCommandLine, UnescapesBackslashCharacters) {
+  std::vector<std::string> Backslash = UnescapeJsonCommandLine("a\\\\\\\\");
+  ASSERT_EQ(1ul, Backslash.size());
+  EXPECT_EQ("a\\", Backslash[0]);
+  std::vector<std::string> Quote = UnescapeJsonCommandLine("a\\\\\\\"");
+  ASSERT_EQ(1ul, Quote.size());
+  EXPECT_EQ("a\"", Quote[0]);
+}
+
+TEST(UnescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine("\\\"  a  b  \\\"");
+  ASSERT_EQ(1ul, Result.size());
+  EXPECT_EQ("  a  b  ", Result[0]);
+}
+
+TEST(UnescapeJsonCommandLine, AllowsMultipleQuotedArguments) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine(
+      "  \\\" a \\\"  \\\" b \\\"  ");
+  ASSERT_EQ(2ul, Result.size());
+  EXPECT_EQ(" a ", Result[0]);
+  EXPECT_EQ(" b ", Result[1]);
+}
+
+TEST(UnescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine(
+      "\\\"\\\"\\\"\\\"");
+  ASSERT_EQ(1ul, Result.size());
+  EXPECT_TRUE(Result[0].empty()) << Result[0];
+}
+
+TEST(UnescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine(
+      "\\\"\\\\\\\"\\\"");
+  ASSERT_EQ(1ul, Result.size());
+  EXPECT_EQ("\"", Result[0]);
+}
+
+TEST(UnescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {
+  std::vector<std::string> Result = UnescapeJsonCommandLine(
+      "  \\\\\\\"  \\\"a \\\\\\\" b \\\"     \\\"and\\\\\\\\c\\\"   \\\\\\\"");
+  ASSERT_EQ(4ul, Result.size());
+  EXPECT_EQ("\"", Result[0]);
+  EXPECT_EQ("a \" b ", Result[1]);
+  EXPECT_EQ("and\\c", Result[2]);
+  EXPECT_EQ("\"", Result[3]);
+}
+
+TEST(UnescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {
+  std::vector<std::string> QuotedNoSpaces = UnescapeJsonCommandLine(
+      "\\\"a\\\"\\\"b\\\"");
+  ASSERT_EQ(1ul, QuotedNoSpaces.size());
+  EXPECT_EQ("ab", QuotedNoSpaces[0]);
+
+  std::vector<std::string> MixedNoSpaces = UnescapeJsonCommandLine(
+      "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
+  ASSERT_EQ(1ul, MixedNoSpaces.size());
+  EXPECT_EQ("abcdefg", MixedNoSpaces[0]);
+}
+
+TEST(JsonCompileCommandLineParser, FailsOnEmptyString) {
+  JsonCompileCommandLineParser Parser("", NULL);
+  EXPECT_FALSE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, DoesNotReadAfterInput) {
+  JsonCompileCommandLineParser Parser(llvm::StringRef(NULL, 0), NULL);
+  EXPECT_FALSE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesEmptyArray) {
+  JsonCompileCommandLineParser Parser("[]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsIfNotClosingArray) {
+  JsonCompileCommandLineParser JustOpening("[", NULL);
+  EXPECT_FALSE(JustOpening.Parse()) << JustOpening.GetErrorMessage();
+  JsonCompileCommandLineParser WithSpaces("  [  ", NULL);
+  EXPECT_FALSE(WithSpaces.Parse()) << WithSpaces.GetErrorMessage();
+  JsonCompileCommandLineParser WithGarbage("  [x", NULL);
+  EXPECT_FALSE(WithGarbage.Parse()) << WithGarbage.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesEmptyArrayWithWhitespace) {
+  JsonCompileCommandLineParser Spaces("   [   ]   ", NULL);
+  EXPECT_TRUE(Spaces.Parse()) << Spaces.GetErrorMessage();
+  JsonCompileCommandLineParser AllWhites("\t\r\n[\t\n \t\r ]\t\r \n\n", NULL);
+  EXPECT_TRUE(AllWhites.Parse()) << AllWhites.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsIfNotStartingArray) {
+  JsonCompileCommandLineParser ObjectStart("{", NULL);
+  EXPECT_FALSE(ObjectStart.Parse()) << ObjectStart.GetErrorMessage();
+  // We don't implement a full JSON parser, and thus parse only a subset
+  // of valid JSON.
+  JsonCompileCommandLineParser Object("{}", NULL);
+  EXPECT_FALSE(Object.Parse()) << Object.GetErrorMessage();
+  JsonCompileCommandLineParser Character("x", NULL);
+  EXPECT_FALSE(Character.Parse()) << Character.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesEmptyObject) {
+  JsonCompileCommandLineParser Parser("[{}]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesObject) {
+  JsonCompileCommandLineParser Parser("[{\"a\":\"/b\"}]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesMultipleKeyValuePairsInObject) {
+  JsonCompileCommandLineParser Parser(
+      "[{\"a\":\"/b\",\"c\":\"d\",\"e\":\"f\"}]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsIfNotClosingObject) {
+  JsonCompileCommandLineParser MissingCloseOnEmpty("[{]", NULL);
+  EXPECT_FALSE(MissingCloseOnEmpty.Parse())
+      << MissingCloseOnEmpty.GetErrorMessage();
+  JsonCompileCommandLineParser MissingCloseAfterPair("[{\"a\":\"b\"]", NULL);
+  EXPECT_FALSE(MissingCloseAfterPair.Parse())
+      << MissingCloseAfterPair.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsIfMissingColon) {
+  JsonCompileCommandLineParser StringString("[{\"a\"\"/b\"}]", NULL);
+  EXPECT_FALSE(StringString.Parse()) << StringString.GetErrorMessage();
+  JsonCompileCommandLineParser StringSpaceString("[{\"a\" \"b\"}]", NULL);
+  EXPECT_FALSE(StringSpaceString.Parse())
+      << StringSpaceString.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsOnMissingQuote) {
+  JsonCompileCommandLineParser OpenQuote("[{a\":\"b\"}]", NULL);
+  EXPECT_FALSE(OpenQuote.Parse()) << OpenQuote.GetErrorMessage();
+  JsonCompileCommandLineParser CloseQuote("[{\"a\":\"b}]", NULL);
+  EXPECT_FALSE(CloseQuote.Parse()) << CloseQuote.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesEscapedQuotes) {
+  JsonCompileCommandLineParser Parser(
+      "[{\"a\":\"\\\"b\\\"  \\\" \\\"\"}]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesEmptyString) {
+  JsonCompileCommandLineParser Parser("[{\"a\":\"\"}]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsOnMissingString) {
+  JsonCompileCommandLineParser MissingValue("[{\"a\":}]", NULL);
+  EXPECT_FALSE(MissingValue.Parse()) << MissingValue.GetErrorMessage();
+  JsonCompileCommandLineParser MissingKey("[{:\"b\"}]", NULL);
+  EXPECT_FALSE(MissingKey.Parse()) << MissingKey.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesMultipleObjects) {
+  JsonCompileCommandLineParser Parser(
+      "["
+      " { \"a\" : \"b\" },"
+      " { \"a\" : \"b\" },"
+      " { \"a\" : \"b\" }"
+      "]", NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsOnMissingComma) {
+  JsonCompileCommandLineParser Parser(
+      "["
+      " { \"a\" : \"b\" }"
+      " { \"a\" : \"b\" }"
+      "]", NULL);
+  EXPECT_FALSE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, FailsOnSuperfluousComma) {
+  JsonCompileCommandLineParser Parser(
+      "[ { \"a\" : \"b\" }, ]", NULL);
+  EXPECT_FALSE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+TEST(JsonCompileCommandLineParser, ParsesSpacesInBetweenTokens) {
+  JsonCompileCommandLineParser Parser(
+      " \t \n\n \r [ \t \n\n \r"
+      " \t \n\n \r { \t \n\n \r\"a\"\t \n\n \r :"
+      " \t \n\n \r \"b\"\t \n\n \r } \t \n\n \r,\t \n\n \r"
+      " \t \n\n \r { \t \n\n \r\"a\"\t \n\n \r :"
+      " \t \n\n \r \"b\"\t \n\n \r } \t \n\n \r]\t \n\n \r",
+      NULL);
+  EXPECT_TRUE(Parser.Parse()) << Parser.GetErrorMessage();
+}
+
+} // end namespace tooling
+} // end namespace clang

Modified: cfe/trunk/unittests/Tooling/ToolingTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/ToolingTest.cpp?rev=130306&r1=130305&r2=130306&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/ToolingTest.cpp (original)
+++ cfe/trunk/unittests/Tooling/ToolingTest.cpp Wed Apr 27 11:39:14 2011
@@ -7,6 +7,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "llvm/ADT/Twine.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclGroup.h"
@@ -86,6 +87,89 @@
   EXPECT_FALSE(FoundClassDeclX);
 }
 
+TEST(FindCompileArgsInJsonDatabase, FindsNothingIfEmpty) {
+  std::string ErrorMessage;
+  CompileCommand NotFound = FindCompileArgsInJsonDatabase(
+      "a-file.cpp", "", ErrorMessage);
+  EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
+  EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
+}
+
+TEST(FindCompileArgsInJsonDatabase, ReadsSingleEntry) {
+  llvm::StringRef Directory("/some/directory");
+  llvm::StringRef FileName("/path/to/a-file.cpp");
+  llvm::StringRef Command("/path/to/compiler and some arguments");
+  std::string ErrorMessage;
+  CompileCommand FoundCommand = FindCompileArgsInJsonDatabase(
+      FileName,
+      (llvm::Twine("[{\"directory\":\"") + Directory + "\"," +
+                     "\"command\":\"" + Command + "\","
+                     "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage);
+  EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
+  ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage;
+  EXPECT_EQ("/path/to/compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
+  EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage;
+  EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage;
+  EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage;
+
+  CompileCommand NotFound = FindCompileArgsInJsonDatabase(
+      "a-file.cpp",
+      (llvm::Twine("[{\"directory\":\"") + Directory + "\"," +
+                     "\"command\":\"" + Command + "\","
+                     "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage);
+  EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
+  EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
+}
+
+TEST(FindCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
+  llvm::StringRef Directory("/some/directory");
+  llvm::StringRef FileName("/path/to/a-file.cpp");
+  llvm::StringRef Command("\\\"/path to compiler\\\" \\\"and an argument\\\"");
+  std::string ErrorMessage;
+  CompileCommand FoundCommand = FindCompileArgsInJsonDatabase(
+      FileName,
+      (llvm::Twine("[{\"directory\":\"") + Directory + "\"," +
+                     "\"command\":\"" + Command + "\","
+                     "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage);
+  ASSERT_EQ(2u, FoundCommand.CommandLine.size());
+  EXPECT_EQ("/path to compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
+  EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage;
+}
+
+TEST(FindCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) {
+  llvm::StringRef Directory("/some directory / with spaces");
+  llvm::StringRef FileName("/path/to/a-file.cpp");
+  llvm::StringRef Command("a command");
+  std::string ErrorMessage;
+  CompileCommand FoundCommand = FindCompileArgsInJsonDatabase(
+      FileName,
+      (llvm::Twine("[{\"directory\":\"") + Directory + "\"," +
+                     "\"command\":\"" + Command + "\","
+                     "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage);
+  EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
+}
+
+TEST(FindCompileArgsInJsonDatabase, FindsEntry) {
+  llvm::StringRef Directory("directory");
+  llvm::StringRef FileName("file");
+  llvm::StringRef Command("command");
+  std::string JsonDatabase = "[";
+  for (int I = 0; I < 10; ++I) {
+    if (I > 0) JsonDatabase += ",";
+    JsonDatabase += (llvm::Twine(
+        "{\"directory\":\"") + Directory + llvm::Twine(I) + "\"," +
+         "\"command\":\"" + Command + llvm::Twine(I) + "\","
+         "\"file\":\"" + FileName + llvm::Twine(I) + "\"}").str();
+  }
+  JsonDatabase += "]";
+  std::string ErrorMessage;
+  CompileCommand FoundCommand = FindCompileArgsInJsonDatabase(
+      "file4", JsonDatabase, ErrorMessage);
+  EXPECT_EQ("directory4", FoundCommand.Directory) << ErrorMessage;
+  ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage;
+  EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage;
+}
+
 } // end namespace tooling
 } // end namespace clang
 





More information about the cfe-commits mailing list