diff options
| author | Manuel Klimek <klimek@google.com> | 2011-04-27 16:39:14 +0000 |
|---|---|---|
| committer | Manuel Klimek <klimek@google.com> | 2011-04-27 16:39:14 +0000 |
| commit | 6825eebcd6e420e6d6415efefd6bfe75836ebe1e (patch) | |
| tree | 5df643a09ca35c4d6ef0051d7ad0fb13f270c2b2 /clang/lib/Tooling | |
| parent | 27c0c9bb87cd535c684b00c1617e344067351dd2 (diff) | |
| download | bcm5719-llvm-6825eebcd6e420e6d6415efefd6bfe75836ebe1e.tar.gz bcm5719-llvm-6825eebcd6e420e6d6415efefd6bfe75836ebe1e.zip | |
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.
llvm-svn: 130306
Diffstat (limited to 'clang/lib/Tooling')
| -rw-r--r-- | clang/lib/Tooling/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | clang/lib/Tooling/JsonCompileCommandLineDatabase.cpp | 214 | ||||
| -rw-r--r-- | clang/lib/Tooling/JsonCompileCommandLineDatabase.h | 107 | ||||
| -rw-r--r-- | clang/lib/Tooling/Tooling.cpp | 110 |
4 files changed, 429 insertions, 3 deletions
diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt index 6736d6537d3..f52cf6c8917 100644 --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -1,5 +1,6 @@ SET(LLVM_USED_LIBS clangBasic clangFrontend clangAST) add_clang_library(clangTooling + JsonCompileCommandLineDatabase.cpp Tooling.cpp ) diff --git a/clang/lib/Tooling/JsonCompileCommandLineDatabase.cpp b/clang/lib/Tooling/JsonCompileCommandLineDatabase.cpp new file mode 100644 index 00000000000..7f027cfbead --- /dev/null +++ b/clang/lib/Tooling/JsonCompileCommandLineDatabase.cpp @@ -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 diff --git a/clang/lib/Tooling/JsonCompileCommandLineDatabase.h b/clang/lib/Tooling/JsonCompileCommandLineDatabase.h new file mode 100644 index 00000000000..dcd1e4bbbb5 --- /dev/null +++ b/clang/lib/Tooling/JsonCompileCommandLineDatabase.h @@ -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 diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp index a5bd1acf0e2..8fc76b6b754 100644 --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -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 @@ std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command) { 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 @@ bool RunSyntaxOnlyToolOnCode( 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 |

