diff --git a/README.md b/README.md index 1e8c53e79826..f34569d865a8 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,9 @@ Several `-Z` flags are relevant for Miri: is enforced by default. This is mostly useful for debugging; it means Miri will miss bugs in your program. However, this can also help to make Miri run faster. +* `-Zmiri-enable-communication` enables communication between the host + environment and Miri, i.e., all the host environment variables are available + during Miri runtime. * `-Zmir-opt-level` controls how many MIR optimizations are performed. Miri overrides the default to be `0`; be advised that using any higher level can make Miri miss bugs in your program because they got optimized away. diff --git a/benches/helpers/miri_helper.rs b/benches/helpers/miri_helper.rs index 203a8b1133a5..5cb938659a6c 100644 --- a/benches/helpers/miri_helper.rs +++ b/benches/helpers/miri_helper.rs @@ -25,7 +25,12 @@ fn after_analysis(&mut self, compiler: &interface::Compiler) -> Compilation { ); self.bencher.iter(|| { - let config = miri::MiriConfig { validate: true, args: vec![], seed: None }; + let config = miri::MiriConfig { + validate: true, + communicate: false, + 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 dae5189937a3..9ef64c38638b 100644 --- a/src/bin/miri-rustc-tests.rs +++ b/src/bin/miri-rustc-tests.rs @@ -48,7 +48,12 @@ impl<'tcx, 'hir> itemlikevisit::ItemLikeVisitor<'hir> for Visitor<'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.check_name(syntax::symbol::sym::test)) { - let config = MiriConfig { validate: true, args: vec![], seed: None }; + let config = MiriConfig { + validate: true, + communicate: false, + 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 +66,12 @@ fn visit_impl_item(&mut self, _impl_item: &'hir hir::ImplItem) {} } 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![], seed: None }; + let config = MiriConfig { + validate: true, + communicate: false, + 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 29076828d814..1aa89fc5a610 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -130,6 +130,7 @@ fn main() { // Parse our arguments and split them across `rustc` and `miri`. let mut validate = true; + let mut communicate = false; let mut seed: Option = None; let mut rustc_args = vec![]; let mut miri_args = vec![]; @@ -147,6 +148,9 @@ fn main() { "-Zmiri-disable-validation" => { validate = false; }, + "-Zmiri-enable-communication" => { + communicate = true; + }, "--" => { after_dashdash = true; } @@ -196,7 +200,7 @@ fn main() { debug!("rustc arguments: {:?}", rustc_args); debug!("miri arguments: {:?}", miri_args); - let miri_config = miri::MiriConfig { validate, args: miri_args, seed }; + let miri_config = miri::MiriConfig { validate, communicate, 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/eval.rs b/src/eval.rs index bc9d97b0280f..0970edb2b75f 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -12,16 +12,19 @@ InterpResult, InterpError, InterpCx, StackPopCleanup, struct_error, Scalar, Tag, Pointer, FnVal, MemoryExtra, MiriMemoryKind, Evaluator, TlsEvalContextExt, HelpersEvalContextExt, + EnvVars, }; /// Configuration needed to spawn a Miri instance. #[derive(Clone)] pub struct MiriConfig { + /// Determine if validity checking and Stacked Borrows are enabled. pub validate: bool, + /// Determines if communication with the host environment is enabled. + pub communicate: bool, pub args: Vec, - - // The seed to use when non-determinism is required (e.g. getrandom()) - pub seed: Option + /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`). + pub seed: Option, } // Used by priroda. @@ -33,10 +36,14 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( let mut ecx = InterpCx::new( tcx.at(syntax::source_map::DUMMY_SP), ty::ParamEnv::reveal_all(), - Evaluator::new(), + Evaluator::new(config.communicate), MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate), ); + // Complete initialization. + EnvVars::init(&mut ecx, config.communicate); + + // Setup first stack-frame let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id); let main_mir = ecx.load_mir(main_instance.def)?; @@ -158,7 +165,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( cur_ptr = cur_ptr.offset(char_size, tcx)?; } } - + assert!(args.next().is_none(), "start lang item has more arguments than expected"); Ok(ecx) diff --git a/src/lib.rs b/src/lib.rs index 58f572bf7011..738419c2498b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ pub use crate::shims::intrinsics::EvalContextExt as IntrinsicsEvalContextExt; pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData}; pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt}; +pub use crate::shims::env::EnvVars; pub use crate::operator::EvalContextExt as OperatorEvalContextExt; pub use crate::range_map::RangeMap; pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt}; diff --git a/src/machine.rs b/src/machine.rs index f2c81c92d3d0..b4aac147f94f 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -3,7 +3,6 @@ use std::rc::Rc; use std::borrow::Cow; -use std::collections::HashMap; use std::cell::RefCell; use rand::rngs::StdRng; @@ -79,7 +78,7 @@ pub fn new(rng: StdRng, validate: bool) -> Self { pub struct Evaluator<'tcx> { /// Environment variables set by `setenv`. /// Miri does not expose env vars from the host to the emulated program. - pub(crate) env_vars: HashMap, Pointer>, + pub(crate) env_vars: EnvVars, /// Program arguments (`Option` because we can only initialize them after creating the ecx). /// These are *pointers* to argc/argv because macOS. @@ -93,17 +92,23 @@ pub struct Evaluator<'tcx> { /// TLS state. pub(crate) tls: TlsData<'tcx>, + + /// If enabled, the `env_vars` field is populated with the host env vars during initialization. + pub(crate) communicate: bool, } impl<'tcx> Evaluator<'tcx> { - pub(crate) fn new() -> Self { + pub(crate) fn new(communicate: bool) -> Self { Evaluator { - env_vars: HashMap::default(), + // `env_vars` could be initialized properly here if `Memory` were available before + // calling this method. + env_vars: EnvVars::default(), argc: None, argv: None, cmd_line: None, last_error: 0, tls: TlsData::default(), + communicate, } } } diff --git a/src/shims/env.rs b/src/shims/env.rs new file mode 100644 index 000000000000..05c5fbb04309 --- /dev/null +++ b/src/shims/env.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; + +use rustc::ty::layout::{Size, Align}; +use rustc_mir::interpret::{Pointer, Memory}; +use crate::stacked_borrows::Tag; +use crate::*; + +#[derive(Default)] +pub struct EnvVars { + map: HashMap, Pointer>, +} + +impl EnvVars { + pub(crate) fn init<'mir, 'tcx>( + ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>, + communicate: bool, + ) { + if communicate { + for (name, value) in std::env::vars() { + let value = alloc_env_value(value.as_bytes(), ecx.memory_mut()); + ecx.machine.env_vars.map.insert(name.into_bytes(), value); + } + } + } + + pub(crate) fn get(&self, name: &[u8]) -> Option<&Pointer> { + self.map.get(name) + } + + pub(crate) fn unset(&mut self, name: &[u8]) -> Option> { + self.map.remove(name) + } + + pub(crate) fn set(&mut self, name: Vec, ptr: Pointer) -> Option>{ + self.map.insert(name, ptr) + } +} + +pub(crate) fn alloc_env_value<'mir, 'tcx>( + bytes: &[u8], + memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>, +) -> Pointer { + let tcx = {memory.tcx.tcx}; + let length = bytes.len() as u64; + // `+1` for the null terminator. + let ptr = memory.allocate( + Size::from_bytes(length + 1), + Align::from_bytes(1).unwrap(), + MiriMemoryKind::Env.into(), + ); + // We just allocated these, so the write cannot fail. + let alloc = memory.get_mut(ptr.alloc_id).unwrap(); + alloc.write_bytes(&tcx, ptr, &bytes).unwrap(); + let trailing_zero_ptr = ptr.offset( + Size::from_bytes(length), + &tcx, + ).unwrap(); + alloc.write_bytes(&tcx, trailing_zero_ptr, &[0]).unwrap(); + ptr +} diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 0288773044ac..2d2b917eb275 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -8,6 +8,7 @@ use syntax::symbol::sym; use crate::*; +use crate::shims::env::alloc_env_value; impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { @@ -440,7 +441,7 @@ fn emulate_foreign_item( if !this.is_null(name_ptr)? { let name = this.memory().read_c_str(name_ptr)?.to_owned(); if !name.is_empty() && !name.contains(&b'=') { - success = Some(this.machine.env_vars.remove(&name)); + success = Some(this.machine.env_vars.unset(&name)); } } } @@ -468,26 +469,8 @@ fn emulate_foreign_item( } } if let Some((name, value)) = new { - // `+1` for the null terminator. - let value_copy = this.memory_mut().allocate( - Size::from_bytes((value.len() + 1) as u64), - Align::from_bytes(1).unwrap(), - MiriMemoryKind::Env.into(), - ); - // We just allocated these, so the write cannot fail. - let alloc = this.memory_mut().get_mut(value_copy.alloc_id).unwrap(); - alloc.write_bytes(tcx, value_copy, &value).unwrap(); - let trailing_zero_ptr = value_copy.offset( - Size::from_bytes(value.len() as u64), - tcx, - ).unwrap(); - alloc.write_bytes(tcx, trailing_zero_ptr, &[0]).unwrap(); - - if let Some(var) = this.machine.env_vars.insert( - name.to_owned(), - value_copy, - ) - { + let value_copy = alloc_env_value(&value, this.memory_mut()); + if let Some(var) = this.machine.env_vars.set(name.to_owned(), value_copy) { this.memory_mut().deallocate(var, None, MiriMemoryKind::Env.into())?; } this.write_null(dest)?; diff --git a/src/shims/mod.rs b/src/shims/mod.rs index c06373005ff9..96a1f34152b3 100644 --- a/src/shims/mod.rs +++ b/src/shims/mod.rs @@ -2,6 +2,7 @@ pub mod intrinsics; pub mod tls; pub mod dlsym; +pub mod env; use rustc::{ty, mir}; diff --git a/tests/compiletest.rs b/tests/compiletest.rs index beb30f184af6..d0705b26b7a9 100644 --- a/tests/compiletest.rs +++ b/tests/compiletest.rs @@ -116,6 +116,9 @@ fn compile_fail_miri(opt: bool) { } fn test_runner(_tests: &[&()]) { + // Add a test env var to do environment communication tests + std::env::set_var("MIRI_ENV_VAR_TEST", "0"); + run_pass_miri(false); run_pass_miri(true); diff --git a/tests/run-pass/communication.rs b/tests/run-pass/communication.rs new file mode 100644 index 000000000000..e3fb0c5bd5e0 --- /dev/null +++ b/tests/run-pass/communication.rs @@ -0,0 +1,6 @@ +// ignore-windows: TODO env var emulation stubbed out on Windows +// compile-flags: -Zmiri-enable-communication + +fn main() { + assert_eq!(std::env::var("MIRI_ENV_VAR_TEST"), Ok("0".to_owned())); +} diff --git a/tests/run-pass/env.rs b/tests/run-pass/env.rs index 91e15f249d45..faf947420347 100644 --- a/tests/run-pass/env.rs +++ b/tests/run-pass/env.rs @@ -6,4 +6,6 @@ fn main() { assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent)); env::set_var("MIRI_TEST", "the answer"); assert_eq!(env::var("MIRI_TEST"), Ok("the answer".to_owned())); + // Test that miri environment is isolated when communication is disabled. + assert!(env::var("MIRI_ENV_VAR_TEST").is_err()); }