Adds a tooling library.

Provides an API to run clang tools (FrontendActions) as standalone tools,
or repeatedly in-memory in a process. This is useful for unit-testing,
map-reduce style applications, source transformation daemons or command line
tools.

The ability to run over multiple translation units with different command
line arguments enables building up refactoring tools that need to apply
transformations across translation unit boundaries.

See tools/clang-check/ClangCheck.cpp for an example.

llvm-svn: 154008
This commit is contained in:
Manuel Klimek 2012-04-04 12:07:46 +00:00
parent b21b865fe8
commit 47c245a537
19 changed files with 1382 additions and 3 deletions

View File

@ -0,0 +1,164 @@
//===--- CompilationDatabase.h - --------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file provides an interface and multiple implementations for
// CompilationDatabases.
//
// While C++ refactoring and analysis tools are not compilers, and thus
// don't run as part of the build system, they need the exact information
// of a build in order to be able to correctly understand the C++ code of
// the project. This information is provided via the CompilationDatabase
// interface.
//
// To create a CompilationDatabase from a build directory one can call
// CompilationDatabase::loadFromDirectory(), which deduces the correct
// compilation database from the root of the build tree.
//
// See the concrete subclasses of CompilationDatabase for currently supported
// formats.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLING_COMPILATION_DATABASE_H
#define LLVM_CLANG_TOOLING_COMPILATION_DATABASE_H
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/OwningPtr.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include <string>
#include <vector>
namespace llvm {
class MemoryBuffer;
} // end namespace llvm
namespace clang {
namespace tooling {
/// \brief Specifies the working directory and command of a compilation.
struct CompileCommand {
CompileCommand() {}
CompileCommand(StringRef Directory, ArrayRef<std::string> CommandLine)
: Directory(Directory), CommandLine(CommandLine) {}
/// \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 Interface for compilation databases.
///
/// A compilation database allows the user to retrieve all compile command lines
/// that a specified file is compiled with in a project.
/// The retrieved compile command lines can be used to run clang tools over
/// a subset of the files in a project.
class CompilationDatabase {
public:
virtual ~CompilationDatabase();
/// \brief Loads a compilation database from a build directory.
///
/// Looks at the specified 'BuildDirectory' and creates a compilation database
/// that allows to query compile commands for source files in the
/// corresponding source tree.
///
/// Returns NULL and sets ErrorMessage if we were not able to build up a
/// compilation database for the build directory.
///
/// FIXME: Currently only supports JSON compilation databases, which
/// are named 'compile_commands.json' in the given directory. Extend this
/// for other build types (like ninja build files).
static CompilationDatabase *loadFromDirectory(StringRef BuildDirectory,
std::string &ErrorMessage);
/// \brief Returns all compile commands in which the specified file was
/// compiled.
///
/// This includes compile comamnds that span multiple source files.
/// For example, consider a project with the following compilations:
/// $ clang++ -o test a.cc b.cc t.cc
/// $ clang++ -o production a.cc b.cc -DPRODUCTION
/// A compilation database representing the project would return both command
/// lines for a.cc and b.cc and only the first command line for t.cc.
virtual std::vector<CompileCommand> getCompileCommands(
StringRef FilePath) const = 0;
};
/// \brief A JSON based compilation database.
///
/// JSON compilation database files must contain a list of JSON objects which
/// provide the command lines in the attributes 'directory', 'command' and
/// 'file':
/// [
/// { "directory": "<working directory of the compile>",
/// "command": "<compile command line>",
/// "file": "<path to source file>"
/// },
/// ...
/// ]
/// Each object entry defines one compile action. The specified file is
/// considered to be the main source file for the translation unit.
///
/// JSON compilation databases can for example be generated in CMake projects
/// by setting the flag -DCMAKE_EXPORT_COMPILE_COMMANDS.
class JSONCompilationDatabase : public CompilationDatabase {
public:
/// \brief Loads a JSON compilation database from the specified file.
///
/// Returns NULL and sets ErrorMessage if the database could not be
/// loaded from the given file.
static JSONCompilationDatabase *loadFromFile(StringRef FilePath,
std::string &ErrorMessage);
/// \brief Loads a JSON compilation database from a data buffer.
///
/// Returns NULL and sets ErrorMessage if the database could not be loaded.
static JSONCompilationDatabase *loadFromBuffer(StringRef DatabaseString,
std::string &ErrorMessage);
/// \brief Returns all compile comamnds in which the specified file was
/// compiled.
///
/// FIXME: Currently FilePath must be an absolute path inside the
/// source directory which does not have symlinks resolved.
virtual std::vector<CompileCommand> getCompileCommands(
StringRef FilePath) const;
private:
/// \brief Constructs a JSON compilation database on a memory buffer.
JSONCompilationDatabase(llvm::MemoryBuffer *Database)
: Database(Database) {}
/// \brief Parses the database file and creates the index.
///
/// Returns whether parsing succeeded. Sets ErrorMessage if parsing
/// failed.
bool parse(std::string &ErrorMessage);
// Tuple (directory, commandline) where 'commandline' is a JSON escaped bash
// escaped command line.
typedef std::pair<StringRef, StringRef> CompileCommandRef;
// Maps file paths to the compile command lines for that file.
llvm::StringMap< std::vector<CompileCommandRef> > IndexByFile;
llvm::OwningPtr<llvm::MemoryBuffer> Database;
};
} // end namespace tooling
} // end namespace clang
#endif // LLVM_CLANG_TOOLING_COMPILATION_DATABASE_H

View File

@ -0,0 +1,213 @@
//===--- Tooling.h - Framework for standalone Clang tools -------*- 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 functions to run clang tools standalone instead
// of running them as a plugin.
//
// A ClangTool is initialized with a CompilationDatabase and a set of files
// to run over. The tool will then run a user-specified FrontendAction over
// all TUs in which the given files are compiled.
//
// It is also possible to run a FrontendAction over a snippet of code by
// calling runSyntaxOnlyToolOnCode, which is useful for unit testing.
//
// Applications that need more fine grained control over how to run
// multiple FrontendActions over code can use ToolInvocation.
//
// Example tools:
// - running clang -fsyntax-only over source code from an editor to get
// fast syntax checks
// - running match/replace tools over C++ code
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLING_TOOLING_H
#define LLVM_CLANG_TOOLING_TOOLING_H
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/Twine.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LLVM.h"
#include "clang/Driver/Util.h"
#include <string>
#include <vector>
namespace clang {
namespace driver {
class Compilation;
} // end namespace driver
class CompilerInvocation;
class SourceManager;
class FrontendAction;
namespace tooling {
class CompilationDatabase;
/// \brief Interface to generate clang::FrontendActions.
class FrontendActionFactory {
public:
virtual ~FrontendActionFactory();
/// \brief Returns a new clang::FrontendAction.
///
/// The caller takes ownership of the returned action.
virtual clang::FrontendAction *create() = 0;
};
/// \brief Returns a new FrontendActionFactory for a given type.
///
/// T must extend clang::FrontendAction.
///
/// Example:
/// FrontendActionFactory *Factory =
/// newFrontendActionFactory<clang::SyntaxOnlyAction>();
template <typename T>
FrontendActionFactory *newFrontendActionFactory();
/// \brief Returns a new FrontendActionFactory for any type that provides an
/// implementation of newFrontendAction().
///
/// FactoryT must implement: FrontendAction *newFrontendAction().
///
/// Example:
/// struct ProvidesFrontendActions {
/// FrontendAction *newFrontendAction();
/// } Factory;
/// FrontendActionFactory *FactoryAdapter =
/// newFrontendActionFactory(&Factory);
template <typename FactoryT>
FrontendActionFactory *newFrontendActionFactory(FactoryT *ActionFactory);
/// \brief Runs (and deletes) the tool on 'Code' with the -fsyntax-only flag.
///
/// \param ToolAction The action to run over the code.
/// \param Code C++ code.
/// \param FileName The file name which 'Code' will be mapped as.
///
/// \return - True if 'ToolAction' was successfully executed.
bool runToolOnCode(clang::FrontendAction *ToolAction, const Twine &Code,
const Twine &FileName = "input.cc");
/// \brief Utility to run a FrontendAction in a single clang invocation.
class ToolInvocation {
public:
/// \brief Create a tool invocation.
///
/// \param CommandLine The command line arguments to clang.
/// \param ToolAction The action to be executed. Class takes ownership.
/// \param Files The FileManager used for the execution. Class does not take
/// ownership.
ToolInvocation(ArrayRef<std::string> CommandLine, FrontendAction *ToolAction,
FileManager *Files);
/// \brief Map a virtual file to be used while running the tool.
///
/// \param FilePath The path at which the content will be mapped.
/// \param Content A null terminated buffer of the file's content.
void mapVirtualFile(StringRef FilePath, StringRef Content);
/// \brief Run the clang invocation.
///
/// \returns True if there were no errors during execution.
bool run();
private:
void addFileMappingsTo(SourceManager &SourceManager);
bool runInvocation(const char *BinaryName,
clang::driver::Compilation *Compilation,
clang::CompilerInvocation *Invocation,
const clang::driver::ArgStringList &CC1Args,
clang::FrontendAction *ToolAction);
std::vector<std::string> CommandLine;
llvm::OwningPtr<FrontendAction> ToolAction;
FileManager *Files;
// Maps <file name> -> <file content>.
llvm::StringMap<StringRef> MappedFileContents;
};
/// \brief Utility to run a FrontendAction over a set of files.
///
/// This class is written to be usable for command line utilities.
class ClangTool {
public:
/// \brief Constructs a clang tool to run over a list of files.
///
/// \param Compilations The CompilationDatabase which contains the compile
/// command lines for the given source paths.
/// \param SourcePaths The source files to run over. If a source files is
/// not found in Compilations, it is skipped.
ClangTool(const CompilationDatabase &Compilations,
ArrayRef<std::string> SourcePaths);
/// \brief Map a virtual file to be used while running the tool.
///
/// \param FilePath The path at which the content will be mapped.
/// \param Content A null terminated buffer of the file's content.
void mapVirtualFile(StringRef FilePath, StringRef Content);
/// Runs a frontend action over all files specified in the command line.
///
/// \param ActionFactory Factory generating the frontend actions. The function
/// takes ownership of this parameter. A new action is generated for every
/// processed translation unit.
int run(FrontendActionFactory *ActionFactory);
/// \brief Returns the file manager used in the tool.
///
/// The file manager is shared between all translation units.
FileManager &getFiles() { return Files; }
private:
// We store command lines as pair (file name, command line).
typedef std::pair< std::string, std::vector<std::string> > CommandLine;
std::vector<CommandLine> CommandLines;
FileManager Files;
// Contains a list of pairs (<file name>, <file content>).
std::vector< std::pair<StringRef, StringRef> > MappedFileContents;
};
template <typename T>
FrontendActionFactory *newFrontendActionFactory() {
class SimpleFrontendActionFactory : public FrontendActionFactory {
public:
virtual clang::FrontendAction *create() { return new T; }
};
return new SimpleFrontendActionFactory;
}
template <typename FactoryT>
FrontendActionFactory *newFrontendActionFactory(FactoryT *ActionFactory) {
class FrontendActionFactoryAdapter : public FrontendActionFactory {
public:
explicit FrontendActionFactoryAdapter(FactoryT *ActionFactory)
: ActionFactory(ActionFactory) {}
virtual clang::FrontendAction *create() {
return ActionFactory->newFrontendAction();
}
private:
FactoryT *ActionFactory;
};
return new FrontendActionFactoryAdapter(ActionFactory);
}
} // end namespace tooling
} // end namespace clang
#endif // LLVM_CLANG_TOOLING_TOOLING_H

View File

@ -13,5 +13,6 @@ add_subdirectory(Driver)
add_subdirectory(Serialization) add_subdirectory(Serialization)
add_subdirectory(Frontend) add_subdirectory(Frontend)
add_subdirectory(FrontendTool) add_subdirectory(FrontendTool)
add_subdirectory(Tooling)
add_subdirectory(Index) add_subdirectory(Index)
add_subdirectory(StaticAnalyzer) add_subdirectory(StaticAnalyzer)

View File

@ -10,7 +10,7 @@ CLANG_LEVEL := ..
PARALLEL_DIRS = Headers Basic Lex Parse AST Sema CodeGen Analysis \ PARALLEL_DIRS = Headers Basic Lex Parse AST Sema CodeGen Analysis \
StaticAnalyzer Edit Rewrite ARCMigrate Serialization Frontend \ StaticAnalyzer Edit Rewrite ARCMigrate Serialization Frontend \
FrontendTool Index Driver FrontendTool Tooling Index Driver
include $(CLANG_LEVEL)/Makefile include $(CLANG_LEVEL)/Makefile

View File

@ -0,0 +1,7 @@
set(LLVM_LINK_COMPONENTS support)
SET(LLVM_USED_LIBS clangBasic clangFrontend clangAST)
add_clang_library(clangTooling
CompilationDatabase.cpp
Tooling.cpp
)

View File

@ -0,0 +1,230 @@
//===--- CompilationDatabase.cpp - ----------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains multiple implementations for CompilationDatabases.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/JSONParser.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/system_error.h"
namespace clang {
namespace tooling {
namespace {
/// \brief A parser for JSON escaped strings of command line arguments.
///
/// Assumes \-escaping for quoted arguments (see the documentation of
/// unescapeJSONCommandLine(...)).
class CommandLineArgumentParser {
public:
CommandLineArgumentParser(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 StringRef Input;
StringRef::iterator Position;
std::vector<std::string> CommandLine;
};
std::vector<std::string> unescapeJSONCommandLine(
StringRef JSONEscapedCommandLine) {
CommandLineArgumentParser parser(JSONEscapedCommandLine);
return parser.parse();
}
} // end namespace
CompilationDatabase::~CompilationDatabase() {}
CompilationDatabase *
CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,
std::string &ErrorMessage) {
llvm::SmallString<1024> JSONDatabasePath(BuildDirectory);
llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
llvm::OwningPtr<CompilationDatabase> Database(
JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage));
if (!Database) {
return NULL;
}
return Database.take();
}
JSONCompilationDatabase *
JSONCompilationDatabase::loadFromFile(StringRef FilePath,
std::string &ErrorMessage) {
llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer;
llvm::error_code Result =
llvm::MemoryBuffer::getFile(FilePath, DatabaseBuffer);
if (Result != 0) {
ErrorMessage = "Error while opening JSON database: " + Result.message();
return NULL;
}
llvm::OwningPtr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(DatabaseBuffer.take()));
if (!Database->parse(ErrorMessage))
return NULL;
return Database.take();
}
JSONCompilationDatabase *
JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
std::string &ErrorMessage) {
llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer(
llvm::MemoryBuffer::getMemBuffer(DatabaseString));
llvm::OwningPtr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(DatabaseBuffer.take()));
if (!Database->parse(ErrorMessage))
return NULL;
return Database.take();
}
std::vector<CompileCommand>
JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const {
llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
CommandsRefI = IndexByFile.find(FilePath);
if (CommandsRefI == IndexByFile.end())
return std::vector<CompileCommand>();
const std::vector<CompileCommandRef> &CommandsRef = CommandsRefI->getValue();
std::vector<CompileCommand> Commands;
for (int I = 0, E = CommandsRef.size(); I != E; ++I) {
Commands.push_back(CompileCommand(
// FIXME: Escape correctly:
CommandsRef[I].first,
unescapeJSONCommandLine(CommandsRef[I].second)));
}
return Commands;
}
bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
llvm::SourceMgr SM;
llvm::JSONParser Parser(Database->getBuffer(), &SM);
llvm::JSONValue *Root = Parser.parseRoot();
if (Root == NULL) {
ErrorMessage = "Error while parsing JSON.";
return false;
}
llvm::JSONArray *Array = dyn_cast<llvm::JSONArray>(Root);
if (Array == NULL) {
ErrorMessage = "Expected array.";
return false;
}
for (llvm::JSONArray::const_iterator AI = Array->begin(), AE = Array->end();
AI != AE; ++AI) {
const llvm::JSONObject *Object = dyn_cast<llvm::JSONObject>(*AI);
if (Object == NULL) {
ErrorMessage = "Expected object.";
return false;
}
StringRef EntryDirectory;
StringRef EntryFile;
StringRef EntryCommand;
for (llvm::JSONObject::const_iterator KVI = Object->begin(),
KVE = Object->end();
KVI != KVE; ++KVI) {
const llvm::JSONValue *Value = (*KVI)->Value;
if (Value == NULL) {
ErrorMessage = "Expected value.";
return false;
}
const llvm::JSONString *ValueString =
dyn_cast<llvm::JSONString>(Value);
if (ValueString == NULL) {
ErrorMessage = "Expected string as value.";
return false;
}
if ((*KVI)->Key->getRawText() == "directory") {
EntryDirectory = ValueString->getRawText();
} else if ((*KVI)->Key->getRawText() == "file") {
EntryFile = ValueString->getRawText();
} else if ((*KVI)->Key->getRawText() == "command") {
EntryCommand = ValueString->getRawText();
} else {
ErrorMessage = (Twine("Unknown key: \"") +
(*KVI)->Key->getRawText() + "\"").str();
return false;
}
}
IndexByFile[EntryFile].push_back(
CompileCommandRef(EntryDirectory, EntryCommand));
}
return true;
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,13 @@
##===- clang/lib/Tooling/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 := ../..
LIBRARYNAME := clangTooling
include $(CLANG_LEVEL)/Makefile

