diff --git a/README.md b/README.md index d847486870..6654de10ab 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,10 @@ Now you can run your project in Miri: The first time you run Miri, it will perform some extra setup and install some dependencies. It will ask you for confirmation before installing anything. -You can pass arguments to Miri after the first `--`, and pass arguments to the -interpreted program or test suite after the second `--`. For example, `cargo -miri run -- -Zmiri-disable-stacked-borrows` runs the program without checking -the aliasing of references. To filter the tests being run, use `cargo miri test --- -- filter`. +`cargo miri run/test` supports the exact same flags as `cargo run/test`. You +can pass arguments to Miri via `MIRIFLAGS`. For example, +`MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run` runs the program +without checking the aliasing of references. Miri supports cross-execution: if you want to run the program as if it was a Linux program, you can do `cargo miri run --target x86_64-unknown-linux-gnu`. @@ -163,7 +162,8 @@ up the sysroot. If you are using `miri` (the Miri driver) directly, see the ## Miri `-Z` flags and environment variables [miri-flags]: #miri--z-flags-and-environment-variables -Miri adds its own set of `-Z` flags: +Miri adds its own set of `-Z` flags, which are usually set via the `MIRIFLAGS` +environment variable: * `-Zmiri-disable-alignment-check` disables checking pointer alignment, so you can focus on other failures, but it means Miri can miss bugs in your program. @@ -229,14 +229,14 @@ Moreover, Miri recognizes some environment variables: * `MIRI_LOG`, `MIRI_BACKTRACE` control logging and backtrace printing during Miri executions, also [see above][testing-miri]. +* `MIRIFLAGS` (recognized by `cargo miri` and the test suite) defines extra + flags to be passed to Miri. * `MIRI_SYSROOT` (recognized by `cargo miri` and the test suite) indicates the sysroot to use. To do the same thing with `miri` directly, use the `--sysroot` flag. * `MIRI_TEST_TARGET` (recognized by the test suite) indicates which target architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same purpose. -* `MIRI_TEST_FLAGS` (recognized by the test suite) defines extra flags to be - passed to Miri. The following environment variables are internal, but used to communicate between different Miri binaries, and as such worth documenting: @@ -244,6 +244,10 @@ different Miri binaries, and as such worth documenting: * `MIRI_BE_RUSTC` when set to any value tells the Miri driver to actually not interpret the code but compile it like rustc would. This is useful to be sure that the compiled `rlib`s are compatible with Miri. +* `MIRI_CWD` when set to any value tells the Miri driver to change to the given + directory after loading all the source files, but before commencing + interpretation. This is useful if the interpreted program wants a different + working directory at run-time than at build-time. ## Miri `extern` functions diff --git a/cargo-miri/Cargo.lock b/cargo-miri/Cargo.lock index 0052bfa183..bb3b05db03 100644 --- a/cargo-miri/Cargo.lock +++ b/cargo-miri/Cargo.lock @@ -45,7 +45,6 @@ dependencies = [ name = "cargo-miri" version = "0.1.0" dependencies = [ - "cargo_metadata", "directories", "rustc-workspace-hack", "rustc_version", @@ -54,17 +53,6 @@ dependencies = [ "vergen", ] -[[package]] -name = "cargo_metadata" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89fec17b16f1ac67908af82e47d0a90a7afd0e1827b181cd77504323d3263d35" -dependencies = [ - "semver 0.10.0", - "serde", - "serde_json", -] - [[package]] name = "cfg-if" version = "0.1.10" @@ -228,7 +216,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] @@ -246,16 +234,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394cec28fa623e00903caf7ba4fa6fb9a0e260280bb8cdbbba029611108a0190" -dependencies = [ - "semver-parser", - "serde", -] - [[package]] name = "semver-parser" version = "0.7.0" diff --git a/cargo-miri/Cargo.toml b/cargo-miri/Cargo.toml index 91c4783694..2de581c1c2 100644 --- a/cargo-miri/Cargo.toml +++ b/cargo-miri/Cargo.toml @@ -14,7 +14,6 @@ test = false # we have no unit tests doctest = false # and no doc tests [dependencies] -cargo_metadata = "0.11" directories = "2.0" rustc_version = "0.2.3" serde_json = "1.0.40" diff --git a/cargo-miri/bin.rs b/cargo-miri/bin.rs index 98304d247f..f50bb44755 100644 --- a/cargo-miri/bin.rs +++ b/cargo-miri/bin.rs @@ -1,37 +1,32 @@ use std::env; use std::ffi::OsString; use std::fs::{self, File}; -use std::io::{self, BufRead, Write}; +use std::io::{self, BufRead, BufReader, BufWriter, Write}; use std::ops::Not; use std::path::{Path, PathBuf}; use std::process::Command; +use serde::{Deserialize, Serialize}; + use rustc_version::VersionMeta; const XARGO_MIN_VERSION: (u32, u32, u32) = (0, 3, 22); -const CARGO_MIRI_HELP: &str = r#"Interprets bin crates and tests in Miri +const CARGO_MIRI_HELP: &str = r#"Runs binary crates and tests in Miri Usage: - cargo miri [subcommand] [...] [--] [...] [--] [...] + cargo miri [subcommand] [...] [--] [...] Subcommands: - run Run binaries (default) + run Run binaries test Run tests setup Only perform automatic setup, but without asking questions (for getting a proper libstd) -Common options: - -h, --help Print this message - --features Features to compile for the package - -V, --version Print version info and exit - -Other [options] are the same as `cargo check`. Everything after the first "--" is -passed verbatim to Miri, which will pass everything after the second "--" verbatim -to the interpreted program. +The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively. Examples: - cargo miri run -- -Zmiri-disable-stacked-borrows - cargo miri test -- -- test-suite-filter + cargo miri run + cargo miri test -- test-suite-filter "#; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -41,6 +36,35 @@ enum MiriCommand { Setup, } +/// The inforamtion Miri needs to run a crate. Stored as JSON when the crate is "compiled". +#[derive(Serialize, Deserialize)] +struct CrateRunInfo { + /// The command-line arguments. + args: Vec, + /// The environment. + env: Vec<(OsString, OsString)>, + /// The current working directory. + current_dir: OsString, +} + +impl CrateRunInfo { + /// Gather all the information we need. + fn collect(args: env::Args) -> Self { + let args = args.collect(); + let env = env::vars_os().collect(); + let current_dir = env::current_dir().unwrap().into_os_string(); + CrateRunInfo { args, env, current_dir } + } + + fn store(&self, filename: &Path) { + let file = File::create(filename) + .unwrap_or_else(|_| show_error(format!("cannot create `{}`", filename.display()))); + let file = BufWriter::new(file); + serde_json::ser::to_writer(file, self) + .unwrap_or_else(|_| show_error(format!("cannot write to `{}`", filename.display()))); + } +} + fn show_help() { println!("{}", CARGO_MIRI_HELP); } @@ -116,48 +140,13 @@ fn xargo_check() -> Command { Command::new(env::var_os("XARGO_CHECK").unwrap_or_else(|| OsString::from("xargo-check"))) } -fn list_targets() -> impl Iterator { - // We need to get the manifest, and then the metadata, to enumerate targets. - let manifest_path = - get_arg_flag_value("--manifest-path").map(|m| Path::new(&m).canonicalize().unwrap()); - - let mut cmd = cargo_metadata::MetadataCommand::new(); - if let Some(manifest_path) = &manifest_path { - cmd.manifest_path(manifest_path); +/// Execute the command. If it fails, fail this process with the same exit code. +/// Otherwise, continue. +fn exec(mut cmd: Command) { + let exit_status = cmd.status().expect("failed to run command"); + if exit_status.success().not() { + std::process::exit(exit_status.code().unwrap_or(-1)) } - let mut metadata = if let Ok(metadata) = cmd.exec() { - metadata - } else { - show_error(format!("Could not obtain Cargo metadata; likely an ill-formed manifest")); - }; - - let current_dir = std::env::current_dir(); - - let package_index = metadata - .packages - .iter() - .position(|package| { - let package_manifest_path = Path::new(&package.manifest_path); - if let Some(manifest_path) = &manifest_path { - package_manifest_path == manifest_path - } else { - let current_dir = current_dir.as_ref().expect("could not read current directory"); - let package_manifest_directory = package_manifest_path - .parent() - .expect("could not find parent directory of package manifest"); - package_manifest_directory == current_dir - } - }) - .unwrap_or_else(|| { - show_error(format!( - "this seems to be a workspace, which is not supported by `cargo miri`.\n\ - Try to `cd` into the crate you want to test, and re-run `cargo miri` there." - )) - }); - let package = metadata.packages.remove(package_index); - - // Finally we got the list of targets to build - package.targets.into_iter() } fn xargo_version() -> Option<(u32, u32, u32)> { @@ -215,15 +204,15 @@ fn ask_to_run(mut cmd: Command, ask: bool, text: &str) { match buf.trim().to_lowercase().as_ref() { // Proceed. "" | "y" | "yes" => {} - "n" | "no" => show_error(format!("Aborting as per your request")), - a => show_error(format!("I do not understand `{}`", a)), + "n" | "no" => show_error(format!("aborting as per your request")), + a => show_error(format!("invalid answer `{}`", a)), }; } else { println!("Running `{:?}` to {}.", cmd, text); } if cmd.status().expect(&format!("failed to execute {:?}", cmd)).success().not() { - show_error(format!("Failed to {}", text)); + show_error(format!("failed to {}", text)); } } @@ -246,7 +235,7 @@ fn setup(subcommand: MiriCommand) { if xargo_version().map_or(true, |v| v < XARGO_MIN_VERSION) { if std::env::var_os("XARGO_CHECK").is_some() { // The user manually gave us a xargo binary; don't do anything automatically. - show_error(format!("Your xargo is too old; please upgrade to the latest version")) + show_error(format!("xargo is too old; please upgrade to the latest version")) } let mut cmd = cargo(); cmd.args(&["install", "xargo", "-f"]); @@ -286,7 +275,7 @@ fn setup(subcommand: MiriCommand) { } }; if !rust_src.exists() { - show_error(format!("Given Rust source directory `{}` does not exist.", rust_src.display())); + show_error(format!("given Rust source directory `{}` does not exist.", rust_src.display())); } // Next, we need our own libstd. Prepare a xargo project for that purpose. @@ -360,7 +349,7 @@ path = "lib.rs" command.env_remove("RUSTFLAGS"); // Finally run it! if command.status().expect("failed to run xargo").success().not() { - show_error(format!("Failed to run xargo")); + show_error(format!("failed to run xargo")); } // That should be it! But we need to figure out where xargo built stuff. @@ -378,173 +367,122 @@ path = "lib.rs" } } -enum CargoTargets { - All, - Filtered { lib: bool, bin: Vec, test: Vec }, -} - -impl CargoTargets { - fn matches(&self, kind: &str, name: &str) -> bool { - match self { - CargoTargets::All => true, - CargoTargets::Filtered { lib, bin, test } => match kind { - "lib" => *lib, - "bin" => bin.iter().any(|n| n == name), - "test" => test.iter().any(|n| n == name), - _ => false, - }, - } +fn phase_cargo_miri(mut args: env::Args) { + // Check for version and help flags even when invoked as `cargo-miri`. + if has_arg_flag("--help") || has_arg_flag("-h") { + show_help(); + return; } -} - -fn parse_cargo_miri_args( - mut args: impl Iterator, -) -> (CargoTargets, Vec, Vec) { - let mut lib_present = false; - let mut bin_targets = Vec::new(); - let mut test_targets = Vec::new(); - let mut additional_args = Vec::new(); - while let Some(arg) = args.next() { - match arg { - arg if arg == "--" => { - // Miri arguments begin after the first "--". - break; - } - arg if arg == "--lib" => lib_present = true, - arg if arg == "--bin" => { - if let Some(binary) = args.next() { - if binary == "--" { - show_error(format!("\"--bin\" takes one argument.")); - } else { - bin_targets.push(binary) - } - } else { - show_error(format!("\"--bin\" takes one argument.")); - } - } - arg if arg.starts_with("--bin=") => bin_targets.push((&arg["--bin=".len()..]).to_string()), - arg if arg == "--test" => { - if let Some(test) = args.next() { - if test == "--" { - show_error(format!("\"--test\" takes one argument.")); - } else { - test_targets.push(test) - } - } else { - show_error(format!("\"--test\" takes one argument.")); - } - } - arg if arg.starts_with("--test=") => test_targets.push((&arg["--test=".len()..]).to_string()), - other => additional_args.push(other), - } + if has_arg_flag("--version") || has_arg_flag("-V") { + show_version(); + return; } - let targets = if !lib_present && bin_targets.len() == 0 && test_targets.len() == 0 { - CargoTargets::All - } else { - CargoTargets::Filtered { lib: lib_present, bin: bin_targets, test: test_targets } - }; - (targets, additional_args, args.collect()) -} -fn in_cargo_miri() { - let (subcommand, skip) = match std::env::args().nth(2).as_deref() { - Some("test") => (MiriCommand::Test, 3), - Some("run") => (MiriCommand::Run, 3), - Some("setup") => (MiriCommand::Setup, 3), - // Default command, if there is an option or nothing. - Some(s) if s.starts_with("-") => (MiriCommand::Run, 2), - None => (MiriCommand::Run, 2), + // Require a subcommand before any flags. + // We cannot know which of those flags take arguments and which do not, + // so we cannot detect subcommands later. + let subcommand = match args.next().as_deref() { + Some("test") => MiriCommand::Test, + Some("run") => MiriCommand::Run, + Some("setup") => MiriCommand::Setup, // Invalid command. - Some(s) => show_error(format!("Unknown command `{}`", s)), + _ => show_error(format!("`cargo miri` supports the following subcommands: `run`, `test`, and `setup`.")), }; let verbose = has_arg_flag("-v"); // We always setup. setup(subcommand); - if subcommand == MiriCommand::Setup { - // Stop here. - return; - } - // FIXME: this accepts --test, --lib, and multiple --bin for `cargo miri run`. - let (target_filters, cargo_args, miri_args) = - parse_cargo_miri_args(std::env::args().skip(skip)); + // Invoke actual cargo for the job, but with different flags. + let miri_path = std::env::current_exe().expect("current executable path invalid"); + let cargo_cmd = match subcommand { + MiriCommand::Test => "test", + MiriCommand::Run => "run", + MiriCommand::Setup => return, // `cargo miri setup` stops here. + }; + let mut cmd = cargo(); + cmd.arg(cargo_cmd); + + // Make sure we know the build target, and cargo does, too. + // This is needed to make the `CARGO_TARGET_*_RUNNER` env var do something, + // and it later helps us detect which crates are proc-macro/build-script + // (host crates) and which crates are needed for the program itself. + let target = if let Some(target) = get_arg_flag_value("--target") { + target + } else { + // No target given. Pick default and tell cargo about it. + let host = version_info().host; + cmd.arg("--target"); + cmd.arg(&host); + host + }; - // Now run the command. - for target in list_targets() { - let kind = target - .kind - .get(0) - .expect("badly formatted cargo metadata: target::kind is an empty array"); - if !target_filters.matches(kind, &target.name) { - continue; - } - // Now we run `cargo check $FLAGS $ARGS`, giving the user the - // change to add additional arguments. `FLAGS` is set to identify - // this target. The user gets to control what gets actually passed to Miri. - let mut cmd = cargo(); - cmd.arg("check"); - match (subcommand, kind.as_str()) { - (MiriCommand::Run, "bin") => { - // FIXME: we default to running all binaries here. - cmd.arg("--bin").arg(target.name); - } - (MiriCommand::Test, "test") => { - cmd.arg("--test").arg(target.name); - } - (MiriCommand::Test, "lib") => { - // There can be only one lib. - cmd.arg("--lib").arg("--profile").arg("test"); - } - (MiriCommand::Test, "bin") => { - cmd.arg("--bin").arg(target.name).arg("--profile").arg("test"); + // Forward all further arguments. We do some processing here because we want to + // detect people still using the old way of passing flags to Miri + // (`cargo miri -- -Zmiri-foo`). + while let Some(arg) = args.next() { + cmd.arg(&arg); + if arg == "--" { + // Check if the next argument starts with `-Zmiri`. If yes, we assume + // this is an old-style invocation. + if let Some(next_arg) = args.next() { + if next_arg.starts_with("-Zmiri") { + eprintln!( + "WARNING: it seems like you are setting Miri's flags in `cargo miri` the old way,\n\ + i.e., by passing them after the first `--`. This style is deprecated; please set\n\ + the MIRIFLAGS environment variable instead. `cargo miri run/test` now interprets\n\ + arguments the exact same way as `cargo run/test`." + ); + // Old-style invocation. Turn these into MIRIFLAGS. + let mut miriflags = env::var("MIRIFLAGS").unwrap_or_default(); + miriflags.push(' '); + miriflags.push_str(&next_arg); + while let Some(further_arg) = args.next() { + if further_arg == "--" { + // End of the Miri flags! + break; + } + miriflags.push(' '); + miriflags.push_str(&further_arg); + } + env::set_var("MIRIFLAGS", miriflags); + // Pass the remaining flags to cargo. + cmd.args(args); + break; + } + // Not a Miri argument after all, make sure we pass it to cargo. + cmd.arg(next_arg); } - // The remaining targets we do not even want to build. - _ => continue, - } - // Forward further `cargo` args. - for arg in cargo_args.iter() { - cmd.arg(arg); - } - // We want to always run `cargo` with `--target`. This later helps us detect - // which crates are proc-macro/build-script (host crates) and which crates are - // needed for the program itself. - if get_arg_flag_value("--target").is_none() { - // When no `--target` is given, default to the host. - cmd.arg("--target"); - cmd.arg(version_info().host); } + } - // Serialize the remaining args into a special environemt variable. - // This will be read by `inside_cargo_rustc` when we go to invoke - // our actual target crate (the binary or the test we are running). - // Since we're using "cargo check", we have no other way of passing - // these arguments. - cmd.env("MIRI_ARGS", serde_json::to_string(&miri_args).expect("failed to serialize args")); - - // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, - // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish - // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.) - if env::var_os("RUSTC_WRAPPER").is_some() { - println!("WARNING: Ignoring existing `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."); - } - let path = std::env::current_exe().expect("current executable path invalid"); - cmd.env("RUSTC_WRAPPER", path); - if verbose { - cmd.env("MIRI_VERBOSE", ""); // this makes `inside_cargo_rustc` verbose. - eprintln!("+ {:?}", cmd); - } + // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, + // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish + // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.) + if env::var_os("RUSTC_WRAPPER").is_some() { + println!("WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."); + } + cmd.env("RUSTC_WRAPPER", &miri_path); + if verbose { + eprintln!("+ RUSTC_WRAPPER={:?}", miri_path); + } - let exit_status = - cmd.spawn().expect("could not run cargo").wait().expect("failed to wait for cargo?"); + // Set the runner for the current target to us as well, so we can interpret the binaries. + let runner_env_name = format!("CARGO_TARGET_{}_RUNNER", target.to_uppercase().replace('-', "_")); + cmd.env(runner_env_name, &miri_path); - if !exit_status.success() { - std::process::exit(exit_status.code().unwrap_or(-1)) - } + // Set rustdoc to us as well, so we can make it do nothing (see issue #584). + cmd.env("RUSTDOC", &miri_path); + + // Run cargo. + if verbose { + cmd.env("MIRI_VERBOSE", ""); // This makes the other phases verbose. + eprintln!("[cargo-miri miri] {:?}", cmd); } + exec(cmd) } -fn inside_cargo_rustc() { +fn phase_cargo_rustc(args: env::Args) { /// Determines if we are being invoked (as rustc) to build a crate for /// the "target" architecture, in contrast to the "host" architecture. /// Host crates are for build scripts and proc macros and still need to @@ -564,82 +502,221 @@ fn inside_cargo_rustc() { /// Cargo does not give us this information directly, so we need to check /// various command-line flags. fn is_runnable_crate() -> bool { - let is_bin = get_arg_flag_value("--crate-type").as_deref() == Some("bin"); + let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin"; let is_test = has_arg_flag("--test"); - is_bin || is_test + let print = get_arg_flag_value("--print").is_some(); + (is_bin || is_test) && !print + } + + fn out_filename(prefix: &str, suffix: &str) -> PathBuf { + let mut path = PathBuf::from(get_arg_flag_value("--out-dir").unwrap()); + path.push(format!( + "{}{}{}{}", + prefix, + get_arg_flag_value("--crate-name").unwrap(), + // This is technically a `-C` flag but the prefix seems unique enough... + // (and cargo passes this before the filename so it should be unique) + get_arg_flag_value("extra-filename").unwrap_or(String::new()), + suffix, + )); + path } let verbose = std::env::var_os("MIRI_VERBOSE").is_some(); let target_crate = is_target_crate(); - let mut cmd = miri(); - // Forward arguments. - cmd.args(std::env::args().skip(2)); // skip `cargo-miri rustc` + if target_crate && is_runnable_crate() { + // This is the binary or test crate that we want to interpret under Miri. + // But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not + // like we want them. + // Instead of compiling, we write JSON into the output file with all the relevant command-line flags + // and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase. + let info = CrateRunInfo::collect(args); + let filename = out_filename("", ""); + if verbose { + eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display()); + } + + info.store(&filename); + // For Windows, do the same thing again with `.exe` appended to the filename. + // (Need to do this here as cargo moves that "binary" to a different place before running it.) + info.store(&out_filename("", ".exe")); - // We make sure to only specify our custom Xargo sysroot for target crates - that is, - // crates which are needed for interpretation by Miri. proc-macros and build scripts - // should use the default sysroot. + return; + } + + let mut cmd = miri(); + let mut emit_link_hack = false; + // Arguments are treated very differently depending on whether this crate is + // for interpretation by Miri, or for use by a build script / proc macro. if target_crate { + // Forward arguments, but remove "link" from "--emit" to make this a check-only build. + let emit_flag = "--emit"; + for arg in args { + if arg.starts_with(emit_flag) { + // Patch this argument. First, extract its value. + let val = &arg[emit_flag.len()..]; + assert!(val.starts_with("="), "`cargo` should pass `--emit=X` as one argument"); + let val = &val[1..]; + let mut val: Vec<_> = val.split(',').collect(); + // Now make sure "link" is not in there, but "metadata" is. + if let Some(i) = val.iter().position(|&s| s == "link") { + emit_link_hack = true; + val.remove(i); + if !val.iter().any(|&s| s == "metadata") { + val.push("metadata"); + } + } + cmd.arg(format!("{}={}", emit_flag, val.join(","))); + } else { + cmd.arg(arg); + } + } + + // Use our custom sysroot. let sysroot = - env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT"); + env::var_os("MIRI_SYSROOT").expect("the wrapper should have set MIRI_SYSROOT"); cmd.arg("--sysroot"); cmd.arg(sysroot); + } else { + // For host crates, just forward everything. + cmd.args(args); } - // If this is a runnable target crate, we want Miri to start interpretation; - // otherwise we want Miri to behave like rustc and build the crate as usual. - if target_crate && is_runnable_crate() { - // This is the binary or test crate that we want to interpret under Miri. - // (Testing `target_crate` is needed to exclude build scripts.) - // We deserialize the arguments that are meant for Miri from the special environment - // variable "MIRI_ARGS", and feed them to the 'miri' binary. - // - // `env::var` is okay here, well-formed JSON is always UTF-8. - let magic = std::env::var("MIRI_ARGS").expect("missing MIRI_ARGS"); - let miri_args: Vec = - serde_json::from_str(&magic).expect("failed to deserialize MIRI_ARGS"); - cmd.args(miri_args); - } else { - // We want to compile, not interpret. - cmd.env("MIRI_BE_RUSTC", "1"); - }; + // We want to compile, not interpret. We still use Miri to make sure the compiler version etc + // are the exact same as what is used for interpretation. + cmd.env("MIRI_BE_RUSTC", "1"); // Run it. if verbose { - eprintln!("+ {:?}", cmd); + eprintln!("[cargo-miri rustc] {:?}", cmd); } - match cmd.status() { - Ok(exit) => - if !exit.success() { - std::process::exit(exit.code().unwrap_or(42)); - }, - Err(e) => panic!("error running {:?}:\n{:?}", cmd, e), + exec(cmd); + + // Create a stub .rlib file if "link" was requested by cargo. + if emit_link_hack { + // Some platforms prepend "lib", some do not... let's just create both files. + let filename = out_filename("lib", ".rlib"); + File::create(filename).expect("failed to create rlib file"); + let filename = out_filename("", ".rlib"); + File::create(filename).expect("failed to create rlib file"); } } -fn main() { - // Check for version and help flags even when invoked as `cargo-miri`. - if has_arg_flag("--help") || has_arg_flag("-h") { - show_help(); - return; +fn phase_cargo_runner(binary: &Path, binary_args: env::Args) { + let verbose = std::env::var_os("MIRI_VERBOSE").is_some(); + + let file = File::open(&binary) + .unwrap_or_else(|_| show_error(format!("file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary))); + let file = BufReader::new(file); + let info: CrateRunInfo = serde_json::from_reader(file) + .unwrap_or_else(|_| show_error(format!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary))); + + // Set missing env vars. Looks like `build.rs` vars are still set at run-time, but + // `CARGO_BIN_EXE_*` are not. This means we can give the run-time environment precedence, + // to rather do too little than too much. + for (name, val) in info.env { + if env::var_os(&name).is_none() { + env::set_var(name, val); + } } - if has_arg_flag("--version") || has_arg_flag("-V") { - show_version(); - return; + + let mut cmd = miri(); + // Forward rustc arguments. + // We need to patch "--extern" filenames because we forced a check-only + // build without cargo knowing about that: replace `.rlib` suffix by + // `.rmeta`. + // We also need to remove `--error-format` as cargo specifies that to be JSON, + // but when we run here, cargo does not interpret the JSON any more. `--json` + // then also nees to be dropped. + let mut args = info.args.into_iter(); + let extern_flag = "--extern"; + let error_format_flag = "--error-format"; + let json_flag = "--json"; + while let Some(arg) = args.next() { + if arg == extern_flag { + // `--extern` is always passed as a separate argument by cargo. + let next_arg = args.next().expect("`--extern` should be followed by a filename"); + let next_arg = next_arg.strip_suffix(".rlib").expect("all extern filenames should end in `.rlib`"); + cmd.arg(extern_flag); + cmd.arg(format!("{}.rmeta", next_arg)); + } else if arg.starts_with(error_format_flag) { + let suffix = &arg[error_format_flag.len()..]; + assert!(suffix.starts_with('=')); + // Drop this argument. + } else if arg.starts_with(json_flag) { + let suffix = &arg[json_flag.len()..]; + assert!(suffix.starts_with('=')); + // Drop this argument. + } else { + cmd.arg(arg); + } + } + // Set sysroot. + let sysroot = + env::var_os("MIRI_SYSROOT").expect("the wrapper should have set MIRI_SYSROOT"); + cmd.arg("--sysroot"); + cmd.arg(sysroot); + // Respect `MIRIFLAGS`. + if let Ok(a) = env::var("MIRIFLAGS") { + // This code is taken from `RUSTFLAGS` handling in cargo. + let args = a + .split(' ') + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_string); + cmd.args(args); } - if let Some("miri") = std::env::args().nth(1).as_deref() { - // This arm is for when `cargo miri` is called. We call `cargo check` for each applicable target, - // but with the `RUSTC` env var set to the `cargo-miri` binary so that we come back in the other branch, - // and dispatch the invocations to `rustc` and `miri`, respectively. - in_cargo_miri(); - } else if let Some("rustc") = std::env::args().nth(1).as_deref() { - // This arm is executed when `cargo-miri` runs `cargo check` with the `RUSTC_WRAPPER` env var set to itself: - // dependencies get dispatched to `rustc`, the final test/binary to `miri`. - inside_cargo_rustc(); - } else { - show_error(format!( - "`cargo-miri` must be called with either `miri` or `rustc` as first argument." - )) + // Then pass binary arguments. + cmd.arg("--"); + cmd.args(binary_args); + + // Make sure we use the build-time working directory for interpreting Miri/rustc arguments. + // But then we need to switch to the run-time one, which we instruct Miri do do by setting `MIRI_CWD`. + cmd.current_dir(info.current_dir); + cmd.env("MIRI_CWD", env::current_dir().unwrap()); + + // Run it. + if verbose { + eprintln!("[cargo-miri runner] {:?}", cmd); + } + exec(cmd) +} + +fn main() { + // Rustc does not support non-UTF-8 arguments so we make no attempt either. + // (We do support non-UTF-8 environment variables though.) + let mut args = std::env::args(); + // Skip binary name. + args.next().unwrap(); + + // Dispatch to `cargo-miri` phase. There are three phases: + // - When we are called via `cargo miri`, we run as the frontend and invoke the underlying + // cargo. We set RUSTC_WRAPPER and CARGO_TARGET_RUNNER to ourselves. + // - When we are executed due to RUSTC_WRAPPER, we build crates or store the flags of + // binary crates for later interpretation. + // - When we are executed due to CARGO_TARGET_RUNNER, we start interpretation based on the + // flags that were stored earlier. + // On top of that, we are also called as RUSTDOC, but that is just a stub currently. + match args.next().as_deref() { + Some("miri") => phase_cargo_miri(args), + Some("rustc") => phase_cargo_rustc(args), + Some(arg) => { + // We have to distinguish the "runner" and "rustfmt" cases. + // As runner, the first argument is the binary (a file that should exist, with an absolute path); + // as rustfmt, the first argument is a flag (`--something`). + let binary = Path::new(arg); + if binary.exists() { + assert!(!arg.starts_with("--")); // not a flag + phase_cargo_runner(binary, args); + } else if arg.starts_with("--") { + // We are rustdoc. + eprintln!("Running doctests is not currently supported by Miri.") + } else { + show_error(format!("`cargo-miri` called with unexpected first argument `{}`; please only invoke this binary through `cargo miri`", arg)); + } + } + _ => show_error(format!("`cargo-miri` called without first argument; please only invoke this binary through `cargo miri`")), } } diff --git a/ci.sh b/ci.sh index 915a4cf2fd..12683a2fcc 100755 --- a/ci.sh +++ b/ci.sh @@ -26,7 +26,7 @@ function run_tests { if ! [ -n "${MIRI_TEST_TARGET+exists}" ]; then # Only for host architecture: tests with MIR optimizations # FIXME: only testing level 2 because of . - MIRI_TEST_FLAGS="-Z mir-opt-level=2" ./miri test --locked + MIRIFLAGS="-Z mir-opt-level=2" ./miri test --locked fi # "miri test" has built the sysroot for us, now this should pass without # any interactive questions. diff --git a/miri b/miri index 37e87ec798..aef61b6dd6 100755 --- a/miri +++ b/miri @@ -39,6 +39,11 @@ EOF TARGET=$(rustc --version --verbose | grep "^host:" | cut -d ' ' -f 2) SYSROOT=$(rustc --print sysroot) LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib +MIRIDIR=$(dirname "$0") +if readlink -e . >/dev/null; then + # This platform supports `readlink -e`. + MIRIDIR=$(readlink -e "$MIRIDIR") +fi if ! test -d "$LIBDIR"; then echo "Something went wrong determining the library dir." echo "I got $LIBDIR but that does not exist." @@ -51,7 +56,7 @@ if [ -z "$CARGO_INCREMENTAL" ]; then fi if [ -z "$CARGO_TARGET_DIR" ]; then # Share target dir between `miri` and `cargo-miri`. - export CARGO_TARGET_DIR="$(dirname "$0")"/target + export CARGO_TARGET_DIR="$MIRIDIR/target" fi # We set the rpath so that Miri finds the private rustc libraries it needs. # We enable debug-assertions to get tracing. @@ -63,9 +68,9 @@ export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR -C debug-assertions -C debugin # Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`. build_sysroot() { # Build once, for the user to see. - cargo run $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/cargo-miri/Cargo.toml -- miri setup "$@" + cargo run $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup "$@" # Call again, to just set env var. - export MIRI_SYSROOT="$(cargo run $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")" + export MIRI_SYSROOT="$(cargo run $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -q -- miri setup --print-sysroot "$@")" } # Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account @@ -108,18 +113,18 @@ case "$COMMAND" in install|install-debug) # "--locked" to respect the Cargo.lock file if it exists, # "--offline" to avoid querying the registry (for yanked packages). - cargo install $CARGO_INSTALL_FLAGS --path "$(dirname "$0")" --force --locked --offline "$@" - cargo install $CARGO_INSTALL_FLAGS --path "$(dirname "$0")"/cargo-miri --force --locked --offline "$@" + cargo install $CARGO_INSTALL_FLAGS --path "$MIRIDIR" --force --locked --offline "$@" + cargo install $CARGO_INSTALL_FLAGS --path "$MIRIDIR"/cargo-miri --force --locked --offline "$@" ;; check|check-debug) # Check, and let caller control flags. - cargo check $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/Cargo.toml "$@" - cargo check $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/cargo-miri/Cargo.toml "$@" + cargo check $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" + cargo check $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" ;; build|build-debug) # Build, and let caller control flags. - cargo build $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/Cargo.toml "$@" - cargo build $CARGO_BUILD_FLAGS --manifest-path "$(dirname "$0")"/cargo-miri/Cargo.toml "$@" + cargo build $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" + cargo build $CARGO_BUILD_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" ;; test|test-debug) # First build and get a sysroot. diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 1347020475..4363f9a150 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -45,6 +45,11 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { // Add filename to `miri` arguments. config.args.insert(0, compiler.input().filestem().to_string()); + // Adjust working directory for interpretation. + if let Some(cwd) = env::var_os("MIRI_CWD") { + env::set_current_dir(cwd).unwrap(); + } + if let Some(return_code) = miri::eval_main(tcx, entry_def_id.to_def_id(), config) { std::process::exit( i32::try_from(return_code).expect("Return value was too large!"), diff --git a/test-cargo-miri/Cargo.lock b/test-cargo-miri/Cargo.lock index ecf71a5f43..6bc70135a8 100644 --- a/test-cargo-miri/Cargo.lock +++ b/test-cargo-miri/Cargo.lock @@ -4,120 +4,122 @@ name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cargo-miri-test" version = "0.1.0" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "num_cpus", + "rand", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "hermit-abi" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "libc" version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "num_cpus" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" dependencies = [ - "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] name = "ppv-lite86" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", + "rand_pcg", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core", ] [[package]] name = "rand_pcg" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core", ] +[[package]] +name = "subcrate" +version = "0.1.0" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" -"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" -"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/test-cargo-miri/Cargo.toml b/test-cargo-miri/Cargo.toml index 3abb437049..4900ce9675 100644 --- a/test-cargo-miri/Cargo.toml +++ b/test-cargo-miri/Cargo.toml @@ -1,7 +1,10 @@ +[workspace] +members = ["subcrate"] + [package] name = "cargo-miri-test" version = "0.1.0" -authors = ["Oliver Schneider "] +authors = ["Miri Team"] edition = "2018" [dependencies] @@ -10,3 +13,6 @@ byteorder = "1.0" [dev-dependencies] rand = { version = "0.7", features = ["small_rng"] } num_cpus = "1.10.1" + +[lib] +test = false # test that this is respected (will show in the output) diff --git a/test-cargo-miri/build.rs b/test-cargo-miri/build.rs index b1f5fc1726..9851ccf39f 100644 --- a/test-cargo-miri/build.rs +++ b/test-cargo-miri/build.rs @@ -12,4 +12,6 @@ fn not_in_miri() -> i32 { fn main() { not_in_miri(); println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=MIRITESTVAR"); + println!("cargo:rustc-env=MIRITESTVAR=testval"); } diff --git a/test-cargo-miri/run-test.py b/test-cargo-miri/run-test.py index a258c7f73c..e7c341a1f0 100755 --- a/test-cargo-miri/run-test.py +++ b/test-cargo-miri/run-test.py @@ -21,67 +21,88 @@ def cargo_miri(cmd): args += ["--target", os.environ['MIRI_TEST_TARGET']] return args -def test(name, cmd, stdout_ref, stderr_ref): - print("==> Testing `{}` <==".format(name)) +def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}): + print("Testing {}...".format(name)) ## Call `cargo miri`, capture all output + p_env = os.environ.copy() + p_env.update(env) p = subprocess.Popen( cmd, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, + env=p_env, ) - (stdout, stderr) = p.communicate() + (stdout, stderr) = p.communicate(input=stdin) stdout = stdout.decode("UTF-8") stderr = stderr.decode("UTF-8") + if p.returncode == 0 and stdout == open(stdout_ref).read() and stderr == open(stderr_ref).read(): + # All good! + return # Show output - print("=> captured stdout <=") + print("--- BEGIN stdout ---") print(stdout, end="") - print("=> captured stderr <=") + print("--- END stdout ---") + print("--- BEGIN stderr ---") print(stderr, end="") - # Test for failures - if p.returncode != 0: - fail("Non-zero exit status") - if stdout != open(stdout_ref).read(): - fail("stdout does not match reference") - if stderr != open(stderr_ref).read(): - fail("stderr does not match reference") + print("--- END stderr ---") + fail("exit code was {}".format(p.returncode)) def test_cargo_miri_run(): - test("cargo miri run", + test("`cargo miri run` (no isolation)", cargo_miri("run"), - "stdout.ref", "stderr.ref" + "stdout.ref1", "stderr.ref1", + stdin=b'12\n21\n', + env={ + 'MIRIFLAGS': "-Zmiri-disable-isolation", + 'MIRITESTVAR': "wrongval", # make sure the build.rs value takes precedence + }, ) - test("cargo miri run (with target)", - cargo_miri("run") + ["--bin", "cargo-miri-test"], - "stdout.ref", "stderr.ref" + test("`cargo miri run` (with arguments and target)", + cargo_miri("run") + ["--bin", "cargo-miri-test", "--", "hello world", '"hello world"'], + "stdout.ref2", "stderr.ref2", ) - test("cargo miri run (with arguments)", - cargo_miri("run") + ["--", "--", "hello world", '"hello world"'], - "stdout.ref", "stderr.ref2" + test("`cargo miri run` (subcrate, no ioslation)", + cargo_miri("run") + ["-p", "subcrate"], + "stdout.ref3", "stderr.ref3", + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, ) def test_cargo_miri_test(): - test("cargo miri test", - cargo_miri("test") + ["--", "-Zmiri-seed=feed"], - "test.stdout.ref", "test.stderr.ref" + # rustdoc is not run on foreign targets + is_foreign = 'MIRI_TEST_TARGET' in os.environ + rustdoc_ref = "test.stderr.ref2" if is_foreign else "test.stderr.ref1" + + test("`cargo miri test`", + cargo_miri("test"), + "test.stdout.ref1", rustdoc_ref, + env={'MIRIFLAGS': "-Zmiri-seed=feed"}, + ) + test("`cargo miri test` (no isolation)", + cargo_miri("test"), + "test.stdout.ref1", rustdoc_ref, + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, ) - test("cargo miri test (with filter)", - cargo_miri("test") + ["--", "--", "le1"], - "test.stdout.ref2", "test.stderr.ref" + test("`cargo miri test` (with filter)", + cargo_miri("test") + ["--", "--format=pretty", "le1"], + "test.stdout.ref2", rustdoc_ref, ) - test("cargo miri test (without isolation)", - cargo_miri("test") + ["--", "-Zmiri-disable-isolation", "--", "num_cpus"], - "test.stdout.ref3", "test.stderr.ref" + test("`cargo miri test` (test target)", + cargo_miri("test") + ["--test", "test", "--", "--format=pretty"], + "test.stdout.ref3", "test.stderr.ref2", ) - test("cargo miri test (test target)", - cargo_miri("test") + ["--test", "test"], - "test.stdout.ref4", "test.stderr.ref" + test("`cargo miri test` (bin target)", + cargo_miri("test") + ["--bin", "cargo-miri-test", "--", "--format=pretty"], + "test.stdout.ref4", "test.stderr.ref2", ) - test("cargo miri test (bin target)", - cargo_miri("test") + ["--bin", "cargo-miri-test"], - "test.stdout.ref5", "test.stderr.ref" + test("`cargo miri test` (subcrate, no isolation)", + cargo_miri("test") + ["-p", "subcrate"], + "test.stdout.ref5", "test.stderr.ref2", + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, ) os.chdir(os.path.dirname(os.path.realpath(__file__))) +os.environ["RUST_TEST_NOCAPTURE"] = "0" # this affects test output, so make sure it is not set target_str = " for target {}".format(os.environ['MIRI_TEST_TARGET']) if 'MIRI_TEST_TARGET' in os.environ else "" print(CGREEN + CBOLD + "## Running `cargo miri` tests{}".format(target_str) + CEND) diff --git a/test-cargo-miri/src/lib.rs b/test-cargo-miri/src/lib.rs new file mode 100644 index 0000000000..4e2c8b572c --- /dev/null +++ b/test-cargo-miri/src/lib.rs @@ -0,0 +1,7 @@ +/// Doc-test test +/// ```rust +/// assert!(cargo_miri_test::make_true()); +/// ``` +pub fn make_true() -> bool { + true +} diff --git a/test-cargo-miri/src/main.rs b/test-cargo-miri/src/main.rs index d3663ec849..3a2b910fb7 100644 --- a/test-cargo-miri/src/main.rs +++ b/test-cargo-miri/src/main.rs @@ -1,6 +1,13 @@ use byteorder::{BigEndian, ByteOrder}; +use std::env; +use std::path::PathBuf; +#[cfg(unix)] +use std::io::{self, BufRead}; fn main() { + // Check env var set by `build.rs`. + assert_eq!(env!("MIRITESTVAR"), "testval"); + // Exercise external crate, printing to stdout. let buf = &[1,2,3,4]; let n = ::read_u32(buf); @@ -11,6 +18,29 @@ fn main() { for arg in std::env::args() { eprintln!("{}", arg); } + + // If there were no arguments, access stdin and test working dir. + if std::env::args().len() <= 1 { + let env_dir = env::current_dir().unwrap(); + // CWD should be crate root. + let crate_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + assert_eq!(env_dir, crate_dir); + + #[cfg(unix)] + for line in io::stdin().lock().lines() { + let num: i32 = line.unwrap().parse().unwrap(); + println!("{}", 2*num); + } + // On non-Unix, reading from stdin is not support. So we hard-code the right answer. + #[cfg(not(unix))] + { + println!("24"); + println!("42"); + } + } } #[cfg(test)] diff --git a/test-cargo-miri/stderr.ref b/test-cargo-miri/stderr.ref1 similarity index 100% rename from test-cargo-miri/stderr.ref rename to test-cargo-miri/stderr.ref1 diff --git a/test-cargo-miri/test.stderr.ref b/test-cargo-miri/stderr.ref3 similarity index 100% rename from test-cargo-miri/test.stderr.ref rename to test-cargo-miri/stderr.ref3 diff --git a/test-cargo-miri/stdout.ref1 b/test-cargo-miri/stdout.ref1 new file mode 100644 index 0000000000..2eab8df967 --- /dev/null +++ b/test-cargo-miri/stdout.ref1 @@ -0,0 +1,3 @@ +0x01020304 +24 +42 diff --git a/test-cargo-miri/stdout.ref b/test-cargo-miri/stdout.ref2 similarity index 100% rename from test-cargo-miri/stdout.ref rename to test-cargo-miri/stdout.ref2 diff --git a/test-cargo-miri/stdout.ref3 b/test-cargo-miri/stdout.ref3 new file mode 100644 index 0000000000..53340a5023 --- /dev/null +++ b/test-cargo-miri/stdout.ref3 @@ -0,0 +1 @@ +subcrate running diff --git a/test-cargo-miri/subcrate/Cargo.toml b/test-cargo-miri/subcrate/Cargo.toml new file mode 100644 index 0000000000..78552e6aed --- /dev/null +++ b/test-cargo-miri/subcrate/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "subcrate" +version = "0.1.0" +authors = ["Miri Team"] +edition = "2018" + +[[bin]] +name = "subcrate" +path = "main.rs" + +[[test]] +name = "subtest" +path = "test.rs" +harness = false diff --git a/test-cargo-miri/subcrate/main.rs b/test-cargo-miri/subcrate/main.rs new file mode 100644 index 0000000000..5ccdc5a1c3 --- /dev/null +++ b/test-cargo-miri/subcrate/main.rs @@ -0,0 +1,15 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + println!("subcrate running"); + + let env_dir = env::current_dir().unwrap(); + // CWD should be workspace root, i.e., one level up from crate root. + let crate_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let crate_dir = crate_dir.parent().unwrap(); + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + assert_eq!(env_dir, crate_dir); +} diff --git a/test-cargo-miri/subcrate/test.rs b/test-cargo-miri/subcrate/test.rs new file mode 100644 index 0000000000..f21d6e13ff --- /dev/null +++ b/test-cargo-miri/subcrate/test.rs @@ -0,0 +1,14 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + println!("subcrate testing"); + + let env_dir = env::current_dir().unwrap(); + // CWD should be crate root. + let crate_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + // We have to normalize slashes, as the env var might be set for a different target's conventions. + let env_dir = env_dir.to_string_lossy().replace("\\", "/"); + let crate_dir = crate_dir.to_string_lossy().replace("\\", "/"); + assert_eq!(env_dir, crate_dir); +} diff --git a/test-cargo-miri/test.stderr.ref1 b/test-cargo-miri/test.stderr.ref1 new file mode 100644 index 0000000000..a310169e30 --- /dev/null +++ b/test-cargo-miri/test.stderr.ref1 @@ -0,0 +1 @@ +Running doctests is not currently supported by Miri. diff --git a/test-cargo-miri/test.stderr.ref2 b/test-cargo-miri/test.stderr.ref2 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test-cargo-miri/test.stdout.ref b/test-cargo-miri/test.stdout.ref deleted file mode 100644 index 4260f5b3cb..0000000000 --- a/test-cargo-miri/test.stdout.ref +++ /dev/null @@ -1,18 +0,0 @@ - -running 1 test -test test::rng ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out - - -running 7 tests -test do_panic ... ok -test does_not_work_on_miri ... ignored -test entropy_rng ... ok -test fail_index_check ... ok -test num_cpus ... ok -test simple1 ... ok -test simple2 ... ok - -test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out - diff --git a/test-cargo-miri/test.stdout.ref1 b/test-cargo-miri/test.stdout.ref1 new file mode 100644 index 0000000000..1eb18fe887 --- /dev/null +++ b/test-cargo-miri/test.stdout.ref1 @@ -0,0 +1,10 @@ + +running 1 test +. +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out + + +running 8 tests +..i..... +test result: ok. 7 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out + diff --git a/test-cargo-miri/test.stdout.ref2 b/test-cargo-miri/test.stdout.ref2 index 37efb8c3ee..d426bdf6db 100644 --- a/test-cargo-miri/test.stdout.ref2 +++ b/test-cargo-miri/test.stdout.ref2 @@ -7,5 +7,5 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out running 1 test test simple1 ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 6 filtered out +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 7 filtered out diff --git a/test-cargo-miri/test.stdout.ref3 b/test-cargo-miri/test.stdout.ref3 index c5c39de109..32bbcf9bf2 100644 --- a/test-cargo-miri/test.stdout.ref3 +++ b/test-cargo-miri/test.stdout.ref3 @@ -1,11 +1,13 @@ -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out - - -running 1 test +running 8 tests +test cargo_env ... ok +test do_panic ... ok +test does_not_work_on_miri ... ignored +test entropy_rng ... ok +test fail_index_check ... ok test num_cpus ... ok +test simple1 ... ok +test simple2 ... ok -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 6 filtered out +test result: ok. 7 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out diff --git a/test-cargo-miri/test.stdout.ref4 b/test-cargo-miri/test.stdout.ref4 index b6403bf6c0..4caa30a7f0 100644 --- a/test-cargo-miri/test.stdout.ref4 +++ b/test-cargo-miri/test.stdout.ref4 @@ -1,12 +1,6 @@ -running 7 tests -test do_panic ... ok -test does_not_work_on_miri ... ignored -test entropy_rng ... ok -test fail_index_check ... ok -test num_cpus ... ok -test simple1 ... ok -test simple2 ... ok +running 1 test +test test::rng ... ok -test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out diff --git a/test-cargo-miri/test.stdout.ref5 b/test-cargo-miri/test.stdout.ref5 index 4caa30a7f0..67e5c7f8e9 100644 --- a/test-cargo-miri/test.stdout.ref5 +++ b/test-cargo-miri/test.stdout.ref5 @@ -1,6 +1,6 @@ -running 1 test -test test::rng ... ok +running 0 tests -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +subcrate testing diff --git a/test-cargo-miri/tests/test.rs b/test-cargo-miri/tests/test.rs index 68d5426802..eb97713b4b 100644 --- a/test-cargo-miri/tests/test.rs +++ b/test-cargo-miri/tests/test.rs @@ -42,22 +42,21 @@ fn num_cpus() { assert_eq!(num_cpus::get(), 1); } +#[test] +fn cargo_env() { + assert_eq!(env!("CARGO_PKG_NAME"), "cargo-miri-test"); + env!("CARGO_BIN_EXE_cargo-miri-test"); // Asserts that this exists. +} -// FIXME: Remove this `cfg` once we fix https://github.com/rust-lang/miri/issues/1059. -// We cfg-gate the `should_panic` attribute and the `panic!` itself, so that the test -// stdout does not depend on the target. #[test] -#[cfg_attr(not(windows), should_panic(expected="Explicit panic"))] +#[should_panic(expected="Explicit panic")] fn do_panic() { // In large, friendly letters :) - #[cfg(not(windows))] panic!("Explicit panic from test!"); } -// FIXME: see above #[test] +#[should_panic(expected="the len is 0 but the index is 42")] #[allow(unconditional_panic)] -#[cfg_attr(not(windows), should_panic(expected="the len is 0 but the index is 42"))] fn fail_index_check() { - #[cfg(not(windows))] [][42] } diff --git a/tests/compiletest.rs b/tests/compiletest.rs index a64f0edb94..35c1de3399 100644 --- a/tests/compiletest.rs +++ b/tests/compiletest.rs @@ -27,7 +27,7 @@ fn run_tests(mode: &str, path: &str, target: &str) { if let Ok(sysroot) = std::env::var("MIRI_SYSROOT") { flags.push(format!("--sysroot {}", sysroot)); } - if let Ok(extra_flags) = std::env::var("MIRI_TEST_FLAGS") { + if let Ok(extra_flags) = std::env::var("MIRIFLAGS") { flags.push(extra_flags); }