Auto merge of - RalfJung:cargo-miri-redone, r=oli-obk

Redo cargo-miri logic

This rewrite the cargo-miri logic for running the requested crate(s) following what we outlined in https://github.com/rust-lang/miri/issues/739: `cargo miri run/test $FLAGS` (almost) literally invokes `cargo run/test $FLAGS` but with some environment variables set so that we can control what happens:
* `RUSTC_WRAPPER` is set so that we get invoked instead of `rustc`. We use that power to mess with the flags being used for the build (things to be interpreted by Miri use a different sysroot), and when we are detecting a binary crate meant to be run by Miri, we grab the info we care about and put it into a JSON file for later use.
* `CARGO_TARGET_$TARGET_RUNNER` is set so what we get invoked when cargo wants to run a binary. At that point we take that JSON info from before and use it to invoke Miri.

Overall this works great! We get all sorts of cargo magic for free, and do not even need `cargo-metadata` any more. There's one annoying point though, which I have not managed to entirely work around yet: this means we are doing a full build, not just a check-build. Given that our sysroot is MIR-only, I was surprised that this even worked, but still -- this means we are doing more work than we should. So I also added some patching of arguments to make sure `--emit` does not contain `link`, and then more patching was required of the `--extern` flags for the binary because those referenced the `.rlib` files but now only `.rmeta` exists, and that is still not fully working because cargo seems to expect those `.rmeta` files and now triggers a rebuild each time as those files are still missing. My current plan is to make our wrapper create some empty dummy files with the right names, but the amount of hacks we are stacking on top of each other here is getting worrysome.^^ `@ehuss` your input would be welcome on this issue.

Due to re-using cargo more literally, this also changes flag parsing to match `cargo`. So `-Zmiri` flags now have to be passed via an environment variable (Cc https://github.com/rust-lang/miri/issues/1416).

This PR is not ready yet, but the parts that are there I think can be reviewed already. TODO:
* [x] [Fix Windows](https://github.com/rust-lang/miri/pull/1540#issuecomment-688733741).
* [x] Figure out how we can do check-only builds without the rebuild problem above. ~~I am also worried about cases where `build.rs` script might detect check-only builds and then do less work; I think some crates in rustc are doing that and if this is a thing in the wider ecosystem we need to find a way to support this as well.~~ (postponed that until we have a concrete example)
* [x] Currently cargo runs doctests as well, and they are not run in Miri. We should at least figure out a way to not run them at all (resolving https://github.com/rust-lang/miri/issues/584 is left for a future PR).
* [x] For some time, detect the old way of passing `-Zmiri` flags and warn that this will need updating. For some simple situations we can probably make it still support the old way, but I plan to remove those hacks after a bit. This is just to give people and me time to go around and send PRs to all projects that use Miri on CI, and update their use of the flags.
* [x] Add a test for stdin handling (https://github.com/rust-lang/miri/issues/1505). This should work now but we should be sure.
* [x] Add a test for cargo env vars (https://github.com/rust-lang/miri/issues/1515).
* [x] Check if https://github.com/rust-lang/miri/issues/1516 is resolved.
* [x] Check if https://github.com/rust-lang/miri/issues/1001 and https://github.com/rust-lang/miri/issues/1514 are resolved.
* [x] Check if https://github.com/rust-lang/miri/issues/1312 is resolved (not sure if it is wort adding a test).
* [x] Check if https://github.com/rust-lang/miri/issues/1512 is resolved (not sure if it is wort adding a test).

Fixes https://github.com/rust-lang/miri/issues/700.
Fixes https://github.com/rust-lang/miri/issues/739.
Fixes https://github.com/rust-lang/miri/issues/1001.
Fixes https://github.com/rust-lang/miri/issues/1312 (without a test, as we run without cargo's stdin/stdout wrapping now, as the test for stdin confirms).
Fixes https://github.com/rust-lang/miri/issues/1416.
Fixes https://github.com/rust-lang/miri/issues/1505.
Fixes https://github.com/rust-lang/miri/issues/1512 (without a test, as cargo now does all that handling anyway, which various other tests confirm).
Fixes https://github.com/rust-lang/miri/issues/1514.
Fixes https://github.com/rust-lang/miri/issues/1516.

Cc `@alecmocatta` who reported many of the bugs above; would be great if you could help with the tests e.g. by providing some small examples I could try.
r? `@oli-obk`
This commit is contained in:
bors 2020-09-17 16:52:23 +00:00
commit ce29fbf406
31 changed files with 608 additions and 426 deletions

@ -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

24
cargo-miri/Cargo.lock generated

@ -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"

@ -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"

@ -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 options>...] [--] [<miri options>...] [--] [<program/test suite options>...]
cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
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<String>,
/// 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<Item = cargo_metadata::Target> {
// 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)> {
@ -218,15 +207,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));
}
}
@ -249,7 +238,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"]);
@ -289,7 +278,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.
@ -363,7 +352,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.
@ -381,173 +370,127 @@ path = "lib.rs"
}
}
enum CargoTargets {
All,
Filtered { lib: bool, bin: Vec<String>, test: Vec<String> },
}
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<Item = String>,
) -> (CargoTargets, Vec<String>, Vec<String>) {
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;
// Invoke actual cargo for the job, but with different flags.
// We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
// requires some extra work to make the build check-only (see all the `--emit` hacks below).
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
// approach that uses `cargo check`, making that part easier but target and binary handling
// harder.
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
};
// 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);
}
}
}
// 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));
// 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");
}
// 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);
}
let exit_status =
cmd.spawn().expect("could not run cargo").wait().expect("failed to wait for cargo?");
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1))
}
// 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);
}
// 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);
// 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
@ -567,82 +510,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`
// 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.
if target_crate {
let sysroot =
env::var_os("MIRI_SYSROOT").expect("The wrapper should have set MIRI_SYSROOT");
cmd.arg("--sysroot");
cmd.arg(sysroot);
}
// 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<String> =
serde_json::from_str(&magic).expect("failed to deserialize MIRI_ARGS");
cmd.args(miri_args);
// 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"));
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");
cmd.arg("--sysroot");
cmd.arg(sysroot);
} else {
// We want to compile, not interpret.
cmd.env("MIRI_BE_RUSTC", "1");
};
// For host crates, just forward everything.
cmd.args(args);
}
// 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 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);
}
}
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);
}
// 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() {
// Check for version and help flags even when invoked as `cargo-miri`.
if has_arg_flag("--help") || has_arg_flag("-h") {
show_help();
return;
}
if has_arg_flag("--version") || has_arg_flag("-V") {
show_version();
return;
}
// 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();
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."
))
// 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`")),
}
}

