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