From 67d3779b0ce5a1f2e735e4497af5321a9000105a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 27 Jun 2019 23:59:00 +0200 Subject: [PATCH] move most of the stuff from lib.rs into machine.rs, and initialization + main loop into eval.rs --- src/eval.rs | 248 +++++++++++++++++++++ src/helpers.rs | 2 +- src/lib.rs | 578 +----------------------------------------------- src/machine.rs | 384 ++++++++++++++++++++++++++++++++ src/memory.rs | 51 ----- src/operator.rs | 2 +- 6 files changed, 642 insertions(+), 623 deletions(-) create mode 100644 src/eval.rs create mode 100644 src/machine.rs delete mode 100644 src/memory.rs diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 00000000000..5f7d85b7445 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,248 @@ +use rand::rngs::StdRng; +use rand::SeedableRng; + +use syntax::source_map::DUMMY_SP; +use rustc::ty::{self, TyCtxt}; +use rustc::ty::layout::{LayoutOf, Size, Align}; +use rustc::hir::def_id::DefId; +use rustc::mir; + +use crate::{ + InterpResult, InterpError, InterpretCx, StackPopCleanup, struct_error, + Scalar, Tag, Pointer, + MiriMemoryKind, Evaluator, TlsEvalContextExt, +}; + +/// Configuration needed to spawn a Miri instance. +#[derive(Clone)] +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. +pub fn create_ecx<'mir, 'tcx: 'mir>( + tcx: TyCtxt<'tcx>, + main_id: DefId, + config: MiriConfig, +) -> InterpResult<'tcx, InterpretCx<'mir, 'tcx, Evaluator<'tcx>>> { + let mut ecx = InterpretCx::new( + tcx.at(syntax::source_map::DUMMY_SP), + ty::ParamEnv::reveal_all(), + Evaluator::new(config.validate), + ); + + // FIXME: InterpretCx::new should take an initial MemoryExtra + ecx.memory_mut().extra.rng = config.seed.map(StdRng::seed_from_u64); + + let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id); + let main_mir = ecx.load_mir(main_instance.def)?; + + if !main_mir.return_ty().is_unit() || main_mir.arg_count != 0 { + return err!(Unimplemented( + "miri does not support main functions without `fn()` type signatures" + .to_owned(), + )); + } + + let start_id = tcx.lang_items().start_fn().unwrap(); + let main_ret_ty = tcx.fn_sig(main_id).output(); + let main_ret_ty = main_ret_ty.no_bound_vars().unwrap(); + let start_instance = ty::Instance::resolve( + ecx.tcx.tcx, + ty::ParamEnv::reveal_all(), + start_id, + ecx.tcx.mk_substs( + ::std::iter::once(ty::subst::Kind::from(main_ret_ty))) + ).unwrap(); + let start_mir = ecx.load_mir(start_instance.def)?; + + if start_mir.arg_count != 3 { + return err!(AbiViolation(format!( + "'start' lang item should have three arguments, but has {}", + start_mir.arg_count + ))); + } + + // Return value (in static memory so that it does not count as leak). + let ret = ecx.layout_of(start_mir.return_ty())?; + let ret_ptr = ecx.allocate(ret, MiriMemoryKind::Static.into()); + + // Push our stack frame. + ecx.push_stack_frame( + start_instance, + // There is no call site. + DUMMY_SP, + start_mir, + Some(ret_ptr.into()), + StackPopCleanup::None { cleanup: true }, + )?; + + let mut args = ecx.frame().body.args_iter(); + + // First argument: pointer to `main()`. + let main_ptr = ecx.memory_mut().create_fn_alloc(main_instance); + let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + ecx.write_scalar(Scalar::Ptr(main_ptr), dest)?; + + // Second argument (argc): `1`. + let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + let argc = Scalar::from_uint(config.args.len() as u128, dest.layout.size); + ecx.write_scalar(argc, dest)?; + // Store argc for macOS's `_NSGetArgc`. + { + let argc_place = ecx.allocate(dest.layout, MiriMemoryKind::Env.into()); + ecx.write_scalar(argc, argc_place.into())?; + ecx.machine.argc = Some(argc_place.ptr.to_ptr()?); + } + + // FIXME: extract main source file path. + // Third argument (`argv`): created from `config.args`. + let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + // 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())); + } + // Don't forget `0` terminator. + cmd.push(std::char::from_u32(0).unwrap()); + // 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(), MiriMemoryKind::Static.into())); + } + // 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 pointer to that place as the argument. + let argv = argvs_place.ptr; + ecx.write_scalar(argv, dest)?; + // Store `argv` for macOS `_NSGetArgv`. + { + 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 command line as UTF-16 for Windows `GetCommandLineW`. + { + let tcx = &{ecx.tcx.tcx}; + 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(), + MiriMemoryKind::Env.into(), + ); + ecx.machine.cmd_line = Some(cmd_ptr); + // Store the UTF-16 string. + let char_size = Size::from_bytes(2); + let cmd_alloc = ecx.memory_mut().get_mut(cmd_ptr.alloc_id)?; + let mut cur_ptr = cmd_ptr; + for &c in cmd_utf16.iter() { + cmd_alloc.write_scalar( + tcx, + cur_ptr, + Scalar::from_uint(c, char_size).into(), + char_size, + )?; + cur_ptr = cur_ptr.offset(char_size, tcx)?; + } + } + + assert!(args.next().is_none(), "start lang item has more arguments than expected"); + + Ok(ecx) +} + +pub fn eval_main<'tcx>( + tcx: TyCtxt<'tcx>, + main_id: DefId, + config: MiriConfig, +) { + let mut ecx = match create_ecx(tcx, main_id, config) { + Ok(ecx) => ecx, + Err(mut err) => { + err.print_backtrace(); + panic!("Miri initialziation error: {}", err.kind) + } + }; + + // Perform the main execution. + let res: InterpResult = (|| { + ecx.run()?; + ecx.run_tls_dtors() + })(); + + // Process the result. + match res { + Ok(()) => { + let leaks = ecx.memory().leak_report(); + // Disable the leak test on some platforms where we do not + // correctly implement TLS destructors. + let target_os = ecx.tcx.tcx.sess.target.target.target_os.to_lowercase(); + let ignore_leaks = target_os == "windows" || target_os == "macos"; + if !ignore_leaks && leaks != 0 { + tcx.sess.err("the evaluated program leaked memory"); + } + } + Err(mut e) => { + // Special treatment for some error kinds + let msg = match e.kind { + InterpError::Exit(code) => std::process::exit(code), + InterpError::NoMirFor(..) => + format!("{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.", e), + _ => e.to_string() + }; + e.print_backtrace(); + if let Some(frame) = ecx.stack().last() { + let block = &frame.body.basic_blocks()[frame.block]; + let span = if frame.stmt < block.statements.len() { + block.statements[frame.stmt].source_info.span + } else { + block.terminator().source_info.span + }; + + let msg = format!("Miri evaluation error: {}", msg); + let mut err = struct_error(ecx.tcx.tcx.at(span), msg.as_str()); + let frames = ecx.generate_stacktrace(None); + err.span_label(span, msg); + // We iterate with indices because we need to look at the next frame (the caller). + for idx in 0..frames.len() { + let frame_info = &frames[idx]; + let call_site_is_local = frames.get(idx+1).map_or(false, + |caller_info| caller_info.instance.def_id().is_local()); + if call_site_is_local { + err.span_note(frame_info.call_site, &frame_info.to_string()); + } else { + err.note(&frame_info.to_string()); + } + } + err.emit(); + } else { + ecx.tcx.sess.err(&msg); + } + + for (i, frame) in ecx.stack().iter().enumerate() { + trace!("-------------------"); + trace!("Frame {}", i); + trace!(" return: {:?}", frame.return_place.map(|p| *p)); + for (i, local) in frame.locals.iter().enumerate() { + trace!(" local {}: {:?}", i, local.value); + } + } + } + } +} diff --git a/src/helpers.rs b/src/helpers.rs index 62a546767e8..f65eef557c9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,6 +1,6 @@ use std::mem; -use rustc::ty::{self, layout}; +use rustc::ty::{self, layout::{self, Size}}; use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX}; use crate::*; diff --git a/src/lib.rs b/src/lib.rs index 6b1ada69d39..ab9e9854c34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,39 +21,24 @@ mod mono_hash_map; mod stacked_borrows; mod intptrcast; -mod memory; +mod machine; +mod eval; -use std::collections::HashMap; -use std::borrow::Cow; -use std::rc::Rc; - -use rand::rngs::StdRng; -use rand::SeedableRng; - -use rustc::ty::{self, TyCtxt, query::TyCtxtAt}; -use rustc::ty::layout::{LayoutOf, Size, Align}; -use rustc::hir::def_id::DefId; -use rustc::mir; +// Make all those symbols available in the same place as our own. pub use rustc_mir::interpret::*; // Resolve ambiguity. pub use rustc_mir::interpret::{self, AllocMap, PlaceTy}; -use syntax::attr; -use syntax::source_map::DUMMY_SP; -use syntax::symbol::sym; pub use crate::fn_call::EvalContextExt as MissingFnsEvalContextExt; pub use crate::operator::EvalContextExt as OperatorEvalContextExt; pub use crate::intrinsic::EvalContextExt as IntrinsicEvalContextExt; pub use crate::tls::{EvalContextExt as TlsEvalContextExt, TlsData}; -use crate::range_map::RangeMap; -#[allow(unused_imports)] // FIXME: rustc bug, issue . +pub use crate::range_map::RangeMap; pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt}; -use crate::mono_hash_map::MonoHashMap; -pub use crate::stacked_borrows::{EvalContextExt as StackedBorEvalContextExt}; -use crate::memory::AllocExtra; - -// Used by priroda. -pub use crate::stacked_borrows::{Tag, Permission, Stack, Stacks, Item}; +pub use crate::mono_hash_map::MonoHashMap; +pub use crate::stacked_borrows::{EvalContextExt as StackedBorEvalContextExt, Tag, Permission, Stack, Stacks, Item}; +pub use crate::machine::{MemoryExtra, AllocExtra, MiriMemoryKind, Evaluator, MiriEvalContext, MiriEvalContextExt}; +pub use crate::eval::{eval_main, create_ecx, MiriConfig}; /// Insert rustc arguments at the beginning of the argument list that Miri wants to be /// set per default, for maximal validation power. @@ -62,550 +47,3 @@ pub fn miri_default_args() -> &'static [&'static str] { // set, which happens in `bootstrap/bin/rustc.rs` in the rustc sources. &["-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, - - // The seed to use when non-determinism is required (e.g. getrandom()) - pub seed: Option -} - -// Used by priroda. -pub fn create_ecx<'mir, 'tcx: 'mir>( - tcx: TyCtxt<'tcx>, - main_id: DefId, - config: MiriConfig, -) -> InterpResult<'tcx, InterpretCx<'mir, 'tcx, Evaluator<'tcx>>> { - let mut ecx = InterpretCx::new( - tcx.at(syntax::source_map::DUMMY_SP), - ty::ParamEnv::reveal_all(), - Evaluator::new(config.validate), - ); - - // FIXME: InterpretCx::new should take an initial MemoryExtra - ecx.memory_mut().extra.rng = config.seed.map(StdRng::seed_from_u64); - - let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id); - let main_mir = ecx.load_mir(main_instance.def)?; - - if !main_mir.return_ty().is_unit() || main_mir.arg_count != 0 { - return err!(Unimplemented( - "miri does not support main functions without `fn()` type signatures" - .to_owned(), - )); - } - - let start_id = tcx.lang_items().start_fn().unwrap(); - let main_ret_ty = tcx.fn_sig(main_id).output(); - let main_ret_ty = main_ret_ty.no_bound_vars().unwrap(); - let start_instance = ty::Instance::resolve( - ecx.tcx.tcx, - ty::ParamEnv::reveal_all(), - start_id, - ecx.tcx.mk_substs( - ::std::iter::once(ty::subst::Kind::from(main_ret_ty))) - ).unwrap(); - let start_mir = ecx.load_mir(start_instance.def)?; - - if start_mir.arg_count != 3 { - return err!(AbiViolation(format!( - "'start' lang item should have three arguments, but has {}", - start_mir.arg_count - ))); - } - - // Return value (in static memory so that it does not count as leak). - let ret = ecx.layout_of(start_mir.return_ty())?; - let ret_ptr = ecx.allocate(ret, MiriMemoryKind::Static.into()); - - // Push our stack frame. - ecx.push_stack_frame( - start_instance, - // There is no call site. - DUMMY_SP, - start_mir, - Some(ret_ptr.into()), - StackPopCleanup::None { cleanup: true }, - )?; - - let mut args = ecx.frame().body.args_iter(); - - // First argument: pointer to `main()`. - let main_ptr = ecx.memory_mut().create_fn_alloc(main_instance); - let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; - ecx.write_scalar(Scalar::Ptr(main_ptr), dest)?; - - // Second argument (argc): `1`. - let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; - let argc = Scalar::from_uint(config.args.len() as u128, dest.layout.size); - ecx.write_scalar(argc, dest)?; - // Store argc for macOS's `_NSGetArgc`. - { - let argc_place = ecx.allocate(dest.layout, MiriMemoryKind::Env.into()); - ecx.write_scalar(argc, argc_place.into())?; - ecx.machine.argc = Some(argc_place.ptr.to_ptr()?); - } - - // FIXME: extract main source file path. - // Third argument (`argv`): created from `config.args`. - let dest = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; - // 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())); - } - // Don't forget `0` terminator. - cmd.push(std::char::from_u32(0).unwrap()); - // 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(), MiriMemoryKind::Static.into())); - } - // 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 pointer to that place as the argument. - let argv = argvs_place.ptr; - ecx.write_scalar(argv, dest)?; - // Store `argv` for macOS `_NSGetArgv`. - { - 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 command line as UTF-16 for Windows `GetCommandLineW`. - { - let tcx = &{ecx.tcx.tcx}; - 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(), - MiriMemoryKind::Env.into(), - ); - ecx.machine.cmd_line = Some(cmd_ptr); - // Store the UTF-16 string. - let char_size = Size::from_bytes(2); - let cmd_alloc = ecx.memory_mut().get_mut(cmd_ptr.alloc_id)?; - let mut cur_ptr = cmd_ptr; - for &c in cmd_utf16.iter() { - cmd_alloc.write_scalar( - tcx, - cur_ptr, - Scalar::from_uint(c, char_size).into(), - char_size, - )?; - cur_ptr = cur_ptr.offset(char_size, tcx)?; - } - } - - assert!(args.next().is_none(), "start lang item has more arguments than expected"); - - Ok(ecx) -} - -pub fn eval_main<'tcx>( - tcx: TyCtxt<'tcx>, - main_id: DefId, - config: MiriConfig, -) { - let mut ecx = match create_ecx(tcx, main_id, config) { - Ok(ecx) => ecx, - Err(mut err) => { - err.print_backtrace(); - panic!("Miri initialziation error: {}", err.kind) - } - }; - - // Perform the main execution. - let res: InterpResult = (|| { - ecx.run()?; - ecx.run_tls_dtors() - })(); - - // Process the result. - match res { - Ok(()) => { - let leaks = ecx.memory().leak_report(); - // Disable the leak test on some platforms where we do not - // correctly implement TLS destructors. - let target_os = ecx.tcx.tcx.sess.target.target.target_os.to_lowercase(); - let ignore_leaks = target_os == "windows" || target_os == "macos"; - if !ignore_leaks && leaks != 0 { - tcx.sess.err("the evaluated program leaked memory"); - } - } - Err(mut e) => { - // Special treatment for some error kinds - let msg = match e.kind { - InterpError::Exit(code) => std::process::exit(code), - InterpError::NoMirFor(..) => - format!("{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.", e), - _ => e.to_string() - }; - e.print_backtrace(); - if let Some(frame) = ecx.stack().last() { - let block = &frame.body.basic_blocks()[frame.block]; - let span = if frame.stmt < block.statements.len() { - block.statements[frame.stmt].source_info.span - } else { - block.terminator().source_info.span - }; - - let msg = format!("Miri evaluation error: {}", msg); - let mut err = struct_error(ecx.tcx.tcx.at(span), msg.as_str()); - let frames = ecx.generate_stacktrace(None); - err.span_label(span, msg); - // We iterate with indices because we need to look at the next frame (the caller). - for idx in 0..frames.len() { - let frame_info = &frames[idx]; - let call_site_is_local = frames.get(idx+1).map_or(false, - |caller_info| caller_info.instance.def_id().is_local()); - if call_site_is_local { - err.span_note(frame_info.call_site, &frame_info.to_string()); - } else { - err.note(&frame_info.to_string()); - } - } - err.emit(); - } else { - ecx.tcx.sess.err(&msg); - } - - for (i, frame) in ecx.stack().iter().enumerate() { - trace!("-------------------"); - trace!("Frame {}", i); - trace!(" return: {:?}", frame.return_place.map(|p| *p)); - for (i, local) in frame.locals.iter().enumerate() { - trace!(" local {}: {:?}", i, local.value); - } - } - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum MiriMemoryKind { - /// `__rust_alloc` memory. - Rust, - /// `malloc` memory. - C, - /// Part of env var emulation. - Env, - /// Statics. - Static, -} - -impl Into> for MiriMemoryKind { - #[inline(always)] - fn into(self) -> MemoryKind { - MemoryKind::Machine(self) - } -} - -impl MayLeak for MiriMemoryKind { - #[inline(always)] - fn may_leak(self) -> bool { - use self::MiriMemoryKind::*; - match self { - Rust | C => false, - Env | Static => true, - } - } -} - -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>, - - /// Program arguments (`Option` because we can only initialize them after creating the ecx). - /// These are *pointers* to argc/argv because macOS. - /// We also need the full command line as one string because of Windows. - pub(crate) argc: Option>, - pub(crate) argv: Option>, - pub(crate) cmd_line: Option>, - - /// Last OS error. - pub(crate) last_error: u32, - - /// TLS state. - pub(crate) tls: TlsData<'tcx>, - - /// Whether to enforce the validity invariant. - pub(crate) validate: bool, -} - -impl<'tcx> Evaluator<'tcx> { - fn new(validate: bool) -> Self { - Evaluator { - env_vars: HashMap::default(), - argc: None, - argv: None, - cmd_line: None, - last_error: 0, - tls: TlsData::default(), - validate, - } - } -} - -// FIXME: rustc issue . -#[allow(dead_code)] -type MiriEvalContext<'mir, 'tcx> = InterpretCx<'mir, 'tcx, Evaluator<'tcx>>; - -// A little trait that's useful to be inherited by extension traits. -pub trait MiriEvalContextExt<'mir, 'tcx> { - fn eval_context_ref(&self) -> &MiriEvalContext<'mir, 'tcx>; - fn eval_context_mut(&mut self) -> &mut MiriEvalContext<'mir, 'tcx>; -} -impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx> { - #[inline(always)] - fn eval_context_ref(&self) -> &MiriEvalContext<'mir, 'tcx> { - self - } - #[inline(always)] - fn eval_context_mut(&mut self) -> &mut MiriEvalContext<'mir, 'tcx> { - self - } -} - -impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> { - type MemoryKinds = MiriMemoryKind; - - type FrameExtra = stacked_borrows::CallId; - type MemoryExtra = memory::MemoryExtra; - type AllocExtra = memory::AllocExtra; - type PointerTag = Tag; - - type MemoryMap = MonoHashMap, Allocation)>; - - const STATIC_KIND: Option = Some(MiriMemoryKind::Static); - - #[inline(always)] - fn enforce_validity(ecx: &InterpretCx<'mir, 'tcx, Self>) -> bool { - ecx.machine.validate - } - - /// Returns `Ok()` when the function was handled; fail otherwise. - #[inline(always)] - fn find_fn( - ecx: &mut InterpretCx<'mir, 'tcx, Self>, - instance: ty::Instance<'tcx>, - args: &[OpTy<'tcx, Tag>], - dest: Option>, - ret: Option, - ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> { - ecx.find_fn(instance, args, dest, ret) - } - - #[inline(always)] - fn call_intrinsic( - ecx: &mut rustc_mir::interpret::InterpretCx<'mir, 'tcx, Self>, - instance: ty::Instance<'tcx>, - args: &[OpTy<'tcx, Tag>], - dest: PlaceTy<'tcx, Tag>, - ) -> InterpResult<'tcx> { - ecx.call_intrinsic(instance, args, dest) - } - - #[inline(always)] - fn ptr_op( - ecx: &rustc_mir::interpret::InterpretCx<'mir, 'tcx, Self>, - bin_op: mir::BinOp, - left: ImmTy<'tcx, Tag>, - right: ImmTy<'tcx, Tag>, - ) -> InterpResult<'tcx, (Scalar, bool)> { - ecx.ptr_op(bin_op, left, right) - } - - fn box_alloc( - ecx: &mut InterpretCx<'mir, 'tcx, Self>, - dest: PlaceTy<'tcx, Tag>, - ) -> InterpResult<'tcx> { - trace!("box_alloc for {:?}", dest.layout.ty); - // Call the `exchange_malloc` lang item. - let malloc = ecx.tcx.lang_items().exchange_malloc_fn().unwrap(); - let malloc = ty::Instance::mono(ecx.tcx.tcx, malloc); - let malloc_mir = ecx.load_mir(malloc.def)?; - ecx.push_stack_frame( - malloc, - malloc_mir.span, - malloc_mir, - Some(dest), - // Don't do anything when we are done. The `statement()` function will increment - // the old stack frame's stmt counter to the next statement, which means that when - // `exchange_malloc` returns, we go on evaluating exactly where we want to be. - StackPopCleanup::None { cleanup: true }, - )?; - - let mut args = ecx.frame().body.args_iter(); - let layout = ecx.layout_of(dest.layout.ty.builtin_deref(false).unwrap().ty)?; - - // First argument: `size`. - // (`0` is allowed here -- this is expected to be handled by the lang item). - let arg = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; - let size = layout.size.bytes(); - ecx.write_scalar(Scalar::from_uint(size, arg.layout.size), arg)?; - - // Second argument: `align`. - let arg = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; - let align = layout.align.abi.bytes(); - ecx.write_scalar(Scalar::from_uint(align, arg.layout.size), arg)?; - - // No more arguments. - assert!( - args.next().is_none(), - "`exchange_malloc` lang item has more arguments than expected" - ); - Ok(()) - } - - fn find_foreign_static( - def_id: DefId, - tcx: TyCtxtAt<'tcx>, - ) -> InterpResult<'tcx, Cow<'tcx, Allocation>> { - let attrs = tcx.get_attrs(def_id); - let link_name = match attr::first_attr_value_str_by_name(&attrs, sym::link_name) { - Some(name) => name.as_str(), - None => tcx.item_name(def_id).as_str(), - }; - - let alloc = match link_name.get() { - "__cxa_thread_atexit_impl" => { - // This should be all-zero, pointer-sized. - let size = tcx.data_layout.pointer_size; - let data = vec![0; size.bytes() as usize]; - Allocation::from_bytes(&data, tcx.data_layout.pointer_align.abi) - } - _ => return err!(Unimplemented( - format!("can't access foreign static: {}", link_name), - )), - }; - Ok(Cow::Owned(alloc)) - } - - #[inline(always)] - fn before_terminator(_ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> - { - // We are not interested in detecting loops. - Ok(()) - } - - fn tag_allocation<'b>( - id: AllocId, - alloc: Cow<'b, Allocation>, - kind: Option>, - memory: &Memory<'mir, 'tcx, Self>, - ) -> (Cow<'b, Allocation>, Self::PointerTag) { - let kind = kind.expect("we set our STATIC_KIND so this cannot be None"); - let alloc = alloc.into_owned(); - let (stacks, base_tag) = Stacks::new_allocation( - id, - Size::from_bytes(alloc.bytes.len() as u64), - Rc::clone(&memory.extra.stacked_borrows), - kind, - ); - if kind != MiriMemoryKind::Static.into() { - assert!(alloc.relocations.is_empty(), "Only statics can come initialized with inner pointers"); - // Now we can rely on the inner pointers being static, too. - } - let mut memory_extra = memory.extra.stacked_borrows.borrow_mut(); - let alloc: Allocation = Allocation { - bytes: alloc.bytes, - relocations: Relocations::from_presorted( - alloc.relocations.iter() - // The allocations in the relocations (pointers stored *inside* this allocation) - // all get the base pointer tag. - .map(|&(offset, ((), alloc))| (offset, (memory_extra.static_base_ptr(alloc), alloc))) - .collect() - ), - undef_mask: alloc.undef_mask, - align: alloc.align, - mutability: alloc.mutability, - extra: AllocExtra { - stacked_borrows: stacks, - intptrcast: Default::default(), - }, - }; - (Cow::Owned(alloc), base_tag) - } - - #[inline(always)] - fn tag_static_base_pointer( - id: AllocId, - memory: &Memory<'mir, 'tcx, Self>, - ) -> Self::PointerTag { - memory.extra.stacked_borrows.borrow_mut().static_base_ptr(id) - } - - #[inline(always)] - fn retag( - ecx: &mut InterpretCx<'mir, 'tcx, Self>, - kind: mir::RetagKind, - place: PlaceTy<'tcx, Tag>, - ) -> InterpResult<'tcx> { - if !ecx.tcx.sess.opts.debugging_opts.mir_emit_retag || !Self::enforce_validity(ecx) { - // No tracking, or no retagging. The latter is possible because a dependency of ours - // might be called with different flags than we are, so there are `Retag` - // statements but we do not want to execute them. - // Also, honor the whitelist in `enforce_validity` because otherwise we might retag - // uninitialized data. - Ok(()) - } else { - ecx.retag(kind, place) - } - } - - #[inline(always)] - fn stack_push( - ecx: &mut InterpretCx<'mir, 'tcx, Self>, - ) -> InterpResult<'tcx, stacked_borrows::CallId> { - Ok(ecx.memory().extra.stacked_borrows.borrow_mut().new_call()) - } - - #[inline(always)] - fn stack_pop( - ecx: &mut InterpretCx<'mir, 'tcx, Self>, - extra: stacked_borrows::CallId, - ) -> InterpResult<'tcx> { - Ok(ecx.memory().extra.stacked_borrows.borrow_mut().end_call(extra)) - } - - fn int_to_ptr( - int: u64, - memory: &Memory<'mir, 'tcx, Self>, - ) -> InterpResult<'tcx, Pointer> { - if int == 0 { - err!(InvalidNullPointerUsage) - } else if memory.extra.rng.is_none() { - err!(ReadBytesAsPointer) - } else { - intptrcast::GlobalState::int_to_ptr(int, memory) - } - } - - fn ptr_to_int( - ptr: Pointer, - memory: &Memory<'mir, 'tcx, Self>, - ) -> InterpResult<'tcx, u64> { - if memory.extra.rng.is_none() { - err!(ReadPointerAsBytes) - } else { - intptrcast::GlobalState::ptr_to_int(ptr, memory) - } - } -} diff --git a/src/machine.rs b/src/machine.rs new file mode 100644 index 00000000000..eb177fa2a18 --- /dev/null +++ b/src/machine.rs @@ -0,0 +1,384 @@ +use std::rc::Rc; +use std::borrow::Cow; +use std::collections::HashMap; + +use rand::rngs::StdRng; + +use syntax::attr; +use syntax::symbol::sym; +use rustc::hir::def_id::DefId; +use rustc::ty::{self, layout::{Size, LayoutOf}, query::TyCtxtAt}; +use rustc::mir; + +use crate::*; + +/// Extra memory kinds +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MiriMemoryKind { + /// `__rust_alloc` memory. + Rust, + /// `malloc` memory. + C, + /// Part of env var emulation. + Env, + /// Statics. + Static, +} + +impl Into> for MiriMemoryKind { + #[inline(always)] + fn into(self) -> MemoryKind { + MemoryKind::Machine(self) + } +} + +/// Extra per-allocation data +#[derive(Debug, Clone)] +pub struct AllocExtra { + pub stacked_borrows: stacked_borrows::AllocExtra, + pub intptrcast: intptrcast::AllocExtra, +} + +/// Extra global memory data +#[derive(Default, Clone, Debug)] +pub struct MemoryExtra { + pub stacked_borrows: stacked_borrows::MemoryExtra, + pub intptrcast: intptrcast::MemoryExtra, + /// The random number generator to use if Miri is running in non-deterministic mode and to + /// enable intptrcast + pub(crate) rng: Option +} + +impl MemoryExtra { + pub fn with_rng(rng: Option) -> Self { + MemoryExtra { + stacked_borrows: Default::default(), + intptrcast: Default::default(), + rng, + } + } +} + +/// The machine itself. +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>, + + /// Program arguments (`Option` because we can only initialize them after creating the ecx). + /// These are *pointers* to argc/argv because macOS. + /// We also need the full command line as one string because of Windows. + pub(crate) argc: Option>, + pub(crate) argv: Option>, + pub(crate) cmd_line: Option>, + + /// Last OS error. + pub(crate) last_error: u32, + + /// TLS state. + pub(crate) tls: TlsData<'tcx>, + + /// Whether to enforce the validity invariant. + pub(crate) validate: bool, +} + +impl<'tcx> Evaluator<'tcx> { + pub(crate) fn new(validate: bool) -> Self { + Evaluator { + env_vars: HashMap::default(), + argc: None, + argv: None, + cmd_line: None, + last_error: 0, + tls: TlsData::default(), + validate, + } + } +} + +/// A rustc InterpretCx for Miri. +pub type MiriEvalContext<'mir, 'tcx> = InterpretCx<'mir, 'tcx, Evaluator<'tcx>>; + +/// A little trait that's useful to be inherited by extension traits. +pub trait MiriEvalContextExt<'mir, 'tcx> { + fn eval_context_ref(&self) -> &MiriEvalContext<'mir, 'tcx>; + fn eval_context_mut(&mut self) -> &mut MiriEvalContext<'mir, 'tcx>; +} +impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx> { + #[inline(always)] + fn eval_context_ref(&self) -> &MiriEvalContext<'mir, 'tcx> { + self + } + #[inline(always)] + fn eval_context_mut(&mut self) -> &mut MiriEvalContext<'mir, 'tcx> { + self + } +} + +/// Machine hook implementations. +impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> { + type MemoryKinds = MiriMemoryKind; + + type FrameExtra = stacked_borrows::CallId; + type MemoryExtra = MemoryExtra; + type AllocExtra = AllocExtra; + type PointerTag = Tag; + + type MemoryMap = MonoHashMap, Allocation)>; + + const STATIC_KIND: Option = Some(MiriMemoryKind::Static); + + #[inline(always)] + fn enforce_validity(ecx: &InterpretCx<'mir, 'tcx, Self>) -> bool { + ecx.machine.validate + } + + /// Returns `Ok()` when the function was handled; fail otherwise. + #[inline(always)] + fn find_fn( + ecx: &mut InterpretCx<'mir, 'tcx, Self>, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx, Tag>], + dest: Option>, + ret: Option, + ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> { + ecx.find_fn(instance, args, dest, ret) + } + + #[inline(always)] + fn call_intrinsic( + ecx: &mut rustc_mir::interpret::InterpretCx<'mir, 'tcx, Self>, + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx, Tag>], + dest: PlaceTy<'tcx, Tag>, + ) -> InterpResult<'tcx> { + ecx.call_intrinsic(instance, args, dest) + } + + #[inline(always)] + fn ptr_op( + ecx: &rustc_mir::interpret::InterpretCx<'mir, 'tcx, Self>, + bin_op: mir::BinOp, + left: ImmTy<'tcx, Tag>, + right: ImmTy<'tcx, Tag>, + ) -> InterpResult<'tcx, (Scalar, bool)> { + ecx.ptr_op(bin_op, left, right) + } + + fn box_alloc( + ecx: &mut InterpretCx<'mir, 'tcx, Self>, + dest: PlaceTy<'tcx, Tag>, + ) -> InterpResult<'tcx> { + trace!("box_alloc for {:?}", dest.layout.ty); + // Call the `exchange_malloc` lang item. + let malloc = ecx.tcx.lang_items().exchange_malloc_fn().unwrap(); + let malloc = ty::Instance::mono(ecx.tcx.tcx, malloc); + let malloc_mir = ecx.load_mir(malloc.def)?; + ecx.push_stack_frame( + malloc, + malloc_mir.span, + malloc_mir, + Some(dest), + // Don't do anything when we are done. The `statement()` function will increment + // the old stack frame's stmt counter to the next statement, which means that when + // `exchange_malloc` returns, we go on evaluating exactly where we want to be. + StackPopCleanup::None { cleanup: true }, + )?; + + let mut args = ecx.frame().body.args_iter(); + let layout = ecx.layout_of(dest.layout.ty.builtin_deref(false).unwrap().ty)?; + + // First argument: `size`. + // (`0` is allowed here -- this is expected to be handled by the lang item). + let arg = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + let size = layout.size.bytes(); + ecx.write_scalar(Scalar::from_uint(size, arg.layout.size), arg)?; + + // Second argument: `align`. + let arg = ecx.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + let align = layout.align.abi.bytes(); + ecx.write_scalar(Scalar::from_uint(align, arg.layout.size), arg)?; + + // No more arguments. + assert!( + args.next().is_none(), + "`exchange_malloc` lang item has more arguments than expected" + ); + Ok(()) + } + + fn find_foreign_static( + def_id: DefId, + tcx: TyCtxtAt<'tcx>, + ) -> InterpResult<'tcx, Cow<'tcx, Allocation>> { + let attrs = tcx.get_attrs(def_id); + let link_name = match attr::first_attr_value_str_by_name(&attrs, sym::link_name) { + Some(name) => name.as_str(), + None => tcx.item_name(def_id).as_str(), + }; + + let alloc = match link_name.get() { + "__cxa_thread_atexit_impl" => { + // This should be all-zero, pointer-sized. + let size = tcx.data_layout.pointer_size; + let data = vec![0; size.bytes() as usize]; + Allocation::from_bytes(&data, tcx.data_layout.pointer_align.abi) + } + _ => return err!(Unimplemented( + format!("can't access foreign static: {}", link_name), + )), + }; + Ok(Cow::Owned(alloc)) + } + + #[inline(always)] + fn before_terminator(_ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> + { + // We are not interested in detecting loops. + Ok(()) + } + + fn tag_allocation<'b>( + id: AllocId, + alloc: Cow<'b, Allocation>, + kind: Option>, + memory: &Memory<'mir, 'tcx, Self>, + ) -> (Cow<'b, Allocation>, Self::PointerTag) { + let kind = kind.expect("we set our STATIC_KIND so this cannot be None"); + let alloc = alloc.into_owned(); + let (stacks, base_tag) = Stacks::new_allocation( + id, + Size::from_bytes(alloc.bytes.len() as u64), + Rc::clone(&memory.extra.stacked_borrows), + kind, + ); + if kind != MiriMemoryKind::Static.into() { + assert!(alloc.relocations.is_empty(), "Only statics can come initialized with inner pointers"); + // Now we can rely on the inner pointers being static, too. + } + let mut memory_extra = memory.extra.stacked_borrows.borrow_mut(); + let alloc: Allocation = Allocation { + bytes: alloc.bytes, + relocations: Relocations::from_presorted( + alloc.relocations.iter() + // The allocations in the relocations (pointers stored *inside* this allocation) + // all get the base pointer tag. + .map(|&(offset, ((), alloc))| (offset, (memory_extra.static_base_ptr(alloc), alloc))) + .collect() + ), + undef_mask: alloc.undef_mask, + align: alloc.align, + mutability: alloc.mutability, + extra: AllocExtra { + stacked_borrows: stacks, + intptrcast: Default::default(), + }, + }; + (Cow::Owned(alloc), base_tag) + } + + #[inline(always)] + fn tag_static_base_pointer( + id: AllocId, + memory: &Memory<'mir, 'tcx, Self>, + ) -> Self::PointerTag { + memory.extra.stacked_borrows.borrow_mut().static_base_ptr(id) + } + + #[inline(always)] + fn retag( + ecx: &mut InterpretCx<'mir, 'tcx, Self>, + kind: mir::RetagKind, + place: PlaceTy<'tcx, Tag>, + ) -> InterpResult<'tcx> { + if !ecx.tcx.sess.opts.debugging_opts.mir_emit_retag || !Self::enforce_validity(ecx) { + // No tracking, or no retagging. The latter is possible because a dependency of ours + // might be called with different flags than we are, so there are `Retag` + // statements but we do not want to execute them. + // Also, honor the whitelist in `enforce_validity` because otherwise we might retag + // uninitialized data. + Ok(()) + } else { + ecx.retag(kind, place) + } + } + + #[inline(always)] + fn stack_push( + ecx: &mut InterpretCx<'mir, 'tcx, Self>, + ) -> InterpResult<'tcx, stacked_borrows::CallId> { + Ok(ecx.memory().extra.stacked_borrows.borrow_mut().new_call()) + } + + #[inline(always)] + fn stack_pop( + ecx: &mut InterpretCx<'mir, 'tcx, Self>, + extra: stacked_borrows::CallId, + ) -> InterpResult<'tcx> { + Ok(ecx.memory().extra.stacked_borrows.borrow_mut().end_call(extra)) + } + + fn int_to_ptr( + int: u64, + memory: &Memory<'mir, 'tcx, Self>, + ) -> InterpResult<'tcx, Pointer> { + if int == 0 { + err!(InvalidNullPointerUsage) + } else if memory.extra.rng.is_none() { + err!(ReadBytesAsPointer) + } else { + intptrcast::GlobalState::int_to_ptr(int, memory) + } + } + + fn ptr_to_int( + ptr: Pointer, + memory: &Memory<'mir, 'tcx, Self>, + ) -> InterpResult<'tcx, u64> { + if memory.extra.rng.is_none() { + err!(ReadPointerAsBytes) + } else { + intptrcast::GlobalState::ptr_to_int(ptr, memory) + } + } +} + +impl AllocationExtra for AllocExtra { + #[inline(always)] + fn memory_read<'tcx>( + alloc: &Allocation, + ptr: Pointer, + size: Size, + ) -> InterpResult<'tcx> { + alloc.extra.stacked_borrows.memory_read(ptr, size) + } + + #[inline(always)] + fn memory_written<'tcx>( + alloc: &mut Allocation, + ptr: Pointer, + size: Size, + ) -> InterpResult<'tcx> { + alloc.extra.stacked_borrows.memory_written(ptr, size) + } + + #[inline(always)] + fn memory_deallocated<'tcx>( + alloc: &mut Allocation, + ptr: Pointer, + size: Size, + ) -> InterpResult<'tcx> { + alloc.extra.stacked_borrows.memory_deallocated(ptr, size) + } +} + +impl MayLeak for MiriMemoryKind { + #[inline(always)] + fn may_leak(self) -> bool { + use self::MiriMemoryKind::*; + match self { + Rust | C => false, + Env | Static => true, + } + } +} diff --git a/src/memory.rs b/src/memory.rs deleted file mode 100644 index ea8f01a808c..00000000000 --- a/src/memory.rs +++ /dev/null @@ -1,51 +0,0 @@ -use rand::rngs::StdRng; - -use rustc_mir::interpret::{Pointer, Allocation, AllocationExtra, InterpResult}; -use rustc_target::abi::Size; - -use crate::{stacked_borrows, intptrcast}; -use crate::stacked_borrows::Tag; - -#[derive(Default, Clone, Debug)] -pub struct MemoryExtra { - pub stacked_borrows: stacked_borrows::MemoryExtra, - pub intptrcast: intptrcast::MemoryExtra, - /// The random number generator to use if Miri is running in non-deterministic mode and to - /// enable intptrcast - pub(crate) rng: Option -} - -#[derive(Debug, Clone)] -pub struct AllocExtra { - pub stacked_borrows: stacked_borrows::AllocExtra, - pub intptrcast: intptrcast::AllocExtra, -} - -impl AllocationExtra for AllocExtra { - #[inline(always)] - fn memory_read<'tcx>( - alloc: &Allocation, - ptr: Pointer, - size: Size, - ) -> InterpResult<'tcx> { - alloc.extra.stacked_borrows.memory_read(ptr, size) - } - - #[inline(always)] - fn memory_written<'tcx>( - alloc: &mut Allocation, - ptr: Pointer, - size: Size, - ) -> InterpResult<'tcx> { - alloc.extra.stacked_borrows.memory_written(ptr, size) - } - - #[inline(always)] - fn memory_deallocated<'tcx>( - alloc: &mut Allocation, - ptr: Pointer, - size: Size, - ) -> InterpResult<'tcx> { - alloc.extra.stacked_borrows.memory_deallocated(ptr, size) - } -} diff --git a/src/operator.rs b/src/operator.rs index 572794a44ad..0e25de7da5a 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -1,4 +1,4 @@ -use rustc::ty::Ty; +use rustc::ty::{Ty, layout::{Size, LayoutOf}}; use rustc::mir; use crate::*; -- 2.44.0