diff options
author | Manuel Klimek <klimek@google.com> | 2011-04-21 18:37:41 +0000 |
---|---|---|
committer | Manuel Klimek <klimek@google.com> | 2011-04-21 18:37:41 +0000 |
commit | cf3ce5d89628b3955c638fb102c8dc4c24e1986d (patch) | |
tree | 23b117ad61ea2246d4550d24f954d12dc2e90d49 | |
parent | 0e02f6ef48a098cc27a250e644415038c2fa52a5 (diff) | |
download | clang-cf3ce5d89628b3955c638fb102c8dc4c24e1986d.tar.gz clang-cf3ce5d89628b3955c638fb102c8dc4c24e1986d.tar.bz2 clang-cf3ce5d89628b3955c638fb102c8dc4c24e1986d.tar.xz |
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.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@129924 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | include/clang/Tooling/Tooling.h | 38 | ||||
-rw-r--r-- | lib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/Tooling/CMakeLists.txt | 5 | ||||
-rw-r--r-- | lib/Tooling/Tooling.cpp | 218 | ||||
-rw-r--r-- | unittests/CMakeLists.txt | 5 | ||||
-rw-r--r-- | unittests/Tooling/ToolingTest.cpp | 91 |
6 files changed, 358 insertions, 0 deletions
diff --git a/include/clang/Tooling/Tooling.h b/include/clang/Tooling/Tooling.h new file mode 100644 index 0000000000..f07adeb832 --- /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 b4574344bc..0943e2b1c7 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 0000000000..6736d6537d --- /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 0000000000..a5bd1acf0e --- /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 112d6a0e50..dd6bad5fa9 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 0000000000..da89c0ba10 --- /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 + |