1 | //===- Job.cpp - Command to Execute ---------------------------------------===// |
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/Driver/Job.h" |
10 | #include "clang/Basic/LLVM.h" |
11 | #include "clang/Driver/Driver.h" |
12 | #include "clang/Driver/DriverDiagnostic.h" |
13 | #include "clang/Driver/InputInfo.h" |
14 | #include "clang/Driver/Tool.h" |
15 | #include "clang/Driver/ToolChain.h" |
16 | #include "llvm/ADT/ArrayRef.h" |
17 | #include "llvm/ADT/SmallString.h" |
18 | #include "llvm/ADT/SmallVector.h" |
19 | #include "llvm/ADT/StringExtras.h" |
20 | #include "llvm/ADT/StringRef.h" |
21 | #include "llvm/ADT/StringSet.h" |
22 | #include "llvm/ADT/StringSwitch.h" |
23 | #include "llvm/Support/CrashRecoveryContext.h" |
24 | #include "llvm/Support/FileSystem.h" |
25 | #include "llvm/Support/Path.h" |
26 | #include "llvm/Support/PrettyStackTrace.h" |
27 | #include "llvm/Support/Program.h" |
28 | #include "llvm/Support/raw_ostream.h" |
29 | #include <algorithm> |
30 | #include <cassert> |
31 | #include <cstddef> |
32 | #include <string> |
33 | #include <system_error> |
34 | #include <utility> |
35 | |
36 | using namespace clang; |
37 | using namespace driver; |
38 | |
39 | Command::Command(const Action &Source, const Tool &Creator, |
40 | ResponseFileSupport ResponseSupport, const char *Executable, |
41 | const llvm::opt::ArgStringList &Arguments, |
42 | ArrayRef<InputInfo> Inputs, ArrayRef<InputInfo> Outputs, |
43 | const char *PrependArg) |
44 | : Source(Source), Creator(Creator), ResponseSupport(ResponseSupport), |
45 | Executable(Executable), PrependArg(PrependArg), Arguments(Arguments) { |
46 | for (const auto &II : Inputs) |
47 | if (II.isFilename()) |
48 | InputInfoList.push_back(x: II); |
49 | for (const auto &II : Outputs) |
50 | if (II.isFilename()) |
51 | OutputFilenames.push_back(x: II.getFilename()); |
52 | } |
53 | |
54 | /// Check if the compiler flag in question should be skipped when |
55 | /// emitting a reproducer. Also track how many arguments it has and if the |
56 | /// option is some kind of include path. |
57 | static bool skipArgs(const char *Flag, bool HaveCrashVFS, int &SkipNum, |
58 | bool &IsInclude) { |
59 | SkipNum = 2; |
60 | // These flags are all of the form -Flag <Arg> and are treated as two |
61 | // arguments. Therefore, we need to skip the flag and the next argument. |
62 | bool ShouldSkip = llvm::StringSwitch<bool>(Flag) |
63 | .Cases(S0: "-MF" , S1: "-MT" , S2: "-MQ" , S3: "-serialize-diagnostic-file" , Value: true) |
64 | .Cases(S0: "-o" , S1: "-dependency-file" , Value: true) |
65 | .Cases(S0: "-fdebug-compilation-dir" , S1: "-diagnostic-log-file" , Value: true) |
66 | .Cases(S0: "-dwarf-debug-flags" , S1: "-ivfsoverlay" , Value: true) |
67 | .Default(Value: false); |
68 | if (ShouldSkip) |
69 | return true; |
70 | |
71 | // Some include flags shouldn't be skipped if we have a crash VFS |
72 | IsInclude = llvm::StringSwitch<bool>(Flag) |
73 | .Cases(S0: "-include" , S1: "-header-include-file" , Value: true) |
74 | .Cases(S0: "-idirafter" , S1: "-internal-isystem" , S2: "-iwithprefix" , Value: true) |
75 | .Cases(S0: "-internal-externc-isystem" , S1: "-iprefix" , Value: true) |
76 | .Cases(S0: "-iwithprefixbefore" , S1: "-isystem" , S2: "-iquote" , Value: true) |
77 | .Cases(S0: "-isysroot" , S1: "-I" , S2: "-F" , S3: "-resource-dir" , Value: true) |
78 | .Cases(S0: "-iframework" , S1: "-include-pch" , Value: true) |
79 | .Default(Value: false); |
80 | if (IsInclude) |
81 | return !HaveCrashVFS; |
82 | |
83 | // The remaining flags are treated as a single argument. |
84 | |
85 | // These flags are all of the form -Flag and have no second argument. |
86 | ShouldSkip = llvm::StringSwitch<bool>(Flag) |
87 | .Cases(S0: "-M" , S1: "-MM" , S2: "-MG" , S3: "-MP" , S4: "-MD" , Value: true) |
88 | .Case(S: "-MMD" , Value: true) |
89 | .Default(Value: false); |
90 | |
91 | // Match found. |
92 | SkipNum = 1; |
93 | if (ShouldSkip) |
94 | return true; |
95 | |
96 | // These flags are treated as a single argument (e.g., -F<Dir>). |
97 | StringRef FlagRef(Flag); |
98 | IsInclude = FlagRef.starts_with(Prefix: "-F" ) || FlagRef.starts_with(Prefix: "-I" ); |
99 | if (IsInclude) |
100 | return !HaveCrashVFS; |
101 | if (FlagRef.starts_with(Prefix: "-fmodules-cache-path=" )) |
102 | return true; |
103 | |
104 | SkipNum = 0; |
105 | return false; |
106 | } |
107 | |
108 | void Command::writeResponseFile(raw_ostream &OS) const { |
109 | // In a file list, we only write the set of inputs to the response file |
110 | if (ResponseSupport.ResponseKind == ResponseFileSupport::RF_FileList) { |
111 | for (const auto *Arg : InputFileList) { |
112 | OS << Arg << '\n'; |
113 | } |
114 | return; |
115 | } |
116 | |
117 | // In regular response files, we send all arguments to the response file. |
118 | // Wrapping all arguments in double quotes ensures that both Unix tools and |
119 | // Windows tools understand the response file. |
120 | for (const auto *Arg : Arguments) { |
121 | OS << '"'; |
122 | |
123 | for (; *Arg != '\0'; Arg++) { |
124 | if (*Arg == '\"' || *Arg == '\\') { |
125 | OS << '\\'; |
126 | } |
127 | OS << *Arg; |
128 | } |
129 | |
130 | OS << "\" " ; |
131 | } |
132 | } |
133 | |
134 | void Command::buildArgvForResponseFile( |
135 | llvm::SmallVectorImpl<const char *> &Out) const { |
136 | // When not a file list, all arguments are sent to the response file. |
137 | // This leaves us to set the argv to a single parameter, requesting the tool |
138 | // to read the response file. |
139 | if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList) { |
140 | Out.push_back(Elt: Executable); |
141 | Out.push_back(Elt: ResponseFileFlag.c_str()); |
142 | return; |
143 | } |
144 | |
145 | llvm::StringSet<> Inputs; |
146 | for (const auto *InputName : InputFileList) |
147 | Inputs.insert(key: InputName); |
148 | Out.push_back(Elt: Executable); |
149 | |
150 | if (PrependArg) |
151 | Out.push_back(Elt: PrependArg); |
152 | |
153 | // In a file list, build args vector ignoring parameters that will go in the |
154 | // response file (elements of the InputFileList vector) |
155 | bool FirstInput = true; |
156 | for (const auto *Arg : Arguments) { |
157 | if (Inputs.count(Key: Arg) == 0) { |
158 | Out.push_back(Elt: Arg); |
159 | } else if (FirstInput) { |
160 | FirstInput = false; |
161 | Out.push_back(Elt: ResponseSupport.ResponseFlag); |
162 | Out.push_back(Elt: ResponseFile); |
163 | } |
164 | } |
165 | } |
166 | |
167 | /// Rewrite relative include-like flag paths to absolute ones. |
168 | static void |
169 | rewriteIncludes(const llvm::ArrayRef<const char *> &Args, size_t Idx, |
170 | size_t NumArgs, |
171 | llvm::SmallVectorImpl<llvm::SmallString<128>> &IncFlags) { |
172 | using namespace llvm; |
173 | using namespace sys; |
174 | |
175 | auto getAbsPath = [](StringRef InInc, SmallVectorImpl<char> &OutInc) -> bool { |
176 | if (path::is_absolute(path: InInc)) // Nothing to do here... |
177 | return false; |
178 | std::error_code EC = fs::current_path(result&: OutInc); |
179 | if (EC) |
180 | return false; |
181 | path::append(path&: OutInc, a: InInc); |
182 | return true; |
183 | }; |
184 | |
185 | SmallString<128> NewInc; |
186 | if (NumArgs == 1) { |
187 | StringRef FlagRef(Args[Idx + NumArgs - 1]); |
188 | assert((FlagRef.starts_with("-F" ) || FlagRef.starts_with("-I" )) && |
189 | "Expecting -I or -F" ); |
190 | StringRef Inc = FlagRef.slice(Start: 2, End: StringRef::npos); |
191 | if (getAbsPath(Inc, NewInc)) { |
192 | SmallString<128> NewArg(FlagRef.slice(Start: 0, End: 2)); |
193 | NewArg += NewInc; |
194 | IncFlags.push_back(Elt: std::move(NewArg)); |
195 | } |
196 | return; |
197 | } |
198 | |
199 | assert(NumArgs == 2 && "Not expecting more than two arguments" ); |
200 | StringRef Inc(Args[Idx + NumArgs - 1]); |
201 | if (!getAbsPath(Inc, NewInc)) |
202 | return; |
203 | IncFlags.push_back(Elt: SmallString<128>(Args[Idx])); |
204 | IncFlags.push_back(Elt: std::move(NewInc)); |
205 | } |
206 | |
207 | void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
208 | CrashReportInfo *CrashInfo) const { |
209 | // Always quote the exe. |
210 | OS << ' '; |
211 | llvm::sys::printArg(OS, Arg: Executable, /*Quote=*/true); |
212 | |
213 | ArrayRef<const char *> Args = Arguments; |
214 | SmallVector<const char *, 128> ArgsRespFile; |
215 | if (ResponseFile != nullptr) { |
216 | buildArgvForResponseFile(Out&: ArgsRespFile); |
217 | Args = ArrayRef<const char *>(ArgsRespFile).slice(N: 1); // no executable name |
218 | } else if (PrependArg) { |
219 | OS << ' '; |
220 | llvm::sys::printArg(OS, Arg: PrependArg, /*Quote=*/true); |
221 | } |
222 | |
223 | bool HaveCrashVFS = CrashInfo && !CrashInfo->VFSPath.empty(); |
224 | for (size_t i = 0, e = Args.size(); i < e; ++i) { |
225 | const char *const Arg = Args[i]; |
226 | |
227 | if (CrashInfo) { |
228 | int NumArgs = 0; |
229 | bool IsInclude = false; |
230 | if (skipArgs(Flag: Arg, HaveCrashVFS, SkipNum&: NumArgs, IsInclude)) { |
231 | i += NumArgs - 1; |
232 | continue; |
233 | } |
234 | |
235 | // Relative includes need to be expanded to absolute paths. |
236 | if (HaveCrashVFS && IsInclude) { |
237 | SmallVector<SmallString<128>, 2> NewIncFlags; |
238 | rewriteIncludes(Args, Idx: i, NumArgs, IncFlags&: NewIncFlags); |
239 | if (!NewIncFlags.empty()) { |
240 | for (auto &F : NewIncFlags) { |
241 | OS << ' '; |
242 | llvm::sys::printArg(OS, Arg: F.c_str(), Quote); |
243 | } |
244 | i += NumArgs - 1; |
245 | continue; |
246 | } |
247 | } |
248 | |
249 | auto Found = llvm::find_if(Range: InputInfoList, P: [&Arg](const InputInfo &II) { |
250 | return II.getFilename() == Arg; |
251 | }); |
252 | if (Found != InputInfoList.end() && |
253 | (i == 0 || StringRef(Args[i - 1]) != "-main-file-name" )) { |
254 | // Replace the input file name with the crashinfo's file name. |
255 | OS << ' '; |
256 | StringRef ShortName = llvm::sys::path::filename(path: CrashInfo->Filename); |
257 | llvm::sys::printArg(OS, Arg: ShortName.str(), Quote); |
258 | continue; |
259 | } |
260 | } |
261 | |
262 | OS << ' '; |
263 | llvm::sys::printArg(OS, Arg, Quote); |
264 | } |
265 | |
266 | if (CrashInfo && HaveCrashVFS) { |
267 | OS << ' '; |
268 | llvm::sys::printArg(OS, Arg: "-ivfsoverlay" , Quote); |
269 | OS << ' '; |
270 | llvm::sys::printArg(OS, Arg: CrashInfo->VFSPath.str(), Quote); |
271 | |
272 | // The leftover modules from the crash are stored in |
273 | // <name>.cache/vfs/modules |
274 | // Leave it untouched for pcm inspection and provide a clean/empty dir |
275 | // path to contain the future generated module cache: |
276 | // <name>.cache/vfs/repro-modules |
277 | SmallString<128> RelModCacheDir = llvm::sys::path::parent_path( |
278 | path: llvm::sys::path::parent_path(path: CrashInfo->VFSPath)); |
279 | llvm::sys::path::append(path&: RelModCacheDir, a: "repro-modules" ); |
280 | |
281 | std::string ModCachePath = "-fmodules-cache-path=" ; |
282 | ModCachePath.append(s: RelModCacheDir.c_str()); |
283 | |
284 | OS << ' '; |
285 | llvm::sys::printArg(OS, Arg: ModCachePath, Quote); |
286 | } |
287 | |
288 | if (ResponseFile != nullptr) { |
289 | OS << "\n Arguments passed via response file:\n" ; |
290 | writeResponseFile(OS); |
291 | // Avoiding duplicated newline terminator, since FileLists are |
292 | // newline-separated. |
293 | if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList) |
294 | OS << "\n" ; |
295 | OS << " (end of response file)" ; |
296 | } |
297 | |
298 | OS << Terminator; |
299 | } |
300 | |
301 | void Command::setResponseFile(const char *FileName) { |
302 | ResponseFile = FileName; |
303 | ResponseFileFlag = ResponseSupport.ResponseFlag; |
304 | ResponseFileFlag += FileName; |
305 | } |
306 | |
307 | void Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) { |
308 | Environment.reserve(n: NewEnvironment.size() + 1); |
309 | Environment.assign(first: NewEnvironment.begin(), last: NewEnvironment.end()); |
310 | Environment.push_back(x: nullptr); |
311 | } |
312 | |
313 | void Command::setRedirectFiles( |
314 | const std::vector<std::optional<std::string>> &Redirects) { |
315 | RedirectFiles = Redirects; |
316 | } |
317 | |
318 | void Command::PrintFileNames() const { |
319 | if (PrintInputFilenames) { |
320 | for (const auto &Arg : InputInfoList) |
321 | llvm::outs() << llvm::sys::path::filename(path: Arg.getFilename()) << "\n" ; |
322 | llvm::outs().flush(); |
323 | } |
324 | } |
325 | |
326 | int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, |
327 | std::string *ErrMsg, bool *ExecutionFailed) const { |
328 | PrintFileNames(); |
329 | |
330 | SmallVector<const char *, 128> Argv; |
331 | if (ResponseFile == nullptr) { |
332 | Argv.push_back(Elt: Executable); |
333 | if (PrependArg) |
334 | Argv.push_back(Elt: PrependArg); |
335 | Argv.append(in_start: Arguments.begin(), in_end: Arguments.end()); |
336 | Argv.push_back(Elt: nullptr); |
337 | } else { |
338 | // If the command is too large, we need to put arguments in a response file. |
339 | std::string RespContents; |
340 | llvm::raw_string_ostream SS(RespContents); |
341 | |
342 | // Write file contents and build the Argv vector |
343 | writeResponseFile(OS&: SS); |
344 | buildArgvForResponseFile(Out&: Argv); |
345 | Argv.push_back(Elt: nullptr); |
346 | SS.flush(); |
347 | |
348 | // Save the response file in the appropriate encoding |
349 | if (std::error_code EC = writeFileWithEncoding( |
350 | FileName: ResponseFile, Contents: RespContents, Encoding: ResponseSupport.ResponseEncoding)) { |
351 | if (ErrMsg) |
352 | *ErrMsg = EC.message(); |
353 | if (ExecutionFailed) |
354 | *ExecutionFailed = true; |
355 | // Return -1 by convention (see llvm/include/llvm/Support/Program.h) to |
356 | // indicate the requested executable cannot be started. |
357 | return -1; |
358 | } |
359 | } |
360 | |
361 | std::optional<ArrayRef<StringRef>> Env; |
362 | std::vector<StringRef> ArgvVectorStorage; |
363 | if (!Environment.empty()) { |
364 | assert(Environment.back() == nullptr && |
365 | "Environment vector should be null-terminated by now" ); |
366 | ArgvVectorStorage = llvm::toStringRefArray(Strings: Environment.data()); |
367 | Env = ArrayRef(ArgvVectorStorage); |
368 | } |
369 | |
370 | auto Args = llvm::toStringRefArray(Strings: Argv.data()); |
371 | |
372 | // Use Job-specific redirect files if they are present. |
373 | if (!RedirectFiles.empty()) { |
374 | std::vector<std::optional<StringRef>> RedirectFilesOptional; |
375 | for (const auto &Ele : RedirectFiles) |
376 | if (Ele) |
377 | RedirectFilesOptional.push_back(x: std::optional<StringRef>(*Ele)); |
378 | else |
379 | RedirectFilesOptional.push_back(x: std::nullopt); |
380 | |
381 | return llvm::sys::ExecuteAndWait(Program: Executable, Args, Env, |
382 | Redirects: ArrayRef(RedirectFilesOptional), |
383 | /*secondsToWait=*/SecondsToWait: 0, /*memoryLimit=*/MemoryLimit: 0, |
384 | ErrMsg, ExecutionFailed, ProcStat: &ProcStat); |
385 | } |
386 | |
387 | return llvm::sys::ExecuteAndWait(Program: Executable, Args, Env, Redirects, |
388 | /*secondsToWait*/ SecondsToWait: 0, /*memoryLimit*/ MemoryLimit: 0, |
389 | ErrMsg, ExecutionFailed, ProcStat: &ProcStat); |
390 | } |
391 | |
392 | CC1Command::CC1Command(const Action &Source, const Tool &Creator, |
393 | ResponseFileSupport ResponseSupport, |
394 | const char *Executable, |
395 | const llvm::opt::ArgStringList &Arguments, |
396 | ArrayRef<InputInfo> Inputs, ArrayRef<InputInfo> Outputs, |
397 | const char *PrependArg) |
398 | : Command(Source, Creator, ResponseSupport, Executable, Arguments, Inputs, |
399 | Outputs, PrependArg) { |
400 | InProcess = true; |
401 | } |
402 | |
403 | void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
404 | CrashReportInfo *CrashInfo) const { |
405 | if (InProcess) |
406 | OS << " (in-process)\n" ; |
407 | Command::Print(OS, Terminator, Quote, CrashInfo); |
408 | } |
409 | |
410 | int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, |
411 | std::string *ErrMsg, bool *ExecutionFailed) const { |
412 | // FIXME: Currently, if there're more than one job, we disable |
413 | // -fintegrate-cc1. If we're no longer a integrated-cc1 job, fallback to |
414 | // out-of-process execution. See discussion in https://reviews.llvm.org/D74447 |
415 | if (!InProcess) |
416 | return Command::Execute(Redirects, ErrMsg, ExecutionFailed); |
417 | |
418 | PrintFileNames(); |
419 | |
420 | SmallVector<const char *, 128> Argv; |
421 | Argv.push_back(Elt: getExecutable()); |
422 | Argv.append(in_start: getArguments().begin(), in_end: getArguments().end()); |
423 | Argv.push_back(Elt: nullptr); |
424 | Argv.pop_back(); // The terminating null element shall not be part of the |
425 | // slice (main() behavior). |
426 | |
427 | // This flag simply indicates that the program couldn't start, which isn't |
428 | // applicable here. |
429 | if (ExecutionFailed) |
430 | *ExecutionFailed = false; |
431 | |
432 | llvm::CrashRecoveryContext CRC; |
433 | CRC.DumpStackAndCleanupOnFailure = true; |
434 | |
435 | const void *PrettyState = llvm::SavePrettyStackState(); |
436 | const Driver &D = getCreator().getToolChain().getDriver(); |
437 | |
438 | int R = 0; |
439 | // Enter ExecuteCC1Tool() instead of starting up a new process |
440 | if (!CRC.RunSafely(Fn: [&]() { R = D.CC1Main(Argv); })) { |
441 | llvm::RestorePrettyStackState(State: PrettyState); |
442 | return CRC.RetCode; |
443 | } |
444 | return R; |
445 | } |
446 | |
447 | void CC1Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) { |
448 | // We don't support set a new environment when calling into ExecuteCC1Tool() |
449 | llvm_unreachable( |
450 | "The CC1Command doesn't support changing the environment vars!" ); |
451 | } |
452 | |
453 | void JobList::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
454 | CrashReportInfo *CrashInfo) const { |
455 | for (const auto &Job : *this) |
456 | Job.Print(OS, Terminator, Quote, CrashInfo); |
457 | } |
458 | |
459 | void JobList::clear() { Jobs.clear(); } |
460 | |