1//===- lib/Tooling/AllTUsExecution.cpp - Execute actions on all TUs. ------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/Tooling/AllTUsExecution.h"
10#include "clang/Tooling/ToolExecutorPluginRegistry.h"
11#include "llvm/Support/Regex.h"
12#include "llvm/Support/ThreadPool.h"
13#include "llvm/Support/Threading.h"
14#include "llvm/Support/VirtualFileSystem.h"
15
16namespace clang {
17namespace tooling {
18
19const char *AllTUsToolExecutor::ExecutorName = "AllTUsToolExecutor";
20
21namespace {
22llvm::Error make_string_error(const llvm::Twine &Message) {
23  return llvm::make_error<llvm::StringError>(Message,
24                                             llvm::inconvertibleErrorCode());
25}
26
27ArgumentsAdjuster getDefaultArgumentsAdjusters() {
28  return combineAdjusters(
29      getClangStripOutputAdjuster(),
30      combineAdjusters(getClangSyntaxOnlyAdjuster(),
31                       getClangStripDependencyFileAdjuster()));
32}
33
34class ThreadSafeToolResults : public ToolResults {
35public:
36  void addResult(StringRef Key, StringRef Value) override {
37    std::unique_lock<std::mutex> LockGuard(Mutex);
38    Results.addResult(Key, Value);
39  }
40
41  std::vector<std::pair<llvm::StringRef, llvm::StringRef>>
42  AllKVResults() override {
43    return Results.AllKVResults();
44  }
45
46  void forEachResult(llvm::function_ref<void(StringRef Key, StringRef Value)>
47                         Callback) override {
48    Results.forEachResult(Callback);
49  }
50
51private:
52  InMemoryToolResults Results;
53  std::mutex Mutex;
54};
55
56} // namespace
57
58llvm::cl::opt<std::string>
59    Filter("filter",
60           llvm::cl::desc("Only process files that match this filter. "
61                          "This flag only applies to all-TUs."),
62           llvm::cl::init(".*"));
63
64AllTUsToolExecutor::AllTUsToolExecutor(
65    const CompilationDatabase &Compilations, unsigned ThreadCount,
66    std::shared_ptr<PCHContainerOperations> PCHContainerOps)
67    : Compilations(Compilations), Results(new ThreadSafeToolResults),
68      Context(Results.get()), ThreadCount(ThreadCount) {}
69
70AllTUsToolExecutor::AllTUsToolExecutor(
71    CommonOptionsParser Options, unsigned ThreadCount,
72    std::shared_ptr<PCHContainerOperations> PCHContainerOps)
73    : OptionsParser(std::move(Options)),
74      Compilations(OptionsParser->getCompilations()),
75      Results(new ThreadSafeToolResults), Context(Results.get()),
76      ThreadCount(ThreadCount) {}
77
78llvm::Error AllTUsToolExecutor::execute(
79    llvm::ArrayRef<
80        std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>>
81        Actions) {
82  if (Actions.empty())
83    return make_string_error("No action to execute.");
84
85  if (Actions.size() != 1)
86    return make_string_error(
87        "Only support executing exactly 1 action at this point.");
88
89  std::string ErrorMsg;
90  std::mutex TUMutex;
91  auto AppendError = [&](llvm::Twine Err) {
92    std::unique_lock<std::mutex> LockGuard(TUMutex);
93    ErrorMsg += Err.str();
94  };
95
96  auto Log = [&](llvm::Twine Msg) {
97    std::unique_lock<std::mutex> LockGuard(TUMutex);
98    llvm::errs() << Msg.str() << "\n";
99  };
100
101  std::vector<std::string> Files;
102  llvm::Regex RegexFilter(Filter);
103  for (const auto& File : Compilations.getAllFiles()) {
104    if (RegexFilter.match(File))
105      Files.push_back(File);
106  }
107  // Add a counter to track the progress.
108  const std::string TotalNumStr = std::to_string(Files.size());
109  unsigned Counter = 0;
110  auto Count = [&]() {
111    std::unique_lock<std::mutex> LockGuard(TUMutex);
112    return ++Counter;
113  };
114
115  auto &Action = Actions.front();
116
117  {
118    llvm::ThreadPool Pool(llvm::hardware_concurrency(ThreadCount));
119    for (std::string File : Files) {
120      Pool.async(
121          [&](std::string Path) {
122            Log("[" + std::to_string(Count()) + "/" + TotalNumStr +
123                "] Processing file " + Path);
124            // Each thread gets an indepent copy of a VFS to allow different
125            // concurrent working directories.
126            IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS =
127                llvm::vfs::createPhysicalFileSystem().release();
128            ClangTool Tool(Compilations, {Path},
129                           std::make_shared<PCHContainerOperations>(), FS);
130            Tool.appendArgumentsAdjuster(Action.second);
131            Tool.appendArgumentsAdjuster(getDefaultArgumentsAdjusters());
132            for (const auto &FileAndContent : OverlayFiles)
133              Tool.mapVirtualFile(FileAndContent.first(),
134                                  FileAndContent.second);
135            if (Tool.run(Action.first.get()))
136              AppendError(llvm::Twine("Failed to run action on ") + Path +
137                          "\n");
138          },
139          File);
140    }
141    // Make sure all tasks have finished before resetting the working directory.
142    Pool.wait();
143  }
144
145  if (!ErrorMsg.empty())
146    return make_string_error(ErrorMsg);
147
148  return llvm::Error::success();
149}
150
151llvm::cl::opt<unsigned> ExecutorConcurrency(
152    "execute-concurrency",
153    llvm::cl::desc("The number of threads used to process all files in "
154                   "parallel. Set to 0 for hardware concurrency. "
155                   "This flag only applies to all-TUs."),
156    llvm::cl::init(0));
157
158class AllTUsToolExecutorPlugin : public ToolExecutorPlugin {
159public:
160  llvm::Expected<std::unique_ptr<ToolExecutor>>
161  create(CommonOptionsParser &OptionsParser) override {
162    if (OptionsParser.getSourcePathList().empty())
163      return make_string_error(
164          "[AllTUsToolExecutorPlugin] Please provide a directory/file path in "
165          "the compilation database.");
166    return std::make_unique<AllTUsToolExecutor>(std::move(OptionsParser),
167                                                 ExecutorConcurrency);
168  }
169};
170
171static ToolExecutorPluginRegistry::Add<AllTUsToolExecutorPlugin>
172    X("all-TUs", "Runs FrontendActions on all TUs in the compilation database. "
173                 "Tool results are stored in memory.");
174
175// This anchor is used to force the linker to link in the generated object file
176// and thus register the plugin.
177volatile int AllTUsToolExecutorAnchorSource = 0;
178
179} // end namespace tooling
180} // end namespace clang
181