Skip to content

Commit 1889b9f

Browse files
authored
Merge pull request #616 from RalfJung/cargo-miri
Fix cargo miri on unit tests, refactor it a bit, set rust flag instead of feature flag
2 parents 2d4ebf0 + 5766b32 commit 1889b9f

File tree

8 files changed

+416
-176
lines changed

8 files changed

+416
-176
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ rustc_tests = []
5555
[dev-dependencies]
5656
compiletest_rs = { version = "0.3.17", features = ["tmp"] }
5757
colored = "1.6"
58+
59+
[profile.release]
60+
debug = true

src/bin/cargo-miri.rs

Lines changed: 175 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use std::io::{self, Write, BufRead};
77
use std::process::Command;
88
use std::fs::{self, File};
99

10-
const CARGO_MIRI_HELP: &str = r#"Interprets bin crates
10+
const CARGO_MIRI_HELP: &str = r#"Interprets bin crates and tests in Miri
1111
1212
Usage:
13-
cargo miri [subcommand] [options] [--] [<opts>...]
13+
cargo miri [subcommand] [options] [--] [<miri opts>...]
1414
1515
Subcommands:
1616
run Run binaries (default)
@@ -22,12 +22,13 @@ Common options:
2222
--features Features to compile for the package
2323
-V, --version Print version info and exit
2424
25-
Other options are the same as `cargo rustc`.
25+
Other [options] are the same as `cargo rustc`. Everything after the "--" is
26+
passed verbatim to Miri.
2627
27-
The feature `cargo-miri` is automatically defined for convenience. You can use
28+
The config flag `miri` is automatically defined for convenience. You can use
2829
it to configure the resource limits
2930
30-
#![cfg_attr(feature = "cargo-miri", memory_size = 42)]
31+
#![cfg_attr(miri, memory_size = 42)]
3132
3233
available resource limits are `memory_size`, `step_limit`, `stack_limit`
3334
"#;
@@ -53,23 +54,32 @@ fn show_error(msg: String) -> ! {
5354
std::process::exit(1)
5455
}
5556

