/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Driver/Job.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 56.7k | Executable(Executable), PrependArg(PrependArg), Arguments(Arguments) { |
46 | 56.7k | for (const auto &II : Inputs) |
47 | 63.4k | if (II.isFilename()) |
48 | 57.5k | InputInfoList.push_back(II); |
49 | 56.7k | for (const auto &II : Outputs) |
50 | 56.8k | if (II.isFilename()) |
51 | 24.8k | OutputFilenames.push_back(II.getFilename()); |
52 | 56.7k | } |
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 | 2.46k | bool &IsInclude) { |
59 | 2.46k | 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 | 2.46k | bool ShouldSkip = llvm::StringSwitch<bool>(Flag) |
63 | 2.46k | .Cases("-MF", "-MT", "-MQ", "-serialize-diagnostic-file", true) |
64 | 2.46k | .Cases("-o", "-dependency-file", true) |
65 | 2.46k | .Cases("-fdebug-compilation-dir", "-diagnostic-log-file", true) |
66 | 2.46k | .Cases("-dwarf-debug-flags", "-ivfsoverlay", true) |
67 | 2.46k | .Default(false); |
68 | 2.46k | if (ShouldSkip) |
69 | 22 | return true; |
70 | | |
71 | | // Some include flags shouldn't be skipped if we have a crash VFS |
72 | 2.44k | IsInclude = llvm::StringSwitch<bool>(Flag) |
73 | 2.44k | .Cases("-include", "-header-include-file", true) |
74 | 2.44k | .Cases("-idirafter", "-internal-isystem", "-iwithprefix", true) |
75 | 2.44k | .Cases("-internal-externc-isystem", "-iprefix", true) |
76 | 2.44k | .Cases("-iwithprefixbefore", "-isystem", "-iquote", true) |
77 | 2.44k | .Cases("-isysroot", "-I", "-F", "-resource-dir", true) |
78 | 2.44k | .Cases("-iframework", "-include-pch", true) |
79 | 2.44k | .Default(false); |
80 | 2.44k | if (IsInclude) |
81 | 266 | 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 | 2.17k | ShouldSkip = llvm::StringSwitch<bool>(Flag) |
87 | 2.17k | .Cases("-M", "-MM", "-MG", "-MP", "-MD", true) |
88 | 2.17k | .Case("-MMD", true) |
89 | 2.17k | .Default(false); |
90 | | |
91 | | // Match found. |
92 | 2.17k | SkipNum = 1; |
93 | 2.17k | if (ShouldSkip) |
94 | 0 | return true; |
95 | | |
96 | | // These flags are treated as a single argument (e.g., -F<Dir>). |
97 | 2.17k | StringRef FlagRef(Flag); |
98 | 2.17k | IsInclude = FlagRef.startswith("-F") || FlagRef.startswith("-I")2.16k ; |
99 | 2.17k | if (IsInclude) |
100 | 10 | return !HaveCrashVFS; |
101 | 2.16k | if (FlagRef.startswith("-fmodules-cache-path=")) |
102 | 14 | return true; |
103 | | |
104 | 2.15k | SkipNum = 0; |
105 | 2.15k | return false; |
106 | 2.16k | } |
107 | | |
108 | 1 | void Command::writeResponseFile(raw_ostream &OS) const { |
109 | | // In a file list, we only write the set of inputs to the response file |
110 | 1 | if (ResponseSupport.ResponseKind == ResponseFileSupport::RF_FileList) { |
111 | 0 | for (const auto *Arg : InputFileList) { |
112 | 0 | OS << Arg << '\n'; |
113 | 0 | } |
114 | 0 | return; |
115 | 0 | } |
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 | 600k | for (const auto *Arg : Arguments)1 { |
121 | 600k | OS << '"'; |
122 | | |
123 | 2.40M | for (; *Arg != '\0'; Arg++1.80M ) { |
124 | 1.80M | if (*Arg == '\"' || *Arg == '\\') { |
125 | 0 | OS << '\\'; |
126 | 0 | } |
127 | 1.80M | OS << *Arg; |
128 | 1.80M | } |
129 | | |
130 | 600k | OS << "\" "; |
131 | 600k | } |
132 | 1 | } |
133 | | |
134 | | void Command::buildArgvForResponseFile( |
135 | 1 | 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 | 1 | if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList) { |
140 | 1 | Out.push_back(Executable); |
141 | 1 | Out.push_back(ResponseFileFlag.c_str()); |
142 | 1 | return; |
143 | 1 | } |
144 | | |
145 | 0 | llvm::StringSet<> Inputs; |
146 | 0 | for (const auto *InputName : InputFileList) |
147 | 0 | Inputs.insert(InputName); |
148 | 0 | Out.push_back(Executable); |
149 | |
|
150 | 0 | if (PrependArg) |
151 | 0 | Out.push_back(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 | 0 | bool FirstInput = true; |
156 | 0 | for (const auto *Arg : Arguments) { |
157 | 0 | if (Inputs.count(Arg) == 0) { |
158 | 0 | Out.push_back(Arg); |
159 | 0 | } else if (FirstInput) { |
160 | 0 | FirstInput = false; |
161 | 0 | Out.push_back(ResponseSupport.ResponseFlag); |
162 | 0 | Out.push_back(ResponseFile); |
163 | 0 | } |
164 | 0 | } |
165 | 0 | } |
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 | 69 | llvm::SmallVectorImpl<llvm::SmallString<128>> &IncFlags) { |
172 | 69 | using namespace llvm; |
173 | 69 | using namespace sys; |
174 | | |
175 | 69 | auto getAbsPath = [](StringRef InInc, SmallVectorImpl<char> &OutInc) -> bool { |
176 | 69 | if (path::is_absolute(InInc)) // Nothing to do here... |
177 | 68 | return false; |
178 | 1 | std::error_code EC = fs::current_path(OutInc); |
179 | 1 | if (EC) |
180 | 0 | return false; |
181 | 1 | path::append(OutInc, InInc); |
182 | 1 | return true; |
183 | 1 | }; |
184 | | |
185 | 69 | SmallString<128> NewInc; |
186 | 69 | if (NumArgs == 1) { |
187 | 2 | StringRef FlagRef(Args[Idx + NumArgs - 1]); |
188 | 2 | assert((FlagRef.startswith("-F") || FlagRef.startswith("-I")) && |
189 | 2 | "Expecting -I or -F"); |
190 | 2 | StringRef Inc = FlagRef.slice(2, StringRef::npos); |
191 | 2 | if (getAbsPath(Inc, NewInc)) { |
192 | 0 | SmallString<128> NewArg(FlagRef.slice(0, 2)); |
193 | 0 | NewArg += NewInc; |
194 | 0 | IncFlags.push_back(std::move(NewArg)); |
195 | 0 | } |
196 | 2 | return; |
197 | 2 | } |
198 | | |
199 | 67 | assert(NumArgs == 2 && "Not expecting more than two arguments"); |
200 | 67 | StringRef Inc(Args[Idx + NumArgs - 1]); |
201 | 67 | if (!getAbsPath(Inc, NewInc)) |
202 | 66 | return; |
203 | 1 | IncFlags.push_back(SmallString<128>(Args[Idx])); |
204 | 1 | IncFlags.push_back(std::move(NewInc)); |
205 | 1 | } |
206 | | |
207 | | void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
208 | 16.3k | CrashReportInfo *CrashInfo) const { |
209 | | // Always quote the exe. |
210 | 16.3k | OS << ' '; |
211 | 16.3k | llvm::sys::printArg(OS, Executable, /*Quote=*/true); |
212 | | |
213 | 16.3k | ArrayRef<const char *> Args = Arguments; |
214 | 16.3k | SmallVector<const char *, 128> ArgsRespFile; |
215 | 16.3k | if (ResponseFile != nullptr) { |
216 | 1 | buildArgvForResponseFile(ArgsRespFile); |
217 | 1 | Args = ArrayRef<const char *>(ArgsRespFile).slice(1); // no executable name |
218 | 16.3k | } else if (PrependArg) { |
219 | 0 | OS << ' '; |
220 | 0 | llvm::sys::printArg(OS, PrependArg, /*Quote=*/true); |
221 | 0 | } |
222 | | |
223 | 16.3k | bool HaveCrashVFS = CrashInfo && !CrashInfo->VFSPath.empty()42 ; |
224 | 859k | for (size_t i = 0, e = Args.size(); i < e; ++i843k ) { |
225 | 843k | const char *const Arg = Args[i]; |
226 | | |
227 | 843k | if (CrashInfo) { |
228 | 2.46k | int NumArgs = 0; |
229 | 2.46k | bool IsInclude = false; |
230 | 2.46k | if (skipArgs(Arg, HaveCrashVFS, NumArgs, IsInclude)) { |
231 | 243 | i += NumArgs - 1; |
232 | 243 | continue; |
233 | 243 | } |
234 | | |
235 | | // Relative includes need to be expanded to absolute paths. |
236 | 2.22k | if (HaveCrashVFS && IsInclude859 ) { |
237 | 69 | SmallVector<SmallString<128>, 2> NewIncFlags; |
238 | 69 | rewriteIncludes(Args, i, NumArgs, NewIncFlags); |
239 | 69 | if (!NewIncFlags.empty()) { |
240 | 2 | for (auto &F : NewIncFlags) { |
241 | 2 | OS << ' '; |
242 | 2 | llvm::sys::printArg(OS, F.c_str(), Quote); |
243 | 2 | } |
244 | 1 | i += NumArgs - 1; |
245 | 1 | continue; |
246 | 1 | } |
247 | 69 | } |
248 | | |
249 | 2.22k | auto Found = llvm::find_if(InputInfoList, [&Arg](const InputInfo &II) { |
250 | 2.22k | return II.getFilename() == Arg; |
251 | 2.22k | }); |
252 | 2.22k | if (Found != InputInfoList.end() && |
253 | 2.22k | (42 i == 042 || StringRef(Args[i - 1]) != "-main-file-name"42 )) { |
254 | | // Replace the input file name with the crashinfo's file name. |
255 | 42 | OS << ' '; |
256 | 42 | StringRef ShortName = llvm::sys::path::filename(CrashInfo->Filename); |
257 | 42 | llvm::sys::printArg(OS, ShortName.str(), Quote); |
258 | 42 | continue; |
259 | 42 | } |
260 | 2.22k | } |
261 | | |
262 | 842k | OS << ' '; |
263 | 842k | llvm::sys::printArg(OS, Arg, Quote); |
264 | 842k | } |
265 | | |
266 | 16.3k | if (CrashInfo && HaveCrashVFS42 ) { |
267 | 14 | OS << ' '; |
268 | 14 | llvm::sys::printArg(OS, "-ivfsoverlay", Quote); |
269 | 14 | OS << ' '; |
270 | 14 | llvm::sys::printArg(OS, 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 | 14 | SmallString<128> RelModCacheDir = llvm::sys::path::parent_path( |
278 | 14 | llvm::sys::path::parent_path(CrashInfo->VFSPath)); |
279 | 14 | llvm::sys::path::append(RelModCacheDir, "repro-modules"); |
280 | | |
281 | 14 | std::string ModCachePath = "-fmodules-cache-path="; |
282 | 14 | ModCachePath.append(RelModCacheDir.c_str()); |
283 | | |
284 | 14 | OS << ' '; |
285 | 14 | llvm::sys::printArg(OS, ModCachePath, Quote); |
286 | 14 | } |
287 | | |
288 | 16.3k | if (ResponseFile != nullptr) { |
289 | 1 | OS << "\n Arguments passed via response file:\n"; |
290 | 1 | writeResponseFile(OS); |
291 | | // Avoiding duplicated newline terminator, since FileLists are |
292 | | // newline-separated. |
293 | 1 | if (ResponseSupport.ResponseKind != ResponseFileSupport::RF_FileList) |
294 | 1 | OS << "\n"; |
295 | 1 | OS << " (end of response file)"; |
296 | 1 | } |
297 | | |
298 | 16.3k | OS << Terminator; |
299 | 16.3k | } |
300 | | |
301 | 1 | void Command::setResponseFile(const char *FileName) { |
302 | 1 | ResponseFile = FileName; |
303 | 1 | ResponseFileFlag = ResponseSupport.ResponseFlag; |
304 | 1 | ResponseFileFlag += FileName; |
305 | 1 | } |
306 | | |
307 | 0 | void Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) { |
308 | 0 | Environment.reserve(NewEnvironment.size() + 1); |
309 | 0 | Environment.assign(NewEnvironment.begin(), NewEnvironment.end()); |
310 | 0 | Environment.push_back(nullptr); |
311 | 0 | } |
312 | | |
313 | | void Command::setRedirectFiles( |
314 | 4 | const std::vector<std::optional<std::string>> &Redirects) { |
315 | 4 | RedirectFiles = Redirects; |
316 | 4 | } |
317 | | |
318 | 8.73k | void Command::PrintFileNames() const { |
319 | 8.73k | if (PrintInputFilenames) { |
320 | 4 | for (const auto &Arg : InputInfoList) |
321 | 4 | llvm::outs() << llvm::sys::path::filename(Arg.getFilename()) << "\n"; |
322 | 4 | llvm::outs().flush(); |
323 | 4 | } |
324 | 8.73k | } |
325 | | |
326 | | int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, |
327 | 2.97k | std::string *ErrMsg, bool *ExecutionFailed) const { |
328 | 2.97k | PrintFileNames(); |
329 | | |
330 | 2.97k | SmallVector<const char *, 128> Argv; |
331 | 2.97k | if (ResponseFile == nullptr) { |
332 | 2.97k | Argv.push_back(Executable); |
333 | 2.97k | if (PrependArg) |
334 | 0 | Argv.push_back(PrependArg); |
335 | 2.97k | Argv.append(Arguments.begin(), Arguments.end()); |
336 | 2.97k | Argv.push_back(nullptr); |
337 | 2.97k | } else { |
338 | | // If the command is too large, we need to put arguments in a response file. |
339 | 0 | std::string RespContents; |
340 | 0 | llvm::raw_string_ostream SS(RespContents); |
341 | | |
342 | | // Write file contents and build the Argv vector |
343 | 0 | writeResponseFile(SS); |
344 | 0 | buildArgvForResponseFile(Argv); |
345 | 0 | Argv.push_back(nullptr); |
346 | 0 | SS.flush(); |
347 | | |
348 | | // Save the response file in the appropriate encoding |
349 | 0 | if (std::error_code EC = writeFileWithEncoding( |
350 | 0 | ResponseFile, RespContents, ResponseSupport.ResponseEncoding)) { |
351 | 0 | if (ErrMsg) |
352 | 0 | *ErrMsg = EC.message(); |
353 | 0 | if (ExecutionFailed) |
354 | 0 | *ExecutionFailed = true; |
355 | | // Return -1 by convention (see llvm/include/llvm/Support/Program.h) to |
356 | | // indicate the requested executable cannot be started. |
357 | 0 | return -1; |
358 | 0 | } |
359 | 0 | } |
360 | | |
361 | 2.97k | std::optional<ArrayRef<StringRef>> Env; |
362 | 2.97k | std::vector<StringRef> ArgvVectorStorage; |
363 | 2.97k | if (!Environment.empty()) { |
364 | 0 | assert(Environment.back() == nullptr && |
365 | 0 | "Environment vector should be null-terminated by now"); |
366 | 0 | ArgvVectorStorage = llvm::toStringRefArray(Environment.data()); |
367 | 0 | Env = ArrayRef(ArgvVectorStorage); |
368 | 0 | } |
369 | | |
370 | 2.97k | auto Args = llvm::toStringRefArray(Argv.data()); |
371 | | |
372 | | // Use Job-specific redirect files if they are present. |
373 | 2.97k | if (!RedirectFiles.empty()) { |
374 | 0 | std::vector<std::optional<StringRef>> RedirectFilesOptional; |
375 | 0 | for (const auto &Ele : RedirectFiles) |
376 | 0 | if (Ele) |
377 | 0 | RedirectFilesOptional.push_back(std::optional<StringRef>(*Ele)); |
378 | 0 | else |
379 | 0 | RedirectFilesOptional.push_back(std::nullopt); |
380 | |
|
381 | 0 | return llvm::sys::ExecuteAndWait(Executable, Args, Env, |
382 | 0 | ArrayRef(RedirectFilesOptional), |
383 | 0 | /*secondsToWait=*/0, /*memoryLimit=*/0, |
384 | 0 | ErrMsg, ExecutionFailed, &ProcStat); |
385 | 0 | } |
386 | | |
387 | 2.97k | return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects, |
388 | 2.97k | /*secondsToWait*/ 0, /*memoryLimit*/ 0, |
389 | 2.97k | ErrMsg, ExecutionFailed, &ProcStat); |
390 | 2.97k | } |
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 | 16.7k | Outputs, PrependArg) { |
400 | 16.7k | InProcess = true; |
401 | 16.7k | } |
402 | | |
403 | | void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
404 | 10.5k | CrashReportInfo *CrashInfo) const { |
405 | 10.5k | if (InProcess) |
406 | 5.03k | OS << " (in-process)\n"; |
407 | 10.5k | Command::Print(OS, Terminator, Quote, CrashInfo); |
408 | 10.5k | } |
409 | | |
410 | | int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, |
411 | 6.03k | 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 | 6.03k | if (!InProcess) |
416 | 271 | return Command::Execute(Redirects, ErrMsg, ExecutionFailed); |
417 | | |
418 | 5.76k | PrintFileNames(); |
419 | | |
420 | 5.76k | SmallVector<const char *, 128> Argv; |
421 | 5.76k | Argv.push_back(getExecutable()); |
422 | 5.76k | Argv.append(getArguments().begin(), getArguments().end()); |
423 | 5.76k | Argv.push_back(nullptr); |
424 | 5.76k | 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 | 5.76k | if (ExecutionFailed) |
430 | 5.76k | *ExecutionFailed = false; |
431 | | |
432 | 5.76k | llvm::CrashRecoveryContext CRC; |
433 | 5.76k | CRC.DumpStackAndCleanupOnFailure = true; |
434 | | |
435 | 5.76k | const void *PrettyState = llvm::SavePrettyStackState(); |
436 | 5.76k | const Driver &D = getCreator().getToolChain().getDriver(); |
437 | | |
438 | 5.76k | int R = 0; |
439 | | // Enter ExecuteCC1Tool() instead of starting up a new process |
440 | 5.76k | if (!CRC.RunSafely([&]() { R = D.CC1Main(Argv); })) { |
441 | 21 | llvm::RestorePrettyStackState(PrettyState); |
442 | 21 | return CRC.RetCode; |
443 | 21 | } |
444 | 5.74k | return R; |
445 | 5.76k | } |
446 | | |
447 | 0 | void CC1Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) { |
448 | | // We don't support set a new environment when calling into ExecuteCC1Tool() |
449 | 0 | llvm_unreachable( |
450 | 0 | "The CC1Command doesn't support changing the environment vars!"); |
451 | 0 | } |
452 | | |
453 | | void JobList::Print(raw_ostream &OS, const char *Terminator, bool Quote, |
454 | 10.0k | CrashReportInfo *CrashInfo) const { |
455 | 10.0k | for (const auto &Job : *this) |
456 | 16.2k | Job.Print(OS, Terminator, Quote, CrashInfo); |
457 | 10.0k | } |
458 | | |
459 | 45 | void JobList::clear() { Jobs.clear(); } |