Auto merge of #894 - christianpoveda:env-vars-communication, r=RalfJung

Enable env communication

related issue: https://github.com/rust-lang/miri/issues/800. r? @RalfJung
This commit is contained in:
bors 2019-08-14 18:58:06 +00:00
commit 1f504ea7be
13 changed files with 124 additions and 34 deletions

View file

@ -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.

View file

@ -25,7 +25,12 @@ impl rustc_driver::Callbacks for MiriCompilerCalls<'_> {
);
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);
});
});

View file

@ -48,7 +48,12 @@ 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(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 @@ 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![], 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();

View file

@ -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<u64> = 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);

View file

@ -12,16 +12,19 @@ use crate::{
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<String>,
// The seed to use when non-determinism is required (e.g. getrandom())
pub seed: Option<u64>
/// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
pub seed: Option<u64>,
}
// 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)

View file

@ -33,6 +33,7 @@ pub use crate::shims::foreign_items::EvalContextExt as ForeignItemsEvalContextEx
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};

View file

@ -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 @@ impl MemoryExtra {
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<Vec<u8>, Pointer<Tag>>,
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,
}
}
}

60
src/shims/env.rs Normal file
View file

@ -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<Vec<u8>, Pointer<Tag>>,
}
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<Tag>> {
self.map.get(name)
}
pub(crate) fn unset(&mut self, name: &[u8]) -> Option<Pointer<Tag>> {
self.map.remove(name)
}
pub(crate) fn set(&mut self, name: Vec<u8>, ptr: Pointer<Tag>) -> Option<Pointer<Tag>>{
self.map.insert(name, ptr)
}
}
pub(crate) fn alloc_env_value<'mir, 'tcx>(
bytes: &[u8],
memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>,
) -> Pointer<Tag> {
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
}

View file

@ -8,6 +8,7 @@ use syntax::attr;
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 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
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 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
}
}
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)?;

View file

@ -2,6 +2,7 @@ pub mod foreign_items;
pub mod intrinsics;
pub mod tls;
pub mod dlsym;
pub mod env;
use rustc::{ty, mir};

View file

@ -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);

View file

@ -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()));
}

View file

@ -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());
}