Index: include/clang/Tooling/CommonOptionsParser.h =================================================================== --- include/clang/Tooling/CommonOptionsParser.h +++ include/clang/Tooling/CommonOptionsParser.h @@ -30,6 +30,7 @@ #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" namespace clang { namespace tooling { @@ -86,13 +87,16 @@ /// All options not belonging to \p Category become hidden. /// /// It also allows calls to set the required number of positional parameters. - /// - /// This constructor exits program in case of error. CommonOptionsParser(int &argc, const char **argv, llvm::cl::OptionCategory &Category, llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview = nullptr); + static llvm::Expected> + create(int &argc, const char **argv, llvm::cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, + const char *Overview = nullptr); + /// Returns a reference to the loaded compilations database. CompilationDatabase &getCompilations() { return *Compilations; @@ -106,6 +110,13 @@ static const char *const HelpMessage; private: + CommonOptionsParser() = default; + + static llvm::Error init(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, + const char *Overview, CommonOptionsParser *Parser); + std::unique_ptr Compilations; std::vector SourcePathList; }; Index: include/clang/Tooling/Execution.h =================================================================== --- /dev/null +++ include/clang/Tooling/Execution.h @@ -0,0 +1,171 @@ +//===--- Execution.h - Executing clang frontend actions -*- C++ ---------*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines framework for executing clang frontend actions. +// +// The framework can be extended to support different execution plans including +// standalone execution on the given TUs or parallel execution on all TUs in +// the codebase. +// +// In order to enable multiprocessing execution, tool actions are expected to +// output result into the ToolResults provided by the executor. The +// `ToolResults` is an interface that abstracts how results are stored e.g. +// in-memory for standalone execution or on-disk for large-scale execution. +// +// New executors can be registered as ToolExecutorPlugins via the +// `ToolExecutorPluginRegistry`. CLI tools can use +// `createExecutorFromCommandLineArgs` to create a specific registered executor +// according to the command-line arguments. +// +// This is still experimental but expected to replace the existing `ClangTool` +// interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_EXECUTION_H +#define LLVM_CLANG_TOOLING_EXECUTION_H + +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace tooling { + +/// \brief An abstraction for the result of a tool execution. For example, the +/// underlying result can be in-memory or on-disk. +/// +/// Results should be string key-value pairs. For example, a refactoring tool +/// can use source location as key and a replacement in YAML format as value. +class ToolResults { +public: + virtual ~ToolResults() {} + virtual void addResult(StringRef Key, StringRef Value) = 0; + virtual std::vector> AllKVResults() = 0; + virtual void forEachResult( + llvm::function_ref Callback) = 0; +}; + +class InMemoryToolResults : public ToolResults { +public: + void addResult(StringRef Key, StringRef Value) override; + std::vector> AllKVResults() override; + void forEachResult(llvm::function_ref + Callback) override; + +private: + std::vector> KVResults; +}; + +/// \brief The context of an execution, including the information about +/// compilation and results. +class ExecutionContext { +public: + virtual ~ExecutionContext() {} + + /// \brief Initializes a context. This does not take ownership of `Results`. + explicit ExecutionContext(ToolResults *Results) : Results(Results) {} + + virtual ToolResults *getToolResults() const { return Results; } + + // Returns the source control system's revision number if applicable. + // Otherwise returns an empty string. + virtual std::string getRevision() { return ""; } + + // Returns the corpus being analyzed, e.g. "llvm" for the LLVM codebase, if + // applicable. + virtual std::string getCorpus() { return ""; } + + // Returns the currently processed compilation unit if available. + virtual std::string getCurrentCompilationUnit() { return ""; } + +private: + // A reference to the results container. Not owned! + ToolResults *Results; +}; + +/// \brief Specifies the actions to be executed in a tool execution and the +/// compilation arguments to be used. +struct ExecutionConfig { + ExecutionConfig() = default; + + // Config for executing a single `Action` with the default configuration e.g. + // `ClangStripOutputAdjuster` and `ClangSyntaxOnlyAdjuster`. + explicit ExecutionConfig(std::unique_ptr Action); + + // Config for executing multiple actions that use customized argument + // adjusters in addition to the default one. + std::vector>> + Actions; +}; + +/// \brief Interface for executing clang frontend actions. +/// +/// This can be extended to support running tool actions in different +/// execution mode, e.g. on a specific set of TUs or many TUs in parallel. +/// +/// New executors can be registered as ToolExecutorPlugins via the +/// `ToolExecutorPluginRegistry`. CLI tools can use +/// `createExecutorFromCommandLineArgs` to create a specific registered +/// executor according to the command-line arguments. +class ToolExecutor { +public: + virtual ~ToolExecutor() {} + + /// \brief Returns the name of a specific executor. + virtual StringRef getExecutorName() const = 0; + + /// \brief Executes actions specified in the execution configuration. + virtual llvm::Error execute(const ExecutionConfig &Config) = 0; + + /// \brief Returns a reference of the execution context. + virtual ExecutionContext *getExecutionContext() = 0; + + /// \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 buffer of the file's content. + virtual void mapVirtualFile(StringRef FilePath, StringRef Content) = 0; +}; + +/// \brief Interface for factories that create speicifc executors based on the +/// commandline arguments. This is also used as a plugin to be registered into +/// ToolExecutorPluginRegistry. +class ToolExecutorPlugin { +public: + virtual ~ToolExecutorPlugin() {} + + /// \brief Create a ToolExecutor based on the commandline arguments. If the + /// set of commandline arguments is not supported by the ToolExecutor, this + /// returns an error. + /// Expected commandline options should be registered in the implementation. + virtual llvm::Expected> + create(int &argc, const char **argv, llvm::cl::OptionCategory &Category) = 0; +}; + +/// \brief This creates a ToolExecutor that is in the global registry and +/// supports the given commandline arguments. +/// +/// This tries creating all registered executors from the given command-line +/// arguments and returns the first executor that matches all arguments. +/// +/// This should only be called once in a program. +/// +/// By default, this creates a `StandaloneToolExecutor` if not other tool +/// flags are used. +llvm::Expected> +createExecutorFromCommandLineArgs(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview = nullptr); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_EXECUTION_H Index: include/clang/Tooling/StandaloneExecution.h =================================================================== --- /dev/null +++ include/clang/Tooling/StandaloneExecution.h @@ -0,0 +1,84 @@ +//===--- Execution.h - Standalone clang action execution. -*- C++ -------*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines standalone execution of clang tools. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_STANDALONEEXECUTION_H +#define LLVM_CLANG_TOOLING_STANDALONEEXECUTION_H + +#include "clang/Tooling/Execution.h" + +namespace clang { +namespace tooling { + +/// \brief A standalone executor that runs FrontendActions on a given set of +/// TUs in sequence. +class StandaloneToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + /// \brief Init with \p CompilationDatabase and the paths of all files to be + /// proccessed. + StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + /// \brief Init with \p CommonOptionsParser. This is expected to be used by + /// `createExecutorFromCommandLineArgs` based on commandline options. + /// + /// The executor takes ownership of \p Options. + StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error execute(const ExecutionConfig &Config) override; + + /// \brief Set a \c DiagnosticConsumer to use during parsing. + void setDiagnosticConsumer(DiagnosticConsumer *DiagConsumer) { + Tool.setDiagnosticConsumer(DiagConsumer); + } + + /// \brief Returns a reference to the execution context. Tool results will be + /// put into the results container in the context during the execution. + ExecutionContext *getExecutionContext() override { return &Context; }; + + llvm::ArrayRef getSourcePaths() const { + return Tool.getSourcePaths(); + } + + void mapVirtualFile(StringRef FilePath, StringRef Content) override { + Tool.mapVirtualFile(FilePath, Content); + } + + /// \brief Returns the file manager used in the tool. + /// + /// The file manager is shared between all translation units. + FileManager &getFiles() { return Tool.getFiles(); } + +private: + // Used to store the parser when the executor is initialized with parser. + std::unique_ptr OptionsParser; + // FIXME: The standalone executor is currently just a wrapper of `ClangTool`. + // Merge `ClangTool` implementation into the this. + ClangTool Tool; + ExecutionContext Context; + InMemoryToolResults Results; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_STANDALONEEXECUTION_H Index: include/clang/Tooling/ToolExecutorPluginRegistry.h =================================================================== --- /dev/null +++ include/clang/Tooling/ToolExecutorPluginRegistry.h @@ -0,0 +1,24 @@ +//===--- ToolExecutorPluginRegistry.h - -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H +#define LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H + +#include "clang/Tooling/Execution.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace tooling { + +typedef llvm::Registry ToolExecutorPluginRegistry; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H Index: include/clang/Tooling/Tooling.h =================================================================== --- include/clang/Tooling/Tooling.h +++ include/clang/Tooling/Tooling.h @@ -31,12 +31,12 @@ #define LLVM_CLANG_TOOLING_TOOLING_H #include "clang/AST/ASTConsumer.h" -#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "clang/Driver/Util.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Lex/ModuleLoader.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" @@ -337,7 +337,9 @@ /// The file manager is shared between all translation units. FileManager &getFiles() { return *Files; } - private: + llvm::ArrayRef getSourcePaths() const { return SourcePaths; } + +private: const CompilationDatabase &Compilations; std::vector SourcePaths; std::shared_ptr PCHContainerOps; Index: lib/Tooling/CMakeLists.txt =================================================================== --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -11,11 +11,13 @@ ArgumentsAdjusters.cpp CommonOptionsParser.cpp CompilationDatabase.cpp + Execution.cpp FileMatchTrie.cpp FixIt.cpp JSONCompilationDatabase.cpp Refactoring.cpp RefactoringCallbacks.cpp + StandaloneExecution.cpp Tooling.cpp DEPENDS Index: lib/Tooling/CommonOptionsParser.cpp =================================================================== --- lib/Tooling/CommonOptionsParser.cpp +++ lib/Tooling/CommonOptionsParser.cpp @@ -24,9 +24,9 @@ // //===----------------------------------------------------------------------===// -#include "llvm/Support/CommandLine.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" using namespace clang::tooling; using namespace llvm; @@ -81,9 +81,11 @@ return Commands; } -CommonOptionsParser::CommonOptionsParser( - int &argc, const char **argv, cl::OptionCategory &Category, - llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { +llvm::Error +CommonOptionsParser::init(int &argc, const char **argv, + cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, + const char *Overview, CommonOptionsParser *Parser) { static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden, cl::sub(*cl::AllSubCommands)); @@ -105,41 +107,76 @@ cl::desc("Additional argument to prepend to the compiler command line"), cl::cat(Category), cl::sub(*cl::AllSubCommands)); + cl::ResetAllOptionOccurrences(); + cl::HideUnrelatedOptions(Category); std::string ErrorMessage; - Compilations = + Parser->Compilations = FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); - if (!Compilations && !ErrorMessage.empty()) - llvm::errs() << ErrorMessage; - cl::ParseCommandLineOptions(argc, argv, Overview); + if (!ErrorMessage.empty()) + ErrorMessage.append("\n"); + llvm::raw_string_ostream OS(ErrorMessage); + // Stop initializing if command-line option parsing failed. + if (!cl::ParseCommandLineOptions(argc, argv, Overview, &OS)) { + OS.flush(); + return llvm::make_error("[CommonOptionsParser]: " + + ErrorMessage, + llvm::inconvertibleErrorCode()); + } + cl::PrintOptionValues(); - SourcePathList = SourcePaths; + Parser->SourcePathList = SourcePaths; if ((OccurrencesFlag == cl::ZeroOrMore || OccurrencesFlag == cl::Optional) && - SourcePathList.empty()) - return; - if (!Compilations) { + Parser->SourcePathList.empty()) + return llvm::Error::success(); + if (!Parser->Compilations) { if (!BuildPath.empty()) { - Compilations = + Parser->Compilations = CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); } else { - Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], - ErrorMessage); + Parser->Compilations = CompilationDatabase::autoDetectFromSource( + SourcePaths[0], ErrorMessage); } - if (!Compilations) { + if (!Parser->Compilations) { llvm::errs() << "Error while trying to load a compilation database:\n" << ErrorMessage << "Running without flags.\n"; - Compilations.reset( + Parser->Compilations.reset( new FixedCompilationDatabase(".", std::vector())); } } auto AdjustingCompilations = llvm::make_unique( - std::move(Compilations)); + std::move(Parser->Compilations)); AdjustingCompilations->appendArgumentsAdjuster( getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); AdjustingCompilations->appendArgumentsAdjuster( getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); - Compilations = std::move(AdjustingCompilations); + Parser->Compilations = std::move(AdjustingCompilations); + return llvm::Error::success(); +} + +llvm::Expected> +CommonOptionsParser::create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, + const char *Overview) { + std::unique_ptr Parser(new CommonOptionsParser); + llvm::Error Err = + init(argc, argv, Category, OccurrencesFlag, Overview, Parser.get()); + if (Err) + return std::move(Err); + return Parser; +} + +CommonOptionsParser::CommonOptionsParser( + int &argc, const char **argv, cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { + llvm::Error Err = init(argc, argv, Category, OccurrencesFlag, Overview, this); + if (Err) { + llvm::report_fatal_error( + "CommonOptionsParser: failed to parse command-line arguments. " + + llvm::toString(std::move(Err))); + } } Index: lib/Tooling/Execution.cpp =================================================================== --- /dev/null +++ lib/Tooling/Execution.cpp @@ -0,0 +1,70 @@ +//===- lib/Tooling/Execution.cpp - Implements tool execution framework. ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "clang/Tooling/Tooling.h" + +LLVM_INSTANTIATE_REGISTRY(clang::tooling::ToolExecutorPluginRegistry) + +namespace clang { +namespace tooling { + +void InMemoryToolResults::addResult(StringRef Key, StringRef Value) { + KVResults.push_back({Key.str(), Value.str()}); +} + +std::vector> +InMemoryToolResults::AllKVResults() { + return KVResults; +} + +void InMemoryToolResults::forEachResult( + llvm::function_ref Callback) { + for (const auto &KV : KVResults) { + Callback(KV.first, KV.second); + } +} + +ExecutionConfig::ExecutionConfig(std::unique_ptr Action) { + Actions.push_back(std::make_pair(nullptr, std::move(Action))); +} + +llvm::Expected> +createExecutorFromCommandLineArgs(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview) { + std::string ErrorMessage; + llvm::raw_string_ostream OS(ErrorMessage); + // Find the first executor plugin that matches the given command-line + // arguments. + for (auto I = ToolExecutorPluginRegistry::begin(), + E = ToolExecutorPluginRegistry::end(); + I != E; ++I) { + std::unique_ptr Plugin(I->instantiate()); + std::vector args(argv, argv + argc); + int arg_num = argc; + llvm::Expected> Executor = + Plugin->create(arg_num, argv, Category); + if (!Executor) { + OS << "Failed to create '" << I->getName() + << "': " << llvm::toString(Executor.takeError()) << "\n"; + llvm::cl::ResetAllOptionOccurrences(); + continue; + } + argc = arg_num; + return std::move(*Executor); + } + OS.flush(); + return llvm::make_error(ErrorMessage, + llvm::inconvertibleErrorCode()); +} + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/StandaloneExecution.cpp =================================================================== --- /dev/null +++ lib/Tooling/StandaloneExecution.cpp @@ -0,0 +1,81 @@ +//===- lib/Tooling/Execution.cpp - Standalone clang action execution. -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" + +namespace clang { +namespace tooling { + +static llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +const char *StandaloneToolExecutor::ExecutorName = "StandaloneToolExecutor"; + +StandaloneToolExecutor::StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps) + : Tool(Compilations, SourcePaths), Context(&Results) {} + +StandaloneToolExecutor::StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps) + : OptionsParser(std::move(Options)), + Tool(OptionsParser->getCompilations(), OptionsParser->getSourcePathList(), + PCHContainerOps), + Context(&Results) {} + +llvm::Error StandaloneToolExecutor::execute(const ExecutionConfig &Config) { + if (Config.Actions.empty()) + return make_string_error("No action to execute."); + + if (Config.Actions.size() != 1) + return make_string_error( + "Only support executing exactly 1 ToolAction at this point."); + + auto &Action = Config.Actions.front(); + if (Action.first) + Tool.appendArgumentsAdjuster(Action.first); + if (int Ret = Tool.run(Action.second.get())) + return make_string_error("Failed to run ToolAction."); + + return llvm::Error::success(); +} + +class StandaloneToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + auto OptionsParser = + CommonOptionsParser::create(argc, argv, Category, llvm::cl::OneOrMore, + /*Overview=*/nullptr); + if (!OptionsParser) + return make_string_error("[StandaloneToolExecutorPlugin] " + + llvm::toString(OptionsParser.takeError())); + if ((*OptionsParser)->getSourcePathList().empty()) + return make_string_error( + "[StandaloneToolExecutorPlugin] No positional argument found."); + return llvm::make_unique(std::move(*OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +volatile int ToolExecutorPluginAnchorSource = 0; + +static ToolExecutorPluginRegistry::Add + X("standalone-executor", + "Runs ToolActions on a set of files provided via positional arguments."); + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -29,6 +29,7 @@ #include "llvm/Config/llvm-config.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" Index: unittests/Tooling/CMakeLists.txt =================================================================== --- unittests/Tooling/CMakeLists.txt +++ unittests/Tooling/CMakeLists.txt @@ -16,6 +16,7 @@ CommentHandlerTest.cpp CompilationDatabaseTest.cpp DiagnosticsYamlTest.cpp + ExecutionTest.cpp FixItTest.cpp LexicallyOrderedRecursiveASTVisitorTest.cpp LookupTest.cpp Index: unittests/Tooling/ExecutionTest.cpp =================================================================== --- /dev/null +++ unittests/Tooling/ExecutionTest.cpp @@ -0,0 +1,248 @@ +//===- unittest/Tooling/ExecutionTest.cpp - Tool execution 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/RecursiveASTVisitor.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" +#include +#include + +namespace clang { +namespace tooling { + +namespace { + +// This traverses the AST and outputs function name as key and "1" as value for +// each function declaration. +class ASTConsumerWithResult + : public ASTConsumer, + public RecursiveASTVisitor { +public: + using ASTVisitor = RecursiveASTVisitor; + + explicit ASTConsumerWithResult(ExecutionContext *Context) : Context(Context) { + assert(Context != nullptr); + } + + void HandleTranslationUnit(clang::ASTContext &Context) override { + TraverseDecl(Context.getTranslationUnitDecl()); + } + + bool TraverseFunctionDecl(clang::FunctionDecl *Decl) { + Context->getToolResults()->addResult(Decl->getNameAsString(), "1"); + return ASTVisitor::TraverseFunctionDecl(Decl); + } + +private: + ExecutionContext *const Context; +}; + +class ReportResultAction : public ASTFrontendAction { +public: + explicit ReportResultAction(ExecutionContext *Context) : Context(Context) { + assert(Context != nullptr); + } + +protected: + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &compiler, + StringRef /* dummy */) override { + std::unique_ptr ast_consumer{ + new ASTConsumerWithResult(Context)}; + return ast_consumer; + } + +private: + ExecutionContext *const Context; +}; + +class ReportResultActionFactory : public FrontendActionFactory { +public: + ReportResultActionFactory(ExecutionContext *Context) : Context(Context) {} + FrontendAction *create() override { return new ReportResultAction(Context); } + +private: + ExecutionContext *const Context; +}; + +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +} // namespace + +class TestToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + TestToolExecutor(std::unique_ptr Options) + : OptionsParser(std::move(Options)) {} + + StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error execute(const ExecutionConfig &) override { + return llvm::Error::success(); + } + + ExecutionContext *getExecutionContext() override { return nullptr; }; + + llvm::ArrayRef getSourcePaths() const { + return OptionsParser->getSourcePathList(); + } + + void mapVirtualFile(StringRef FilePath, StringRef Content) override { + VFS[FilePath] = Content; + } + +private: + std::unique_ptr OptionsParser; + std::string SourcePaths; + std::map VFS; +}; + +const char *TestToolExecutor::ExecutorName = "TestToolExecutor"; + +class TestToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + // Depending on the test runner, test cases might run in different threads + // in the same process, so we put test options in the stack and manually + // remove it from the global registry later so that tests do not interfere + // with each other. + llvm::cl::opt TestExecutor("test_executor", + llvm::cl::desc("Use TestToolExecutor")); + auto OptionsParser = CommonOptionsParser::create( + argc, argv, Category, llvm::cl::OneOrMore, /*Overview=*/nullptr); + TestExecutor.removeArgument(); + if (!OptionsParser) + return make_string_error("[TestToolExecutorPlugin] " + + llvm::toString(OptionsParser.takeError())); + if (!TestExecutor) + return make_string_error( + "[TestToolExecutorPlugin] --test_executor is not set."); + return llvm::make_unique(std::move(*OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +extern volatile int ToolExecutorPluginAnchorSource; + +static int LLVM_ATTRIBUTE_UNUSED TestToolExecutorPluginAnchorDest = + ToolExecutorPluginAnchorSource; + +static ToolExecutorPluginRegistry::Add + X("test-executor", "Plugin for TestToolExecutor."); + +llvm::cl::OptionCategory TestCategory("execution-test options"); + +TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) { + std::vector argv = {"prog", "--fake_flag_no_no_no", "f"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_FALSE((bool)Executor); + llvm::consumeError(Executor.takeError()); +} + +TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) { + llvm::cl::opt BeforeReset( + "before_reset", llvm::cl::desc("Defined before reset."), + llvm::cl::init("")); + + llvm::cl::ResetAllOptionOccurrences(); + + std::vector argv = {"prog", "--before_reset=set", "f"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(BeforeReset, "set"); + BeforeReset.removeArgument(); +} + +TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) { + std::vector argv = {"prog", "standalone.cpp"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), + StandaloneToolExecutor::ExecutorName); +} + +TEST(CreateToolExecutorTest, CreateTestToolExecutor) { + std::vector argv = {"prog", "test.cpp", "--test_executor"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName); +} + +TEST(StandaloneToolTest, SynctaxOnlyActionOnSimpleCode) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0;"); + + auto Err = Executor.execute( + ExecutionConfig(newFrontendActionFactory())); + ASSERT_TRUE(!Err); +} + +TEST(StandaloneToolTest, SimpleAction) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0;"); + + ExecutionContext *Context = Executor.getExecutionContext(); + + auto Err = Executor.execute(ExecutionConfig( + std::unique_ptr(new ReportResultActionFactory(Context)))); + ASSERT_TRUE(!Err); + auto KVs = Context->getToolResults()->AllKVResults(); + ASSERT_EQ(KVs.size(), 0u); +} + +TEST(StandaloneToolTest, SimpleActionWithResult) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0; void f() {}"); + + ExecutionContext *Context = Executor.getExecutionContext(); + + auto Err = Executor.execute(ExecutionConfig( + std::unique_ptr(new ReportResultActionFactory(Context)))); + ASSERT_TRUE(!Err); + auto KVs = Context->getToolResults()->AllKVResults(); + ASSERT_EQ(KVs.size(), 1u); + EXPECT_EQ("f", KVs[0].first); + EXPECT_EQ("1", KVs[0].second); + + Context->getToolResults()->forEachResult( + [](StringRef, StringRef Value) { EXPECT_EQ("1", Value); }); +} + +} // end namespace tooling +} // end namespace clang