]> git.lizzy.rs Git - rust.git/blob - cargo-miri/bin.rs
Auto merge of #1743 - RalfJung:callee-checks, r=RalfJung
[rust.git] / cargo-miri / bin.rs
1 use std::env;
2 use std::ffi::OsString;
3 use std::fs::{self, File};
4 use std::io::{self, BufRead, BufReader, BufWriter, Write};
5 use std::iter::TakeWhile;
6 use std::ops::Not;
7 use std::path::{Path, PathBuf};
8 use std::process::Command;
9
10 use serde::{Deserialize, Serialize};
11
12 use rustc_version::VersionMeta;
13
14 const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 22);
15
16 const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
17
18 Usage:
19     cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
20
21 Subcommands:
22     run                      Run binaries
23     test                     Run tests
24     setup                    Only perform automatic setup, but without asking questions (for getting a proper libstd)
25
26 The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
27
28 Examples:
29     cargo miri run
30     cargo miri test -- test-suite-filter
31 "#;
32
33 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
34 enum MiriCommand {
35     Run,
36     Test,
37     Setup,
38 }
39
40 /// The information to run a crate with the given environment.
41 #[derive(Serialize, Deserialize)]
42 struct CrateRunEnv {
43     /// The command-line arguments.
44     args: Vec<String>,
45     /// The environment.
46     env: Vec<(OsString, OsString)>,
47     /// The current working directory.
48     current_dir: OsString,
49 }
50
51 /// The information Miri needs to run a crate. Stored as JSON when the crate is "compiled".
52 #[derive(Serialize, Deserialize)]
53 enum CrateRunInfo {
54     /// Run it with the given environment.
55     RunWith(CrateRunEnv),
56     /// Skip it as Miri does not support interpreting such kind of crates.
57     SkipProcMacroTest,
58 }
59
60 impl CrateRunInfo {
61     /// Gather all the information we need.
62     fn collect(args: env::Args) -> Self {
63         let args = args.collect();
64         let env = env::vars_os().collect();
65         let current_dir = env::current_dir().unwrap().into_os_string();
66         Self::RunWith(CrateRunEnv { args, env, current_dir })
67     }
68
69     fn store(&self, filename: &Path) {
70         let file = File::create(filename)
71             .unwrap_or_else(|_| show_error(format!("cannot create `{}`", filename.display())));
72         let file = BufWriter::new(file);
73         serde_json::ser::to_writer(file, self)
74             .unwrap_or_else(|_| show_error(format!("cannot write to `{}`", filename.display())));
75     }
76 }
77
78 fn show_help() {
79     println!("{}", CARGO_MIRI_HELP);
80 }
81
82 fn show_version() {
83     println!(
84         "miri {} ({} {})",
85         env!("CARGO_PKG_VERSION"),
86         env!("VERGEN_SHA_SHORT"),
87         env!("VERGEN_COMMIT_DATE")
88     );
89 }
90
91 fn show_error(msg: String) -> ! {
92     eprintln!("fatal error: {}", msg);
93     std::process::exit(1)
94 }
95
96 // Determines whether a `--flag` is present.
97 fn has_arg_flag(name: &str) -> bool {
98     let mut args = std::env::args().take_while(|val| val != "--");
99     args.any(|val| val == name)
100 }
101
102 /// Yields all values of command line flag `name`.
103 struct ArgFlagValueIter<'a> {
104     args: TakeWhile<env::Args, fn(&String) -> bool>,
105     name: &'a str,
106 }
107
108 impl<'a> ArgFlagValueIter<'a> {
109     fn new(name: &'a str) -> Self {
110         Self {
111             // Stop searching at `--`.
112             args: env::args().take_while(|val| val != "--"),
113             name,
114         }
115     }
116 }
117
118 impl Iterator for ArgFlagValueIter<'_> {
119     type Item = String;
120
121     fn next(&mut self) -> Option<Self::Item> {
122         loop {
123             let arg = self.args.next()?;
124             if !arg.starts_with(self.name) {
125                 continue;
126             }
127             // Strip leading `name`.
128             let suffix = &arg[self.name.len()..];
129             if suffix.is_empty() {
130                 // This argument is exactly `name`; the next one is the value.
131                 return self.args.next();
132             } else if suffix.starts_with('=') {
133                 // This argument is `name=value`; get the value.
134                 // Strip leading `=`.
135                 return Some(suffix[1..].to_owned());
136             }
137         }
138     }
139 }
140
141 /// Gets the value of a `--flag`.
142 fn get_arg_flag_value(name: &str) -> Option<String> {
143     ArgFlagValueIter::new(name).next()
144 }
145
146 fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
147     cmd.arg("--extern"); // always forward flag, but adjust filename:
148     let path = args.next().expect("`--extern` should be followed by a filename");
149     if let Some(lib) = path.strip_suffix(".rlib") {
150         // If this is an rlib, make it an rmeta.
151         cmd.arg(format!("{}.rmeta", lib));
152     } else {
153         // Some other extern file (e.g. a `.so`). Forward unchanged.
154         cmd.arg(path);
155     }
156 }
157
158 /// Returns the path to the `miri` binary
159 fn find_miri() -> PathBuf {
160     if let Some(path) = env::var_os("MIRI") {
161         return path.into();
162     }
163     let mut path = std::env::current_exe().expect("current executable path invalid");
164     path.set_file_name("miri");
165     path
166 }
167
168 fn miri() -> Command {
169     Command::new(find_miri())
170 }
171
172 fn version_info() -> VersionMeta {
173     VersionMeta::for_command(miri()).expect("failed to determine underlying rustc version of Miri")
174 }
175
176 fn cargo() -> Command {
177     Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
178 }
179
180 fn xargo_check() -> Command {
181     Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check")))
182 }
183
184 /// Execute the command. If it fails, fail this process with the same exit code.
185 /// Otherwise, continue.
186 fn exec(mut cmd: Command) {
187     let exit_status = cmd.status().expect("failed to run command");
188     if exit_status.success().not() {
189         std::process::exit(exit_status.code().unwrap_or(-1))
190     }
191 }
192
193 fn xargo_version() -> Option<(u32, u32, u32)> {
194     let out = xargo_check().arg("--version").output().ok()?;
195     if !out.status.success() {
196         return None;
197     }
198     // Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)".
199     let line = out
200         .stderr
201         .lines()
202         .nth(0)
203         .expect("malformed `xargo --version` output: not at least one line")
204         .expect("malformed `xargo --version` output: error reading first line");
205     let (name, version) = {
206         let mut split = line.split(' ');
207         (
208             split.next().expect("malformed `xargo --version` output: empty"),
209             split.next().expect("malformed `xargo --version` output: not at least two words"),
210         )
211     };
212     if name != "xargo" {
213         // This is some fork of xargo
214         return None;
215     }
216     let mut version_pieces = version.split('.');
217     let major = version_pieces
218         .next()
219         .expect("malformed `xargo --version` output: not a major version piece")
220         .parse()
221         .expect("malformed `xargo --version` output: major version is not an integer");
222     let minor = version_pieces
223         .next()
224         .expect("malformed `xargo --version` output: not a minor version piece")
225         .parse()
226         .expect("malformed `xargo --version` output: minor version is not an integer");
227     let patch = version_pieces
228         .next()
229         .expect("malformed `xargo --version` output: not a patch version piece")
230         .parse()
231         .expect("malformed `xargo --version` output: patch version is not an integer");
232     if !version_pieces.next().is_none() {
233         panic!("malformed `xargo --version` output: more than three pieces in version");
234     }
235     Some((major, minor, patch))
236 }
237
238 fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
239     // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
240     // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
241     // so we also check their `TF_BUILD`.
242     let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
243     if ask && !is_ci {
244         let mut buf = String::new();
245         print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text);
246         io::stdout().flush().unwrap();
247         io::stdin().read_line(&mut buf).unwrap();
248         match buf.trim().to_lowercase().as_ref() {
249             // Proceed.
250             "" | "y" | "yes" => {}
251             "n" | "no" => show_error(format!("aborting as per your request")),
252             a => show_error(format!("invalid answer `{}`", a)),
253         };
254     } else {
255         println!("Running `{:?}` to {}.", cmd, text);
256     }
257
258     if cmd.status().expect(&format!("failed to execute {:?}", cmd)).success().not() {
259         show_error(format!("failed to {}", text));
260     }
261 }
262
263 /// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
264 /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
265 /// done all this already.
266 fn setup(subcommand: MiriCommand) {
267     if std::env::var_os("MIRI_SYSROOT").is_some() {
268         if subcommand == MiriCommand::Setup {
269             println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
270         }
271         return;
272     }
273
274     // Subcommands other than `setup` will do a setup if necessary, but
275     // interactively confirm first.
276     let ask_user = subcommand != MiriCommand::Setup;
277
278     // First, we need xargo.
279     if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) {
280         if std::env::var_os("XARGO_CHECK").is_some() {
281             // The user manually gave us a xargo binary; don't do anything automatically.
282             show_error(format!("xargo is too old; please upgrade to the latest version"))
283         }
284         let mut cmd = cargo();
285         cmd.args(&["install", "xargo"]);
286         ask_to_run(cmd, ask_user, "install a recent enough xargo");
287     }
288
289     // Determine where the rust sources are located.  `XARGO_RUST_SRC` env var trumps everything.
290     let rust_src = match std::env::var_os("XARGO_RUST_SRC") {
291         Some(path) => {
292             let path = PathBuf::from(path);
293             // Make path absolute if possible.
294             path.canonicalize().unwrap_or(path)
295         }
296         None => {
297             // Check for `rust-src` rustup component.
298             let sysroot = miri()
299                 .args(&["--print", "sysroot"])
300                 .output()
301                 .expect("failed to determine sysroot")
302                 .stdout;
303             let sysroot = std::str::from_utf8(&sysroot).unwrap();
304             let sysroot = Path::new(sysroot.trim_end_matches('\n'));
305             // Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`.
306             let rustup_src =
307                 sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
308             if !rustup_src.join("std").join("Cargo.toml").exists() {
309                 // Ask the user to install the `rust-src` component, and use that.
310                 let mut cmd = Command::new("rustup");
311                 cmd.args(&["component", "add", "rust-src"]);
312                 ask_to_run(
313                     cmd,
314                     ask_user,
315                     "install the `rust-src` component for the selected toolchain",
316                 );
317             }
318             rustup_src
319         }
320     };
321     if !rust_src.exists() {
322         show_error(format!("given Rust source directory `{}` does not exist.", rust_src.display()));
323     }
324
325     // Next, we need our own libstd. Prepare a xargo project for that purpose.
326     // We will do this work in whatever is a good cache dir for this platform.
327     let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
328     let dir = dirs.cache_dir();
329     if !dir.exists() {
330         fs::create_dir_all(&dir).unwrap();
331     }
332     // The interesting bit: Xargo.toml
333     File::create(dir.join("Xargo.toml"))
334         .unwrap()
335         .write_all(
336             br#"
337 [dependencies.std]
338 default_features = false
339 # We support unwinding, so enable that panic runtime.
340 features = ["panic_unwind", "backtrace"]
341
342 [dependencies.test]
343 "#,
344         )
345         .unwrap();
346     // The boring bits: a dummy project for xargo.
347     // FIXME: With xargo-check, can we avoid doing this?
348     File::create(dir.join("Cargo.toml"))
349         .unwrap()
350         .write_all(
351             br#"
352 [package]
353 name = "miri-xargo"
354 description = "A dummy project for building libstd with xargo."
355 version = "0.0.0"
356
357 [lib]
358 path = "lib.rs"
359 "#,
360         )
361         .unwrap();
362     File::create(dir.join("lib.rs")).unwrap();
363
364     // Determine architectures.
365     // We always need to set a target so rustc bootstrap can tell apart host from target crates.
366     let host = version_info().host;
367     let target = get_arg_flag_value("--target");
368     let target = target.as_ref().unwrap_or(&host);
369     // Now invoke xargo.
370     let mut command = xargo_check();
371     command.arg("check").arg("-q");
372     command.arg("--target").arg(target);
373     command.current_dir(&dir);
374     command.env("XARGO_HOME", &dir);
375     command.env("XARGO_RUST_SRC", &rust_src);
376     // Use Miri as rustc to build a libstd compatible with us (and use the right flags).
377     // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
378     // because we still need bootstrap to distinguish between host and target crates.
379     // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
380     // for target crates.
381     // We set ourselves (`cargo-miri`) instead of Miri directly to be able to patch the flags
382     // for `libpanic_abort` (usually this is done by bootstrap but we have to do it ourselves).
383     // The `MIRI_BE_RUSTC` will mean we dispatch to `phase_setup_rustc`.
384     let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
385     if env::var_os("RUSTC_STAGE").is_some() {
386         command.env("RUSTC_REAL", &cargo_miri_path);
387     } else {
388         command.env("RUSTC", &cargo_miri_path);
389     }
390     command.env("MIRI_BE_RUSTC", "1");
391     // Make sure there are no other wrappers or flags getting in our way
392     // (Cc https://github.com/rust-lang/miri/issues/1421).
393     // This is consistent with normal `cargo build` that does not apply `RUSTFLAGS`
394     // to the sysroot either.
395     command.env_remove("RUSTC_WRAPPER");
396     command.env_remove("RUSTFLAGS");
397     // Disable debug assertions in the standard library -- Miri is already slow enough.
398     // But keep the overflow checks, they are cheap.
399     command.env("RUSTFLAGS", "-Cdebug-assertions=off -Coverflow-checks=on");
400     // Finally run it!
401     if command.status().expect("failed to run xargo").success().not() {
402         show_error(format!("failed to run xargo"));
403     }
404
405     // That should be it! But we need to figure out where xargo built stuff.
406     // Unfortunately, it puts things into a different directory when the
407     // architecture matches the host.
408     let sysroot = if target == &host { dir.join("HOST") } else { PathBuf::from(dir) };
409     std::env::set_var("MIRI_SYSROOT", &sysroot); // pass the env var to the processes we spawn, which will turn it into "--sysroot" flags
410     // Figure out what to print.
411     let print_sysroot = subcommand == MiriCommand::Setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
412     if print_sysroot {
413         // Print just the sysroot and nothing else; this way we do not need any escaping.
414         println!("{}", sysroot.display());
415     } else if subcommand == MiriCommand::Setup {
416         println!("A libstd for Miri is now available in `{}`.", sysroot.display());
417     }
418 }
419
420 fn phase_setup_rustc(args: env::Args) {
421     // Mostly we just forward everything.
422     // `MIRI_BE_RUST` is already set.
423     let mut cmd = miri();
424     cmd.args(args);
425
426     // Patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
427     if get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort") {
428         cmd.arg("-C").arg("panic=abort");
429     }
430
431     // Run it!
432     exec(cmd);
433 }
434
435 fn phase_cargo_miri(mut args: env::Args) {
436     // Check for version and help flags even when invoked as `cargo-miri`.
437     if has_arg_flag("--help") || has_arg_flag("-h") {
438         show_help();
439         return;
440     }
441     if has_arg_flag("--version") || has_arg_flag("-V") {
442         show_version();
443         return;
444     }
445
446     // Require a subcommand before any flags.
447     // We cannot know which of those flags take arguments and which do not,
448     // so we cannot detect subcommands later.
449     let subcommand = match args.next().as_deref() {
450         Some("test") => MiriCommand::Test,
451         Some("run") => MiriCommand::Run,
452         Some("setup") => MiriCommand::Setup,
453         // Invalid command.
454         _ => show_error(format!("`cargo miri` supports the following subcommands: `run`, `test`, and `setup`.")),
455     };
456     let verbose = has_arg_flag("-v");
457
458     // We always setup.
459     setup(subcommand);
460
461     // Invoke actual cargo for the job, but with different flags.
462     // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
463     // requires some extra work to make the build check-only (see all the `--emit` hacks below).
464     // <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
465     // approach that uses `cargo check`, making that part easier but target and binary handling
466     // harder.
467     let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
468     let cargo_cmd = match subcommand {
469         MiriCommand::Test => "test",
470         MiriCommand::Run => "run",
471         MiriCommand::Setup => return, // `cargo miri setup` stops here.
472     };
473     let mut cmd = cargo();
474     cmd.arg(cargo_cmd);
475
476     // Make sure we know the build target, and cargo does, too.
477     // This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something,
478     // and it later helps us detect which crates are proc-macro/build-script
479     // (host crates) and which crates are needed for the program itself.
480     let host = version_info().host;
481     let target = get_arg_flag_value("--target");
482     let target = if let Some(ref target) = target {
483         target
484     } else {
485         // No target given. Pick default and tell cargo about it.
486         cmd.arg("--target");
487         cmd.arg(&host);
488         &host
489     };
490
491     // Forward all further arguments. We do some processing here because we want to
492     // detect people still using the old way of passing flags to Miri
493     // (`cargo miri -- -Zmiri-foo`).
494     while let Some(arg) = args.next() {
495         cmd.arg(&arg);
496         if arg == "--" {
497             // Check if the next argument starts with `-Zmiri`. If yes, we assume
498             // this is an old-style invocation.
499             if let Some(next_arg) = args.next() {
500                 if next_arg.starts_with("-Zmiri") || next_arg == "--" {
501                     eprintln!(
502                         "WARNING: it seems like you are setting Miri's flags in `cargo miri` the old way,\n\
503                         i.e., by passing them after the first `--`. This style is deprecated; please set\n\
504                         the MIRIFLAGS environment variable instead. `cargo miri run/test` now interprets\n\
505                         arguments the exact same way as `cargo run/test`."
506                     );
507                     // Old-style invocation. Turn these into MIRIFLAGS, if there are any.
508                     if next_arg != "--" {
509                         let mut miriflags = env::var("MIRIFLAGS").unwrap_or_default();
510                         miriflags.push(' ');
511                         miriflags.push_str(&next_arg);
512                         while let Some(further_arg) = args.next() {
513                             if further_arg == "--" {
514                                 // End of the Miri flags!
515                                 break;
516                             }
517                             miriflags.push(' ');
518                             miriflags.push_str(&further_arg);
519                         }
520                         env::set_var("MIRIFLAGS", miriflags);
521                     }
522                     // Pass the remaining flags to cargo.
523                     cmd.args(args);
524                     break;
525                 }
526                 // Not a Miri argument after all, make sure we pass it to cargo.
527                 cmd.arg(next_arg);
528             }
529         }
530     }
531
532     // Set `RUSTC_WRAPPER` to ourselves.  Cargo will prepend that binary to its usual invocation,
533     // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
534     // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
535     if env::var_os("RUSTC_WRAPPER").is_some() {
536         println!("WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping.");
537     }
538     cmd.env("RUSTC_WRAPPER", &cargo_miri_path);
539
540     let runner_env_name = |triple: &str| {
541         format!("CARGO_TARGET_{}_RUNNER", triple.to_uppercase().replace('-', "_"))
542     };
543     let host_runner_env_name = runner_env_name(&host);
544     let target_runner_env_name = runner_env_name(target);
545     // Set the target runner to us, so we can interpret the binaries.
546     cmd.env(&target_runner_env_name, &cargo_miri_path);
547     // Unit tests of `proc-macro` crates are run on the host, so we set the host runner to
548     // us in order to skip them.
549     cmd.env(&host_runner_env_name, &cargo_miri_path);
550
551     // Set rustdoc to us as well, so we can make it do nothing (see issue #584).
552     cmd.env("RUSTDOC", &cargo_miri_path);
553
554     // Run cargo.
555     if verbose {
556         eprintln!("[cargo-miri miri] RUSTC_WRAPPER={:?}", cargo_miri_path);
557         eprintln!("[cargo-miri miri] {}={:?}", target_runner_env_name, cargo_miri_path);
558         if *target != host {
559             eprintln!("[cargo-miri miri] {}={:?}", host_runner_env_name, cargo_miri_path);
560         }
561         eprintln!("[cargo-miri miri] RUSTDOC={:?}", cargo_miri_path);
562         eprintln!("[cargo-miri miri] {:?}", cmd);
563         cmd.env("MIRI_VERBOSE", ""); // This makes the other phases verbose.
564     }
565     exec(cmd)
566 }
567
568 fn phase_cargo_rustc(mut args: env::Args) {
569     /// Determines if we are being invoked (as rustc) to build a crate for
570     /// the "target" architecture, in contrast to the "host" architecture.
571     /// Host crates are for build scripts and proc macros and still need to
572     /// be built like normal; target crates need to be built for or interpreted
573     /// by Miri.
574     ///
575     /// Currently, we detect this by checking for "--target=", which is
576     /// never set for host crates. This matches what rustc bootstrap does,
577     /// which hopefully makes it "reliable enough". This relies on us always
578     /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
579     fn is_target_crate() -> bool {
580         get_arg_flag_value("--target").is_some()
581     }
582
583     /// Returns whether or not Cargo invoked the wrapper (this binary) to compile
584     /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
585     /// Cargo does not give us this information directly, so we need to check
586     /// various command-line flags.
587     fn is_runnable_crate() -> bool {
588         let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
589         let is_test = has_arg_flag("--test");
590         is_bin || is_test
591     }
592
593     fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
594         let mut path = PathBuf::from(get_arg_flag_value("--out-dir").unwrap());
595         path.push(format!(
596             "{}{}{}{}",
597             prefix,
598             get_arg_flag_value("--crate-name").unwrap(),
599             // This is technically a `-C` flag but the prefix seems unique enough...
600             // (and cargo passes this before the filename so it should be unique)
601             get_arg_flag_value("extra-filename").unwrap_or(String::new()),
602             suffix,
603         ));
604         path
605     }
606
607     let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
608     let target_crate = is_target_crate();
609     let print = get_arg_flag_value("--print").is_some(); // whether this is cargo passing `--print` to get some infos
610
611     let store_json = |info: CrateRunInfo| {
612         // Create a stub .d file to stop Cargo from "rebuilding" the crate:
613         // https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693
614         // As we store a JSON file instead of building the crate here, an empty file is fine.
615         let dep_info_name = out_filename("", ".d");
616         if verbose {
617             eprintln!("[cargo-miri rustc] writing stub dep-info to `{}`", dep_info_name.display());
618         }
619         File::create(dep_info_name).expect("failed to create fake .d file");
620
621         let filename = out_filename("", "");
622         if verbose {
623             eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
624         }
625         info.store(&filename);
626         // For Windows, do the same thing again with `.exe` appended to the filename.
627         // (Need to do this here as cargo moves that "binary" to a different place before running it.)
628         info.store(&out_filename("", ".exe"));
629     };
630
631     let runnable_crate = !print && is_runnable_crate();
632
633     if runnable_crate && target_crate {
634         // This is the binary or test crate that we want to interpret under Miri.
635         // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
636         // like we want them.
637         // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
638         // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
639         store_json(CrateRunInfo::collect(args));
640         return;
641     }
642
643     if runnable_crate && ArgFlagValueIter::new("--extern").any(|krate| krate == "proc_macro") {
644         // This is a "runnable" `proc-macro` crate (unit tests). We do not support
645         // interpreting that under Miri now, so we write a JSON file to (display a
646         // helpful message and) skip it in the runner phase.
647         store_json(CrateRunInfo::SkipProcMacroTest);
648         return;
649     }
650
651     let mut cmd = miri();
652     let mut emit_link_hack = false;
653     // Arguments are treated very differently depending on whether this crate is
654     // for interpretation by Miri, or for use by a build script / proc macro.
655     if !print && target_crate {
656         // Forward arguments, but remove "link" from "--emit" to make this a check-only build.
657         let emit_flag = "--emit";
658         while let Some(arg) = args.next() {
659             if arg.starts_with(emit_flag) {
660                 // Patch this argument. First, extract its value.
661                 let val = &arg[emit_flag.len()..];
662                 assert!(val.starts_with("="), "`cargo` should pass `--emit=X` as one argument");
663                 let val = &val[1..];
664                 let mut val: Vec<_> = val.split(',').collect();
665                 // Now make sure "link" is not in there, but "metadata" is.
666                 if let Some(i) = val.iter().position(|&s| s == "link") {
667                     emit_link_hack = true;
668                     val.remove(i);
669                     if !val.iter().any(|&s| s == "metadata") {
670                         val.push("metadata");
671                     }
672                 }
673                 cmd.arg(format!("{}={}", emit_flag, val.join(",")));
674             } else if arg == "--extern" {
675                 // Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files:
676                 // https://github.com/rust-lang/miri/issues/1705
677                 forward_patched_extern_arg(&mut args, &mut cmd);
678             } else {
679                 cmd.arg(arg);
680             }
681         }
682
683         // Use our custom sysroot.
684         let sysroot =
685             env::var_os("MIRI_SYSROOT").expect("the wrapper should have set MIRI_SYSROOT");
686         cmd.arg("--sysroot");
687         cmd.arg(sysroot);
688     } else {
689         // For host crates or when we are printing, just forward everything.
690         cmd.args(args);
691     }
692
693     // We want to compile, not interpret. We still use Miri to make sure the compiler version etc
694     // are the exact same as what is used for interpretation.
695     cmd.env("MIRI_BE_RUSTC", "1");
696
697     // Run it.
698     if verbose {
699         eprintln!("[cargo-miri rustc] {:?}", cmd);
700     }
701     exec(cmd);
702
703     // Create a stub .rlib file if "link" was requested by cargo.
704     // This is necessary to prevent cargo from doing rebuilds all the time.
705     if emit_link_hack {
706         // Some platforms prepend "lib", some do not... let's just create both files.
707         File::create(out_filename("lib", ".rlib")).expect("failed to create fake .rlib file");
708         File::create(out_filename("", ".rlib")).expect("failed to create fake .rlib file");
709         // Just in case this is a cdylib or staticlib, also create those fake files.
710         File::create(out_filename("lib", ".so")).expect("failed to create fake .so file");
711         File::create(out_filename("lib", ".a")).expect("failed to create fake .a file");
712         File::create(out_filename("lib", ".dylib")).expect("failed to create fake .dylib file");
713         File::create(out_filename("", ".dll")).expect("failed to create fake .dll file");
714         File::create(out_filename("", ".lib")).expect("failed to create fake .lib file");
715     }
716 }
717
718 fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
719     let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
720
721     let file = File::open(&binary)
722         .unwrap_or_else(|_| show_error(format!("file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary)));
723     let file = BufReader::new(file);
724
725     let info = serde_json::from_reader(file)
726         .unwrap_or_else(|_| show_error(format!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)));
727     let info = match info {
728         CrateRunInfo::RunWith(info) => info,
729         CrateRunInfo::SkipProcMacroTest => {
730             eprintln!("Running unit tests of `proc-macro` crates is not currently supported by Miri.");
731             return;
732         }
733     };
734
735     let mut cmd = miri();
736
737     // Set missing env vars. We prefer build-time env vars over run-time ones; see
738     // <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
739     for (name, val) in info.env {
740         if verbose {
741             if let Some(old_val) = env::var_os(&name) {
742                 if old_val != val {
743                     eprintln!("[cargo-miri runner] Overwriting run-time env var {:?}={:?} with build-time value {:?}", name, old_val, val);
744                 }
745             }
746         }
747         cmd.env(name, val);
748     }
749
750     // Forward rustc arguments.
751     // We need to patch "--extern" filenames because we forced a check-only
752     // build without cargo knowing about that: replace `.rlib` suffix by
753     // `.rmeta`.
754     // We also need to remove `--error-format` as cargo specifies that to be JSON,
755     // but when we run here, cargo does not interpret the JSON any more. `--json`
756     // then also nees to be dropped.
757     let mut args = info.args.into_iter();
758     let error_format_flag = "--error-format";
759     let json_flag = "--json";
760     while let Some(arg) = args.next() {
761         if arg == "--extern" {
762             forward_patched_extern_arg(&mut args, &mut cmd);
763         } else if arg.starts_with(error_format_flag) {
764             let suffix = &arg[error_format_flag.len()..];
765             assert!(suffix.starts_with('='));
766             // Drop this argument.
767         } else if arg.starts_with(json_flag) {
768             let suffix = &arg[json_flag.len()..];
769             assert!(suffix.starts_with('='));
770             // Drop this argument.
771         } else {
772             cmd.arg(arg);
773         }
774     }
775     // Set sysroot.
776     let sysroot =
777         env::var_os("MIRI_SYSROOT").expect("the wrapper should have set MIRI_SYSROOT");
778     cmd.arg("--sysroot");
779     cmd.arg(sysroot);
780     // Respect `MIRIFLAGS`.
781     if let Ok(a) = env::var("MIRIFLAGS") {
782         // This code is taken from `RUSTFLAGS` handling in cargo.
783         let args = a
784             .split(' ')
785             .map(str::trim)
786             .filter(|s| !s.is_empty())
787             .map(str::to_string);
788         cmd.args(args);
789     }
790
791     // Then pass binary arguments.
792     cmd.arg("--");
793     cmd.args(binary_args);
794
795     // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
796     // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`.
797     cmd.current_dir(info.current_dir);
798     cmd.env("MIRI_CWD", env::current_dir().unwrap());
799
800     // Run it.
801     if verbose {
802         eprintln!("[cargo-miri runner] {:?}", cmd);
803     }
804     exec(cmd)
805 }
806
807 fn main() {
808     // Rustc does not support non-UTF-8 arguments so we make no attempt either.
809     // (We do support non-UTF-8 environment variables though.)
810     let mut args = std::env::args();
811     // Skip binary name.
812     args.next().unwrap();
813
814     // Dispatch running as part of sysroot compilation.
815     if env::var_os("MIRI_BE_RUSTC").is_some() {
816         phase_setup_rustc(args);
817         return;
818     }
819
820     // Dispatch to `cargo-miri` phase. There are three phases:
821     // - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
822     //   cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
823     // - When we are executed due to RUSTC_WRAPPER, we build crates or store the flags of
824     //   binary crates for later interpretation.
825     // - When we are executed due to CARGO_TARGET_RUNNER, we start interpretation based on the
826     //   flags that were stored earlier.
827     // On top of that, we are also called as RUSTDOC, but that is just a stub currently.
828     match args.next().as_deref() {
829         Some("miri") => phase_cargo_miri(args),
830         Some("rustc") => phase_cargo_rustc(args),
831         Some(arg) => {
832             // We have to distinguish the "runner" and "rustfmt" cases.
833             // As runner, the first argument is the binary (a file that should exist, with an absolute path);
834             // as rustfmt, the first argument is a flag (`--something`).
835             let binary = Path::new(arg);
836             if binary.exists() {
837                 assert!(!arg.starts_with("--")); // not a flag
838                 phase_cargo_runner(binary, args);
839             } else if arg.starts_with("--") {
840                 // We are rustdoc.
841                 eprintln!("Running doctests is not currently supported by Miri.")
842             } else {
843                 show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg));
844             }
845         }
846         _ => show_error(format!("`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`")),
847     }
848 }