diff --git a/Cargo.toml b/Cargo.toml index d580d4b8ec..6c77315bea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ shell-escape = "0.1.4" # See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` # for more information. rustc-workspace-hack = "1.0.0" +hex = "0.3.2" +rand = "0.6.5" [build-dependencies] vergen = "3" diff --git a/benches/helpers/miri_helper.rs b/benches/helpers/miri_helper.rs index 01fef16e4a..44a94cd61a 100644 --- a/benches/helpers/miri_helper.rs +++ b/benches/helpers/miri_helper.rs @@ -24,7 +24,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls<'_> { ); self.bencher.iter(|| { - let config = miri::MiriConfig { validate: true, args: vec![] }; + let config = miri::MiriConfig { validate: true, args: vec![], seed: None }; eval_main(tcx, entry_def_id, config); }); }); diff --git a/src/bin/miri-rustc-tests.rs b/src/bin/miri-rustc-tests.rs index e54be4644f..ce2ad1a271 100644 --- a/src/bin/miri-rustc-tests.rs +++ b/src/bin/miri-rustc-tests.rs @@ -48,7 +48,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { 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.check_name("test")) { - let config = MiriConfig { validate: true, args: vec![] }; + let config = MiriConfig { validate: true, args: vec![], seed: None }; 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, config); @@ -61,7 +61,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { } tcx.hir().krate().visit_all_item_likes(&mut Visitor(tcx)); } else if let Some((entry_def_id, _)) = tcx.entry_fn(LOCAL_CRATE) { - let config = MiriConfig { validate: true, args: vec![] }; + let config = MiriConfig { validate: true, args: vec![], seed: None }; miri::eval_main(tcx, entry_def_id, config); compiler.session().abort_if_errors(); diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 4cc0d955d3..6e68f803f8 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -126,6 +126,7 @@ fn main() { // Parse our arguments and split them across `rustc` and `miri`. let mut validate = true; + let mut seed: Option = None; let mut rustc_args = vec![]; let mut miri_args = vec![]; let mut after_dashdash = false; @@ -145,6 +146,20 @@ fn main() { "--" => { after_dashdash = true; } + arg if arg.starts_with("-Zmiri-seed=") => { + if seed.is_some() { + panic!("Cannot specify -Zmiri-seed multiple times!"); + } + let seed_raw = hex::decode(arg.trim_start_matches("-Zmiri-seed=")).unwrap(); + if seed_raw.len() > 8 { + panic!(format!("-Zmiri-seed must be at most 8 bytes, was {}", seed_raw.len())); + } + + let mut bytes = [0; 8]; + bytes[..seed_raw.len()].copy_from_slice(&seed_raw); + seed = Some(u64::from_be_bytes(bytes)); + + }, _ => { rustc_args.push(arg); } @@ -163,7 +178,7 @@ fn main() { debug!("rustc arguments: {:?}", rustc_args); debug!("miri arguments: {:?}", miri_args); - let miri_config = miri::MiriConfig { validate, args: miri_args }; + let miri_config = miri::MiriConfig { validate, args: miri_args, seed }; let result = rustc_driver::report_ices_to_stderr_if_any(move || { rustc_driver::run_compiler(&rustc_args, &mut MiriCompilerCalls { miri_config }, None, None) }).and_then(|result| result); diff --git a/src/fn_call.rs b/src/fn_call.rs index 83eb37708e..3e12c1dd1c 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -4,6 +4,8 @@ use rustc::hir::def_id::DefId; use rustc::mir; use syntax::attr; +use rand::RngCore; + use crate::*; impl<'a, 'mir, 'tcx> EvalContextExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {} @@ -224,16 +226,26 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' } "syscall" => { - // TODO: read `syscall` IDs like `sysconf` IDs and - // figure out some way to actually process some of them. - // + let sys_getrandom = this.eval_path_scalar(&["libc", "SYS_getrandom"])? + .expect("Failed to get libc::SYS_getrandom") + .to_usize(this)?; + // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` - // is called if a `HashMap` is created the regular way. + // is called if a `HashMap` is created the regular way (e.g. HashMap). match this.read_scalar(args[0])?.to_usize(this)? { - 318 | 511 => { - return err!(Unimplemented( - "miri does not support random number generators".to_owned(), - )) + id if id == sys_getrandom => { + let ptr = this.read_scalar(args[1])?.to_ptr()?; + let len = this.read_scalar(args[2])?.to_usize(this)?; + + // The only supported flags are GRND_RANDOM and GRND_NONBLOCK, + // neither of which have any effect on our current PRNG + let _flags = this.read_scalar(args[3])?.to_i32()?; + + let data = gen_random(this, len as usize)?; + this.memory_mut().get_mut(ptr.alloc_id)? + .write_bytes(tcx, ptr, &data)?; + + this.write_scalar(Scalar::from_uint(len, dest.layout.size), dest)?; } id => { return err!(Unimplemented( @@ -499,18 +511,13 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' ]; let mut result = None; for &(path, path_value) in paths { - if let Ok(instance) = this.resolve_path(path) { - let cid = GlobalId { - instance, - promoted: None, - }; - let const_val = this.const_eval_raw(cid)?; - let const_val = this.read_scalar(const_val.into())?; - let value = const_val.to_i32()?; - if value == name { + if let Some(val) = this.eval_path_scalar(path)? { + let val = val.to_i32()?; + if val == name { result = Some(path_value); break; } + } } if let Some(result) = result { @@ -757,6 +764,17 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' "GetCommandLineW" => { this.write_scalar(Scalar::Ptr(this.machine.cmd_line.unwrap()), dest)?; } + // The actual name of 'RtlGenRandom' + "SystemFunction036" => { + let ptr = this.read_scalar(args[0])?.to_ptr()?; + let len = this.read_scalar(args[1])?.to_usize(this)?; + + let data = gen_random(this, len as usize)?; + this.memory_mut().get_mut(ptr.alloc_id)? + .write_bytes(tcx, ptr, &data)?; + + this.write_scalar(Scalar::from_bool(true), dest)?; + } // We can't execute anything else. _ => { @@ -774,4 +792,42 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' fn write_null(&mut self, dest: PlaceTy<'tcx, Borrow>) -> EvalResult<'tcx> { self.eval_context_mut().write_scalar(Scalar::from_int(0, dest.layout.size), dest) } + + /// Evaluates the scalar at the specified path. Returns Some(val) + /// if the path could be resolved, and None otherwise + fn eval_path_scalar(&mut self, path: &[&str]) -> EvalResult<'tcx, Option>> { + let this = self.eval_context_mut(); + if let Ok(instance) = this.resolve_path(path) { + let cid = GlobalId { + instance, + promoted: None, + }; + let const_val = this.const_eval_raw(cid)?; + let const_val = this.read_scalar(const_val.into())?; + return Ok(Some(const_val)); + } + return Ok(None); + } +} + +fn gen_random<'a, 'mir, 'tcx>( + this: &mut MiriEvalContext<'a, 'mir, 'tcx>, + len: usize, +) -> Result, EvalError<'tcx>> { + + match &mut this.machine.rng { + Some(rng) => { + let mut data = vec![0; len]; + rng.fill_bytes(&mut data); + Ok(data) + } + None => { + err!(Unimplemented( + "miri does not support gathering system entropy in deterministic mode! + Use '-Zmiri-seed=' to enable random number generation. + WARNING: Miri does *not* generate cryptographically secure entropy - + do not use Miri to run any program that needs secure random number generation".to_owned(), + )) + } + } } diff --git a/src/lib.rs b/src/lib.rs index be58c6a6a4..d185ec610c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,9 @@ mod stacked_borrows; use std::collections::HashMap; use std::borrow::Cow; +use rand::rngs::StdRng; +use rand::SeedableRng; + use rustc::ty::{self, TyCtxt, query::TyCtxtAt}; use rustc::ty::layout::{LayoutOf, Size, Align}; use rustc::hir::{self, def_id::DefId}; @@ -60,6 +63,9 @@ pub fn miri_default_args() -> &'static [&'static str] { pub struct MiriConfig { pub validate: bool, pub args: Vec, + + // The seed to use when non-determinism is required (e.g. getrandom()) + pub seed: Option } // Used by priroda. @@ -71,7 +77,7 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( let mut ecx = InterpretCx::new( tcx.at(syntax::source_map::DUMMY_SP), ty::ParamEnv::reveal_all(), - Evaluator::new(config.validate), + Evaluator::new(config.validate, config.seed), ); let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id); @@ -326,10 +332,14 @@ pub struct Evaluator<'tcx> { /// Stacked Borrows state. pub(crate) stacked_borrows: stacked_borrows::State, + + /// The random number generator to use if Miri + /// is running in non-deterministic mode + pub(crate) rng: Option } impl<'tcx> Evaluator<'tcx> { - fn new(validate: bool) -> Self { + fn new(validate: bool, seed: Option) -> Self { Evaluator { env_vars: HashMap::default(), argc: None, @@ -339,6 +349,7 @@ impl<'tcx> Evaluator<'tcx> { tls: TlsData::default(), validate, stacked_borrows: stacked_borrows::State::default(), + rng: seed.map(|s| StdRng::seed_from_u64(s)) } } } diff --git a/tests/compile-fail/getrandom.rs b/tests/compile-fail/getrandom.rs new file mode 100644 index 0000000000..4dc3e863aa --- /dev/null +++ b/tests/compile-fail/getrandom.rs @@ -0,0 +1,13 @@ +// ignore-macos +// ignore-windows + +#![feature(rustc_private)] +extern crate libc; + +fn main() { + let mut buf = [0u8; 5]; + unsafe { + libc::syscall(libc::SYS_getrandom, buf.as_mut_ptr() as *mut libc::c_void, 5 as libc::size_t, 0 as libc::c_uint); + //~^ ERROR constant evaluation error: miri does not support gathering system entropy in deterministic mode! + } +} diff --git a/tests/run-pass/hashmap.rs b/tests/run-pass/hashmap.rs index d89a5ab535..b29b681939 100644 --- a/tests/run-pass/hashmap.rs +++ b/tests/run-pass/hashmap.rs @@ -1,8 +1,9 @@ +// compile-flags: -Zmiri-seed=0000000000000000 + use std::collections::{self, HashMap}; -use std::hash::BuildHasherDefault; +use std::hash::{BuildHasherDefault, BuildHasher}; -fn main() { - let mut map : HashMap> = Default::default(); +fn test_map(mut map: HashMap) { map.insert(0, 0); assert_eq!(map.values().fold(0, |x, y| x+y), 0); @@ -22,4 +23,16 @@ fn main() { assert_eq!(map.values().fold(0, |x, y| x+y), num*(num-1)/2); // TODO: Test Entry API, Iterators, ... + +} + +fn main() { + // TODO: Implement random number generation on OS X + if cfg!(not(target_os = "macos")) { + let map_normal: HashMap = HashMap::new(); + test_map(map_normal); + } else { + let map : HashMap> = Default::default(); + test_map(map); + } }