Adds a function to run FrontendActions over in-memory code. This is the first step towards a standalone Clang tool infrastructure. The plan is to make it easy to build command line tools that run over the AST of source files in a project outside of the build system.
We've been experimenting with this model for tools at Google and got great feedback from our developers, so we now want to make that available to open source developers as well. I'm currently working with the CMake folks to get CMake to output a small Json compile command line database which tools can query to run over code. Adding support for that will be one of the next steps after this CL, and I'll send out a separate mail to discuss the details of that step. Cheers, /Manuel
From 79f4716c8cc8d6bb869ccd545438749f872d30fe Mon Sep 17 00:00:00 2001 From: Manuel Klimek <[email protected]> Date: Thu, 7 Apr 2011 14:53:41 -0700 Subject: [PATCH] Adds a function to run FrontendActions over in-memory code. This is the first step towards a standalone Clang tool infrastructure, to enable writing programs that do something interesting over source files without running them as a plugin in the compiler. --- include/clang/Tooling/Tooling.h | 38 +++++++ lib/CMakeLists.txt | 1 + lib/Tooling/CMakeLists.txt | 5 + lib/Tooling/Tooling.cpp | 218 +++++++++++++++++++++++++++++++++++++ unittests/CMakeLists.txt | 5 + unittests/Tooling/ToolingTest.cpp | 91 +++++++++++++++ 6 files changed, 358 insertions(+), 0 deletions(-) create mode 100644 include/clang/Tooling/Tooling.h create mode 100644 lib/Tooling/CMakeLists.txt create mode 100644 lib/Tooling/Tooling.cpp create mode 100644 unittests/Tooling/ToolingTest.cpp diff --git a/include/clang/Tooling/Tooling.h b/include/clang/Tooling/Tooling.h new file mode 100644 index 0000000..f07adeb --- /dev/null +++ b/include/clang/Tooling/Tooling.h @@ -0,0 +1,38 @@ +//===--- 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. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TOOLING_H +#define LLVM_CLANG_TOOLING_TOOLING_H + +#include "llvm/ADT/StringRef.h" + +namespace clang { + +class FrontendAction; + +namespace tooling { + +/// \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. +/// +/// \return - True if 'ToolAction' was successfully executed. +bool RunSyntaxOnlyToolOnCode( + clang::FrontendAction *ToolAction, llvm::StringRef Code); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_TOOLING_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b457434..0943e2b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory(Frontend) add_subdirectory(FrontendTool) add_subdirectory(Index) add_subdirectory(StaticAnalyzer) +add_subdirectory(Tooling) diff --git a/lib/Tooling/CMakeLists.txt b/lib/Tooling/CMakeLists.txt new file mode 100644 index 0000000..6736d65 --- /dev/null +++ b/lib/Tooling/CMakeLists.txt @@ -0,0 +1,5 @@ +SET(LLVM_USED_LIBS clangBasic clangFrontend clangAST) + +add_clang_library(clangTooling + Tooling.cpp + ) diff --git a/lib/Tooling/Tooling.cpp b/lib/Tooling/Tooling.cpp new file mode 100644 index 0000000..a5bd1ac --- /dev/null +++ b/lib/Tooling/Tooling.cpp @@ -0,0 +1,218 @@ +//===--- 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 "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include "clang/Basic/DiagnosticIDs.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" + +#include <string> +#include <map> +#include <vector> + +namespace clang { +namespace tooling { + +// 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. + +static clang::Diagnostic* NewTextDiagnostics() { + llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagIDs( + new clang::DiagnosticIDs()); + clang::TextDiagnosticPrinter *DiagClient = new clang::TextDiagnosticPrinter( + llvm::errs(), clang::DiagnosticOptions()); + return new clang::Diagnostic(DiagIDs, DiagClient); +} + +// Exists solely for the purpose of lookup of the main executable. +static int StaticSymbol; + +/// \brief Builds a clang driver initialized for running clang tools. +static clang::driver::Driver* NewDriver(clang::Diagnostic* Diagnostics, + const char* BinaryName) { + // This just needs to be some symbol in the binary. + void* const SymbolAddr = &StaticSymbol; + const llvm::sys::Path ExePath = + llvm::sys::Path::GetMainExecutable(BinaryName, SymbolAddr); + + const std::string DefaultOutputName = "a.out"; + clang::driver::Driver* CompilerDriver = new clang::driver::Driver( + ExePath.str(), llvm::sys::getHostTriple(), + DefaultOutputName, false, 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::Diagnostic* 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 (llvm::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::Diagnostic* Diagnostics, + const clang::driver::ArgStringList& CC1Args) { + clang::CompilerInvocation* Invocation = new clang::CompilerInvocation; + clang::CompilerInvocation::CreateFromArgs( + *Invocation, CC1Args.data(), CC1Args.data() + CC1Args.size(), + *Diagnostics); + Invocation->getFrontendOpts().DisableFree = false; + return Invocation; +} + +/// \brief Runs the specified clang tool action and returns whether it executed +/// successfully. +static bool 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); + + // Create the compilers actual diagnostics engine. + Compiler.createDiagnostics(CC1Args.size(), + const_cast<char**>(CC1Args.data())); + if (!Compiler.hasDiagnostics()) + return false; + + // 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); + return Success; +} + +/// \brief Converts a string vector representing a Command line into a C +/// string vector representing the Argv (including the trailing NULL). +std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command) { + std::vector<char*> Result(Command->size() + 1); + for (std::vector<char*>::size_type I = 0; I < Command->size(); ++I) { + Result[I] = const_cast<char*>((*Command)[I].c_str()); + } + Result[Command->size()] = NULL; + return Result; +} + +/// \brief Runs 'ToolAction' on the code specified by 'FileContents'. +/// +/// \param FileContents A mapping from file name to source code. For each +/// entry a virtual file mapping will be created when running the tool. +bool RunToolWithFlagsOnCode( + const std::vector<std::string>& CommandLine, + const std::map<std::string, std::string>& FileContents, + clang::FrontendAction* ToolAction) { + const std::vector<char*> Argv = CommandLineToArgv(&CommandLine); + const char* const BinaryName = Argv[0]; + + const llvm::OwningPtr<clang::Diagnostic> Diagnostics(NewTextDiagnostics()); + const llvm::OwningPtr<clang::driver::Driver> Driver( + NewDriver(Diagnostics.get(), BinaryName)); + + // Since the Input is only virtual, don't check whether it exists. + Driver->setCheckInputsExist(false); + + const llvm::OwningPtr<clang::driver::Compilation> Compilation( + Driver->BuildCompilation(llvm::ArrayRef<const char*>(&Argv[0], + Argv.size() - 1))); + 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)); + + for (std::map<std::string, std::string>::const_iterator + It = FileContents.begin(), End = FileContents.end(); + It != End; ++It) { + // Inject the code as the given file name into the preprocessor options. + const llvm::MemoryBuffer* Input = + llvm::MemoryBuffer::getMemBuffer(It->second.c_str()); + Invocation->getPreprocessorOpts().addRemappedFile(It->first.c_str(), Input); + } + + return RunInvocation(BinaryName, Compilation.get(), + Invocation.take(), *CC1Args, ToolAction); +} + +bool RunSyntaxOnlyToolOnCode( + clang::FrontendAction *ToolAction, llvm::StringRef Code) { + const char* const FileName = "input.cc"; + const char* const CommandLine[] = { + "clang-tool", "-fsyntax-only", FileName + }; + std::map<std::string, std::string> FileContents; + FileContents[FileName] = Code; + return RunToolWithFlagsOnCode( + std::vector<std::string>( + CommandLine, + CommandLine + sizeof(CommandLine)/sizeof(CommandLine[0])), + FileContents, ToolAction); +} + +} // end namespace tooling +} // end namespace clang + diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 112d6a0..dd6bad5 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -59,3 +59,8 @@ add_clang_unittest(Frontend Frontend/FrontendActionTest.cpp USED_LIBS gtest gtest_main clangFrontend ) + +add_clang_unittest(Tooling + Tooling/ToolingTest.cpp + USED_LIBS gtest gtest_main clangTooling + ) diff --git a/unittests/Tooling/ToolingTest.cpp b/unittests/Tooling/ToolingTest.cpp new file mode 100644 index 0000000..da89c0b --- /dev/null +++ b/unittests/Tooling/ToolingTest.cpp @@ -0,0 +1,91 @@ +//===- 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/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, llvm::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 void HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) { + *FoundTopLevelDecl = true; + } + private: + bool * const FoundTopLevelDecl; +}; +} // end namespace + +TEST(RunSyntaxOnlyToolOnCode, FindsTopLevelDeclOnEmptyCode) { + bool FoundTopLevelDecl = false; + EXPECT_TRUE(RunSyntaxOnlyToolOnCode( + new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), "")); + EXPECT_TRUE(FoundTopLevelDecl); +} + +namespace { +class FindClassDeclXConsumer : public clang::ASTConsumer { + public: + FindClassDeclXConsumer(bool *FoundClassDeclX) + : FoundClassDeclX(FoundClassDeclX) {} + virtual void HandleTopLevelDecl(clang::DeclGroupRef GroupRef) { + if (CXXRecordDecl* Record = llvm::dyn_cast<clang::CXXRecordDecl>( + *GroupRef.begin())) { + if (Record->getName() == "X") { + *FoundClassDeclX = true; + } + } + } + private: + bool *FoundClassDeclX; +}; +} // end namespace + +TEST(RunSyntaxOnlyToolOnCode, FindsClassDecl) { + bool FoundClassDeclX = false; + EXPECT_TRUE(RunSyntaxOnlyToolOnCode(new TestAction( + new FindClassDeclXConsumer(&FoundClassDeclX)), "class X;")); + EXPECT_TRUE(FoundClassDeclX); + + FoundClassDeclX = false; + EXPECT_TRUE(RunSyntaxOnlyToolOnCode(new TestAction( + new FindClassDeclXConsumer(&FoundClassDeclX)), "class Y;")); + EXPECT_FALSE(FoundClassDeclX); +} + +} // end namespace tooling +} // end namespace clang + -- 1.7.3.1
_______________________________________________ cfe-commits mailing list [email protected] http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