57+
// Determines whether a --flag is present
58+
fn has_arg_flag(name: &str) -> bool {
59+
let mut args = std::env::args().take_while(|val| val != "--");
60+
args.any(|val| val == name)
61+
}
62+
63+
/// Gets the value of a --flag
5664
fn get_arg_flag_value(name: &str) -> Option<String> {
5765
// stop searching at `--`
58-
let mut args = std::env::args().skip_while(|val| !(val.starts_with(name) || val == "--"));
59-
60-
match args.next() {
61-
Some(ref p) if p == "--" => None,
62-
Some(ref p) if p == name => args.next(),
63-
Some(p) => {
64-
// Make sure this really starts with `$name=`, we didn't test for the `=` yet.
65-
let v = &p[name.len()..]; // strip leading `$name`
66-
if v.starts_with('=') {
67-
Some(v[1..].to_owned()) // strip leading `=`
68-
} else {
69-
None
70-
}
71-
},
72-
None => None,
66+
let mut args = std::env::args().take_while(|val| val != "--");
67+
loop {
68+
let arg = match args.next() {
69+
Some(arg) => arg,
70+
None => return None,
71+
};
72+
if !arg.starts_with(name) {
73+
continue;
74+
}
75+
let suffix = &arg[name.len()..]; // strip leading `name`
76+
if suffix.is_empty() {
77+
// This argument is exactly `name`, the next one is the value
78+
return args.next();
79+
} else if suffix.starts_with('=') {
80+
// This argument is `name=value`, get the value
81+
return Some(suffix[1..].to_owned()); // strip leading `=`
82+
}
7383
}
7484
}
7585

@@ -272,167 +282,163 @@ fn main() {
272282
// each applicable target, but with the RUSTC env var set to the `cargo-miri`
273283
// binary so that we come back in the other branch, and dispatch
274284
// the invocations to rustc and miri, respectively.
275-
276-
let (subcommand, skip) = match std::env::args().nth(2).deref() {
277-
Some("test") => (MiriCommand::Test, 3),
278-
Some("run") => (MiriCommand::Run, 3),
279-
Some("setup") => (MiriCommand::Setup, 3),
280-
// Default command, if there is an option or nothing
281-
Some(s) if s.starts_with("-") => (MiriCommand::Run, 2),
282-
None => (MiriCommand::Run, 2),
283-
// Unvalid command
284-
Some(s) => {
285-
show_error(format!("Unknown command `{}`", s))
286-
}
287-
};
288-
289-
// We always setup
290-
let ask = subcommand != MiriCommand::Setup;
291-
setup(ask);
292-
if subcommand == MiriCommand::Setup {
293-
// Stop here.
294-
return;
295-
}
296-
297-
// Now run the command.
298-
for target in list_targets() {
299-
let args = std::env::args().skip(skip);
300-
let kind = target.kind.get(0).expect(
301-
"badly formatted cargo metadata: target::kind is an empty array",
302-
);
303-
match (subcommand, &kind[..]) {
304-
(MiriCommand::Test, "test") => {
305-
// For test binaries we call `cargo rustc --test target -- <rustc args>`
306-
if let Err(code) = process(
307-
vec!["--test".to_string(), target.name].into_iter().chain(
308-
args,
309-
),
310-
)
311-
{
312-
std::process::exit(code);
313-
}
314-
}
315-
(MiriCommand::Test, "lib") => {
316-
// For libraries we call `cargo rustc -- --test <rustc args>`
317-
// Notice now that `--test` is a rustc arg rather than a cargo arg. This tells
318-
// rustc to build a test harness which calls all #[test] functions.
319-
// We then execute that harness just like any other binary.
320-
if let Err(code) = process(
321-
vec!["--".to_string(), "--test".to_string()].into_iter().chain(
322-
args,
323-
),
324-
)
325-
{
326-
std::process::exit(code);
327-
}
328-
}
329-
(MiriCommand::Run, "bin") => {
330-
// For ordinary binaries we call `cargo rustc --bin target -- <rustc args>`
331-
if let Err(code) = process(
332-
vec!["--bin".to_string(), target.name].into_iter().chain(
333-
args,
334-
),
335-
)
336-
{
337-
std::process::exit(code);
338-
}
339-
}
340-
_ => {}
341-
}
342-
}
285+
in_cargo_miri();
343286
} else if let Some("rustc") = std::env::args().nth(1).as_ref().map(AsRef::as_ref) {
344287
// This arm is executed when cargo-miri runs `cargo rustc` with the `RUSTC_WRAPPER` env var set to itself:
345288
// Dependencies get dispatched to rustc, the final test/binary to miri.
289+
inside_cargo_rustc();
290+
} else {
291+
show_error(format!("Must be called with either `miri` or `rustc` as first argument."))
292+
}
293+
}
346294