View File

@ -0,0 +1,291 @@
//===--- Tooling.cpp - Running clang standalone tools ---------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements functions to run clang tools standalone instead
// of running them as a plugin.
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Tool.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
namespace clang {
namespace tooling {
FrontendActionFactory::~FrontendActionFactory() {}
// 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.
/// \brief Builds a clang driver initialized for running clang tools.
static clang::driver::Driver *newDriver(clang::DiagnosticsEngine *Diagnostics,
const char *BinaryName) {
const std::string DefaultOutputName = "a.out";
clang::driver::Driver *CompilerDriver = new clang::driver::Driver(
BinaryName, llvm::sys::getDefaultTargetTriple(),
DefaultOutputName, false, *Diagnostics);
CompilerDriver->setTitle("clang_based_tool");
return CompilerDriver;
}
/// \brief Retrieves the clang CC1 specific flags out of the compilation's jobs.
///
/// Returns NULL on error.
static const clang::driver::ArgStringList *getCC1Arguments(
clang::DiagnosticsEngine *Diagnostics,
clang::driver::Compilation *Compilation) {
// We expect to get back exactly one Command job, if we didn't something
// failed. Extract that job from the Compilation.
const clang::driver::JobList &Jobs = Compilation->getJobs();
if (Jobs.size() != 1 || !isa<clang::driver::Command>(*Jobs.begin())) {
llvm::SmallString<256> error_msg;
llvm::raw_svector_ostream error_stream(error_msg);
Compilation->PrintJob(error_stream, Compilation->getJobs(), "; ", true);
Diagnostics->Report(clang::diag::err_fe_expected_compiler_job)
<< error_stream.str();
return NULL;
}
// The one job we find should be to invoke clang again.
const clang::driver::Command *Cmd =
cast<clang::driver::Command>(*Jobs.begin());
if (StringRef(Cmd->getCreator().getName()) != "clang") {
Diagnostics->Report(clang::diag::err_fe_expected_clang_command);
return NULL;
}
return &Cmd->getArguments();
}
/// \brief Returns a clang build invocation initialized from the CC1 flags.
static clang::CompilerInvocation *newInvocation(
clang::DiagnosticsEngine *Diagnostics,
const clang::driver::ArgStringList &CC1Args) {
assert(!CC1Args.empty() && "Must at least contain the program name!");
clang::CompilerInvocation *Invocation = new clang::CompilerInvocation;
clang::CompilerInvocation::CreateFromArgs(
*Invocation, CC1Args.data() + 1, CC1Args.data() + CC1Args.size(),
*Diagnostics);
Invocation->getFrontendOpts().DisableFree = false;
return Invocation;
}
bool runToolOnCode(clang::FrontendAction *ToolAction, const Twine &Code,
const Twine &FileName) {
SmallString<16> FileNameStorage;
StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage);
const char *const CommandLine[] = {
"clang-tool", "-fsyntax-only", FileNameRef.data()
};
FileManager Files((FileSystemOptions()));
ToolInvocation Invocation(
std::vector<std::string>(
CommandLine,
CommandLine + llvm::array_lengthof(CommandLine)),
ToolAction, &Files);
SmallString<1024> CodeStorage;
Invocation.mapVirtualFile(FileNameRef,
Code.toNullTerminatedStringRef(CodeStorage));
return Invocation.run();
}
/// \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.
static std::string getAbsolutePath(
StringRef File, StringRef BaseDirectory) {
assert(llvm::sys::path::is_absolute(BaseDirectory));
if (llvm::sys::path::is_absolute(File)) {
return File;
}
StringRef RelativePath(File);
if (RelativePath.startswith("./")) {
RelativePath = RelativePath.substr(strlen("./"));
}
llvm::SmallString<1024> AbsolutePath(BaseDirectory);
llvm::sys::path::append(AbsolutePath, RelativePath);
return AbsolutePath.str();
}
ToolInvocation::ToolInvocation(
ArrayRef<std::string> CommandLine, FrontendAction *ToolAction,
FileManager *Files)
: CommandLine(CommandLine.vec()), ToolAction(ToolAction), Files(Files) {
}
void ToolInvocation::mapVirtualFile(StringRef FilePath, StringRef Content) {
MappedFileContents[FilePath] = Content;
}
bool ToolInvocation::run() {
std::vector<const char*> Argv;
for (int I = 0, E = CommandLine.size(); I != E; ++I)
Argv.push_back(CommandLine[I].c_str());
const char *const BinaryName = Argv[0];
DiagnosticOptions DefaultDiagnosticOptions;
TextDiagnosticPrinter DiagnosticPrinter(
llvm::errs(), DefaultDiagnosticOptions);
DiagnosticsEngine Diagnostics(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(
new DiagnosticIDs()), &DiagnosticPrinter, false);
const llvm::OwningPtr<clang::driver::Driver> Driver(
newDriver(&Diagnostics, BinaryName));
// Since the input might only be virtual, don't check whether it exists.
Driver->setCheckInputsExist(false);
const llvm::OwningPtr<clang::driver::Compilation> Compilation(
Driver->BuildCompilation(llvm::makeArrayRef(Argv)));
const clang::driver::ArgStringList *const CC1Args = getCC1Arguments(
&Diagnostics, Compilation.get());
if (CC1Args == NULL) {
return false;
}
llvm::OwningPtr<clang::CompilerInvocation> Invocation(
newInvocation(&Diagnostics, *CC1Args));
return runInvocation(BinaryName, Compilation.get(),
Invocation.take(), *CC1Args, ToolAction.take());
}
// Exists solely for the purpose of lookup of the resource path.
static int StaticSymbol;
bool ToolInvocation::runInvocation(
const char *BinaryName,
clang::driver::Compilation *Compilation,
clang::CompilerInvocation *Invocation,
const clang::driver::ArgStringList &CC1Args,
clang::FrontendAction *ToolAction) {
llvm::OwningPtr<clang::FrontendAction> ScopedToolAction(ToolAction);
// Show the invocation, with -v.
if (Invocation->getHeaderSearchOpts().Verbose) {
llvm::errs() << "clang Invocation:\n";
Compilation->PrintJob(llvm::errs(), Compilation->getJobs(), "\n", true);
llvm::errs() << "\n";
}
// Create a compiler instance to handle the actual work.
clang::CompilerInstance Compiler;
Compiler.setInvocation(Invocation);
Compiler.setFileManager(Files);
// FIXME: What about LangOpts?
// Create the compilers actual diagnostics engine.
Compiler.createDiagnostics(CC1Args.size(),
const_cast<char**>(CC1Args.data()));
if (!Compiler.hasDiagnostics())
return false;
Compiler.createSourceManager(*Files);
addFileMappingsTo(Compiler.getSourceManager());
// Infer the builtin include path if unspecified.
if (Compiler.getHeaderSearchOpts().UseBuiltinIncludes &&
Compiler.getHeaderSearchOpts().ResourceDir.empty()) {
// This just needs to be some symbol in the binary.
void *const SymbolAddr = &StaticSymbol;
Compiler.getHeaderSearchOpts().ResourceDir =
clang::CompilerInvocation::GetResourcesPath(BinaryName, SymbolAddr);
}
const bool Success = Compiler.ExecuteAction(*ToolAction);
Compiler.resetAndLeakFileManager();
return Success;
}
void ToolInvocation::addFileMappingsTo(SourceManager &Sources) {
for (llvm::StringMap<StringRef>::const_iterator
It = MappedFileContents.begin(), End = MappedFileContents.end();
It != End; ++It) {
// Inject the code as the given file name into the preprocessor options.
const llvm::MemoryBuffer *Input =
llvm::MemoryBuffer::getMemBuffer(It->getValue());
// FIXME: figure out what '0' stands for.
const FileEntry *FromFile = Files->getVirtualFile(
It->getKey(), Input->getBufferSize(), 0);
// FIXME: figure out memory management ('true').
Sources.overrideFileContents(FromFile, Input, true);
}
}
ClangTool::ClangTool(const CompilationDatabase &Compilations,
ArrayRef<std::string> SourcePaths)
: Files((FileSystemOptions())) {
StringRef BaseDirectory(::getenv("PWD"));
for (unsigned I = 0, E = SourcePaths.size(); I != E; ++I) {
llvm::SmallString<1024> File(getAbsolutePath(
SourcePaths[I], BaseDirectory));
std::vector<CompileCommand> CompileCommands =
Compilations.getCompileCommands(File.str());
if (!CompileCommands.empty()) {
for (int I = 0, E = CompileCommands.size(); I != E; ++I) {
CompileCommand &Command = CompileCommands[I];
if (!Command.Directory.empty()) {
// FIXME: What should happen if CommandLine includes -working-directory
// as well?
Command.CommandLine.push_back(
"-working-directory=" + Command.Directory);
}
CommandLines.push_back(std::make_pair(File.str(), Command.CommandLine));
}
} else {
// FIXME: There are two use cases here: doing a fuzzy
// "find . -name '*.cc' |xargs tool" match, where as a user I don't care
// about the .cc files that were not found, and the use case where I
// specify all files I want to run over explicitly, where this should
// be an error. We'll want to add an option for this.
llvm::outs() << "Skipping " << File << ". Command line not found.\n";
}
}
}
void ClangTool::mapVirtualFile(StringRef FilePath, StringRef Content) {
MappedFileContents.push_back(std::make_pair(FilePath, Content));
}
int ClangTool::run(FrontendActionFactory *ActionFactory) {
bool ProcessingFailed = false;
for (unsigned I = 0; I < CommandLines.size(); ++I) {
std::string File = CommandLines[I].first;
std::vector<std::string> &CommandLine = CommandLines[I].second;
llvm::outs() << "Processing: " << File << ".\n";
ToolInvocation Invocation(CommandLine, ActionFactory->create(), &Files);
for (int I = 0, E = MappedFileContents.size(); I != E; ++I) {
Invocation.mapVirtualFile(MappedFileContents[I].first,
MappedFileContents[I].second);
}
if (!Invocation.run()) {
llvm::outs() << "Error while processing " << File << ".\n";
ProcessingFailed = true;
}
}
return ProcessingFailed ? 1 : 0;
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,8 @@
// RUN: rm -rf %t
// RUN: mkdir %t
// RUN: echo '[{"directory":".","command":"clang++ -c %t/test.cpp","file":"%t/test.cpp"}]' > %t/compile_commands.json
// RUN: cp "%s" "%t/test.cpp"
// RUN: clang-check "%t" "%t/test.cpp" 2>&1|FileCheck %s
// FIXME: Make the above easier.
invalid; // CHECK: C++ requires

View File

@ -4,3 +4,4 @@ add_subdirectory(arcmt-test)
add_subdirectory(c-arcmt-test) add_subdirectory(c-arcmt-test)
add_subdirectory(diagtool) add_subdirectory(diagtool)
add_subdirectory(driver) add_subdirectory(driver)
add_subdirectory(clang-check)

View File

@ -8,7 +8,8 @@
##===----------------------------------------------------------------------===## ##===----------------------------------------------------------------------===##
CLANG_LEVEL := .. CLANG_LEVEL := ..
DIRS := driver libclang c-index-test arcmt-test c-arcmt-test diagtool DIRS := driver libclang c-index-test arcmt-test c-arcmt-test diagtool \
clang-check
include $(CLANG_LEVEL)/../../Makefile.config include $(CLANG_LEVEL)/../../Makefile.config

View File

@ -0,0 +1,5 @@
set(LLVM_USED_LIBS clangTooling clangBasic)
add_clang_executable(clang-check
ClangCheck.cpp
)

View File

@ -0,0 +1,62 @@
//===- 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/in/subtree $ find . -name '*.cpp'| xargs clang-check /path/to/source
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/CommandLine.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Tooling.h"
using namespace clang::tooling;
using namespace llvm;
cl::opt<std::string> BuildPath(
cl::Positional,
cl::desc("<build-path>"));
cl::list<std::string> SourcePaths(
cl::Positional,
cl::desc("<source0> [... <sourceN>]"),
cl::OneOrMore);
int main(int argc, char **argv) {
cl::ParseCommandLineOptions(argc, argv);
std::string ErrorMessage;
llvm::OwningPtr<CompilationDatabase> Compilations(
CompilationDatabase::loadFromDirectory(BuildPath, ErrorMessage));
if (!Compilations)
llvm::report_fatal_error(ErrorMessage);
ClangTool Tool(*Compilations, SourcePaths);
return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>());
}

View File

@ -0,0 +1,24 @@
##===- tools/clang-check/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-check
NO_INSTALL = 1
# No plugins, optimize startup time.
TOOL_NO_EXPORTS = 1
LINK_COMPONENTS := support mc
USEDLIBS = clangFrontend.a clangSerialization.a clangDriver.a \
clangTooling.a clangParse.a clangSema.a clangAnalysis.a \
clangAST.a clangLex.a clangBasic.a
include $(CLANG_LEVEL)/Makefile

View File

@ -65,3 +65,9 @@ add_clang_unittest(Frontend
Frontend/FrontendActionTest.cpp Frontend/FrontendActionTest.cpp
USED_LIBS gtest gtest_main clangFrontend USED_LIBS gtest gtest_main clangFrontend
) )
add_clang_unittest(Tooling
Tooling/CompilationDatabaseTest.cpp
Tooling/ToolingTest.cpp
USED_LIBS gtest gtest_main clangTooling
)

