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