347-
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
348-
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
349-
let sys_root = if let Ok(sysroot) = ::std::env::var("MIRI_SYSROOT") {
350-
sysroot
351-
} else if let (Some(home), Some(toolchain)) = (home, toolchain) {
352-
format!("{}/toolchains/{}", home, toolchain)
353-
} else {
354-
option_env!("RUST_SYSROOT")
355-
.map(|s| s.to_owned())
356-
.or_else(|| {
357-
Command::new("rustc")
358-
.arg("--print")
359-
.arg("sysroot")
360-
.output()
361-
.ok()
362-
.and_then(|out| String::from_utf8(out.stdout).ok())
363-
.map(|s| s.trim().to_owned())
364-
})
365-
.expect("need to specify RUST_SYSROOT env var during miri compilation, or use rustup or multirust")
366-
};
295+
fn in_cargo_miri() {
296+
let (subcommand, skip) = match std::env::args().nth(2).deref() {
297+
Some("test") => (MiriCommand::Test, 3),
298+
Some("run") => (MiriCommand::Run, 3),
299+
Some("setup") => (MiriCommand::Setup, 3),
300+
// Default command, if there is an option or nothing
301+
Some(s) if s.starts_with("-") => (MiriCommand::Run, 2),
302+
None => (MiriCommand::Run, 2),
303+
// Unvalid command
304+
Some(s) => {
305+
show_error(format!("Unknown command `{}`", s))
306+
}
307+
};
308+
let verbose = has_arg_flag("-v");
367309

368-
// this conditional check for the --sysroot flag is there so users can call `cargo-miri` directly
369-
// without having to pass --sysroot or anything
370-
let rustc_args = std::env::args().skip(2);
371-
let mut args: Vec<String> = if std::env::args().any(|s| s == "--sysroot") {
372-
rustc_args.collect()
373-
} else {
374-
rustc_args
375-
.chain(Some("--sysroot".to_owned()))
376-
.chain(Some(sys_root))
377-
.collect()
378-
};
379-
args.splice(0..0, miri::miri_default_args().iter().map(ToString::to_string));
380-
args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-miri""#.to_owned()]);
381-
382-
// this check ensures that dependencies are built but not interpreted and the final crate is
383-
// interpreted but not built
384-
let miri_enabled = std::env::args().any(|s| s == "--emit=dep-info,metadata");
385-
let mut command = if miri_enabled {
386-
let mut path = std::env::current_exe().expect("current executable path invalid");
387-
path.set_file_name("miri");
388-
Command::new(path)
389-
} else {
390-
Command::new("rustc")
391-
};
392-
command.args(&args);
310+
// We always setup
311+
let ask = subcommand != MiriCommand::Setup;
312+
setup(ask);
313+
if subcommand == MiriCommand::Setup {
314+
// Stop here.
315+
return;
316+
}
393317

394-
match command.status() {
395-
Ok(exit) => {
396-
if !exit.success() {
397-
std::process::exit(exit.code().unwrap_or(42));
398-
}
318+
// Now run the command.
319+
for target in list_targets() {
320+
let mut args = std::env::args().skip(skip);
321+
let kind = target.kind.get(0).expect(
322+
"badly formatted cargo metadata: target::kind is an empty array",
323+
);
324+
// Now we run `cargo rustc $FLAGS $ARGS`, giving the user the
325+
// change to add additional flags. "FLAGS" is set to identify
326+
// this target. The user gets to control what gets actually passed to Miri.
327+
// However, we need to add a flag to what gets passed to rustc for the finaly
328+
// binary, so that we know to interpret that with Miri.
329+
// So after the first "--", we add "-Zcargo-miri-marker".
330+
let mut cmd = Command::new("cargo");
331+
cmd.arg("rustc");
332+
match (subcommand, &kind[..]) {
333+
(MiriCommand::Run, "bin") => {
334+
// FIXME: We just run all the binaries here.
335+
// We should instead support `cargo miri --bin foo`.
336+
cmd.arg("--bin").arg(target.name);
337+
}
338+
(MiriCommand::Test, "test") => {
339+
cmd.arg("--test").arg(target.name);
399340
}
400-
Err(ref e) if miri_enabled => panic!("error during miri run: {:?}", e),
401-
Err(ref e) => panic!("error during rustc call: {:?}", e),
341+
(MiriCommand::Test, "lib") |
342+
(MiriCommand::Test, "bin") => {
343+
cmd.arg(format!("--{}", kind)).arg(target.name).arg("--profile").arg("test");
344+
}
345+
// The remaining targets we do not even want to build
346+
_ => continue,
347+
}
348+
// add user-defined args until first "--"
349+
while let Some(arg) = args.next() {
350+
if arg == "--" {
351+
break;
352+
}
353+
cmd.arg(arg);
354+
}
355+
// add "--" "-Zcargo-miri-marker" and the remaining user flags
356+
cmd
357+
.arg("--")
358+
.arg("cargo-miri-marker")
359+
.args(args);
360+
let path = std::env::current_exe().expect("current executable path invalid");
361+
cmd.env("RUSTC_WRAPPER", path);
362+
if verbose {
363+
eprintln!("+ {:?}", cmd);
364+
}
365+
366+
let exit_status = cmd
367+
.spawn()
368+
.expect("could not run cargo")
369+
.wait()
370+
.expect("failed to wait for cargo?");
371+
372+
if !exit_status.success() {
373+
std::process::exit(exit_status.code().unwrap_or(-1))
402374
}
403-
} else {
404-
show_error(format!("Must be called with either `miri` or `rustc` as first argument."))
405375
}
406376
}
407377

408-
fn process<I>(old_args: I) -> Result<(), i32>
409-
where
410-
I: Iterator<Item = String>,
411-
{
412-
let mut args = vec!["rustc".to_owned()];
378+
fn inside_cargo_rustc() {
379+
let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
380+
let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
381+
let sys_root = if let Ok(sysroot) = ::std::env::var("MIRI_SYSROOT") {
382+
sysroot
383+
} else if let (Some(home), Some(toolchain)) = (home, toolchain) {
384+
format!("{}/toolchains/{}", home, toolchain)
385+
} else {
386+
option_env!("RUST_SYSROOT")
387+
.map(|s| s.to_owned())
388+
.or_else(|| {
389+
Command::new("rustc")
390+
.arg("--print")
391+
.arg("sysroot")
392+
.output()
393+
.ok()
394+
.and_then(|out| String::from_utf8(out.stdout).ok())
395+
.map(|s| s.trim().to_owned())
396+
})
397+
.expect("need to specify RUST_SYSROOT env var during miri compilation, or use rustup or multirust")
398+
};
413399

414-
let mut found_dashes = false;
415-
for arg in old_args {
416-
found_dashes |= arg == "--";
417-
args.push(arg);
418-
}
419-
if !found_dashes {
420-
args.push("--".to_owned());
421-
}
422-
args.push("--emit=dep-info,metadata".to_owned());
423-
424-
let path = std::env::current_exe().expect("current executable path invalid");
425-
let exit_status = Command::new("cargo")
426-
.args(&args)
427-
.env("RUSTC_WRAPPER", path)
428-
.spawn()
429-
.expect("could not run cargo")
430-
.wait()
431-
.expect("failed to wait for cargo?");
432-
433-
if exit_status.success() {
434-
Ok(())
400+
// this conditional check for the --sysroot flag is there so users can call `cargo-miri` directly
401+
// without having to pass --sysroot or anything
402+
let rustc_args = std::env::args().skip(2);
403+
let mut args: Vec<String> = if std::env::args().any(|s| s == "--sysroot") {
404+
rustc_args.collect()
405+
} else {
406+
rustc_args
407+
.chain(Some("--sysroot".to_owned()))
408+
.chain(Some(sys_root))
409+
.collect()
410+
};
411+
args.splice(0..0, miri::miri_default_args().iter().map(ToString::to_string));
412+
413+
// see if we have cargo-miri-marker, which means we want to interpret this crate in Miri
414+
// (and remove the marker).
415+
let needs_miri = if let Some(pos) = args.iter().position(|arg| arg == "cargo-miri-marker") {
416+
args.remove(pos);
417+
true
435418
} else {
436-
Err(exit_status.code().unwrap_or(-1))
419+
false
420+
};
421+
422+
423+
let mut command = if needs_miri {
424+
let mut path = std::env::current_exe().expect("current executable path invalid");
425+
path.set_file_name("miri");
426+
Command::new(path)
427+
} else {
428+
Command::new("rustc")
429+
};
430+
command.args(&args);
431+
if has_arg_flag("-v") {
432+
eprintln!("+ {:?}", command);
433+
}
434+
435+
match command.status() {
436+
Ok(exit) => {
437+
if !exit.success() {
438+
std::process::exit(exit.code().unwrap_or(42));
439+
}
440+
}
441+
Err(ref e) if needs_miri => panic!("error during miri run: {:?}", e),
442+
Err(ref e) => panic!("error during rustc call: {:?}", e),
437443
}
438444
}

0 commit comments

Comments
 (0)