View File

@ -14,7 +14,7 @@ ifndef CLANG_LEVEL
IS_UNITTEST_LEVEL := 1 IS_UNITTEST_LEVEL := 1
CLANG_LEVEL := .. CLANG_LEVEL := ..
PARALLEL_DIRS = Basic Frontend Lex PARALLEL_DIRS = Basic Frontend Lex Tooling
endif # CLANG_LEVEL endif # CLANG_LEVEL

View File

@ -0,0 +1,223 @@
//===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclGroup.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
namespace clang {
namespace tooling {
static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
StringRef JSONDatabase,
std::string &ErrorMessage) {
llvm::OwningPtr<CompilationDatabase> Database(
JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
if (!Database)
return CompileCommand();
std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName);
EXPECT_LE(Commands.size(), 1u);
if (Commands.empty())
return CompileCommand();
return Commands[0];
}
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) {
StringRef Directory("/some/directory");
StringRef FileName("/path/to/a-file.cpp");
StringRef Command("/path/to/compiler and some arguments");
std::string ErrorMessage;
CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
FileName,
("[{\"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",
("[{\"directory\":\"" + Directory + "\"," +
"\"command\":\"" + Command + "\","
"\"file\":\"" + FileName + "\"}]").str(),
ErrorMessage);
EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
}
TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
StringRef Directory("/some/directory");
StringRef FileName("/path/to/a-file.cpp");
StringRef Command("\\\"/path to compiler\\\" \\\"and an argument\\\"");
std::string ErrorMessage;
CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
FileName,
("[{\"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) {
StringRef Directory("/some directory / with spaces");
StringRef FileName("/path/to/a-file.cpp");
StringRef Command("a command");
std::string ErrorMessage;
CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
FileName,
("[{\"directory\":\"" + Directory + "\"," +
"\"command\":\"" + Command + "\","
"\"file\":\"" + FileName + "\"}]").str(),
ErrorMessage);
EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
}
TEST(findCompileArgsInJsonDatabase, FindsEntry) {
StringRef Directory("directory");
StringRef FileName("file");
StringRef Command("command");
std::string JsonDatabase = "[";
for (int I = 0; I < 10; ++I) {
if (I > 0) JsonDatabase += ",";
JsonDatabase +=
("{\"directory\":\"" + Directory + Twine(I) + "\"," +
"\"command\":\"" + Command + Twine(I) + "\","
"\"file\":\"" + FileName + 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;
}
static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {
std::string JsonDatabase =
("[{\"directory\":\"\", \"file\":\"test\", \"command\": \"" +
Command + "\"}]").str();
std::string ErrorMessage;
CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
"test", JsonDatabase, ErrorMessage);
EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;
return FoundCommand.CommandLine;
}
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(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {
std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");
ASSERT_EQ(1ul, Unclosed.size());
EXPECT_EQ("abc", Unclosed[0]);
std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");
ASSERT_EQ(1ul, Empty.size());
EXPECT_EQ("", Empty[0]);
}
} // end namespace tooling
} // end namespace clang

