Skip to content

implement passing arguments to the interpreted program #624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 6 additions & 3 deletions benches/helpers/miri_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ extern crate rustc;
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<RefCell<&'a mut Bencher>>);
Expand Down Expand Up @@ -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();
Expand Down
32 changes: 22 additions & 10 deletions src/bin/cargo-miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::fs::{self, File};
const CARGO_MIRI_HELP: &str = r#"Interprets bin crates and tests in Miri

Usage:
cargo miri [subcommand] [options] [--] [<miri opts>...]
cargo miri [subcommand] [options] [--] [<miri opts>...] [--] [<program opts>...]

Subcommands:
run Run binaries (default)
Expand All @@ -22,8 +22,9 @@ Common options:
--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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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::<Vec<String>>();
args.append(&mut user_args);
// Run this in Miri
true
} else {
false
Expand Down
8 changes: 6 additions & 2 deletions src/bin/miri-rustc-tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use rustc::ty::TyCtxt;
use syntax::ast;
use rustc::hir::def_id::LOCAL_CRATE;

use miri::MiriConfig;

struct MiriCompilerCalls {
default: Box<RustcDefaultCalls>,
/// whether we are building for the host
Expand Down Expand Up @@ -94,9 +96,10 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
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();
}
}
Expand All @@ -106,7 +109,8 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
}
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 {
Expand Down
67 changes: 43 additions & 24 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ use rustc_codegen_utils::codegen_backend::CodegenBackend;
use rustc::hir::def_id::LOCAL_CRATE;
use syntax::ast;

use miri::MiriConfig;

struct MiriCompilerCalls {
default: Box<RustcDefaultCalls>,

/// Whether to enforce the validity invariant.
validate: bool,
miri_config: MiriConfig,
}

impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
Expand Down Expand Up @@ -79,6 +79,8 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
odir: &Option<PathBuf>,
ofile: &Option<PathBuf>,
) -> 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(
Expand All @@ -89,9 +91,9 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
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
}
Expand All @@ -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();
Expand All @@ -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();
}
Expand Down Expand Up @@ -188,34 +190,51 @@ fn find_sysroot() -> String {

fn main() {
init_early_loggers();
let mut args: Vec<String> = 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);
Expand Down
57 changes: 42 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

// 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);
Expand Down Expand Up @@ -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
{
Expand All @@ -130,26 +137,46 @@ 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::<Pointer<Borrow>>::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()?);
}
// Store cmdline as UTF-16 for Windows GetCommandLineW
{
let tcx = &{ecx.tcx.tcx};
let cmd_utf16: Vec<u16> = CMD.encode_utf16().collect();
let cmd_utf16: Vec<u16> = 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(),
Expand Down Expand Up @@ -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 = (|| {
Expand Down
9 changes: 9 additions & 0 deletions test-cargo-miri/run-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 3 additions & 1 deletion test-cargo-miri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ fn main() {
let n = <BigEndian as ByteOrder>::read_u32(buf);
assert_eq!(n, 0x01020304);
println!("{:#010x}", n);
eprintln!("standard error");
for arg in std::env::args() {
eprintln!("{}", arg);
}
}

#[cfg(test)]
Expand Down
Loading