2
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 <https://github.com/rust-lang/rust/issues/76432>.
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.

23
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.

@ -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!"),

@ -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"

@ -1,7 +1,10 @@
[workspace]
members = ["subcrate"]
[package]
name = "cargo-miri-test"
version = "0.1.0"
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
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)

@ -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");
}

@ -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 (with filter)",
cargo_miri("test") + ["--", "--", "le1"],
"test.stdout.ref2", "test.stderr.ref"
test("`cargo miri test` (no isolation)",
cargo_miri("test"),
"test.stdout.ref1", rustdoc_ref,
env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
)
test("cargo miri test (without isolation)",
cargo_miri("test") + ["--", "-Zmiri-disable-isolation", "--", "num_cpus"],
"test.stdout.ref3", "test.stderr.ref"
test("`cargo miri test` (with filter)",
cargo_miri("test") + ["--", "--format=pretty", "le1"],
"test.stdout.ref2", rustdoc_ref,
)
test("cargo miri test (test target)",
cargo_miri("test") + ["--test", "test"],
"test.stdout.ref4", "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 (bin target)",
cargo_miri("test") + ["--bin", "cargo-miri-test"],
"test.stdout.ref5", "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` (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)

@ -0,0 +1,7 @@
/// Doc-test test
/// ```rust
/// assert!(cargo_miri_test::make_true());
/// ```
pub fn make_true() -> bool {
true
}

@ -1,6 +1,12 @@
use byteorder::{BigEndian, ByteOrder};
use std::env;
#[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 = <BigEndian as ByteOrder>::read_u32(buf);
@ -11,6 +17,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 {
// CWD should be crate root.
// We have to normalize slashes, as the env var might be set for a different target's conventions.
let env_dir = env::current_dir().unwrap();
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
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)]

@ -0,0 +1,3 @@
0x01020304
24
42

@ -0,0 +1 @@
subcrate running

@ -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

@ -0,0 +1,16 @@
use std::env;
use std::path::PathBuf;
fn main() {
println!("subcrate running");
// CWD should be workspace root, i.e., one level up from crate root.
// We have to normalize slashes, as the env var might be set for a different target's conventions.
let env_dir = env::current_dir().unwrap();
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
let crate_dir = PathBuf::from(crate_dir);
let crate_dir = crate_dir.parent().unwrap().to_string_lossy();
assert_eq!(env_dir, crate_dir);
}

@ -0,0 +1,13 @@
use std::env;
fn main() {
println!("subcrate testing");
// CWD should be crate root.
// We have to normalize slashes, as the env var might be set for a different target's conventions.
let env_dir = env::current_dir().unwrap();
let env_dir = env_dir.to_string_lossy().replace("\\", "/");
let crate_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let crate_dir = crate_dir.to_string_lossy().replace("\\", "/");
assert_eq!(env_dir, crate_dir);
}

@ -0,0 +1 @@
Running doctests is not currently supported by Miri.

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -42,6 +42,12 @@ 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.
}
#[test]
#[should_panic(expected="Explicit panic")]
fn do_panic() { // In large, friendly letters :)

@ -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);
}