2 use std::ffi::OsString;
3 use std::fs::{self, File};
4 use std::io::{self, BufRead, BufReader, BufWriter, Write};
6 use std::path::{Path, PathBuf};
7 use std::process::Command;
9 use serde::{Deserialize, Serialize};
11 use rustc_version::VersionMeta;
13 const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 22);
15 const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri
18 cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
23 setup Only perform automatic setup, but without asking questions (for getting a proper libstd)
25 The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
29 cargo miri test -- test-suite-filter
32 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
39 /// The inforamtion Miri needs to run a crate. Stored as JSON when the crate is "compiled".
40 #[derive(Serialize, Deserialize)]
42 /// The command-line arguments.
45 env: Vec<(OsString, OsString)>,
46 /// The current working directory.
47 current_dir: OsString,
51 /// Gather all the information we need.
52 fn collect(args: env::Args) -> Self {
53 let args = args.collect();
54 let env = env::vars_os().collect();
55 let current_dir = env::current_dir().unwrap().into_os_string();
56 CrateRunInfo { args, env, current_dir }
59 fn store(&self, filename: &Path) {
60 let file = File::create(filename)
61 .unwrap_or_else(|_| show_error(format!("Cannot create `{}`", filename.display())));
62 let file = BufWriter::new(file);
63 serde_json::ser::to_writer(file, self)
64 .unwrap_or_else(|_| show_error(format!("Cannot write to `{}`", filename.display())));
69 println!("{}", CARGO_MIRI_HELP);
75 env!("CARGO_PKG_VERSION"),
76 env!("VERGEN_SHA_SHORT"),
77 env!("VERGEN_COMMIT_DATE")
81 fn show_error(msg: String) -> ! {
82 eprintln!("fatal error: {}", msg);
86 // Determines whether a `--flag` is present.
87 fn has_arg_flag(name: &str) -> bool {
88 let mut args = std::env::args().take_while(|val| val != "--");
89 args.any(|val| val == name)
92 /// Gets the value of a `--flag`.
93 fn get_arg_flag_value(name: &str) -> Option<String> {
94 // Stop searching at `--`.
95 let mut args = std::env::args().take_while(|val| val != "--");
97 let arg = match args.next() {
101 if !arg.starts_with(name) {
104 // Strip leading `name`.
105 let suffix = &arg[name.len()..];
106 if suffix.is_empty() {
107 // This argument is exactly `name`; the next one is the value.
109 } else if suffix.starts_with('=') {
110 // This argument is `name=value`; get the value.
111 // Strip leading `=`.
112 return Some(suffix[1..].to_owned());
117 /// Returns the path to the `miri` binary
118 fn find_miri() -> PathBuf {
119 if let Some(path) = env::var_os("MIRI") {
122 let mut path = std::env::current_exe().expect("current executable path invalid");
123 path.set_file_name("miri");
127 fn miri() -> Command {
128 Command::new(find_miri())
131 fn version_info() -> VersionMeta {
132 VersionMeta::for_command(miri()).expect("failed to determine underlying rustc version of Miri")
135 fn cargo() -> Command {
136 Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
139 fn xargo_check() -> Command {
140 Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check")))
143 /// Execute the command if it fails, fail this process with the same exit code.
144 /// Otherwise, continue.
145 fn exec(mut cmd: Command) {
146 let exit_status = cmd.status().expect("failed to run command");
147 if exit_status.success().not() {
148 std::process::exit(exit_status.code().unwrap_or(-1))
152 fn xargo_version() -> Option<(u32, u32, u32)> {
153 let out = xargo_check().arg("--version").output().ok()?;
154 if !out.status.success() {
157 // Parse output. The first line looks like "xargo 0.3.12 (b004f1c 2018-12-13)".
162 .expect("malformed `xargo --version` output: not at least one line")
163 .expect("malformed `xargo --version` output: error reading first line");
164 let (name, version) = {
165 let mut split = line.split(' ');
167 split.next().expect("malformed `xargo --version` output: empty"),
168 split.next().expect("malformed `xargo --version` output: not at least two words"),
172 // This is some fork of xargo
175 let mut version_pieces = version.split('.');
176 let major = version_pieces
178 .expect("malformed `xargo --version` output: not a major version piece")
180 .expect("malformed `xargo --version` output: major version is not an integer");
181 let minor = version_pieces
183 .expect("malformed `xargo --version` output: not a minor version piece")
185 .expect("malformed `xargo --version` output: minor version is not an integer");
186 let patch = version_pieces
188 .expect("malformed `xargo --version` output: not a patch version piece")
190 .expect("malformed `xargo --version` output: patch version is not an integer");
191 if !version_pieces.next().is_none() {
192 panic!("malformed `xargo --version` output: more than three pieces in version");
194 Some((major, minor, patch))
197 fn ask_to_run(mut cmd: Command, ask: bool, text: &str) {
198 // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc).
199 // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft),
200 // so we also check their `TF_BUILD`.
201 let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
203 let mut buf = String::new();
204 print!("I will run `{:?}` to {}. Proceed? [Y/n] ", cmd, text);
205 io::stdout().flush().unwrap();
206 io::stdin().read_line(&mut buf).unwrap();
207 match buf.trim().to_lowercase().as_ref() {
209 "" | "y" | "yes" => {}
210 "n" | "no" => show_error(format!("Aborting as per your request")),
211 a => show_error(format!("I do not understand `{}`", a)),
214 println!("Running `{:?}` to {}.", cmd, text);
217 if cmd.status().expect(&format!("failed to execute {:?}", cmd)).success().not() {
218 show_error(format!("Failed to {}", text));
222 /// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets
223 /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has
224 /// done all this already.
225 fn setup(subcommand: MiriCommand) {
226 if std::env::var_os("MIRI_SYSROOT").is_some() {
227 if subcommand == MiriCommand::Setup {
228 println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
233 // Subcommands other than `setup` will do a setup if necessary, but
234 // interactively confirm first.
235 let ask_user = subcommand != MiriCommand::Setup;
237 // First, we need xargo.
238 if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) {
239 if std::env::var_os("XARGO_CHECK").is_some() {
240 // The user manually gave us a xargo binary; don't do anything automatically.
241 show_error(format!("Your xargo is too old; please upgrade to the latest version"))
243 let mut cmd = cargo();
244 cmd.args(&["install", "xargo", "-f"]);
245 ask_to_run(cmd, ask_user, "install a recent enough xargo");
248 // Determine where the rust sources are located. `XARGO_RUST_SRC` env var trumps everything.
249 let rust_src = match std::env::var_os("XARGO_RUST_SRC") {
251 let path = PathBuf::from(path);
252 // Make path absolute if possible.
253 path.canonicalize().unwrap_or(path)
256 // Check for `rust-src` rustup component.
258 .args(&["--print", "sysroot"])
260 .expect("failed to determine sysroot")
262 let sysroot = std::str::from_utf8(&sysroot).unwrap();
263 let sysroot = Path::new(sysroot.trim_end_matches('\n'));
264 // Check for `$SYSROOT/lib/rustlib/src/rust/library`; test if that contains `std/Cargo.toml`.
266 sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
267 if !rustup_src.join("std").join("Cargo.toml").exists() {
268 // Ask the user to install the `rust-src` component, and use that.
269 let mut cmd = Command::new("rustup");
270 cmd.args(&["component", "add", "rust-src"]);
274 "install the `rust-src` component for the selected toolchain",
280 if !rust_src.exists() {
281 show_error(format!("Given Rust source directory `{}` does not exist.", rust_src.display()));
284 // Next, we need our own libstd. Prepare a xargo project for that purpose.
285 // We will do this work in whatever is a good cache dir for this platform.
286 let dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
287 let dir = dirs.cache_dir();
289 fs::create_dir_all(&dir).unwrap();
291 // The interesting bit: Xargo.toml
292 File::create(dir.join("Xargo.toml"))
297 default_features = false
298 # We need the `panic_unwind` feature because we use the `unwind` panic strategy.
299 # Using `abort` works for libstd, but then libtest will not compile.
300 features = ["panic_unwind"]
306 // The boring bits: a dummy project for xargo.
307 // FIXME: With xargo-check, can we avoid doing this?
308 File::create(dir.join("Cargo.toml"))
314 description = "A dummy project for building libstd with xargo."
322 File::create(dir.join("lib.rs")).unwrap();
324 // Determine architectures.
325 // We always need to set a target so rustc bootstrap can tell apart host from target crates.
326 let host = version_info().host;
327 let target = get_arg_flag_value("--target");
328 let target = target.as_ref().unwrap_or(&host);
330 let mut command = xargo_check();
331 command.arg("check").arg("-q");
332 command.arg("--target").arg(target);
333 command.current_dir(&dir);
334 command.env("XARGO_HOME", &dir);
335 command.env("XARGO_RUST_SRC", &rust_src);
336 // Use Miri as rustc to build a libstd compatible with us (and use the right flags).
337 // However, when we are running in bootstrap, we cannot just overwrite `RUSTC`,
338 // because we still need bootstrap to distinguish between host and target crates.
339 // In that case we overwrite `RUSTC_REAL` instead which determines the rustc used
340 // for target crates.
341 if env::var_os("RUSTC_STAGE").is_some() {
342 command.env("RUSTC_REAL", find_miri());
344 command.env("RUSTC", find_miri());
346 command.env("MIRI_BE_RUSTC", "1");
347 // Make sure there are no other wrappers or flags getting in our way
348 // (Cc https://github.com/rust-lang/miri/issues/1421).
349 // This is consistent with normal `cargo build` that does not apply `RUSTFLAGS`
350 // to the sysroot either.
351 command.env_remove("RUSTC_WRAPPER");
352 command.env_remove("RUSTFLAGS");
354 if command.status().expect("failed to run xargo").success().not() {
355 show_error(format!("Failed to run xargo"));
358 // That should be it! But we need to figure out where xargo built stuff.
359 // Unfortunately, it puts things into a different directory when the
360 // architecture matches the host.
361 let sysroot = if target == &host { dir.join("HOST") } else { PathBuf::from(dir) };
362 std::env::set_var("MIRI_SYSROOT", &sysroot); // pass the env var to the processes we spawn, which will turn it into "--sysroot" flags
363 // Figure out what to print.
364 let print_sysroot = subcommand == MiriCommand::Setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
366 // Print just the sysroot and nothing else; this way we do not need any escaping.
367 println!("{}", sysroot.display());
368 } else if subcommand == MiriCommand::Setup {
369 println!("A libstd for Miri is now available in `{}`.", sysroot.display());
373 fn phase_cargo_miri(mut args: env::Args) {
374 // Check for version and help flags even when invoked as `cargo-miri`.
375 if has_arg_flag("--help") || has_arg_flag("-h") {
379 if has_arg_flag("--version") || has_arg_flag("-V") {
384 // Require a subcommand before any flags.
385 // We cannot know which of those flags take arguments and which do not,
386 // so we cannot detect subcommands later.
387 let subcommand = match args.next().as_deref() {
388 Some("test") => MiriCommand::Test,
389 Some("run") => MiriCommand::Run,
390 Some("setup") => MiriCommand::Setup,
392 _ => show_error(format!("`cargo miri` supports the following subcommands: `run`, `test`, and `setup`.")),
394 let verbose = has_arg_flag("-v");
399 // Invoke actual cargo for the job, but with different flags.
400 let miri_path = std::env::current_exe().expect("current executable path invalid");
401 let cargo_cmd = match subcommand {
402 MiriCommand::Test => "test",
403 MiriCommand::Run => "run",
404 MiriCommand::Setup => return, // `cargo miri setup` stops here.
406 let mut cmd = cargo();
409 // Make sure we know the build target, and cargo does, too.
410 // This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something,
411 // and it later helps us detect which crates are proc-macro/build-script
412 // (host crates) and which crates are needed for the program itself.
413 let target = if let Some(target) = get_arg_flag_value("--target") {
416 // No target given. Pick default and tell cargo about it.
417 let host = version_info().host;
423 // Forward all further arguments. We do some processing here because we want to
424 // detect people still using the old way of passing flags to Miri
425 // (`cargo miri -- -Zmiri-foo`).
426 while let Some(arg) = args.next() {
429 // Check if the next argument starts with `-Zmiri`. If yes, we assume
430 // this is an old-style invocation.
431 if let Some(next_arg) = args.next() {
432 if next_arg.starts_with("-Zmiri") {
434 "WARNING: it seems like you are setting Miri's flags in `cargo miri` the old way,\n\
435 i.e., by passing them after the first `--`. This style is deprecated; please set\n\
436 the MIRIFLAGS environment variable instead. `cargo miri run/test` now interprets\n\
437 arguments the exact same way as `cargo run/test`."
439 // Old-style invocation. Turn these into MIRIFLAGS.
440 let mut miriflags = env::var("MIRIFLAGS").unwrap_or_default();
442 miriflags.push_str(&next_arg);
443 while let Some(further_arg) = args.next() {
444 if further_arg == "--" {
445 // End of the Miri flags!
449 miriflags.push_str(&further_arg);
451 env::set_var("MIRIFLAGS", miriflags);
452 // Pass the remaining flags to cargo.
456 // Not a Miri argument after all, make sure we pass it to cargo.
462 // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
463 // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
464 // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
465 if env::var_os("RUSTC_WRAPPER").is_some() {
466 println!("WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping.");
468 cmd.env("RUSTC_WRAPPER", &miri_path);
470 eprintln!("+ RUSTC_WRAPPER={:?}", miri_path);
473 // Set the runner for the current target to us as well, so we can interpret the binaries.
474 let runner_env_name = format!("CARGO_TARGET_{}_RUNNER", target.to_uppercase().replace('-', "_"));
475 cmd.env(runner_env_name, &miri_path);
477 // Set rustdoc to us as well, so we can make it do nothing (see issue #584).
478 cmd.env("RUSTDOC", &miri_path);
482 cmd.env("MIRI_VERBOSE", ""); // This makes the other phases verbose.
483 eprintln!("[cargo-miri miri] {:?}", cmd);
488 fn phase_cargo_rustc(args: env::Args) {
489 /// Determines if we are being invoked (as rustc) to build a crate for
490 /// the "target" architecture, in contrast to the "host" architecture.
491 /// Host crates are for build scripts and proc macros and still need to
492 /// be built like normal; target crates need to be built for or interpreted
495 /// Currently, we detect this by checking for "--target=", which is
496 /// never set for host crates. This matches what rustc bootstrap does,
497 /// which hopefully makes it "reliable enough". This relies on us always
498 /// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
499 fn is_target_crate() -> bool {
500 get_arg_flag_value("--target").is_some()
503 /// Returns whether or not Cargo invoked the wrapper (this binary) to compile
504 /// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
505 /// Cargo does not give us this information directly, so we need to check
506 /// various command-line flags.
507 fn is_runnable_crate() -> bool {
508 let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
509 let is_test = has_arg_flag("--test");
510 let print = get_arg_flag_value("--print").is_some();
511 (is_bin || is_test) && !print
514 fn out_filename(prefix: &str, suffix: &str) -> PathBuf {
515 let mut path = PathBuf::from(get_arg_flag_value("--out-dir").unwrap());
519 get_arg_flag_value("--crate-name").unwrap(),
520 // This is technically a `-C` flag but the prefix seems unique enough...
521 // (and cargo passes this before the filename so it should be unique)
522 get_arg_flag_value("extra-filename").unwrap_or(String::new()),
528 let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
529 let target_crate = is_target_crate();
531 if target_crate && is_runnable_crate() {
532 // This is the binary or test crate that we want to interpret under Miri.
533 // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
534 // like we want them.
535 // Instead of compiling, we write JSON into the output file with all the relevant command-line flags
536 // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
537 let info = CrateRunInfo::collect(args);
538 let filename = out_filename("", "");
540 eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
543 info.store(&filename);
544 // For Windows, do the same thing again with `.exe` appended to the filename.
545 // (Need to do this here as cargo moves that "binary" to a different place before running it.)
546 info.store(&out_filename("", ".exe"));
551 let mut cmd = miri();
552 let mut emit_link_hack = false;
553 // Arguments are treated very differently depending on whether this crate is
554 // for interpretation by Miri, or for use by a build script / proc macro.
556 // Forward arguments, but remove "link" from "--emit" to make this a check-only build.
557 let emit_flag = "--emit";
559 if arg.starts_with(emit_flag) {
560 // Patch this argument. First, extract its value.
561 let val = &arg[emit_flag.len()..];
562 assert!(val.starts_with("="), "`cargo` should pass `--emit=X` as one argument");
564 let mut val: Vec<_> = val.split(',').collect();
565 // Now make sure "link" is not in there, but "metadata" is.
566 if let Some(i) = val.iter().position(|&s| s == "link") {
567 emit_link_hack = true;
569 if !val.iter().any(|&s| s == "metadata") {
570 val.push("metadata");
573 cmd.arg(format!("{}={}", emit_flag, val.join(",")));
579 // Use our custom sysroot.
581 env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT");
582 cmd.arg("--sysroot");
585 // For host crates, just forward everything.
589 // We want to compile, not interpret. We still use Miri to make sure the compiler version etc
590 // are the exact same as what is used for interpretation.
591 cmd.env("MIRI_BE_RUSTC", "1");
595 eprintln!("[cargo-miri rustc] {:?}", cmd);
599 // Create a stub .rlib file if "link" was requested by cargo.
601 // Some platforms prepend "lib", some do not... let's just create both files.
602 let filename = out_filename("lib", ".rlib");
603 File::create(filename).expect("Failed to create rlib file");
604 let filename = out_filename("", ".rlib");
605 File::create(filename).expect("Failed to create rlib file");
609 fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
610 let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
612 let file = File::open(&binary)
613 .unwrap_or_else(|_| show_error(format!("File {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary)));
614 let file = BufReader::new(file);
615 let info: CrateRunInfo = serde_json::from_reader(file)
616 .unwrap_or_else(|_| show_error(format!("File {:?} does not contain valid JSON", binary)));
618 // Set missing env vars. Looks like `build.rs` vars are still set at run-time, but
619 // `CARGO_BIN_EXE_*` are not. This means we can give the run-time environment precedence,
620 // to rather do too little than too much.
621 for (name, val) in info.env {
622 if env::var_os(&name).is_none() {
623 env::set_var(name, val);
627 let mut cmd = miri();
628 // Forward rustc arguments.
629 // We need to patch "--extern" filenames because we forced a check-only
630 // build without cargo knowing about that: replace `.rlib` suffix by
632 // We also need to remove `--error-format` as cargo specifies that to be JSON,
633 // but when we run here, cargo does not interpret the JSON any more. `--json`
634 // then also nees to be dropped.
635 let mut args = info.args.into_iter();
636 let extern_flag = "--extern";
637 let error_format_flag = "--error-format";
638 let json_flag = "--json";
639 while let Some(arg) = args.next() {
640 if arg == extern_flag {
641 // `--extern` is always passed as a separate argument by cargo.
642 let next_arg = args.next().expect("`--extern` should be followed by a filename");
643 let next_arg = next_arg.strip_suffix(".rlib").expect("all extern filenames should end in `.rlib`");
644 cmd.arg(extern_flag);
645 cmd.arg(format!("{}.rmeta", next_arg));
646 } else if arg.starts_with(error_format_flag) {
647 let suffix = &arg[error_format_flag.len()..];
648 assert!(suffix.starts_with('='));
649 // Drop this argument.
650 } else if arg.starts_with(json_flag) {
651 let suffix = &arg[json_flag.len()..];
652 assert!(suffix.starts_with('='));
653 // Drop this argument.
660 env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT");
661 cmd.arg("--sysroot");
663 // Respect `MIRIFLAGS`.
664 if let Ok(a) = env::var("MIRIFLAGS") {
665 // This code is taken from `RUSTFLAGS` handling in cargo.
669 .filter(|s| !s.is_empty())
670 .map(str::to_string);
674 // Then pass binary arguments.
676 cmd.args(binary_args);
678 // Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
679 // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`.
680 cmd.current_dir(info.current_dir);
681 cmd.env("MIRI_CWD", env::current_dir().unwrap());
685 eprintln!("[cargo-miri runner] {:?}", cmd);
691 // Rustc does not support non-UTF-8 arguments so we make no attempt either.
692 // (We do support non-UTF-8 environment variables though.)
693 let mut args = std::env::args();
695 args.next().unwrap();
697 // Dispatch to `cargo-miri` phase. There are three phases:
698 // - When we are called via `cargo miri`, we run as the frontend and invoke the underlying
699 // cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves.
700 // - When we are executed due to RUSTC_WRAPPER, we build crates or store the flags of
701 // binary crates for later interpretation.
702 // - When we are executed due to CARGO_TARGET_RUNNER, we start interpretation based on the
703 // flags that were stored earlier.
704 // On top of that, we are also called as RUSTDOC, but that is just a stub currently.
705 match args.next().as_deref() {
706 Some("miri") => phase_cargo_miri(args),
707 Some("rustc") => phase_cargo_rustc(args),
709 // We have to distinguish the "runner" and "rustfmt" cases.
710 // As runner, the first argument is the binary (a file that should exist, with an absolute path);
711 // as rustfmt, the first argument is a flag (`--something`).
712 let binary = Path::new(arg);
714 assert!(!arg.starts_with("--")); // not a flag
715 phase_cargo_runner(binary, args);
716 } else if arg.starts_with("--") {
718 eprintln!("Running doctests is not currently supported by Miri.")
720 show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg));
723 _ => show_error(format!("`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`")),