/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Tooling/Tooling.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===- Tooling.cpp - Running clang standalone tools -----------------------===// |
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 | | // This file implements functions to run clang tools standalone instead |
10 | | // of running them as a plugin. |
11 | | // |
12 | | //===----------------------------------------------------------------------===// |
13 | | |
14 | | #include "clang/Tooling/Tooling.h" |
15 | | #include "clang/Basic/Diagnostic.h" |
16 | | #include "clang/Basic/DiagnosticIDs.h" |
17 | | #include "clang/Basic/DiagnosticOptions.h" |
18 | | #include "clang/Basic/FileManager.h" |
19 | | #include "clang/Basic/FileSystemOptions.h" |
20 | | #include "clang/Basic/LLVM.h" |
21 | | #include "clang/Driver/Compilation.h" |
22 | | #include "clang/Driver/Driver.h" |
23 | | #include "clang/Driver/Job.h" |
24 | | #include "clang/Driver/Options.h" |
25 | | #include "clang/Driver/Tool.h" |
26 | | #include "clang/Driver/ToolChain.h" |
27 | | #include "clang/Frontend/ASTUnit.h" |
28 | | #include "clang/Frontend/CompilerInstance.h" |
29 | | #include "clang/Frontend/CompilerInvocation.h" |
30 | | #include "clang/Frontend/FrontendDiagnostic.h" |
31 | | #include "clang/Frontend/FrontendOptions.h" |
32 | | #include "clang/Frontend/TextDiagnosticPrinter.h" |
33 | | #include "clang/Lex/HeaderSearchOptions.h" |
34 | | #include "clang/Lex/PreprocessorOptions.h" |
35 | | #include "clang/Tooling/ArgumentsAdjusters.h" |
36 | | #include "clang/Tooling/CompilationDatabase.h" |
37 | | #include "llvm/ADT/ArrayRef.h" |
38 | | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
39 | | #include "llvm/ADT/SmallString.h" |
40 | | #include "llvm/ADT/StringRef.h" |
41 | | #include "llvm/ADT/Twine.h" |
42 | | #include "llvm/Option/ArgList.h" |
43 | | #include "llvm/Option/OptTable.h" |
44 | | #include "llvm/Option/Option.h" |
45 | | #include "llvm/Support/Casting.h" |
46 | | #include "llvm/Support/Debug.h" |
47 | | #include "llvm/Support/ErrorHandling.h" |
48 | | #include "llvm/Support/FileSystem.h" |
49 | | #include "llvm/Support/Host.h" |
50 | | #include "llvm/Support/MemoryBuffer.h" |
51 | | #include "llvm/Support/Path.h" |
52 | | #include "llvm/Support/VirtualFileSystem.h" |
53 | | #include "llvm/Support/raw_ostream.h" |
54 | | #include <cassert> |
55 | | #include <cstring> |
56 | | #include <memory> |
57 | | #include <string> |
58 | | #include <system_error> |
59 | | #include <utility> |
60 | | #include <vector> |
61 | | |
62 | | #define DEBUG_TYPE "clang-tooling" |
63 | | |
64 | | using namespace clang; |
65 | | using namespace tooling; |
66 | | |
67 | 38.4k | ToolAction::~ToolAction() = default; |
68 | | |
69 | 32.3k | FrontendActionFactory::~FrontendActionFactory() = default; |
70 | | |
71 | | // FIXME: This file contains structural duplication with other parts of the |
72 | | // code that sets up a compiler to run tools on it, and we should refactor |
73 | | // it to be based on the same framework. |
74 | | |
75 | | /// Builds a clang driver initialized for running clang tools. |
76 | | static driver::Driver * |
77 | | newDriver(DiagnosticsEngine *Diagnostics, const char *BinaryName, |
78 | 22.7k | IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) { |
79 | 22.7k | driver::Driver *CompilerDriver = |
80 | 22.7k | new driver::Driver(BinaryName, llvm::sys::getDefaultTargetTriple(), |
81 | 22.7k | *Diagnostics, "clang LLVM compiler", std::move(VFS)); |
82 | 22.7k | CompilerDriver->setTitle("clang_based_tool"); |
83 | 22.7k | return CompilerDriver; |
84 | 22.7k | } |
85 | | |
86 | | /// Retrieves the clang CC1 specific flags out of the compilation's jobs. |
87 | | /// |
88 | | /// Returns nullptr on error. |
89 | | static const llvm::opt::ArgStringList *getCC1Arguments( |
90 | 22.7k | DiagnosticsEngine *Diagnostics, driver::Compilation *Compilation) { |
91 | | // We expect to get back exactly one Command job, if we didn't something |
92 | | // failed. Extract that job from the Compilation. |
93 | 22.7k | const driver::JobList &Jobs = Compilation->getJobs(); |
94 | 22.7k | const driver::ActionList &Actions = Compilation->getActions(); |
95 | 22.7k | bool OffloadCompilation = false; |
96 | 22.7k | if (Jobs.size() > 1) { |
97 | 4 | for (auto A : Actions){ |
98 | | // On MacOSX real actions may end up being wrapped in BindArchAction |
99 | 4 | if (isa<driver::BindArchAction>(A)) |
100 | 3 | A = *A->input_begin(); |
101 | 4 | if (isa<driver::OffloadAction>(A)) { |
102 | | // Offload compilation has 2 top-level actions, one (at the front) is |
103 | | // the original host compilation and the other is offload action |
104 | | // composed of at least one device compilation. For such case, general |
105 | | // tooling will consider host-compilation only. For tooling on device |
106 | | // compilation, device compilation only option, such as |
107 | | // `--cuda-device-only`, needs specifying. |
108 | 1 | assert(Actions.size() > 1); |
109 | 1 | assert( |
110 | 1 | isa<driver::CompileJobAction>(Actions.front()) || |
111 | | // On MacOSX real actions may end up being wrapped in |
112 | | // BindArchAction. |
113 | 1 | (isa<driver::BindArchAction>(Actions.front()) && |
114 | 1 | isa<driver::CompileJobAction>(*Actions.front()->input_begin()))); |
115 | 1 | OffloadCompilation = true; |
116 | 1 | break; |
117 | 1 | } |
118 | 22.7k | } |
119 | 3 | } |
120 | 22.7k | if (Jobs.size() == 0 || !isa<driver::Command>(*Jobs.begin()) || |
121 | 22.7k | (Jobs.size() > 1 && !OffloadCompilation3 )) { |
122 | 3 | SmallString<256> error_msg; |
123 | 3 | llvm::raw_svector_ostream error_stream(error_msg); |
124 | 3 | Jobs.Print(error_stream, "; ", true); |
125 | 3 | Diagnostics->Report(diag::err_fe_expected_compiler_job) |
126 | 3 | << error_stream.str(); |
127 | 3 | return nullptr; |
128 | 3 | } |
129 | | |
130 | | // The one job we find should be to invoke clang again. |
131 | 22.7k | const auto &Cmd = cast<driver::Command>(*Jobs.begin()); |
132 | 22.7k | if (StringRef(Cmd.getCreator().getName()) != "clang") { |
133 | 0 | Diagnostics->Report(diag::err_fe_expected_clang_command); |
134 | 0 | return nullptr; |
135 | 0 | } |
136 | | |
137 | 22.7k | return &Cmd.getArguments(); |
138 | 22.7k | } |
139 | | |
140 | | namespace clang { |
141 | | namespace tooling { |
142 | | |
143 | | /// Returns a clang build invocation initialized from the CC1 flags. |
144 | | CompilerInvocation *newInvocation(DiagnosticsEngine *Diagnostics, |
145 | | const llvm::opt::ArgStringList &CC1Args, |
146 | 22.7k | const char *const BinaryName) { |
147 | 22.7k | assert(!CC1Args.empty() && "Must at least contain the program name!"); |
148 | 22.7k | CompilerInvocation *Invocation = new CompilerInvocation; |
149 | 22.7k | CompilerInvocation::CreateFromArgs(*Invocation, CC1Args, *Diagnostics, |
150 | 22.7k | BinaryName); |
151 | 22.7k | Invocation->getFrontendOpts().DisableFree = false; |
152 | 22.7k | Invocation->getCodeGenOpts().DisableFree = false; |
153 | 22.7k | return Invocation; |
154 | 22.7k | } |
155 | | |
156 | | bool runToolOnCode(std::unique_ptr<FrontendAction> ToolAction, |
157 | | const Twine &Code, const Twine &FileName, |
158 | 118 | std::shared_ptr<PCHContainerOperations> PCHContainerOps) { |
159 | 118 | return runToolOnCodeWithArgs(std::move(ToolAction), Code, |
160 | 118 | std::vector<std::string>(), FileName, |
161 | 118 | "clang-tool", std::move(PCHContainerOps)); |
162 | 118 | } |
163 | | |
164 | | } // namespace tooling |
165 | | } // namespace clang |
166 | | |
167 | | static std::vector<std::string> |
168 | | getSyntaxOnlyToolArgs(const Twine &ToolName, |
169 | | const std::vector<std::string> &ExtraArgs, |
170 | 22.3k | StringRef FileName) { |
171 | 22.3k | std::vector<std::string> Args; |
172 | 22.3k | Args.push_back(ToolName.str()); |
173 | 22.3k | Args.push_back("-fsyntax-only"); |
174 | 22.3k | Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); |
175 | 22.3k | Args.push_back(FileName.str()); |
176 | 22.3k | return Args; |
177 | 22.3k | } |
178 | | |
179 | | namespace clang { |
180 | | namespace tooling { |
181 | | |
182 | | bool runToolOnCodeWithArgs( |
183 | | std::unique_ptr<FrontendAction> ToolAction, const Twine &Code, |
184 | | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, |
185 | | const std::vector<std::string> &Args, const Twine &FileName, |
186 | | const Twine &ToolName, |
187 | 16.3k | std::shared_ptr<PCHContainerOperations> PCHContainerOps) { |
188 | 16.3k | SmallString<16> FileNameStorage; |
189 | 16.3k | StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); |
190 | | |
191 | 16.3k | llvm::IntrusiveRefCntPtr<FileManager> Files( |
192 | 16.3k | new FileManager(FileSystemOptions(), VFS)); |
193 | 16.3k | ArgumentsAdjuster Adjuster = getClangStripDependencyFileAdjuster(); |
194 | 16.3k | ToolInvocation Invocation( |
195 | 16.3k | getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileNameRef), FileNameRef), |
196 | 16.3k | std::move(ToolAction), Files.get(), std::move(PCHContainerOps)); |
197 | 16.3k | return Invocation.run(); |
198 | 16.3k | } |
199 | | |
200 | | bool runToolOnCodeWithArgs( |
201 | | std::unique_ptr<FrontendAction> ToolAction, const Twine &Code, |
202 | | const std::vector<std::string> &Args, const Twine &FileName, |
203 | | const Twine &ToolName, |
204 | | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
205 | 16.3k | const FileContentMappings &VirtualMappedFiles) { |
206 | 16.3k | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
207 | 16.3k | new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
208 | 16.3k | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
209 | 16.3k | new llvm::vfs::InMemoryFileSystem); |
210 | 16.3k | OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
211 | | |
212 | 16.3k | SmallString<1024> CodeStorage; |
213 | 16.3k | InMemoryFileSystem->addFile(FileName, 0, |
214 | 16.3k | llvm::MemoryBuffer::getMemBuffer( |
215 | 16.3k | Code.toNullTerminatedStringRef(CodeStorage))); |
216 | | |
217 | 1.11k | for (auto &FilenameWithContent : VirtualMappedFiles) { |
218 | 1.11k | InMemoryFileSystem->addFile( |
219 | 1.11k | FilenameWithContent.first, 0, |
220 | 1.11k | llvm::MemoryBuffer::getMemBuffer(FilenameWithContent.second)); |
221 | 1.11k | } |
222 | | |
223 | 16.3k | return runToolOnCodeWithArgs(std::move(ToolAction), Code, OverlayFileSystem, |
224 | 16.3k | Args, FileName, ToolName); |
225 | 16.3k | } |
226 | | |
227 | | llvm::Expected<std::string> getAbsolutePath(llvm::vfs::FileSystem &FS, |
228 | 432 | StringRef File) { |
229 | 432 | StringRef RelativePath(File); |
230 | | // FIXME: Should '.\\' be accepted on Win32? |
231 | 432 | if (RelativePath.startswith("./")) { |
232 | 0 | RelativePath = RelativePath.substr(strlen("./")); |
233 | 0 | } |
234 | | |
235 | 432 | SmallString<1024> AbsolutePath = RelativePath; |
236 | 432 | if (auto EC = FS.makeAbsolute(AbsolutePath)) |
237 | 0 | return llvm::errorCodeToError(EC); |
238 | 432 | llvm::sys::path::native(AbsolutePath); |
239 | 432 | return std::string(AbsolutePath.str()); |
240 | 432 | } |
241 | | |
242 | 22 | std::string getAbsolutePath(StringRef File) { |
243 | 22 | return llvm::cantFail(getAbsolutePath(*llvm::vfs::getRealFileSystem(), File)); |
244 | 22 | } |
245 | | |
246 | | void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine, |
247 | 26 | StringRef InvokedAs) { |
248 | 26 | if (CommandLine.empty() || InvokedAs.empty()) |
249 | 1 | return; |
250 | 25 | const auto &Table = driver::getDriverOptTable(); |
251 | | // --target=X |
252 | 25 | const std::string TargetOPT = |
253 | 25 | Table.getOption(driver::options::OPT_target).getPrefixedName(); |
254 | | // -target X |
255 | 25 | const std::string TargetOPTLegacy = |
256 | 25 | Table.getOption(driver::options::OPT_target_legacy_spelling) |
257 | 25 | .getPrefixedName(); |
258 | | // --driver-mode=X |
259 | 25 | const std::string DriverModeOPT = |
260 | 25 | Table.getOption(driver::options::OPT_driver_mode).getPrefixedName(); |
261 | 25 | auto TargetMode = |
262 | 25 | driver::ToolChain::getTargetAndModeFromProgramName(InvokedAs); |
263 | | // No need to search for target args if we don't have a target/mode to insert. |
264 | 25 | bool ShouldAddTarget = TargetMode.TargetIsValid; |
265 | 25 | bool ShouldAddMode = TargetMode.DriverMode != nullptr; |
266 | | // Skip CommandLine[0]. |
267 | 96 | for (auto Token = ++CommandLine.begin(); Token != CommandLine.end(); |
268 | 71 | ++Token) { |
269 | 71 | StringRef TokenRef(*Token); |
270 | 71 | ShouldAddTarget = ShouldAddTarget && !TokenRef.startswith(TargetOPT)8 && |
271 | 7 | !TokenRef.equals(TargetOPTLegacy); |
272 | 71 | ShouldAddMode = ShouldAddMode && !TokenRef.startswith(DriverModeOPT)62 ; |
273 | 71 | } |
274 | 25 | if (ShouldAddMode) { |
275 | 20 | CommandLine.insert(++CommandLine.begin(), TargetMode.DriverMode); |
276 | 20 | } |
277 | 25 | if (ShouldAddTarget) { |
278 | 3 | CommandLine.insert(++CommandLine.begin(), |
279 | 3 | TargetOPT + TargetMode.TargetPrefix); |
280 | 3 | } |
281 | 25 | } |
282 | | |
283 | | } // namespace tooling |
284 | | } // namespace clang |
285 | | |
286 | | namespace { |
287 | | |
288 | | class SingleFrontendActionFactory : public FrontendActionFactory { |
289 | | std::unique_ptr<FrontendAction> Action; |
290 | | |
291 | | public: |
292 | | SingleFrontendActionFactory(std::unique_ptr<FrontendAction> Action) |
293 | 16.3k | : Action(std::move(Action)) {} |
294 | | |
295 | 16.3k | std::unique_ptr<FrontendAction> create() override { |
296 | 16.3k | return std::move(Action); |
297 | 16.3k | } |
298 | | }; |
299 | | |
300 | | } // namespace |
301 | | |
302 | | ToolInvocation::ToolInvocation( |
303 | | std::vector<std::string> CommandLine, ToolAction *Action, |
304 | | FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) |
305 | | : CommandLine(std::move(CommandLine)), Action(Action), OwnsAction(false), |
306 | 6.45k | Files(Files), PCHContainerOps(std::move(PCHContainerOps)) {} |
307 | | |
308 | | ToolInvocation::ToolInvocation( |
309 | | std::vector<std::string> CommandLine, |
310 | | std::unique_ptr<FrontendAction> FAction, FileManager *Files, |
311 | | std::shared_ptr<PCHContainerOperations> PCHContainerOps) |
312 | | : CommandLine(std::move(CommandLine)), |
313 | | Action(new SingleFrontendActionFactory(std::move(FAction))), |
314 | | OwnsAction(true), Files(Files), |
315 | 16.3k | PCHContainerOps(std::move(PCHContainerOps)) {} |
316 | | |
317 | 22.7k | ToolInvocation::~ToolInvocation() { |
318 | 22.7k | if (OwnsAction) |
319 | 16.3k | delete Action; |
320 | 22.7k | } |
321 | | |
322 | 22.7k | bool ToolInvocation::run() { |
323 | 22.7k | std::vector<const char*> Argv; |
324 | 22.7k | for (const std::string &Str : CommandLine) |
325 | 221k | Argv.push_back(Str.c_str()); |
326 | 22.7k | const char *const BinaryName = Argv[0]; |
327 | 22.7k | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); |
328 | 22.7k | unsigned MissingArgIndex, MissingArgCount; |
329 | 22.7k | llvm::opt::InputArgList ParsedArgs = driver::getDriverOptTable().ParseArgs( |
330 | 22.7k | ArrayRef<const char *>(Argv).slice(1), MissingArgIndex, MissingArgCount); |
331 | 22.7k | ParseDiagnosticArgs(*DiagOpts, ParsedArgs); |
332 | 22.7k | TextDiagnosticPrinter DiagnosticPrinter( |
333 | 22.7k | llvm::errs(), &*DiagOpts); |
334 | 22.7k | DiagnosticsEngine Diagnostics( |
335 | 22.7k | IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, |
336 | 22.6k | DiagConsumer ? DiagConsumer78 : &DiagnosticPrinter, false); |
337 | | |
338 | 22.7k | const std::unique_ptr<driver::Driver> Driver( |
339 | 22.7k | newDriver(&Diagnostics, BinaryName, &Files->getVirtualFileSystem())); |
340 | | // The "input file not found" diagnostics from the driver are useful. |
341 | | // The driver is only aware of the VFS working directory, but some clients |
342 | | // change this at the FileManager level instead. |
343 | | // In this case the checks have false positives, so skip them. |
344 | 22.7k | if (!Files->getFileSystemOpts().WorkingDir.empty()) |
345 | 28 | Driver->setCheckInputsExist(false); |
346 | 22.7k | const std::unique_ptr<driver::Compilation> Compilation( |
347 | 22.7k | Driver->BuildCompilation(llvm::makeArrayRef(Argv))); |
348 | 22.7k | if (!Compilation) |
349 | 0 | return false; |
350 | 22.7k | const llvm::opt::ArgStringList *const CC1Args = getCC1Arguments( |
351 | 22.7k | &Diagnostics, Compilation.get()); |
352 | 22.7k | if (!CC1Args) |
353 | 3 | return false; |
354 | 22.7k | std::unique_ptr<CompilerInvocation> Invocation( |
355 | 22.7k | newInvocation(&Diagnostics, *CC1Args, BinaryName)); |
356 | 22.7k | return runInvocation(BinaryName, Compilation.get(), std::move(Invocation), |
357 | 22.7k | std::move(PCHContainerOps)); |
358 | 22.7k | } |
359 | | |
360 | | bool ToolInvocation::runInvocation( |
361 | | const char *BinaryName, driver::Compilation *Compilation, |
362 | | std::shared_ptr<CompilerInvocation> Invocation, |
363 | 22.7k | std::shared_ptr<PCHContainerOperations> PCHContainerOps) { |
364 | | // Show the invocation, with -v. |
365 | 22.7k | if (Invocation->getHeaderSearchOpts().Verbose) { |
366 | 1 | llvm::errs() << "clang Invocation:\n"; |
367 | 1 | Compilation->getJobs().Print(llvm::errs(), "\n", true); |
368 | 1 | llvm::errs() << "\n"; |
369 | 1 | } |
370 | | |
371 | 22.7k | return Action->runInvocation(std::move(Invocation), Files, |
372 | 22.7k | std::move(PCHContainerOps), DiagConsumer); |
373 | 22.7k | } |
374 | | |
375 | | bool FrontendActionFactory::runInvocation( |
376 | | std::shared_ptr<CompilerInvocation> Invocation, FileManager *Files, |
377 | | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
378 | 16.6k | DiagnosticConsumer *DiagConsumer) { |
379 | | // Create a compiler instance to handle the actual work. |
380 | 16.6k | CompilerInstance Compiler(std::move(PCHContainerOps)); |
381 | 16.6k | Compiler.setInvocation(std::move(Invocation)); |
382 | 16.6k | Compiler.setFileManager(Files); |
383 | | |
384 | | // The FrontendAction can have lifetime requirements for Compiler or its |
385 | | // members, and we need to ensure it's deleted earlier than Compiler. So we |
386 | | // pass it to an std::unique_ptr declared after the Compiler variable. |
387 | 16.6k | std::unique_ptr<FrontendAction> ScopedToolAction(create()); |
388 | | |
389 | | // Create the compiler's actual diagnostics engine. |
390 | 16.6k | Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); |
391 | 16.6k | if (!Compiler.hasDiagnostics()) |
392 | 0 | return false; |
393 | | |
394 | 16.6k | Compiler.createSourceManager(*Files); |
395 | | |
396 | 16.6k | const bool Success = Compiler.ExecuteAction(*ScopedToolAction); |
397 | | |
398 | 16.6k | Files->clearStatCache(); |
399 | 16.6k | return Success; |
400 | 16.6k | } |
401 | | |
402 | | ClangTool::ClangTool(const CompilationDatabase &Compilations, |
403 | | ArrayRef<std::string> SourcePaths, |
404 | | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
405 | | IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS, |
406 | | IntrusiveRefCntPtr<FileManager> Files) |
407 | | : Compilations(Compilations), SourcePaths(SourcePaths), |
408 | | PCHContainerOps(std::move(PCHContainerOps)), |
409 | | OverlayFileSystem(new llvm::vfs::OverlayFileSystem(std::move(BaseFS))), |
410 | | InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), |
411 | | Files(Files ? Files |
412 | 341 | : new FileManager(FileSystemOptions(), OverlayFileSystem)) { |
413 | 341 | OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
414 | 341 | appendArgumentsAdjuster(getClangStripOutputAdjuster()); |
415 | 341 | appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); |
416 | 341 | appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); |
417 | 341 | if (Files) |
418 | 71 | Files->setVirtualFileSystem(OverlayFileSystem); |
419 | 341 | } |
420 | | |
421 | 341 | ClangTool::~ClangTool() = default; |
422 | | |
423 | 9.39k | void ClangTool::mapVirtualFile(StringRef FilePath, StringRef Content) { |
424 | 9.39k | MappedFileContents.push_back(std::make_pair(FilePath, Content)); |
425 | 9.39k | } |
426 | | |
427 | 1.35k | void ClangTool::appendArgumentsAdjuster(ArgumentsAdjuster Adjuster) { |
428 | 1.35k | ArgsAdjuster = combineAdjusters(std::move(ArgsAdjuster), std::move(Adjuster)); |
429 | 1.35k | } |
430 | | |
431 | 123 | void ClangTool::clearArgumentsAdjusters() { |
432 | 123 | ArgsAdjuster = nullptr; |
433 | 123 | } |
434 | | |
435 | | static void injectResourceDir(CommandLineArguments &Args, const char *Argv0, |
436 | 415 | void *MainAddr) { |
437 | | // Allow users to override the resource dir. |
438 | 415 | for (StringRef Arg : Args) |
439 | 2.46k | if (Arg.startswith("-resource-dir")) |
440 | 0 | return; |
441 | | |
442 | | // If there's no override in place add our resource dir. |
443 | 415 | Args.push_back("-resource-dir=" + |
444 | 415 | CompilerInvocation::GetResourcesPath(Argv0, MainAddr)); |
445 | 415 | } |
446 | | |
447 | 406 | int ClangTool::run(ToolAction *Action) { |
448 | | // Exists solely for the purpose of lookup of the resource path. |
449 | | // This just needs to be some symbol in the binary. |
450 | 406 | static int StaticSymbol; |
451 | | |
452 | | // First insert all absolute paths into the in-memory VFS. These are global |
453 | | // for all compile commands. |
454 | 406 | if (SeenWorkingDirectories.insert("/").second) |
455 | 339 | for (const auto &MappedFile : MappedFileContents) |
456 | 9.52k | if (llvm::sys::path::is_absolute(MappedFile.first)) |
457 | 13 | InMemoryFileSystem->addFile( |
458 | 13 | MappedFile.first, 0, |
459 | 13 | llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); |
460 | | |
461 | 406 | bool ProcessingFailed = false; |
462 | 406 | bool FileSkipped = false; |
463 | | // Compute all absolute paths before we run any actions, as those will change |
464 | | // the working directory. |
465 | 406 | std::vector<std::string> AbsolutePaths; |
466 | 406 | AbsolutePaths.reserve(SourcePaths.size()); |
467 | 414 | for (const auto &SourcePath : SourcePaths) { |
468 | 414 | auto AbsPath = getAbsolutePath(*OverlayFileSystem, SourcePath); |
469 | 414 | if (!AbsPath) { |
470 | 0 | llvm::errs() << "Skipping " << SourcePath |
471 | 0 | << ". Error while getting an absolute path: " |
472 | 0 | << llvm::toString(AbsPath.takeError()) << "\n"; |
473 | 0 | continue; |
474 | 0 | } |
475 | 414 | AbsolutePaths.push_back(std::move(*AbsPath)); |
476 | 414 | } |
477 | | |
478 | | // Remember the working directory in case we need to restore it. |
479 | 406 | std::string InitialWorkingDir; |
480 | 406 | if (RestoreCWD) { |
481 | 335 | if (auto CWD = OverlayFileSystem->getCurrentWorkingDirectory()) { |
482 | 334 | InitialWorkingDir = std::move(*CWD); |
483 | 1 | } else { |
484 | 1 | llvm::errs() << "Could not get working directory: " |
485 | 1 | << CWD.getError().message() << "\n"; |
486 | 1 | } |
487 | 335 | } |
488 | | |
489 | 411 | for (llvm::StringRef File : AbsolutePaths) { |
490 | | // Currently implementations of CompilationDatabase::getCompileCommands can |
491 | | // change the state of the file system (e.g. prepare generated headers), so |
492 | | // this method needs to run right before we invoke the tool, as the next |
493 | | // file may require a different (incompatible) state of the file system. |
494 | | // |
495 | | // FIXME: Make the compilation database interface more explicit about the |
496 | | // requirements to the order of invocation of its members. |
497 | 411 | std::vector<CompileCommand> CompileCommandsForFile = |
498 | 411 | Compilations.getCompileCommands(File); |
499 | 411 | if (CompileCommandsForFile.empty()) { |
500 | 0 | llvm::errs() << "Skipping " << File << ". Compile command not found.\n"; |
501 | 0 | FileSkipped = true; |
502 | 0 | continue; |
503 | 0 | } |
504 | 415 | for (CompileCommand &CompileCommand : CompileCommandsForFile)411 { |
505 | | // FIXME: chdir is thread hostile; on the other hand, creating the same |
506 | | // behavior as chdir is complex: chdir resolves the path once, thus |
507 | | // guaranteeing that all subsequent relative path operations work |
508 | | // on the same path the original chdir resulted in. This makes a |
509 | | // difference for example on network filesystems, where symlinks might be |
510 | | // switched during runtime of the tool. Fixing this depends on having a |
511 | | // file system abstraction that allows openat() style interactions. |
512 | 415 | if (OverlayFileSystem->setCurrentWorkingDirectory( |
513 | 415 | CompileCommand.Directory)) |
514 | 0 | llvm::report_fatal_error("Cannot chdir into \"" + |
515 | 0 | Twine(CompileCommand.Directory) + "\"!"); |
516 | | |
517 | | // Now fill the in-memory VFS with the relative file mappings so it will |
518 | | // have the correct relative paths. We never remove mappings but that |
519 | | // should be fine. |
520 | 415 | if (SeenWorkingDirectories.insert(CompileCommand.Directory).second) |
521 | 327 | for (const auto &MappedFile : MappedFileContents) |
522 | 9.97k | if (!llvm::sys::path::is_absolute(MappedFile.first)) |
523 | 9.92k | InMemoryFileSystem->addFile( |
524 | 9.92k | MappedFile.first, 0, |
525 | 9.92k | llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); |
526 | | |
527 | 415 | std::vector<std::string> CommandLine = CompileCommand.CommandLine; |
528 | 415 | if (ArgsAdjuster) |
529 | 334 | CommandLine = ArgsAdjuster(CommandLine, CompileCommand.Filename); |
530 | 415 | assert(!CommandLine.empty()); |
531 | | |
532 | | // Add the resource dir based on the binary of this tool. argv[0] in the |
533 | | // compilation database may refer to a different compiler and we want to |
534 | | // pick up the very same standard library that compiler is using. The |
535 | | // builtin headers in the resource dir need to match the exact clang |
536 | | // version the tool is using. |
537 | | // FIXME: On linux, GetMainExecutable is independent of the value of the |
538 | | // first argument, thus allowing ClangTool and runToolOnCode to just |
539 | | // pass in made-up names here. Make sure this works on other platforms. |
540 | 415 | injectResourceDir(CommandLine, "clang_tool", &StaticSymbol); |
541 | | |
542 | | // FIXME: We need a callback mechanism for the tool writer to output a |
543 | | // customized message for each file. |
544 | 415 | LLVM_DEBUG({ llvm::dbgs() << "Processing: " << File << ".\n"; }); |
545 | 415 | ToolInvocation Invocation(std::move(CommandLine), Action, Files.get(), |
546 | 415 | PCHContainerOps); |
547 | 415 | Invocation.setDiagnosticConsumer(DiagConsumer); |
548 | | |
549 | 415 | if (!Invocation.run()) { |
550 | | // FIXME: Diagnostics should be used instead. |
551 | 23 | if (PrintErrorMessage) |
552 | 20 | llvm::errs() << "Error while processing " << File << ".\n"; |
553 | 23 | ProcessingFailed = true; |
554 | 23 | } |
555 | 415 | } |
556 | 411 | } |
557 | | |
558 | 406 | if (!InitialWorkingDir.empty()) { |
559 | 335 | if (auto EC = |
560 | 0 | OverlayFileSystem->setCurrentWorkingDirectory(InitialWorkingDir)) |
561 | 0 | llvm::errs() << "Error when trying to restore working dir: " |
562 | 0 | << EC.message() << "\n"; |
563 | 335 | } |
564 | 383 | return ProcessingFailed ? 123 : (FileSkipped ? 20 : 0); |
565 | 406 | } |
566 | | |
567 | | namespace { |
568 | | |
569 | | class ASTBuilderAction : public ToolAction { |
570 | | std::vector<std::unique_ptr<ASTUnit>> &ASTs; |
571 | | |
572 | | public: |
573 | 6.05k | ASTBuilderAction(std::vector<std::unique_ptr<ASTUnit>> &ASTs) : ASTs(ASTs) {} |
574 | | |
575 | | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
576 | | FileManager *Files, |
577 | | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
578 | 6.05k | DiagnosticConsumer *DiagConsumer) override { |
579 | 6.05k | std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( |
580 | 6.05k | Invocation, std::move(PCHContainerOps), |
581 | 6.05k | CompilerInstance::createDiagnostics(&Invocation->getDiagnosticOpts(), |
582 | 6.05k | DiagConsumer, |
583 | 6.05k | /*ShouldOwnClient=*/false), |
584 | 6.05k | Files); |
585 | 6.05k | if (!AST) |
586 | 0 | return false; |
587 | | |
588 | 6.05k | ASTs.push_back(std::move(AST)); |
589 | 6.05k | return true; |
590 | 6.05k | } |
591 | | }; |
592 | | |
593 | | } // namespace |
594 | | |
595 | 19 | int ClangTool::buildASTs(std::vector<std::unique_ptr<ASTUnit>> &ASTs) { |
596 | 19 | ASTBuilderAction Action(ASTs); |
597 | 19 | return run(&Action); |
598 | 19 | } |
599 | | |
600 | 71 | void ClangTool::setRestoreWorkingDir(bool RestoreCWD) { |
601 | 71 | this->RestoreCWD = RestoreCWD; |
602 | 71 | } |
603 | | |
604 | 72 | void ClangTool::setPrintErrorMessage(bool PrintErrorMessage) { |
605 | 72 | this->PrintErrorMessage = PrintErrorMessage; |
606 | 72 | } |
607 | | |
608 | | namespace clang { |
609 | | namespace tooling { |
610 | | |
611 | | std::unique_ptr<ASTUnit> |
612 | | buildASTFromCode(StringRef Code, StringRef FileName, |
613 | 116 | std::shared_ptr<PCHContainerOperations> PCHContainerOps) { |
614 | 116 | return buildASTFromCodeWithArgs(Code, std::vector<std::string>(), FileName, |
615 | 116 | "clang-tool", std::move(PCHContainerOps)); |
616 | 116 | } |
617 | | |
618 | | std::unique_ptr<ASTUnit> buildASTFromCodeWithArgs( |
619 | | StringRef Code, const std::vector<std::string> &Args, StringRef FileName, |
620 | | StringRef ToolName, std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
621 | | ArgumentsAdjuster Adjuster, const FileContentMappings &VirtualMappedFiles, |
622 | 6.03k | DiagnosticConsumer *DiagConsumer) { |
623 | 6.03k | std::vector<std::unique_ptr<ASTUnit>> ASTs; |
624 | 6.03k | ASTBuilderAction Action(ASTs); |
625 | 6.03k | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
626 | 6.03k | new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
627 | 6.03k | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
628 | 6.03k | new llvm::vfs::InMemoryFileSystem); |
629 | 6.03k | OverlayFileSystem->pushOverlay(InMemoryFileSystem); |
630 | 6.03k | llvm::IntrusiveRefCntPtr<FileManager> Files( |
631 | 6.03k | new FileManager(FileSystemOptions(), OverlayFileSystem)); |
632 | | |
633 | 6.03k | ToolInvocation Invocation( |
634 | 6.03k | getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileName), FileName), |
635 | 6.03k | &Action, Files.get(), std::move(PCHContainerOps)); |
636 | 6.03k | Invocation.setDiagnosticConsumer(DiagConsumer); |
637 | | |
638 | 6.03k | InMemoryFileSystem->addFile(FileName, 0, |
639 | 6.03k | llvm::MemoryBuffer::getMemBufferCopy(Code)); |
640 | 0 | for (auto &FilenameWithContent : VirtualMappedFiles) { |
641 | 0 | InMemoryFileSystem->addFile( |
642 | 0 | FilenameWithContent.first, 0, |
643 | 0 | llvm::MemoryBuffer::getMemBuffer(FilenameWithContent.second)); |
644 | 0 | } |
645 | | |
646 | 6.03k | if (!Invocation.run()) |
647 | 0 | return nullptr; |
648 | | |
649 | 6.03k | assert(ASTs.size() == 1); |
650 | 6.03k | return std::move(ASTs[0]); |
651 | 6.03k | } |
652 | | |
653 | | } // namespace tooling |
654 | | } // namespace clang |