/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Tooling/CompilationDatabase.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===- CompilationDatabase.cpp --------------------------------------------===// |
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 contains implementations of the CompilationDatabase base class |
10 | | // and the FixedCompilationDatabase. |
11 | | // |
12 | | // FIXME: Various functions that take a string &ErrorMessage should be upgraded |
13 | | // to Expected. |
14 | | // |
15 | | //===----------------------------------------------------------------------===// |
16 | | |
17 | | #include "clang/Tooling/CompilationDatabase.h" |
18 | | #include "clang/Basic/Diagnostic.h" |
19 | | #include "clang/Basic/DiagnosticIDs.h" |
20 | | #include "clang/Basic/DiagnosticOptions.h" |
21 | | #include "clang/Basic/LLVM.h" |
22 | | #include "clang/Driver/Action.h" |
23 | | #include "clang/Driver/Compilation.h" |
24 | | #include "clang/Driver/Driver.h" |
25 | | #include "clang/Driver/DriverDiagnostic.h" |
26 | | #include "clang/Driver/Job.h" |
27 | | #include "clang/Frontend/TextDiagnosticPrinter.h" |
28 | | #include "clang/Tooling/CompilationDatabasePluginRegistry.h" |
29 | | #include "clang/Tooling/Tooling.h" |
30 | | #include "llvm/ADT/ArrayRef.h" |
31 | | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
32 | | #include "llvm/ADT/STLExtras.h" |
33 | | #include "llvm/ADT/SmallString.h" |
34 | | #include "llvm/ADT/SmallVector.h" |
35 | | #include "llvm/ADT/StringRef.h" |
36 | | #include "llvm/Option/Arg.h" |
37 | | #include "llvm/Support/Casting.h" |
38 | | #include "llvm/Support/Compiler.h" |
39 | | #include "llvm/Support/ErrorOr.h" |
40 | | #include "llvm/Support/Host.h" |
41 | | #include "llvm/Support/LineIterator.h" |
42 | | #include "llvm/Support/MemoryBuffer.h" |
43 | | #include "llvm/Support/Path.h" |
44 | | #include "llvm/Support/raw_ostream.h" |
45 | | #include <algorithm> |
46 | | #include <cassert> |
47 | | #include <cstring> |
48 | | #include <iterator> |
49 | | #include <memory> |
50 | | #include <sstream> |
51 | | #include <string> |
52 | | #include <system_error> |
53 | | #include <utility> |
54 | | #include <vector> |
55 | | |
56 | | using namespace clang; |
57 | | using namespace tooling; |
58 | | |
59 | | LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) |
60 | | |
61 | 666 | CompilationDatabase::~CompilationDatabase() = default; |
62 | | |
63 | | std::unique_ptr<CompilationDatabase> |
64 | | CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, |
65 | 63 | std::string &ErrorMessage) { |
66 | 63 | llvm::raw_string_ostream ErrorStream(ErrorMessage); |
67 | 63 | for (const CompilationDatabasePluginRegistry::entry &Database : |
68 | 124 | CompilationDatabasePluginRegistry::entries()) { |
69 | 124 | std::string DatabaseErrorMessage; |
70 | 124 | std::unique_ptr<CompilationDatabasePlugin> Plugin(Database.instantiate()); |
71 | 124 | if (std::unique_ptr<CompilationDatabase> DB = |
72 | 124 | Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) |
73 | 22 | return DB; |
74 | 102 | ErrorStream << Database.getName() << ": " << DatabaseErrorMessage << "\n"; |
75 | 102 | } |
76 | 41 | return nullptr; |
77 | 63 | } |
78 | | |
79 | | static std::unique_ptr<CompilationDatabase> |
80 | | findCompilationDatabaseFromDirectory(StringRef Directory, |
81 | 23 | std::string &ErrorMessage) { |
82 | 23 | std::stringstream ErrorStream; |
83 | 23 | bool HasErrorMessage = false; |
84 | 64 | while (!Directory.empty()) { |
85 | 62 | std::string LoadErrorMessage; |
86 | | |
87 | 62 | if (std::unique_ptr<CompilationDatabase> DB = |
88 | 62 | CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) |
89 | 21 | return DB; |
90 | | |
91 | 41 | if (!HasErrorMessage) { |
92 | 10 | ErrorStream << "No compilation database found in " << Directory.str() |
93 | 10 | << " or any parent directory\n" << LoadErrorMessage; |
94 | 10 | HasErrorMessage = true; |
95 | 10 | } |
96 | | |
97 | 41 | Directory = llvm::sys::path::parent_path(Directory); |
98 | 41 | } |
99 | 2 | ErrorMessage = ErrorStream.str(); |
100 | 2 | return nullptr; |
101 | 23 | } |
102 | | |
103 | | std::unique_ptr<CompilationDatabase> |
104 | | CompilationDatabase::autoDetectFromSource(StringRef SourceFile, |
105 | 13 | std::string &ErrorMessage) { |
106 | 13 | SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); |
107 | 13 | StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); |
108 | | |
109 | 13 | std::unique_ptr<CompilationDatabase> DB = |
110 | 13 | findCompilationDatabaseFromDirectory(Directory, ErrorMessage); |
111 | | |
112 | 13 | if (!DB) |
113 | 2 | ErrorMessage = ("Could not auto-detect compilation database for file \"" + |
114 | 2 | SourceFile + "\"\n" + ErrorMessage).str(); |
115 | 13 | return DB; |
116 | 13 | } |
117 | | |
118 | | std::unique_ptr<CompilationDatabase> |
119 | | CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, |
120 | 10 | std::string &ErrorMessage) { |
121 | 10 | SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); |
122 | | |
123 | 10 | std::unique_ptr<CompilationDatabase> DB = |
124 | 10 | findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); |
125 | | |
126 | 10 | if (!DB) |
127 | 0 | ErrorMessage = ("Could not auto-detect compilation database from directory \"" + |
128 | 0 | SourceDir + "\"\n" + ErrorMessage).str(); |
129 | 10 | return DB; |
130 | 10 | } |
131 | | |
132 | 1 | std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const { |
133 | 1 | std::vector<CompileCommand> Result; |
134 | 1 | for (const auto &File : getAllFiles()) { |
135 | 0 | auto C = getCompileCommands(File); |
136 | 0 | std::move(C.begin(), C.end(), std::back_inserter(Result)); |
137 | 0 | } |
138 | 1 | return Result; |
139 | 1 | } |
140 | | |
141 | 124 | CompilationDatabasePlugin::~CompilationDatabasePlugin() = default; |
142 | | |
143 | | namespace { |
144 | | |
145 | | // Helper for recursively searching through a chain of actions and collecting |
146 | | // all inputs, direct and indirect, of compile jobs. |
147 | | struct CompileJobAnalyzer { |
148 | | SmallVector<std::string, 2> Inputs; |
149 | | |
150 | 133 | void run(const driver::Action *A) { |
151 | 133 | runImpl(A, false); |
152 | 133 | } |
153 | | |
154 | | private: |
155 | 653 | void runImpl(const driver::Action *A, bool Collect) { |
156 | 653 | bool CollectChildren = Collect; |
157 | 653 | switch (A->getKind()) { |
158 | 133 | case driver::Action::CompileJobClass: |
159 | 133 | CollectChildren = true; |
160 | 133 | break; |
161 | | |
162 | 133 | case driver::Action::InputClass: |
163 | 133 | if (Collect) { |
164 | 133 | const auto *IA = cast<driver::InputAction>(A); |
165 | 133 | Inputs.push_back(std::string(IA->getInputArg().getSpelling())); |
166 | 133 | } |
167 | 133 | break; |
168 | | |
169 | 387 | default: |
170 | | // Don't care about others |
171 | 387 | break; |
172 | 653 | } |
173 | | |
174 | 653 | for (const driver::Action *AI : A->inputs()) |
175 | 520 | runImpl(AI, CollectChildren); |
176 | 653 | } |
177 | | }; |
178 | | |
179 | | // Special DiagnosticConsumer that looks for warn_drv_input_file_unused |
180 | | // diagnostics from the driver and collects the option strings for those unused |
181 | | // options. |
182 | | class UnusedInputDiagConsumer : public DiagnosticConsumer { |
183 | | public: |
184 | 128 | UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {} |
185 | | |
186 | | void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
187 | 1 | const Diagnostic &Info) override { |
188 | 1 | if (Info.getID() == diag::warn_drv_input_file_unused) { |
189 | | // Arg 1 for this diagnostic is the option that didn't get used. |
190 | 1 | UnusedInputs.push_back(Info.getArgStdStr(0)); |
191 | 1 | } else if (0 DiagLevel >= DiagnosticsEngine::Error0 ) { |
192 | | // If driver failed to create compilation object, show the diagnostics |
193 | | // to user. |
194 | 0 | Other.HandleDiagnostic(DiagLevel, Info); |
195 | 0 | } |
196 | 1 | } |
197 | | |
198 | | DiagnosticConsumer &Other; |
199 | | SmallVector<std::string, 2> UnusedInputs; |
200 | | }; |
201 | | |
202 | | // Filter of tools unused flags such as -no-integrated-as and -Wa,*. |
203 | | // They are not used for syntax checking, and could confuse targets |
204 | | // which don't support these options. |
205 | | struct FilterUnusedFlags { |
206 | 455 | bool operator() (StringRef S) { |
207 | 455 | return (S == "-no-integrated-as") || S.startswith("-Wa,")450 ; |
208 | 455 | } |
209 | | }; |
210 | | |
211 | 284 | std::string GetClangToolCommand() { |
212 | 284 | static int Dummy; |
213 | 284 | std::string ClangExecutable = |
214 | 284 | llvm::sys::fs::getMainExecutable("clang", (void *)&Dummy); |
215 | 284 | SmallString<128> ClangToolPath; |
216 | 284 | ClangToolPath = llvm::sys::path::parent_path(ClangExecutable); |
217 | 284 | llvm::sys::path::append(ClangToolPath, "clang-tool"); |
218 | 284 | return std::string(ClangToolPath.str()); |
219 | 284 | } |
220 | | |
221 | | } // namespace |
222 | | |
223 | | /// Strips any positional args and possible argv[0] from a command-line |
224 | | /// provided by the user to construct a FixedCompilationDatabase. |
225 | | /// |
226 | | /// FixedCompilationDatabase requires a command line to be in this format as it |
227 | | /// constructs the command line for each file by appending the name of the file |
228 | | /// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the |
229 | | /// start of the command line although its value is not important as it's just |
230 | | /// ignored by the Driver invoked by the ClangTool using the |
231 | | /// FixedCompilationDatabase. |
232 | | /// |
233 | | /// FIXME: This functionality should probably be made available by |
234 | | /// clang::driver::Driver although what the interface should look like is not |
235 | | /// clear. |
236 | | /// |
237 | | /// \param[in] Args Args as provided by the user. |
238 | | /// \return Resulting stripped command line. |
239 | | /// \li true if successful. |
240 | | /// \li false if \c Args cannot be used for compilation jobs (e.g. |
241 | | /// contains an option like -E or -version). |
242 | | static bool stripPositionalArgs(std::vector<const char *> Args, |
243 | | std::vector<std::string> &Result, |
244 | 128 | std::string &ErrorMsg) { |
245 | 128 | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); |
246 | 128 | llvm::raw_string_ostream Output(ErrorMsg); |
247 | 128 | TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts); |
248 | 128 | UnusedInputDiagConsumer DiagClient(DiagnosticPrinter); |
249 | 128 | DiagnosticsEngine Diagnostics( |
250 | 128 | IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), |
251 | 128 | &*DiagOpts, &DiagClient, false); |
252 | | |
253 | | // The clang executable path isn't required since the jobs the driver builds |
254 | | // will not be executed. |
255 | 128 | std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( |
256 | 128 | /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), |
257 | 128 | Diagnostics)); |
258 | 128 | NewDriver->setCheckInputsExist(false); |
259 | | |
260 | | // This becomes the new argv[0]. The value is used to detect libc++ include |
261 | | // dirs on Mac, it isn't used for other platforms. |
262 | 128 | std::string Argv0 = GetClangToolCommand(); |
263 | 128 | Args.insert(Args.begin(), Argv0.c_str()); |
264 | | |
265 | | // By adding -c, we force the driver to treat compilation as the last phase. |
266 | | // It will then issue warnings via Diagnostics about un-used options that |
267 | | // would have been used for linking. If the user provided a compiler name as |
268 | | // the original argv[0], this will be treated as a linker input thanks to |
269 | | // insertng a new argv[0] above. All un-used options get collected by |
270 | | // UnusedInputdiagConsumer and get stripped out later. |
271 | 128 | Args.push_back("-c"); |
272 | | |
273 | | // Put a dummy C++ file on to ensure there's at least one compile job for the |
274 | | // driver to construct. If the user specified some other argument that |
275 | | // prevents compilation, e.g. -E or something like -version, we may still end |
276 | | // up with no jobs but then this is the user's fault. |
277 | 128 | Args.push_back("placeholder.cpp"); |
278 | | |
279 | 128 | llvm::erase_if(Args, FilterUnusedFlags()); |
280 | | |
281 | 128 | const std::unique_ptr<driver::Compilation> Compilation( |
282 | 128 | NewDriver->BuildCompilation(Args)); |
283 | 128 | if (!Compilation) |
284 | 0 | return false; |
285 | | |
286 | 128 | const driver::JobList &Jobs = Compilation->getJobs(); |
287 | | |
288 | 128 | CompileJobAnalyzer CompileAnalyzer; |
289 | | |
290 | 134 | for (const auto &Cmd : Jobs) { |
291 | | // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs |
292 | | // we get duplicates since Link jobs point to Assemble jobs as inputs. |
293 | | // -flto* flags make the BackendJobClass, which still needs analyzer. |
294 | 134 | if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || |
295 | 134 | Cmd.getSource().getKind() == driver::Action::BackendJobClass9 || |
296 | 134 | Cmd.getSource().getKind() == driver::Action::CompileJobClass5 ) { |
297 | 133 | CompileAnalyzer.run(&Cmd.getSource()); |
298 | 133 | } |
299 | 134 | } |
300 | | |
301 | 128 | if (CompileAnalyzer.Inputs.empty()) { |
302 | 0 | ErrorMsg = "warning: no compile jobs found\n"; |
303 | 0 | return false; |
304 | 0 | } |
305 | | |
306 | | // Remove all compilation input files from the command line and inputs deemed |
307 | | // unused for compilation. This is necessary so that getCompileCommands() can |
308 | | // construct a command line for each file. |
309 | 128 | std::vector<const char *>::iterator End = |
310 | 449 | llvm::remove_if(Args, [&](StringRef S) { |
311 | 449 | return llvm::is_contained(CompileAnalyzer.Inputs, S) || |
312 | 449 | llvm::is_contained(DiagClient.UnusedInputs, S)318 ; |
313 | 449 | }); |
314 | | // Remove the -c add above as well. It will be at the end right now. |
315 | 128 | assert(strcmp(*(End - 1), "-c") == 0); |
316 | 0 | --End; |
317 | | |
318 | 128 | Result = std::vector<std::string>(Args.begin() + 1, End); |
319 | 128 | return true; |
320 | 128 | } |
321 | | |
322 | | std::unique_ptr<FixedCompilationDatabase> |
323 | | FixedCompilationDatabase::loadFromCommandLine(int &Argc, |
324 | | const char *const *Argv, |
325 | | std::string &ErrorMsg, |
326 | 155 | const Twine &Directory) { |
327 | 155 | ErrorMsg.clear(); |
328 | 155 | if (Argc == 0) |
329 | 1 | return nullptr; |
330 | 154 | const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); |
331 | 154 | if (DoubleDash == Argv + Argc) |
332 | 26 | return nullptr; |
333 | 128 | std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc); |
334 | 128 | Argc = DoubleDash - Argv; |
335 | | |
336 | 128 | std::vector<std::string> StrippedArgs; |
337 | 128 | if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg)) |
338 | 0 | return nullptr; |
339 | 128 | return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); |
340 | 128 | } |
341 | | |
342 | | std::unique_ptr<FixedCompilationDatabase> |
343 | 63 | FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) { |
344 | 63 | ErrorMsg.clear(); |
345 | 63 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File = |
346 | 63 | llvm::MemoryBuffer::getFile(Path); |
347 | 63 | if (std::error_code Result = File.getError()) { |
348 | 61 | ErrorMsg = "Error while opening fixed database: " + Result.message(); |
349 | 61 | return nullptr; |
350 | 61 | } |
351 | 2 | return loadFromBuffer(llvm::sys::path::parent_path(Path), |
352 | 2 | (*File)->getBuffer(), ErrorMsg); |
353 | 63 | } |
354 | | |
355 | | std::unique_ptr<FixedCompilationDatabase> |
356 | | FixedCompilationDatabase::loadFromBuffer(StringRef Directory, StringRef Data, |
357 | 3 | std::string &ErrorMsg) { |
358 | 3 | ErrorMsg.clear(); |
359 | 3 | std::vector<std::string> Args; |
360 | 3 | StringRef Line; |
361 | 14 | while (!Data.empty()) { |
362 | 11 | std::tie(Line, Data) = Data.split('\n'); |
363 | | // Stray whitespace is almost certainly unintended. |
364 | 11 | Line = Line.trim(); |
365 | 11 | if (!Line.empty()) |
366 | 5 | Args.push_back(Line.str()); |
367 | 11 | } |
368 | 3 | return std::make_unique<FixedCompilationDatabase>(Directory, std::move(Args)); |
369 | 3 | } |
370 | | |
371 | | FixedCompilationDatabase::FixedCompilationDatabase( |
372 | 156 | const Twine &Directory, ArrayRef<std::string> CommandLine) { |
373 | 156 | std::vector<std::string> ToolCommandLine(1, GetClangToolCommand()); |
374 | 156 | ToolCommandLine.insert(ToolCommandLine.end(), |
375 | 156 | CommandLine.begin(), CommandLine.end()); |
376 | 156 | CompileCommands.emplace_back(Directory, StringRef(), |
377 | 156 | std::move(ToolCommandLine), |
378 | 156 | StringRef()); |
379 | 156 | } |
380 | | |
381 | | std::vector<CompileCommand> |
382 | 334 | FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { |
383 | 334 | std::vector<CompileCommand> Result(CompileCommands); |
384 | 334 | Result[0].CommandLine.push_back(std::string(FilePath)); |
385 | 334 | Result[0].Filename = std::string(FilePath); |
386 | 334 | return Result; |
387 | 334 | } |
388 | | |
389 | | namespace { |
390 | | |
391 | | class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin { |
392 | | std::unique_ptr<CompilationDatabase> |
393 | 63 | loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { |
394 | 63 | SmallString<1024> DatabasePath(Directory); |
395 | 63 | llvm::sys::path::append(DatabasePath, "compile_flags.txt"); |
396 | 63 | return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage); |
397 | 63 | } |
398 | | }; |
399 | | |
400 | | } // namespace |
401 | | |
402 | | static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin> |
403 | | X("fixed-compilation-database", "Reads plain-text flags file"); |
404 | | |
405 | | namespace clang { |
406 | | namespace tooling { |
407 | | |
408 | | // This anchor is used to force the linker to link in the generated object file |
409 | | // and thus register the JSONCompilationDatabasePlugin. |
410 | | extern volatile int JSONAnchorSource; |
411 | | static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource; |
412 | | |
413 | | } // namespace tooling |
414 | | } // namespace clang |