View File

@ -0,0 +1,17 @@
##===- unittests/Tooling/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 = ../..
TESTNAME = Tooling
LINK_COMPONENTS := support mc
USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \
clangParse.a clangSema.a clangAnalysis.a clangAST.a clangLex.a \
clangBasic.a
include $(CLANG_LEVEL)/unittests/Makefile

View File

@ -0,0 +1,113 @@
//===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclGroup.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
namespace clang {
namespace tooling {
namespace {
/// Takes an ast consumer and returns it from CreateASTConsumer. This only
/// works with single translation unit compilations.
class TestAction : public clang::ASTFrontendAction {
public:
/// Takes ownership of TestConsumer.
explicit TestAction(clang::ASTConsumer *TestConsumer)
: TestConsumer(TestConsumer) {}
protected:
virtual clang::ASTConsumer* CreateASTConsumer(
clang::CompilerInstance& compiler, StringRef dummy) {
/// TestConsumer will be deleted by the framework calling us.
return TestConsumer;
}
private:
clang::ASTConsumer * const TestConsumer;
};
class FindTopLevelDeclConsumer : public clang::ASTConsumer {
public:
explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl)
: FoundTopLevelDecl(FoundTopLevelDecl) {}
virtual bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) {
*FoundTopLevelDecl = true;
return true;
}
private:
bool * const FoundTopLevelDecl;
};
} // end namespace
TEST(runToolOnCode, FindsTopLevelDeclOnEmptyCode) {
bool FoundTopLevelDecl = false;
EXPECT_TRUE(runToolOnCode(
new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), ""));
EXPECT_TRUE(FoundTopLevelDecl);
}
namespace {
class FindClassDeclXConsumer : public clang::ASTConsumer {
public:
FindClassDeclXConsumer(bool *FoundClassDeclX)
: FoundClassDeclX(FoundClassDeclX) {}
virtual bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) {
if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(
*GroupRef.begin())) {
if (Record->getName() == "X") {
*FoundClassDeclX = true;
}
}
return true;
}
private:
bool *FoundClassDeclX;
};
} // end namespace
TEST(runToolOnCode, FindsClassDecl) {
bool FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(new TestAction(
new FindClassDeclXConsumer(&FoundClassDeclX)), "class X;"));
EXPECT_TRUE(FoundClassDeclX);
FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(new TestAction(
new FindClassDeclXConsumer(&FoundClassDeclX)), "class Y;"));
EXPECT_FALSE(FoundClassDeclX);
}
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) {
llvm::OwningPtr<FrontendActionFactory> Factory(
newFrontendActionFactory<SyntaxOnlyAction>());
llvm::OwningPtr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != NULL);
}
struct IndependentFrontendActionCreator {
FrontendAction *newFrontendAction() { return new SyntaxOnlyAction; }
};
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) {
IndependentFrontendActionCreator Creator;
llvm::OwningPtr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Creator));
llvm::OwningPtr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != NULL);
}
} // end namespace tooling
} // end namespace clang