]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/cargo-miri/src/phases.rs
2a469d324e88adb19ca11aa34580e18b9e23db88
[rust.git] / src / tools / miri / cargo-miri / src / phases.rs
1 //! Implements the various phases of `cargo miri run/test`.
2
3 use std::env;
4 use std::fs::{self, File};
5 use std::io::BufReader;
6 use std::path::PathBuf;
7 use std::process::Command;
8
9 use rustc_version::VersionMeta;
10
11 use crate::{setup::*, util::*};
12
13 const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
14
15 Usage:
16     cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
17
18 Subcommands:
19     run, r                   Run binaries
20     test, t                  Run tests
21     nextest                  Run tests with nextest (requires cargo-nextest installed)
22     setup                    Only perform automatic setup, but without asking questions (for getting a proper libstd)
23
24 The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
25
26 Examples:
27     cargo miri run
28     cargo miri test -- test-suite-filter
29
30     cargo miri setup --print sysroot
31         This will print the path to the generated sysroot (and nothing else) on stdout.
32         stderr will still contain progress information about how the build is doing.
33
34 "#;
35
36 fn show_help() {
37     println!("{CARGO_MIRI_HELP}");
38 }
39
40 fn show_version() {
41     print!("miri {}", env!("CARGO_PKG_VERSION"));
42     let version = format!("{} {}", env!("GIT_HASH"), env!("COMMIT_DATE"));
43     if version.len() > 1 {
44         // If there is actually something here, print it.
45         print!(" ({version})");
46     }
47     println!();
48 }
49
50 fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
51     cmd.arg("--extern"); // always forward flag, but adjust filename:
52     let path = args.next().expect("`--extern` should be followed by a filename");
53     if let Some(lib) = path.strip_suffix(".rlib") {
54         // If this is an rlib, make it an rmeta.
55         cmd.arg(format!("{lib}.rmeta"));
56     } else {
57         // Some other extern file (e.g. a `.so`). Forward unchanged.
58         cmd.arg(path);
59     }
60 }
61
62 pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
63     // Check for version and help flags even when invoked as `cargo-miri`.
64     if has_arg_flag("--help") || has_arg_flag("-h") {
65         show_help();
66         return;
67     }
68     if has_arg_flag("--version") || has_arg_flag("-V") {
69         show_version();
70         return;
71     }
72
73     // Require a subcommand before any flags.
74     // We cannot know which of those flags take arguments and which do not,
75     // so we cannot detect subcommands later.
76     let Some(subcommand) = args.next() else {
77         show_error!("`cargo miri` needs to be called with a subcommand (`run`, `test`)");
78     };
79     let subcommand = match &*subcommand {
80         "setup" => MiriCommand::Setup,
81         "test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand),
82         _ =>
83             show_error!(
84                 "`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, and `setup`."
85             ),
86     };
87     let verbose = num_arg_flag("-v");
88
89     // Determine the involved architectures.
90     let rustc_version = VersionMeta::for_command(miri_for_host())
91         .expect("failed to determine underlying rustc version of Miri");
92     let host = &rustc_version.host;
93     let target = get_arg_flag_value("--target");
94     let target = target.as_ref().unwrap_or(host);
95
96     // We always setup.
97     setup(&subcommand, target, &rustc_version, verbose);
98
99     // Invoke actual cargo for the job, but with different flags.
100     // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
101     // requires some extra work to make the build check-only (see all the `--emit` hacks below).
102     // <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
103     // approach that uses `cargo check`, making that part easier but target and binary handling
104     // harder.
105     let cargo_miri_path = std::env::current_exe()
106         .expect("current executable path invalid")
107         .into_os_string()
108         .into_string()
109         .expect("current executable path is not valid UTF-8");
110     let cargo_cmd = match subcommand {
111         MiriCommand::Forward(s) => s,
112         MiriCommand::Setup => return, // `cargo miri setup` stops here.
113     };
114     let metadata = get_cargo_metadata();
115     let mut cmd = cargo();
116     cmd.arg(cargo_cmd);
117
118     // Forward all arguments before `--` other than `--target-dir` and its value to Cargo.
119     // (We want to *change* the target-dir value, so we must not forward it.)
120     let mut target_dir = None;
121     for arg in ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir") {
122         match arg {
123             Ok(value) => {
124                 if target_dir.is_some() {
125                     show_error!("`--target-dir` is provided more than once");
126                 }
127                 target_dir = Some(value.into());
128             }
129             Err(arg) => {
130                 cmd.arg(arg);
131             }
132         }
133     }
134     // Detect the target directory if it's not specified via `--target-dir`.
135     // (`cargo metadata` does not support `--target-dir`, that's why we have to handle this ourselves.)
136     let target_dir = target_dir.get_or_insert_with(|| metadata.target_directory.clone());
137     // Set `--target-dir` to `miri` inside the original target directory.
138     target_dir.push("miri");
139     cmd.arg("--target-dir").arg(target_dir);
140
141     // Make sure the build target is explicitly set.
142     // This is needed to make the `target.runner` settings do something,
143     // and it later helps us detect which crates are proc-macro/build-script
144     // (host crates) and which crates are needed for the program itself.
145     if get_arg_flag_value("--target").is_none() {
146         // No target given. Explicitly pick the host.
147         cmd.arg("--target");
148         cmd.arg(host);
149     }
150
151     // Set ourselves as runner for al binaries invoked by cargo.
152     // We use `all()` since `true` is not a thing in cfg-lang, but the empty conjunction is. :)
153     let cargo_miri_path_for_toml = escape_for_toml(&cargo_miri_path);
154     cmd.arg("--config")
155         .arg(format!("target.'cfg(all())'.runner=[{cargo_miri_path_for_toml}, 'runner']"));
156
157     // Forward all further arguments after `--` to cargo.
158     cmd.arg("--").args(args);
159
160     // Set `RUSTC_WRAPPER` to ourselves.  Cargo will prepend that binary to its usual invocation,
161     // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
162     // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
163     if env::var_os("RUSTC_WRAPPER").is_some() {
164         println!(
165             "WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."
166         );
167     }
168     cmd.env("RUSTC_WRAPPER", &cargo_miri_path);
169     // We are going to invoke `MIRI` for everything, not `RUSTC`.
170     if env::var_os("RUSTC").is_some() && env::var_os("MIRI").is_none() {
171         println!(
172             "WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver."
173         );
174     }
175     // Build scripts (and also cargo: https://github.com/rust-lang/cargo/issues/10885) will invoke
176     // `rustc` even when `RUSTC_WRAPPER` is set. To make sure everything is coherent, we want that
177     // to be the Miri driver, but acting as rustc, on the target level. (Target, rather than host,
178     // is needed for cross-interpretation situations.) This is not a perfect emulation of real rustc
179     // (it might be unable to produce binaries since the sysroot is check-only), but it's as close
180     // as we can get, and it's good enough for autocfg.
181     //
182     // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
183     // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
184     // there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We
185     // explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
186     // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host
187     // builds.
188     cmd.env("RUSTC", &fs::canonicalize(find_miri()).unwrap());
189     cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases!
190
191     // Set rustdoc to us as well, so we can run doctests.
192     cmd.env("RUSTDOC", &cargo_miri_path);
193
194     cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
195     if verbose > 0 {
196         cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
197     }
198
199     // Run cargo.
200     debug_cmd("[cargo-miri miri]", verbose, &cmd);
201     exec(cmd)
202 }
203
204 #[derive(Debug, Copy, Clone, PartialEq)]
205 pub enum RustcPhase {
206     /// `rustc` called during sysroot build.
207     Setup,
208     /// `rustc` called by `cargo` for regular build.
209     Build,
210     /// `rustc` called by `rustdoc` for doctest.
211     Rustdoc,
212 }
213
214 pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
215     /// Determines if we are being invoked (as rustc) to build a crate for
216     /// the "target" architecture, in contrast to the "host" architecture.
217     /// Host crates are for build scripts and proc macros and still need to
218     /// be built like normal; target crates need to be built for or interpreted
219     /// by Miri.
220     ///
221     /// Currently, we detect this by checking for "--target=", which is
222     /// never set for host crates. This matches what rustc bootstrap does,
223     /// which hopefully makes it "reliable enough". This relies on us always
224     /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
225     fn is_target_crate() -> bool {
226         get_arg_flag_value("--target").is_some()
227     }
228
229     /// Returns whether or not Cargo invoked the wrapper (this binary) to compile
230     /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
231     /// Cargo does not give us this information directly, so we need to check
232     /// various command-line flags.
233     fn is_runnable_crate() -> bool {
234         let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
235         let is_test = has_arg_flag("--test");
236         is_bin || is_test
237     }
238
239     fn out_filenames() -> Vec<PathBuf> {
240         if let Some(out_file) = get_arg_flag_value("-o") {
241             // `-o` has precedence over `--out-dir`.
242             vec![PathBuf::from(out_file)]
243         } else {
244             let out_dir = get_arg_flag_value("--out-dir").unwrap_or_default();
245             let path = PathBuf::from(out_dir);
246             // Ask rustc for the filename (since that is target-dependent).
247             let mut rustc = miri_for_host(); // sysroot doesn't matter for this so we just use the host
248             rustc.arg("--print").arg("file-names");
249             for flag in ["--crate-name", "--crate-type", "--target"] {
250                 for val in get_arg_flag_values(flag) {
251                     rustc.arg(flag).arg(val);
252                 }
253             }
254             // This is technically passed as `-C extra-filename=...`, but the prefix seems unique
255             // enough... (and cargo passes this before the filename so it should be unique)
256             if let Some(extra) = get_arg_flag_value("extra-filename") {
257                 rustc.arg("-C").arg(format!("extra-filename={extra}"));
258             }
259             rustc.arg("-");
260
261             let output = rustc.output().expect("cannot run rustc to determine file name");
262             assert!(
263                 output.status.success(),
264                 "rustc failed when determining file name:\n{output:?}"
265             );
266             let output =
267                 String::from_utf8(output.stdout).expect("rustc returned non-UTF-8 filename");
268             output
269                 .lines()
270                 .filter(|l| !l.is_empty())
271                 .map(|l| {
272                     let mut p = path.clone();
273                     p.push(l);
274                     p
275                 })
276                 .collect()
277         }
278     }
279
280     // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
281     // however, if we get called back by cargo here, we'll carefully compute the right flags
282     // ourselves, so we first un-do what the earlier phase did.
283     env::remove_var("MIRI_BE_RUSTC");
284
285     let verbose = std::env::var("MIRI_VERBOSE")
286         .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
287     let target_crate = is_target_crate();
288     // Determine whether this is cargo invoking rustc to get some infos.
289     let info_query = get_arg_flag_value("--print").is_some() || has_arg_flag("-vV");
290
291     let store_json = |info: CrateRunInfo| {
292         if get_arg_flag_value("--emit").unwrap_or_default().split(',').any(|e| e == "dep-info") {
293             // Create a stub .d file to stop Cargo from "rebuilding" the crate:
294             // https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693
295             // As we store a JSON file instead of building the crate here, an empty file is fine.
296             let dep_info_name = format!(
297                 "{}/{}{}.d",
298                 get_arg_flag_value("--out-dir").unwrap(),
299                 get_arg_flag_value("--crate-name").unwrap(),
300                 get_arg_flag_value("extra-filename").unwrap_or_default(),
301             );
302             if verbose > 0 {
303                 eprintln!("[cargo-miri rustc] writing stub dep-info to `{dep_info_name}`");
304             }
305             File::create(dep_info_name).expect("failed to create fake .d file");
306         }
307
308         for filename in out_filenames() {
309             if verbose > 0 {
310                 eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
311             }
312             info.store(&filename);
313         }
314     };
315
316     let runnable_crate = !info_query && is_runnable_crate();
317
318     if runnable_crate && target_crate {
319         assert!(
320             phase != RustcPhase::Setup,
321             "there should be no interpretation during sysroot build"
322         );
323         let inside_rustdoc = phase == RustcPhase::Rustdoc;
324         // This is the binary or test crate that we want to interpret under Miri.
325         // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
326         // like we want them.
327         // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
328         // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
329         let env = CrateRunEnv::collect(args, inside_rustdoc);
330
331         store_json(CrateRunInfo::RunWith(env.clone()));
332
333         // Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
334         // just creating the JSON file is not enough: we need to detect syntax errors,
335         // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
336         if inside_rustdoc {
337             let mut cmd = miri();
338
339             // Ensure --emit argument for a check-only build is present.
340             if let Some(val) =
341                 ArgFlagValueIter::from_str_iter(env.args.iter().map(|s| s as &str), "--emit").next()
342             {
343                 // For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape.
344                 assert_eq!(val, "metadata");
345             } else {
346                 // For all other kinds of tests, we can just add our flag.
347                 cmd.arg("--emit=metadata");
348             }
349
350             // Alter the `-o` parameter so that it does not overwrite the JSON file we stored above.
351             let mut args = env.args;
352             let mut out_filename = None;
353             for i in 0..args.len() {
354                 if args[i] == "-o" {
355                     out_filename = Some(args[i + 1].clone());
356                     args[i + 1].push_str(".miri");
357                 }
358             }
359             let out_filename = out_filename.expect("rustdoc must pass `-o`");
360
361             cmd.args(&args);
362             cmd.env("MIRI_BE_RUSTC", "target");
363
364             if verbose > 0 {
365                 eprintln!(
366                     "[cargo-miri rustc inside rustdoc] captured input:\n{}",
367                     std::str::from_utf8(&env.stdin).unwrap()
368                 );
369                 eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{cmd:?}");
370             }
371
372             exec_with_pipe(cmd, &env.stdin, format!("{out_filename}.stdin"));
373         }
374
375         return;
376     }
377
378     if runnable_crate && get_arg_flag_values("--extern").any(|krate| krate == "proc_macro") {
379         // This is a "runnable" `proc-macro` crate (unit tests). We do not support
380         // interpreting that under Miri now, so we write a JSON file to (display a
381         // helpful message and) skip it in the runner phase.
382         store_json(CrateRunInfo::SkipProcMacroTest);
383         return;
384     }
385
386     let mut cmd = miri();
387     let mut emit_link_hack = false;
388     // Arguments are treated very differently depending on whether this crate is
389     // for interpretation by Miri, or for use by a build script / proc macro.
390     if !info_query && target_crate {
391         // Forward arguments, but remove "link" from "--emit" to make this a check-only build.
392         let emit_flag = "--emit";
393         while let Some(arg) = args.next() {
394             if let Some(val) = arg.strip_prefix(emit_flag) {
395                 // Patch this argument. First, extract its value.
396                 let val =
397                     val.strip_prefix('=').expect("`cargo` should pass `--emit=X` as one argument");
398                 let mut val: Vec<_> = val.split(',').collect();
399                 // Now make sure "link" is not in there, but "metadata" is.
400                 if let Some(i) = val.iter().position(|&s| s == "link") {
401                     emit_link_hack = true;
402                     val.remove(i);
403                     if !val.iter().any(|&s| s == "metadata") {
404                         val.push("metadata");
405                     }
406                 }
407                 cmd.arg(format!("{emit_flag}={}", val.join(",")));
408             } else if arg == "--extern" {
409                 // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files:
410                 // https://github.com/rust-lang/miri/issues/1705
411                 forward_patched_extern_arg(&mut args, &mut cmd);
412             } else {
413                 cmd.arg(arg);
414             }
415         }
416
417         // During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
418         if phase == RustcPhase::Setup
419             && get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort")
420         {
421             cmd.arg("-C").arg("panic=abort");
422         }
423     } else {
424         // For host crates (but not when we are just printing some info),
425         // we might still have to set the sysroot.
426         if !info_query {
427             // When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
428             // due to bootstrap complications.
429             if let Some(sysroot) = std::env::var_os("MIRI_HOST_SYSROOT") {
430                 cmd.arg("--sysroot").arg(sysroot);
431             }
432         }
433
434         // For host crates or when we are printing, just forward everything.
435         cmd.args(args);
436     }
437
438     // We want to compile, not interpret. We still use Miri to make sure the compiler version etc
439     // are the exact same as what is used for interpretation.
440     // MIRI_DEFAULT_ARGS should not be used to build host crates, hence setting "target" or "host"
441     // as the value here to help Miri differentiate them.
442     cmd.env("MIRI_BE_RUSTC", if target_crate { "target" } else { "host" });
443
444     // Run it.
445     if verbose > 0 {
446         eprintln!(
447             "[cargo-miri rustc] target_crate={target_crate} runnable_crate={runnable_crate} info_query={info_query}"
448         );
449     }
450
451     // Create a stub .rlib file if "link" was requested by cargo.
452     // This is necessary to prevent cargo from doing rebuilds all the time.
453     if emit_link_hack {
454         for filename in out_filenames() {
455             if verbose > 0 {
456                 eprintln!("[cargo-miri rustc] creating fake lib file at `{}`", filename.display());
457             }
458             File::create(filename).expect("failed to create fake lib file");
459         }
460     }
461
462     debug_cmd("[cargo-miri rustc]", verbose, &cmd);
463     exec(cmd);
464 }
465
466 #[derive(Debug, Copy, Clone, PartialEq)]
467 pub enum RunnerPhase {
468     /// `cargo` is running a binary
469     Cargo,
470     /// `rustdoc` is running a binary
471     Rustdoc,
472 }
473
474 pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
475     // phase_cargo_miri set `MIRI_BE_RUSTC` for when build scripts directly invoke the driver;
476     // however, if we get called back by cargo here, we'll carefully compute the right flags
477     // ourselves, so we first un-do what the earlier phase did.
478     env::remove_var("MIRI_BE_RUSTC");
479
480     let verbose = std::env::var("MIRI_VERBOSE")
481         .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
482
483     let binary = binary_args.next().unwrap();
484     let file = File::open(&binary)
485         .unwrap_or_else(|_| show_error!(
486             "file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary
487         ));
488     let file = BufReader::new(file);
489
490     let info = serde_json::from_reader(file).unwrap_or_else(|_| {
491         show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)
492     });
493     let info = match info {
494         CrateRunInfo::RunWith(info) => info,
495         CrateRunInfo::SkipProcMacroTest => {
496             eprintln!(
497                 "Running unit tests of `proc-macro` crates is not currently supported by Miri."
498             );
499             return;
500         }
501     };
502
503     let mut cmd = miri();
504
505     // Set missing env vars. We prefer build-time env vars over run-time ones; see
506     // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
507     for (name, val) in info.env {
508         if let Some(old_val) = env::var_os(&name) {
509             if old_val == val {
510                 // This one did not actually change, no need to re-set it.
511                 // (This keeps the `debug_cmd` below more manageable.)
512                 continue;
513             } else if verbose > 0 {
514                 eprintln!(
515                     "[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
516                 );
517             }
518         }
519         cmd.env(name, val);
520     }
521
522     // Forward rustc arguments.
523     // We need to patch "--extern" filenames because we forced a check-only
524     // build without cargo knowing about that: replace `.rlib` suffix by
525     // `.rmeta`.
526     // We also need to remove `--error-format` as cargo specifies that to be JSON,
527     // but when we run here, cargo does not interpret the JSON any more. `--json`
528     // then also nees to be dropped.
529     let mut args = info.args.into_iter();
530     let error_format_flag = "--error-format";
531     let json_flag = "--json";
532     while let Some(arg) = args.next() {
533         if arg == "--extern" {
534             forward_patched_extern_arg(&mut args, &mut cmd);
535         } else if let Some(suffix) = arg.strip_prefix(error_format_flag) {
536             assert!(suffix.starts_with('='));
537             // Drop this argument.
538         } else if let Some(suffix) = arg.strip_prefix(json_flag) {
539             assert!(suffix.starts_with('='));
540             // Drop this argument.
541         } else {
542             cmd.arg(arg);
543         }
544     }
545     // Respect `MIRIFLAGS`.
546     if let Ok(a) = env::var("MIRIFLAGS") {
547         // This code is taken from `RUSTFLAGS` handling in cargo.
548         let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string);
549         cmd.args(args);
550     }
551
552     // Then pass binary arguments.
553     cmd.arg("--");
554     cmd.args(binary_args);
555
556     // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
557     // But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
558     cmd.current_dir(info.current_dir);
559     cmd.env("MIRI_CWD", env::current_dir().unwrap());
560
561     // Run it.
562     debug_cmd("[cargo-miri runner]", verbose, &cmd);
563     match phase {
564         RunnerPhase::Rustdoc => exec_with_pipe(cmd, &info.stdin, format!("{binary}.stdin")),
565         RunnerPhase::Cargo => exec(cmd),
566     }
567 }
568
569 pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
570     let verbose = std::env::var("MIRI_VERBOSE")
571         .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
572
573     // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
574     // just default to a straight-forward invocation for now:
575     let mut cmd = Command::new("rustdoc");
576
577     let extern_flag = "--extern";
578     let runtool_flag = "--runtool";
579     while let Some(arg) = args.next() {
580         if arg == extern_flag {
581             // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
582             forward_patched_extern_arg(&mut args, &mut cmd);
583         } else if arg == runtool_flag {
584             // An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
585             // Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
586             // otherwise, we won't be called as rustdoc at all.
587             show_error!("cross-interpreting doctests is not currently supported by Miri.");
588         } else {
589             cmd.arg(arg);
590         }
591     }
592
593     // Doctests of `proc-macro` crates (and their dependencies) are always built for the host,
594     // so we are not able to run them in Miri.
595     if get_arg_flag_values("--crate-type").any(|crate_type| crate_type == "proc-macro") {
596         eprintln!("Running doctests of `proc-macro` crates is not currently supported by Miri.");
597         return;
598     }
599
600     // For each doctest, rustdoc starts two child processes: first the test is compiled,
601     // then the produced executable is invoked. We want to reroute both of these to cargo-miri,
602     // such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
603     //
604     // rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
605     // it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
606     // Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
607     // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
608     cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
609
610     // The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
611     // which are disabled by default. We first need to enable them explicitly:
612     cmd.arg("-Z").arg("unstable-options");
613
614     // rustdoc needs to know the right sysroot.
615     cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
616     // make sure the 'miri' flag is set for rustdoc
617     cmd.arg("--cfg").arg("miri");
618
619     // Make rustdoc call us back.
620     let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
621     cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
622     cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
623
624     debug_cmd("[cargo-miri rustdoc]", verbose, &cmd);
625     exec(cmd)
626 }