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