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