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