diff --git a/Cargo.toml b/Cargo.toml index 16b5b2361bd..721ebe5cfd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ directories = { version = "1.0", optional = true } rustc_version = { version = "0.2.3", optional = true } env_logger = "0.6" log = "0.4" +shell-escape = "0.1.4" # A noop dependency that changes in the Rust repository, it's a bit of a hack. # See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` # for more information. diff --git a/README.md b/README.md index 1d97b61f43d..21e9fa43a8f 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,13 @@ Now you can run your project in Miri: 3. If you have a binary project, you can run it through Miri using `cargo +nightly miri run`. +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 ++nightly miri run -- -Zmiri-disable-validation` runs the program without +validation of basic type invariants and references. `cargo +nightly miri test +-- -- filter` passes `filter` to the test suite the same way `cargo test filter` +would. + When running code via `cargo miri`, the `miri` config flag is set. You can use this to exclude test cases that will fail under Miri because they do things Miri does not support: diff --git a/benches/helpers/miri_helper.rs b/benches/helpers/miri_helper.rs index a34e137694c..404fe7ae915 100644 --- a/benches/helpers/miri_helper.rs +++ b/benches/helpers/miri_helper.rs @@ -4,11 +4,13 @@ extern crate rustc_driver; extern crate test; -use self::miri::eval_main; -use self::rustc_driver::{driver, Compilation}; +use rustc_driver::{driver, Compilation}; use rustc::hir::def_id::LOCAL_CRATE; use std::cell::RefCell; use std::rc::Rc; + +use miri::{MiriConfig, eval_main}; + use crate::test::Bencher; pub struct MiriCompilerCalls<'a>(Rc>); @@ -50,7 +52,8 @@ pub fn run(filename: &str, bencher: &mut Bencher) { ); bencher.borrow_mut().iter(|| { - eval_main(tcx, entry_def_id, false); + let config = MiriConfig { validate: true, args: vec![] }; + eval_main(tcx, entry_def_id, config); }); state.session.abort_if_errors(); diff --git a/src/bin/cargo-miri.rs b/src/bin/cargo-miri.rs index 9b97822b475..93200f33a26 100644 --- a/src/bin/cargo-miri.rs +++ b/src/bin/cargo-miri.rs @@ -10,7 +10,7 @@ const CARGO_MIRI_HELP: &str = r#"Interprets bin crates and tests in Miri Usage: - cargo miri [subcommand] [options] [--] [...] + cargo miri [subcommand] [options] [--] [...] [--] [...] Subcommands: run Run binaries (default) @@ -22,8 +22,9 @@ --features Features to compile for the package -V, --version Print version info and exit -Other [options] are the same as `cargo rustc`. Everything after the "--" is -passed verbatim to Miri. +Other [options] are the same as `cargo rustc`. Everything after the first "--" is +passed verbatim to Miri, which will pass everything after the second "--" verbatim +to the interpreted program. The config flag `miri` is automatically defined for convenience. You can use it to configure the resource limits @@ -355,11 +356,13 @@ fn in_cargo_miri() { } cmd.arg(arg); } - // add "--" "-Zcargo-miri-marker" and the remaining user flags + // Add "--" (to end the cargo flags), and then the user flags. We add markers around the user flags + // to be able to identify them later. cmd .arg("--") - .arg("cargo-miri-marker") - .args(args); + .arg("cargo-miri-marker-begin") + .args(args) + .arg("cargo-miri-marker-end"); let path = std::env::current_exe().expect("current executable path invalid"); cmd.env("RUSTC_WRAPPER", path); if verbose { @@ -413,10 +416,19 @@ fn inside_cargo_rustc() { }; args.splice(0..0, miri::miri_default_args().iter().map(ToString::to_string)); - // see if we have cargo-miri-marker, which means we want to interpret this crate in Miri - // (and remove the marker). - let needs_miri = if let Some(pos) = args.iter().position(|arg| arg == "cargo-miri-marker") { - args.remove(pos); + // See if we can find the cargo-miri markers. Those only get added to the binary we want to + // run. They also serve to mark the user-defined arguments, which we have to move all the way to the + // end (they get added somewhere in the middle). + let needs_miri = if let Some(begin) = args.iter().position(|arg| arg == "cargo-miri-marker-begin") { + let end = args.iter().position(|arg| arg == "cargo-miri-marker-end").expect("Cannot find end marker"); + // These mark the user arguments. We remove the first and last as they are the markers. + let mut user_args = args.drain(begin..=end); + assert_eq!(user_args.next().unwrap(), "cargo-miri-marker-begin"); + assert_eq!(user_args.next_back().unwrap(), "cargo-miri-marker-end"); + // Collect the rest and add it back at the end + let mut user_args = user_args.collect::>(); + args.append(&mut user_args); + // Run this in Miri true } else { false diff --git a/src/bin/miri-rustc-tests.rs b/src/bin/miri-rustc-tests.rs index 45902dc6722..3a70577cb7f 100644 --- a/src/bin/miri-rustc-tests.rs +++ b/src/bin/miri-rustc-tests.rs @@ -25,6 +25,8 @@ use syntax::ast; use rustc::hir::def_id::LOCAL_CRATE; +use miri::MiriConfig; + struct MiriCompilerCalls { default: Box, /// whether we are building for the host @@ -94,9 +96,10 @@ impl<'a, 'tcx: 'a, 'hir> itemlikevisit::ItemLikeVisitor<'hir> for Visitor<'a, 't fn visit_item(&mut self, i: &'hir hir::Item) { if let hir::ItemKind::Fn(.., body_id) = i.node { if i.attrs.iter().any(|attr| attr.name() == "test") { + let config = MiriConfig { validate: true, args: vec![] }; let did = self.0.hir().body_owner_def_id(body_id); println!("running test: {}", self.0.def_path_debug_str(did)); - miri::eval_main(self.0, did, /*validate*/true); + miri::eval_main(self.0, did, config); self.1.session.abort_if_errors(); } } @@ -106,7 +109,8 @@ fn visit_impl_item(&mut self, _impl_item: &'hir hir::ImplItem) {} } state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(tcx, state)); } else if let Some((entry_def_id, _)) = tcx.entry_fn(LOCAL_CRATE) { - miri::eval_main(tcx, entry_def_id, /*validate*/true); + let config = MiriConfig { validate: true, args: vec![] }; + miri::eval_main(tcx, entry_def_id, config); state.session.abort_if_errors(); } else { diff --git a/src/bin/miri.rs b/src/bin/miri.rs index acfc429ed80..31bd1deb10f 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -27,11 +27,11 @@ use rustc::hir::def_id::LOCAL_CRATE; use syntax::ast; +use miri::MiriConfig; + struct MiriCompilerCalls { default: Box, - - /// Whether to enforce the validity invariant. - validate: bool, + miri_config: MiriConfig, } impl<'a> CompilerCalls<'a> for MiriCompilerCalls { @@ -79,6 +79,8 @@ fn late_callback( odir: &Option, ofile: &Option, ) -> Compilation { + // Called *before* build_controller. Add filename to miri arguments. + self.miri_config.args.insert(0, input.filestem().to_string()); self.default.late_callback(codegen_backend, matches, sess, cstore, input, odir, ofile) } fn build_controller( @@ -89,9 +91,9 @@ fn build_controller( let this = *self; let mut control = this.default.build_controller(sess, matches); control.after_hir_lowering.callback = Box::new(after_hir_lowering); - let validate = this.validate; + let miri_config = this.miri_config; control.after_analysis.callback = - Box::new(move |state| after_analysis(state, validate)); + Box::new(move |state| after_analysis(state, miri_config.clone())); control.after_analysis.stop = Compilation::Stop; control } @@ -107,7 +109,7 @@ fn after_hir_lowering(state: &mut CompileState) { fn after_analysis<'a, 'tcx>( state: &mut CompileState<'a, 'tcx>, - validate: bool, + miri_config: MiriConfig, ) { init_late_loggers(); state.session.abort_if_errors(); @@ -117,7 +119,7 @@ fn after_analysis<'a, 'tcx>( let (entry_def_id, _) = tcx.entry_fn(LOCAL_CRATE).expect("no main function found!"); - miri::eval_main(tcx, entry_def_id, validate); + miri::eval_main(tcx, entry_def_id, miri_config); state.session.abort_if_errors(); } @@ -188,34 +190,51 @@ fn find_sysroot() -> String { fn main() { init_early_loggers(); - let mut args: Vec = std::env::args().collect(); - // Parse our own -Z flags and remove them before rustc gets their hand on them. + // Parse our arguments and split them across rustc and miri let mut validate = true; - args.retain(|arg| { - match arg.as_str() { - "-Zmiri-disable-validation" => { - validate = false; - false - }, - _ => true + let mut rustc_args = vec![]; + let mut miri_args = vec![]; + let mut after_dashdash = false; + for arg in std::env::args() { + if rustc_args.is_empty() { + // Very first arg: for rustc + rustc_args.push(arg); } - }); + else if after_dashdash { + // Everything that comes is Miri args + miri_args.push(arg); + } else { + match arg.as_str() { + "-Zmiri-disable-validation" => { + validate = false; + }, + "--" => { + after_dashdash = true; + } + _ => { + rustc_args.push(arg); + } + } + } + } // Determine sysroot and let rustc know about it let sysroot_flag = String::from("--sysroot"); - if !args.contains(&sysroot_flag) { - args.push(sysroot_flag); - args.push(find_sysroot()); + if !rustc_args.contains(&sysroot_flag) { + rustc_args.push(sysroot_flag); + rustc_args.push(find_sysroot()); } // Finally, add the default flags all the way in the beginning, but after the binary name. - args.splice(1..1, miri::miri_default_args().iter().map(ToString::to_string)); + rustc_args.splice(1..1, miri::miri_default_args().iter().map(ToString::to_string)); - trace!("rustc arguments: {:?}", args); + debug!("rustc arguments: {:?}", rustc_args); + debug!("miri arguments: {:?}", miri_args); + let miri_config = MiriConfig { validate, args: miri_args }; let result = rustc_driver::run(move || { - rustc_driver::run_compiler(&args, Box::new(MiriCompilerCalls { + rustc_driver::run_compiler(&rustc_args, Box::new(MiriCompilerCalls { default: Box::new(RustcDefaultCalls), - validate, + miri_config, }), None, None) }); std::process::exit(result as i32); diff --git a/src/lib.rs b/src/lib.rs index 4ff3e011c6a..f59a476ed94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,16 +57,23 @@ pub fn miri_default_args() -> &'static [&'static str] { &["-Zalways-encode-mir", "-Zmir-emit-retag", "-Zmir-opt-level=0", "--cfg=miri"] } +/// Configuration needed to spawn a Miri instance +#[derive(Clone)] +pub struct MiriConfig { + pub validate: bool, + pub args: Vec, +} + // Used by priroda pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( tcx: TyCtxt<'a, 'tcx, 'tcx>, main_id: DefId, - validate: bool, + config: MiriConfig, ) -> EvalResult<'tcx, EvalContext<'a, 'mir, 'tcx, Evaluator<'tcx>>> { let mut ecx = EvalContext::new( tcx.at(syntax::source_map::DUMMY_SP), ty::ParamEnv::reveal_all(), - Evaluator::new(validate), + Evaluator::new(config.validate), ); let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id); @@ -120,7 +127,7 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( // Second argument (argc): 1 let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?; - let argc = Scalar::from_int(1, dest.layout.size); + let argc = Scalar::from_uint(config.args.len() as u128, dest.layout.size); ecx.write_scalar(argc, dest)?; // Store argc for macOS _NSGetArgc { @@ -130,18 +137,38 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( } // FIXME: extract main source file path - // Third argument (argv): &[b"foo"] - const CMD: &str = "running-in-miri\0"; + // Third argument (argv): Created from config.args let dest = ecx.eval_place(&mir::Place::Local(args.next().unwrap()))?; - let cmd = ecx.memory_mut().allocate_static_bytes(CMD.as_bytes()).with_default_tag(); - let raw_str_layout = ecx.layout_of(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8))?; - let cmd_place = ecx.allocate(raw_str_layout, MiriMemoryKind::Env.into()); - ecx.write_scalar(Scalar::Ptr(cmd), cmd_place.into())?; - ecx.memory_mut().mark_immutable(cmd_place.to_ptr()?.alloc_id)?; + // For Windows, construct a command string with all the aguments + let mut cmd = String::new(); + for arg in config.args.iter() { + if !cmd.is_empty() { + cmd.push(' '); + } + cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into())); + } + cmd.push(std::char::from_u32(0).unwrap()); // don't forget 0 terminator + // Collect the pointers to the individual strings. + let mut argvs = Vec::>::new(); + for arg in config.args { + // Add 0 terminator + let mut arg = arg.into_bytes(); + arg.push(0); + argvs.push(ecx.memory_mut().allocate_static_bytes(arg.as_slice()).with_default_tag()); + } + // Make an array with all these pointers, in the Miri memory. + let argvs_layout = ecx.layout_of(ecx.tcx.mk_array(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8), argvs.len() as u64))?; + let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Env.into()); + for (idx, arg) in argvs.into_iter().enumerate() { + let place = ecx.mplace_field(argvs_place, idx as u64)?; + ecx.write_scalar(Scalar::Ptr(arg), place.into())?; + } + ecx.memory_mut().mark_immutable(argvs_place.to_ptr()?.alloc_id)?; + // Write a pointe to that place as the argument. + let argv = argvs_place.ptr; + ecx.write_scalar(argv, dest)?; // Store argv for macOS _NSGetArgv { - let argv = cmd_place.ptr; - ecx.write_scalar(argv, dest)?; let argv_place = ecx.allocate(dest.layout, MiriMemoryKind::Env.into()); ecx.write_scalar(argv, argv_place.into())?; ecx.machine.argv = Some(argv_place.ptr.to_ptr()?); @@ -149,7 +176,7 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( // Store cmdline as UTF-16 for Windows GetCommandLineW { let tcx = &{ecx.tcx.tcx}; - let cmd_utf16: Vec = CMD.encode_utf16().collect(); + let cmd_utf16: Vec = cmd.encode_utf16().collect(); let cmd_ptr = ecx.memory_mut().allocate( Size::from_bytes(cmd_utf16.len() as u64 * 2), Align::from_bytes(2).unwrap(), @@ -179,9 +206,9 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( pub fn eval_main<'a, 'tcx: 'a>( tcx: TyCtxt<'a, 'tcx, 'tcx>, main_id: DefId, - validate: bool, + config: MiriConfig, ) { - let mut ecx = create_ecx(tcx, main_id, validate).expect("Couldn't create ecx"); + let mut ecx = create_ecx(tcx, main_id, config).expect("Couldn't create ecx"); // Run! The main execution. let res: EvalResult = (|| { diff --git a/test-cargo-miri/run-test.py b/test-cargo-miri/run-test.py index 7f7f2660c06..8c59b6bcdea 100755 --- a/test-cargo-miri/run-test.py +++ b/test-cargo-miri/run-test.py @@ -37,10 +37,19 @@ def test(name, cmd, stdout_ref, stderr_ref): def test_cargo_miri_run(): test("cargo miri run", ["cargo", "miri", "run", "-q"], "stdout.ref", "stderr.ref") + test("cargo miri run (with arguments)", + ["cargo", "miri", "run", "-q", "--", "--", "hello world", '"hello world"'], + "stdout.ref", "stderr.ref2" + ) def test_cargo_miri_test(): test("cargo miri test", ["cargo", "miri", "test", "-q"], "test.stdout.ref", "test.stderr.ref") + test("cargo miri test (with filter)", + ["cargo", "miri", "test", "-q", "--", "--", "impl"], + "test.stdout.ref2", "test.stderr.ref" + ) test_cargo_miri_run() test_cargo_miri_test() +print("TEST SUCCESSFUL!") sys.exit(0) diff --git a/test-cargo-miri/src/main.rs b/test-cargo-miri/src/main.rs index 25e2cfdfa03..32f1bac57d2 100644 --- a/test-cargo-miri/src/main.rs +++ b/test-cargo-miri/src/main.rs @@ -9,7 +9,9 @@ fn main() { let n = ::read_u32(buf); assert_eq!(n, 0x01020304); println!("{:#010x}", n); - eprintln!("standard error"); + for arg in std::env::args() { + eprintln!("{}", arg); + } } #[cfg(test)] diff --git a/test-cargo-miri/stderr.ref b/test-cargo-miri/stderr.ref index aa7d1a2bdec..ba2906d0666 100644 --- a/test-cargo-miri/stderr.ref +++ b/test-cargo-miri/stderr.ref @@ -1 +1 @@ -standard error +main diff --git a/test-cargo-miri/stderr.ref2 b/test-cargo-miri/stderr.ref2 new file mode 100644 index 00000000000..8226b1b7cde --- /dev/null +++ b/test-cargo-miri/stderr.ref2 @@ -0,0 +1,3 @@ +main +hello world +"hello world" diff --git a/test-cargo-miri/test.stdout.ref2 b/test-cargo-miri/test.stdout.ref2 new file mode 100644 index 00000000000..ce3506709d5 --- /dev/null +++ b/test-cargo-miri/test.stdout.ref2 @@ -0,0 +1,11 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out + + +running 1 test +test simple ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out + diff --git a/tests/run-pass/args.rs b/tests/run-pass/args.rs new file mode 100644 index 00000000000..0116dce4992 --- /dev/null +++ b/tests/run-pass/args.rs @@ -0,0 +1,5 @@ +fn main() { + for arg in std::env::args() { + println!("{}", arg); + } +} diff --git a/tests/run-pass/args.stdout b/tests/run-pass/args.stdout new file mode 100644 index 00000000000..9564f5a1aa0 --- /dev/null +++ b/tests/run-pass/args.stdout @@ -0,0 +1 @@ +args