diff --git a/README.md b/README.md index aa8e0315b7..fa3fea79e9 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,29 @@ Then, inside your own project, use `cargo +nightly miri` to run your project, if a bin project, or run `cargo +nightly miri test` to run all tests in your project through miri. +## Running miri with full libstd + +Per default libstd does not contain the MIR of non-polymorphic functions. When +miri hits a call to such a function, execution terminates. To fix this, it is +possible to compile libstd with full MIR: + +```sh +rustup component add rust-src +chmod +x -R ~/.rustup/toolchains/*/lib/rustlib/src/rust/src/jemalloc/include/jemalloc/ +cargo install xargo +cd xargo/ +RUSTFLAGS='-Zalways-encode-mir' xargo build +``` + +Now you can run miri against the libstd compiled by xargo: + +```sh +cargo run --bin miri -- --sysroot ~/.xargo/HOST tests/run-pass/vecs.rs +``` + +Notice that you will have to re-run the last step of the preparations above when +your toolchain changes (e.g., when you update the nightly). + ## Contributing and getting help Check out the issues on this GitHub repository for some ideas. There's lots that diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 3f0a6f778b..3af3bee3c8 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -84,7 +84,7 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) { if i.attrs.iter().any(|attr| attr.name().map_or(false, |n| n == "test")) { let did = self.1.hir.body_owner_def_id(body_id); println!("running test: {}", self.1.hir.def_path(did).to_string(self.1)); - miri::eval_main(self.1, did, self.0); + miri::eval_main(self.1, did, None, self.0); self.2.session.abort_if_errors(); } } @@ -95,7 +95,9 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) { state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(limits, tcx, state)); } else if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() { let entry_def_id = tcx.hir.local_def_id(entry_node_id); - miri::eval_main(tcx, entry_def_id, limits); + let start_wrapper = tcx.lang_items.start_fn().and_then(|start_fn| + if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None }); + miri::eval_main(tcx, entry_def_id, start_wrapper, limits); state.session.abort_if_errors(); } else { diff --git a/src/error.rs b/src/error.rs index fd692ef8b6..7b6542bff9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,9 @@ pub enum EvalError<'tcx> { }, ExecutionTimeLimitReached, StackFrameLimitReached, + OutOfTls, + TlsOutOfBounds, + AbiViolation(String), AlignmentCheckFailed { required: u64, has: u64, @@ -101,6 +104,11 @@ impl<'tcx> Error for EvalError<'tcx> { "reached the configured maximum execution time", EvalError::StackFrameLimitReached => "reached the configured maximum number of stack frames", + EvalError::OutOfTls => + "reached the maximum number of representable TLS keys", + EvalError::TlsOutOfBounds => + "accessed an invalid (unallocated) TLS key", + EvalError::AbiViolation(ref msg) => msg, EvalError::AlignmentCheckFailed{..} => "tried to execute a misaligned read or write", EvalError::CalledClosureAsFunction => diff --git a/src/eval_context.rs b/src/eval_context.rs index 77d9d9b20c..922a36c892 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -126,6 +126,7 @@ impl Default for ResourceLimits { impl<'a, 'tcx> EvalContext<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, limits: ResourceLimits) -> Self { + // Register array drop glue code let source_info = mir::SourceInfo { span: DUMMY_SP, scope: mir::ARGUMENT_VISIBILITY_SCOPE @@ -852,7 +853,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let fn_ptr = self.memory.create_fn_alloc(instance); self.write_value(Value::ByVal(PrimVal::Ptr(fn_ptr)), dest, dest_ty)?; }, - ref other => bug!("reify fn pointer on {:?}", other), + ref other => bug!("closure fn pointer on {:?}", other), }, } } @@ -1557,6 +1558,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } pub(super) fn dump_local(&self, lvalue: Lvalue<'tcx>) { + // Debug output if let Lvalue::Local { frame, local, field } = lvalue { let mut allocs = Vec::new(); let mut msg = format!("{:?}", local); @@ -1676,62 +1678,113 @@ impl<'tcx> Frame<'tcx> { pub fn eval_main<'a, 'tcx: 'a>( tcx: TyCtxt<'a, 'tcx, 'tcx>, - def_id: DefId, + main_id: DefId, + start_wrapper: Option, limits: ResourceLimits, ) { - let mut ecx = EvalContext::new(tcx, limits); - let instance = ty::Instance::mono(tcx, def_id); - let mir = ecx.load_mir(instance.def).expect("main function's MIR not found"); - - if !mir.return_ty.is_nil() || mir.arg_count != 0 { - let msg = "miri does not support main functions without `fn()` type signatures"; - tcx.sess.err(&EvalError::Unimplemented(String::from(msg)).to_string()); - return; - } - - ecx.push_stack_frame( - instance, - DUMMY_SP, - mir, - Lvalue::from_ptr(Pointer::zst_ptr()), - StackPopCleanup::None, - ).expect("could not allocate first stack frame"); - - loop { - match ecx.step() { - Ok(true) => {} - Ok(false) => { - let leaks = ecx.memory.leak_report(); - if leaks != 0 { - tcx.sess.err("the evaluated program leaked memory"); - } - return; + fn run_main<'a, 'tcx: 'a>( + ecx: &mut EvalContext<'a, 'tcx>, + main_id: DefId, + start_wrapper: Option, + ) -> EvalResult<'tcx> { + let main_instance = ty::Instance::mono(ecx.tcx, main_id); + let main_mir = ecx.load_mir(main_instance.def)?; + let mut cleanup_ptr = None; // Pointer to be deallocated when we are done + + if !main_mir.return_ty.is_nil() || main_mir.arg_count != 0 { + return Err(EvalError::Unimplemented("miri does not support main functions without `fn()` type signatures".to_owned())); + } + + if let Some(start_id) = start_wrapper { + let start_instance = ty::Instance::mono(ecx.tcx, start_id); + let start_mir = ecx.load_mir(start_instance.def)?; + + if start_mir.arg_count != 3 { + return Err(EvalError::AbiViolation(format!("'start' lang item should have three arguments, but has {}", start_mir.arg_count))); } - Err(e) => { - report(tcx, &ecx, e); - return; + + // Return value + let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi())?; + cleanup_ptr = Some(ret_ptr); + + // Push our stack frame + ecx.push_stack_frame( + start_instance, + start_mir.span, + start_mir, + Lvalue::from_ptr(ret_ptr), + StackPopCleanup::None, + )?; + + let mut args = ecx.frame().mir.args_iter(); + + // First argument: pointer to main() + let main_ptr = ecx.memory.create_fn_alloc(main_instance); + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let main_ty = main_instance.def.def_ty(ecx.tcx); + let main_ptr_ty = ecx.tcx.mk_fn_ptr(main_ty.fn_sig()); + ecx.write_value(Value::ByVal(PrimVal::Ptr(main_ptr)), dest, main_ptr_ty)?; + + // Second argument (argc): 0 + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let ty = ecx.tcx.types.isize; + ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?; + + // Third argument (argv): 0 + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let ty = ecx.tcx.mk_imm_ptr(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8)); + ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?; + } else { + ecx.push_stack_frame( + main_instance, + main_mir.span, + main_mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + StackPopCleanup::None, + )?; + } + + while ecx.step()? {} + if let Some(cleanup_ptr) = cleanup_ptr { + ecx.memory.deallocate(cleanup_ptr)?; + } + return Ok(()); + } + + let mut ecx = EvalContext::new(tcx, limits); + match run_main(&mut ecx, main_id, start_wrapper) { + Ok(()) => { + let leaks = ecx.memory.leak_report(); + if leaks != 0 { + tcx.sess.err("the evaluated program leaked memory"); } } + Err(e) => { + report(tcx, &ecx, e); + } } } fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) { - let frame = ecx.stack().last().expect("stackframe was empty"); - let block = &frame.mir.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 mut err = tcx.sess.struct_span_err(span, &e.to_string()); - for &Frame { instance, span, .. } in ecx.stack().iter().rev() { - if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { - err.span_note(span, "inside call to closure"); - continue; + if let Some(frame) = ecx.stack().last() { + let block = &frame.mir.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 mut err = tcx.sess.struct_span_err(span, &e.to_string()); + for &Frame { instance, span, .. } in ecx.stack().iter().rev() { + if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { + err.span_note(span, "inside call to closure"); + continue; + } + err.span_note(span, &format!("inside call to {}", instance)); } - err.span_note(span, &format!("inside call to {}", instance)); + err.emit(); + } else { + tcx.sess.err(&e.to_string()); } - err.emit(); } // TODO(solson): Upstream these methods into rustc::ty::layout. diff --git a/src/lvalue.rs b/src/lvalue.rs index 9660b8f4ee..cad4ca9d02 100644 --- a/src/lvalue.rs +++ b/src/lvalue.rs @@ -104,6 +104,15 @@ impl<'tcx> Global<'tcx> { initialized: false, } } + + pub(super) fn initialized(ty: Ty<'tcx>, value: Value, mutable: bool) -> Self { + Global { + value, + mutable, + ty, + initialized: true, + } + } } impl<'a, 'tcx> EvalContext<'a, 'tcx> { diff --git a/src/memory.rs b/src/memory.rs index f2440d3d72..bfdc45c921 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -98,6 +98,18 @@ impl Pointer { pub fn never_ptr() -> Self { Pointer::new(NEVER_ALLOC_ID, 0) } + + pub fn is_null_ptr(&self) -> bool { + return *self == Pointer::from_int(0) + } +} + +pub type TlsKey = usize; + +#[derive(Copy, Clone, Debug)] +pub struct TlsEntry<'tcx> { + data: Pointer, // will eventually become a map from thread IDs to pointers + dtor: Option>, } //////////////////////////////////////////////////////////////////////////////// @@ -149,6 +161,12 @@ pub struct Memory<'a, 'tcx> { /// A cache for basic byte allocations keyed by their contents. This is used to deduplicate /// allocations for string and bytestring literals. literal_alloc_cache: HashMap, AllocId>, + + /// pthreads-style Thread-local storage. We only have one thread, so this is just a map from TLS keys (indices into the vector) to the pointer stored there. + thread_local: HashMap>, + + /// The Key to use for the next thread-local allocation. + next_thread_local: TlsKey, } const ZST_ALLOC_ID: AllocId = AllocId(0); @@ -167,6 +185,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { packed: BTreeSet::new(), static_alloc: HashSet::new(), literal_alloc_cache: HashMap::new(), + thread_local: HashMap::new(), + next_thread_local: 0, } } @@ -345,6 +365,59 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { pub(crate) fn clear_packed(&mut self) { self.packed.clear(); } + + pub(crate) fn create_tls_key(&mut self, dtor: Option>) -> TlsKey { + let new_key = self.next_thread_local; + self.next_thread_local += 1; + self.thread_local.insert(new_key, TlsEntry { data: Pointer::from_int(0), dtor }); + trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor); + return new_key; + } + + pub(crate) fn delete_tls_key(&mut self, key: TlsKey) -> EvalResult<'tcx> { + return match self.thread_local.remove(&key) { + Some(_) => { + trace!("TLS key {} removed", key); + Ok(()) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } + + pub(crate) fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Pointer> { + return match self.thread_local.get(&key) { + Some(&TlsEntry { data, .. }) => { + trace!("TLS key {} loaded: {:?}", key, data); + Ok(data) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } + + pub(crate) fn store_tls(&mut self, key: TlsKey, new_data: Pointer) -> EvalResult<'tcx> { + return match self.thread_local.get_mut(&key) { + Some(&mut TlsEntry { ref mut data, .. }) => { + trace!("TLS key {} stored: {:?}", key, new_data); + *data = new_data; + Ok(()) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } + + // Returns a dtor and its argument, if one is supposed to run + pub(crate) fn fetch_tls_dtor(&mut self) -> Option<(ty::Instance<'tcx>, Pointer)> { + for (_, &mut TlsEntry { ref mut data, dtor }) in self.thread_local.iter_mut() { + if !data.is_null_ptr() { + if let Some(dtor) = dtor { + let old_data = *data; + *data = Pointer::from_int(0); + return Some((dtor, old_data)); + } + } + } + return None; + } } // The derived `Ord` impl sorts first by the first field, then, if the fields are the same diff --git a/src/step.rs b/src/step.rs index 27eb26e333..cbd9871e83 100644 --- a/src/step.rs +++ b/src/step.rs @@ -13,6 +13,7 @@ use error::{EvalResult, EvalError}; use eval_context::{EvalContext, StackPopCleanup}; use lvalue::{Global, GlobalId, Lvalue}; use value::{Value, PrimVal}; +use memory::Pointer; use syntax::codemap::Span; impl<'a, 'tcx> EvalContext<'a, 'tcx> { @@ -31,6 +32,23 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.memory.clear_packed(); self.inc_step_counter_and_check_limit(1)?; if self.stack.is_empty() { + if let Some((instance, ptr)) = self.memory.fetch_tls_dtor() { + trace!("Running TLS dtor {:?} on {:?}", instance, ptr); + // TODO: Potientially, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs + let mir = self.load_mir(instance.def)?; + self.push_stack_frame( + instance, + mir.span, + mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + StackPopCleanup::None, + )?; + let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()))?; + let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); + self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?; + return Ok(true); + } return Ok(false); } @@ -48,11 +66,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { mir, new_constants: &mut new, }.visit_statement(block, stmt, mir::Location { block, statement_index: stmt_id }); + // if ConstantExtractor added new frames, we don't execute anything here + // but await the next call to step if new? == 0 { self.statement(stmt)?; } - // if ConstantExtractor added new frames, we don't execute anything here - // but await the next call to step return Ok(true); } @@ -65,11 +83,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { mir, new_constants: &mut new, }.visit_terminator(block, terminator, mir::Location { block, statement_index: stmt_id }); + // if ConstantExtractor added new frames, we don't execute anything here + // but await the next call to step if new? == 0 { self.terminator(terminator)?; } - // if ConstantExtractor added new frames, we don't execute anything here - // but await the next call to step Ok(true) } @@ -158,6 +176,11 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { if self.ecx.globals.contains_key(&cid) { return; } + if self.ecx.tcx.has_attr(def_id, "linkage") { + trace!("Initializing an extern global with NULL"); + self.ecx.globals.insert(cid, Global::initialized(self.ecx.tcx.type_of(def_id), Value::ByVal(PrimVal::Ptr(Pointer::from_int(0))), !shared)); + return; + } self.try(|this| { let mir = this.ecx.load_mir(instance.def)?; this.ecx.globals.insert(cid, Global::uninitialized(mir.return_ty)); @@ -178,6 +201,7 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { ) }); } + fn try EvalResult<'tcx>>(&mut self, f: F) { if let Ok(ref mut n) = *self.new_constants { *n += 1; diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index a3ff8c1b54..81927ba329 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -1,6 +1,6 @@ use rustc::hir::def_id::DefId; use rustc::mir; -use rustc::ty::{self, Ty}; +use rustc::ty::{self, TypeVariants, Ty, TypeAndMut}; use rustc::ty::layout::Layout; use syntax::codemap::Span; use syntax::attr; @@ -9,7 +9,7 @@ use syntax::abi::Abi; use error::{EvalError, EvalResult}; use eval_context::{EvalContext, IntegerExt, StackPopCleanup, is_inhabited}; use lvalue::Lvalue; -use memory::Pointer; +use memory::{Pointer, TlsKey}; use value::PrimVal; use value::Value; use rustc_data_structures::indexed_vec::Idx; @@ -72,15 +72,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ty::TyFnDef(_, _, real_sig) => { let sig = self.erase_lifetimes(&sig); let real_sig = self.erase_lifetimes(&real_sig); - match instance.def { - // FIXME: this needs checks for weird transmutes - // we need to bail here, because noncapturing closures as fn ptrs fail the checks - ty::InstanceDef::ClosureOnceShim{..} => {} - _ => if sig.abi != real_sig.abi || - sig.variadic != real_sig.variadic || - sig.inputs_and_output != real_sig.inputs_and_output { - return Err(EvalError::FunctionPointerTyMismatch(real_sig, sig)); - }, + if !self.check_sig_compat(sig, real_sig)? { + return Err(EvalError::FunctionPointerTyMismatch(real_sig, sig)); } }, ref other => bug!("instance def ty: {:?}", other), @@ -138,6 +131,70 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } + /// Decides whether it is okay to call the method with signature `real_sig` using signature `sig`. + /// FIXME: This should take into account the platform-dependent ABI description. + fn check_sig_compat( + &mut self, + sig: ty::FnSig<'tcx>, + real_sig: ty::FnSig<'tcx>, + ) -> EvalResult<'tcx, bool> { + fn check_ty_compat<'tcx>( + ty: ty::Ty<'tcx>, + real_ty: ty::Ty<'tcx>, + ) -> bool { + if ty == real_ty { return true; } // This is actually a fast pointer comparison + return match (&ty.sty, &real_ty.sty) { + // Permit changing the pointer type of raw pointers and references as well as + // mutability of raw pointers. + // TODO: Should not be allowed when fat pointers are involved. + (&TypeVariants::TyRawPtr(_), &TypeVariants::TyRawPtr(_)) => true, + (&TypeVariants::TyRef(_, _), &TypeVariants::TyRef(_, _)) => + ty.is_mutable_pointer() == real_ty.is_mutable_pointer(), + // rule out everything else + _ => false + } + } + + if sig.abi == real_sig.abi && + sig.variadic == real_sig.variadic && + sig.inputs_and_output.len() == real_sig.inputs_and_output.len() && + sig.inputs_and_output.iter().zip(real_sig.inputs_and_output).all(|(ty, real_ty)| check_ty_compat(ty, real_ty)) { + // Definitely good. + return Ok(true); + } + + if sig.variadic || real_sig.variadic { + // We're not touching this + return Ok(false); + } + + // We need to allow what comes up when a non-capturing closure is cast to a fn(). + match (sig.abi, real_sig.abi) { + (Abi::Rust, Abi::RustCall) // check the ABIs. This makes the test here non-symmetric. + if check_ty_compat(sig.output(), real_sig.output()) && real_sig.inputs_and_output.len() == 3 => { + // First argument of real_sig must be a ZST + let fst_ty = real_sig.inputs_and_output[0]; + let layout = self.type_layout(fst_ty)?; + let size = layout.size(&self.tcx.data_layout).bytes(); + if size == 0 { + // Second argument must be a tuple matching the argument list of sig + let snd_ty = real_sig.inputs_and_output[1]; + match snd_ty.sty { + TypeVariants::TyTuple(tys, _) if sig.inputs().len() == tys.len() => + if sig.inputs().iter().zip(tys).all(|(ty, real_ty)| check_ty_compat(ty, real_ty)) { + return Ok(true) + }, + _ => {} + } + } + } + _ => {} + }; + + // Nope, this doesn't work. + return Ok(false); + } + fn eval_fn_call( &mut self, instance: ty::Instance<'tcx>, @@ -172,7 +229,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } @@ -202,18 +261,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } ty::InstanceDef::Item(_) => { - match sig.abi { - Abi::C => { - let ty = sig.output(); - let (ret, target) = destination.unwrap(); - self.call_c_abi(instance.def_id(), arg_operands, ret, ty)?; - self.dump_local(ret); - self.goto_block(target); - return Ok(()); - }, - Abi::Rust | Abi::RustCall => {}, - _ => unimplemented!(), - } let mut args = Vec::new(); for arg in arg_operands { let arg_val = self.eval_operand(arg)?; @@ -221,25 +268,23 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { args.push((arg_val, arg_ty)); } + // Push the stack frame, and potentially be entirely done if the call got hooked if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } + // Pass the arguments let mut arg_locals = self.frame().mir.args_iter(); trace!("ABI: {:?}", sig.abi); trace!("arg_locals: {:?}", self.frame().mir.args_iter().collect::>()); trace!("arg_operands: {:?}", arg_operands); match sig.abi { - Abi::Rust => { - for (arg_local, (arg_val, arg_ty)) in arg_locals.zip(args) { - let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; - self.write_value(arg_val, dest, arg_ty)?; - } - } Abi::RustCall => { assert_eq!(args.len(), 2); @@ -282,8 +327,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } else { bug!("rust-call ABI tuple argument was {:?}, {:?}", arg_ty, layout); } + }, + _ => { + for (arg_local, (arg_val, arg_ty)) in arg_locals.zip(args) { + let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_value(arg_val, dest, arg_ty)?; + } } - _ => unimplemented!(), } Ok(()) }, @@ -314,7 +364,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } @@ -361,7 +413,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { &mut self, instance: ty::Instance<'tcx>, destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], span: Span, + sig: ty::FnSig<'tcx>, ) -> EvalResult<'tcx, bool> { trace!("eval_fn_call_inner: {:#?}, {:#?}", instance, destination); @@ -370,28 +424,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let mir = match self.load_mir(instance.def) { Ok(mir) => mir, Err(EvalError::NoMirFor(path)) => { - match &path[..] { - // let's just ignore all output for now - "std::io::_print" => { - self.goto_block(destination.unwrap().1); - return Ok(true); - }, - "std::thread::Builder::new" => return Err(EvalError::Unimplemented("miri does not support threading".to_owned())), - "std::env::args" => return Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), - "std::panicking::rust_panic_with_hook" | - "std::rt::begin_panic_fmt" => return Err(EvalError::Panic), - "std::panicking::panicking" | - "std::rt::panicking" => { - let (lval, block) = destination.expect("std::rt::panicking does not diverge"); - // we abort on panic -> `std::rt::panicking` always returns false - let bool = self.tcx.types.bool; - self.write_primval(lval, PrimVal::from_bool(false), bool)?; - self.goto_block(block); - return Ok(true); - } - _ => {}, - } - return Err(EvalError::NoMirFor(path)); + self.call_missing_fn(instance, destination, arg_operands, sig, path)?; + return Ok(true); }, Err(other) => return Err(other), }; @@ -464,13 +498,56 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { assert!(nndiscr == 0 || nndiscr == 1); Ok(if not_null { nndiscr } else { 1 - nndiscr }) } + + /// Returns Ok() when the function was handled, fail otherwise + fn call_missing_fn( + &mut self, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], + sig: ty::FnSig<'tcx>, + path: String, + ) -> EvalResult<'tcx> { + if sig.abi == Abi::C { + // An external C function + let ty = sig.output(); + let (ret, target) = destination.unwrap(); + self.call_c_abi(instance.def_id(), arg_operands, ret, ty, target)?; + return Ok(()); + } + + // A Rust function is missing, which means we are running with MIR missing for libstd (or other dependencies). + // Still, we can make many things mostly work by "emulating" or ignoring some functions. + match &path[..] { + "std::io::_print" => { + trace!("Ignoring output. To run programs that print, make sure you have a libstd with full MIR."); + self.goto_block(destination.unwrap().1); + Ok(()) + }, + "std::thread::Builder::new" => Err(EvalError::Unimplemented("miri does not support threading".to_owned())), + "std::env::args" => Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), + "std::panicking::rust_panic_with_hook" | + "std::rt::begin_panic_fmt" => Err(EvalError::Panic), + "std::panicking::panicking" | + "std::rt::panicking" => { + let (lval, block) = destination.expect("std::rt::panicking does not diverge"); + // we abort on panic -> `std::rt::panicking` always returns false + let bool = self.tcx.types.bool; + self.write_primval(lval, PrimVal::from_bool(false), bool)?; + self.goto_block(block); + Ok(()) + } + _ => Err(EvalError::NoMirFor(path)), + } + } fn call_c_abi( &mut self, def_id: DefId, - args: &[mir::Operand<'tcx>], + arg_operands: &[mir::Operand<'tcx>], dest: Lvalue<'tcx>, dest_ty: Ty<'tcx>, + dest_block: mir::BasicBlock, ) -> EvalResult<'tcx> { let name = self.tcx.item_name(def_id); let attrs = self.tcx.get_attrs(def_id); @@ -478,7 +555,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { .unwrap_or(name) .as_str(); - let args_res: EvalResult> = args.iter() + let args_res: EvalResult> = arg_operands.iter() .map(|arg| self.eval_operand(arg)) .collect(); let args = args_res?; @@ -517,6 +594,41 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } + "__rust_maybe_catch_panic" => { + // fn __rust_maybe_catch_panic(f: fn(*mut u8), data: *mut u8, data_ptr: *mut usize, vtable_ptr: *mut usize) -> u32 + // We abort on panic, so not much is going on here, but we still have to call the closure + let u8_ptr_ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); + let f = args[0].read_ptr(&self.memory)?; + let data = args[1].read_ptr(&self.memory)?; + let f_instance = self.memory.get_fn(f.alloc_id)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + + // Now we make a function call. TODO: Consider making this re-usable? EvalContext::step does sth. similar for the TLS dtors, + // and of course eval_main. + let mir = self.load_mir(f_instance.def)?; + self.push_stack_frame( + f_instance, + mir.span, + mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + StackPopCleanup::Goto(dest_block), + )?; + + let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("Argument to __rust_maybe_catch_panic does not take enough arguments.".to_owned()))?; + let arg_dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_value(Value::ByVal(PrimVal::Ptr(data)), arg_dest, u8_ptr_ty)?; + + // We ourselbes return 0 + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + + // Don't fall through + return Ok(()); + } + + "__rust_start_panic" => { + return Err(EvalError::Panic); + } + "memcmp" => { let left = args[0].read_ptr(&self.memory)?; let right = args[1].read_ptr(&self.memory)?; @@ -543,9 +655,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let num = self.value_to_primval(args[2], usize)?.to_u64()?; if let Some(idx) = self.memory.read_bytes(ptr, num)?.iter().rev().position(|&c| c == val) { let new_ptr = ptr.offset(num - idx as u64 - 1); - self.write_value(Value::ByVal(PrimVal::Ptr(new_ptr)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } else { - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } } @@ -555,9 +667,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let num = self.value_to_primval(args[2], usize)?.to_u64()?; if let Some(idx) = self.memory.read_bytes(ptr, num)?.iter().position(|&c| c == val) { let new_ptr = ptr.offset(idx as u64); - self.write_value(Value::ByVal(PrimVal::Ptr(new_ptr)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } else { - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } } @@ -567,17 +679,102 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let name = self.memory.read_c_str(name_ptr)?; info!("ignored env var request for `{:?}`", ::std::str::from_utf8(name)); } - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + + "write" => { + let fd = self.value_to_primval(args[0], usize)?.to_u64()?; + let buf = args[1].read_ptr(&self.memory)?; + let n = self.value_to_primval(args[2], usize)?.to_u64()?; + trace!("Called write({:?}, {:?}, {:?})", fd, buf, n); + let result = if fd == 1 || fd == 2 { // stdout/stderr + use std::io::{self, Write}; + + let buf_cont = self.memory.read_bytes(buf, n)?; + let res = if fd == 1 { io::stdout().write(buf_cont) } else { io::stderr().write(buf_cont) }; + match res { Ok(n) => n as isize, Err(_) => -1 } + } else { + info!("Ignored output to FD {}", fd); + n as isize // pretend it all went well + }; // now result is the value we return back to the program + self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?; } - // unix panic code inside libstd will read the return value of this function - "pthread_rwlock_rdlock" => { + // Some things needed for sys::thread initialization to go through + "signal" | "sigaction" | "sigaltstack" => { self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } + "sysconf" => { + let name = self.value_to_primval(args[0], usize)?.to_u64()?; + trace!("sysconf() called with name {}", name); + let result = match name { + 30 => 4096, // _SC_PAGESIZE + _ => return Err(EvalError::Unimplemented(format!("Unimplemented sysconf name: {}", name))) + }; + self.write_primval(dest, PrimVal::Bytes(result), dest_ty)?; + } + + "mmap" => { + // This is a horrible hack, but well... the guard page mechanism calls mmap and expects a particular return value, so we give it that value + let addr = args[0].read_ptr(&self.memory)?; + self.write_primval(dest, PrimVal::Ptr(addr), dest_ty)?; + } + + // Hook pthread calls that go to the thread-local storage memory subsystem + "pthread_key_create" => { + let key_ptr = args[0].read_ptr(&self.memory)?; + + // Extract the function type out of the signature (that seems easier than constructing it ourselves...) + let dtor_ptr = args[1].read_ptr(&self.memory)?; + let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; + + // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. + let key_size = match self.operand_ty(&arg_operands[0]).sty { + TypeVariants::TyRawPtr(TypeAndMut { ty, .. }) => { + let layout = self.type_layout(ty)?; + layout.size(&self.tcx.data_layout) + } + _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned())) + }; + + // Create key and write it into the memory where key_ptr wants it + let key = self.memory.create_tls_key(dtor); + if key >= (1 << key_size.bits()) { + return Err(EvalError::OutOfTls); + } + self.memory.write_int(key_ptr, key as i128, key_size.bytes())?; + + // Return success (0) + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + "pthread_key_delete" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + self.memory.delete_tls_key(key)?; + // Return success (0) + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + "pthread_getspecific" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + let ptr = self.memory.load_tls(key)?; + self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + } + "pthread_setspecific" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + let new_ptr = args[1].read_ptr(&self.memory)?; + self.memory.store_tls(key, new_ptr)?; + + // Return success (0) + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + + // Stub out all the other pthread calls to just return 0 link_name if link_name.starts_with("pthread_") => { warn!("ignoring C ABI call: {}", link_name); - return Ok(()); + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; }, _ => { @@ -588,6 +785,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Since we pushed no stack frame, the main loop will act // as if the call just completed and it's returning to the // current frame. + self.dump_local(dest); + self.goto_block(dest_block); Ok(()) - } + } } diff --git a/tests/compile-fail/cast_fn_ptr2.rs b/tests/compile-fail/cast_fn_ptr2.rs new file mode 100644 index 0000000000..5d902e1f9a --- /dev/null +++ b/tests/compile-fail/cast_fn_ptr2.rs @@ -0,0 +1,9 @@ +fn main() { + fn f(_ : (i32,i32)) {} + + let g = unsafe { + std::mem::transmute::(f) + }; + + g(42) //~ ERROR tried to call a function with sig fn((i32, i32)) through a function pointer of type fn(i32) +} diff --git a/tests/compile-fail/env_args.rs b/tests/compile-fail/env_args.rs deleted file mode 100644 index fe17e0a7b4..0000000000 --- a/tests/compile-fail/env_args.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - let x = std::env::args(); //~ ERROR miri does not support program arguments - assert_eq!(x.count(), 1); -} diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs index 1fd2c4b2bd..d4aebb912e 100644 --- a/tests/compile-fail/oom.rs +++ b/tests/compile-fail/oom.rs @@ -1,7 +1,7 @@ #![feature(custom_attribute, attr_literals)] -#![miri(memory_size=0)] +#![miri(memory_size=4095)] fn main() { - let _x = [42; 10]; - //~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory + let _x = [42; 1024]; + //~^ERROR tried to allocate 4096 more bytes, but only } diff --git a/tests/compile-fail/oom2.rs b/tests/compile-fail/oom2.rs index a87e34474c..1a4a47efe6 100644 --- a/tests/compile-fail/oom2.rs +++ b/tests/compile-fail/oom2.rs @@ -1,5 +1,5 @@ #![feature(box_syntax, custom_attribute, attr_literals)] -#![miri(memory_size=1000)] +#![miri(memory_size=2048)] fn main() { loop { diff --git a/tests/compile-fail/stack_limit.rs b/tests/compile-fail/stack_limit.rs index 2a78fbe539..4b61e12d60 100644 --- a/tests/compile-fail/stack_limit.rs +++ b/tests/compile-fail/stack_limit.rs @@ -1,5 +1,5 @@ #![feature(custom_attribute, attr_literals)] -#![miri(stack_limit=2)] +#![miri(stack_limit=16)] fn bar() { foo(); @@ -10,10 +10,16 @@ fn foo() { } fn cake() { - flubber(); + flubber(3); } -fn flubber() {} +fn flubber(i: u32) { + if i > 0 { + flubber(i-1); + } else { + bar(); + } +} fn main() { bar(); diff --git a/tests/run-pass/cast_fn_ptr.rs b/tests/run-pass/cast_fn_ptr.rs new file mode 100644 index 0000000000..109e8dfc2a --- /dev/null +++ b/tests/run-pass/cast_fn_ptr.rs @@ -0,0 +1,9 @@ +fn main() { + fn f(_: *const u8) {} + + let g = unsafe { + std::mem::transmute::(f) + }; + + g(&42 as *const _); +} diff --git a/tests/run-pass/catch.rs b/tests/run-pass/catch.rs new file mode 100644 index 0000000000..439edc82dd --- /dev/null +++ b/tests/run-pass/catch.rs @@ -0,0 +1,9 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn main() { + let mut i = 3; + let _ = catch_unwind(AssertUnwindSafe(|| {i -= 2;} )); + for _ in 0..i { + println!("I"); + } +} diff --git a/tests/run-pass/format.rs b/tests/run-pass/format.rs new file mode 100644 index 0000000000..78729b9156 --- /dev/null +++ b/tests/run-pass/format.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello {}", 13); +} diff --git a/tests/run-pass/hello.rs b/tests/run-pass/hello.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/tests/run-pass/hello.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/run-pass/thread-local-no-dtor.rs b/tests/run-pass/thread-local-no-dtor.rs new file mode 100644 index 0000000000..8c69be8e2c --- /dev/null +++ b/tests/run-pass/thread-local-no-dtor.rs @@ -0,0 +1,16 @@ +#![feature(libc)] +extern crate libc; + +use std::mem; + +pub type Key = libc::pthread_key_t; + +pub unsafe fn create(dtor: Option) -> Key { + let mut key = 0; + assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + key +} + +fn main() { + let _ = unsafe { create(None) }; +} diff --git a/tests/run-pass/zst.rs b/tests/run-pass/zst.rs index 78d3025587..06e41e59e6 100644 --- a/tests/run-pass/zst.rs +++ b/tests/run-pass/zst.rs @@ -1,9 +1,3 @@ -// the following flag prevents this test from running on the host machine -// this should only be run on miri, because rust doesn't (yet?) optimize ZSTs of different types -// into the same memory location -// ignore-test - - #[derive(PartialEq, Debug)] struct A; diff --git a/xargo/Cargo.lock b/xargo/Cargo.lock new file mode 100644 index 0000000000..031ad9a879 --- /dev/null +++ b/xargo/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "miri-xargo" +version = "0.0.0" + diff --git a/xargo/Cargo.toml b/xargo/Cargo.toml new file mode 100644 index 0000000000..9129c105b1 --- /dev/null +++ b/xargo/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "miri-xargo" +description = "A dummy project for building libstd with xargo." +version = "0.0.0" + +[dependencies] diff --git a/xargo/Xargo.toml b/xargo/Xargo.toml new file mode 100644 index 0000000000..32f45c4a98 --- /dev/null +++ b/xargo/Xargo.toml @@ -0,0 +1,2 @@ +[dependencies] +std = {features = ["panic_unwind", "jemalloc"]} diff --git a/xargo/src/lib.rs b/xargo/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2