From cd6e3e643133fc1c6cdfd6067445236f9b776c7c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 17:36:16 -0700 Subject: [PATCH] If a "start" lang item incl. MIR is present, run that instead of running main directly This fixes the memory leaks when running a simple "Hello World" with MIR-libstd --- src/bin/miri.rs | 6 +- src/eval_context.rs | 147 ++++++++++++++++++++++++++------------ src/step.rs | 12 ++-- src/terminator/mod.rs | 30 +++++--- tests/compile-fail/oom.rs | 4 +- 5 files changed, 134 insertions(+), 65 deletions(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 3f0a6f778b42..4a82d45493b8 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -84,7 +84,7 @@ fn visit_item(&mut self, i: &'hir hir::Item) { 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 visit_impl_item(&mut self, _impl_item: &'hir hir::ImplItem) {} 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/eval_context.rs b/src/eval_context.rs index 77d9d9b20cd8..9a57e75da090 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -126,6 +126,7 @@ fn default() -> Self { 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 @@ pub(super) fn eval_rvalue_into_lvalue( 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), }, } } @@ -1676,62 +1677,120 @@ fn set_local(&mut self, local: mir::Local, field: Option, value: Value) { 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"); + 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)?; - 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; + 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))); + } + + // Push our stack frame + ecx.push_stack_frame( + start_instance, + start_mir.span, + start_mir, + Lvalue::from_ptr(Pointer::zst_ptr()), // we'll fix the return lvalue later + 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, + )?; + } + + // Allocate memory for the return value. We have to do this when a stack frame was already pushed as the type code below + // calls EvalContext::substs, which needs a frame to be allocated (?!?) + let ret_ptr = { + let ty = ecx.tcx.types.isize; + let layout = ecx.type_layout(ty)?; + let size = layout.size(&ecx.tcx.data_layout).bytes(); + let align = layout.align(&ecx.tcx.data_layout).pref(); // FIXME is this right? + ecx.memory.allocate(size, align)? + }; + ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr); + + loop { + if !ecx.step()? { + ecx.memory.deallocate(ret_ptr)?; + return Ok(()); + } + } } - 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; - } - Err(e) => { - report(tcx, &ecx, e); - return; + 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/step.rs b/src/step.rs index 84f849c61225..81bac8c985b8 100644 --- a/src/step.rs +++ b/src/step.rs @@ -43,14 +43,10 @@ pub fn step(&mut self) -> EvalResult<'tcx, bool> { Lvalue::from_ptr(Pointer::zst_ptr()), StackPopCleanup::None, )?; - if let Some(arg_local) = self.frame().mir.args_iter().next() { - 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)?; - } else { - return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned())); - } - + 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); diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index e017a87f0346..09361aa43b3b 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -637,23 +637,33 @@ fn call_c_abi( 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_fn_ty = match self.operand_ty(&arg_operands[1]).sty { - TypeVariants::TyAdt(_, ref substs) => { - substs.type_at(0) - } - _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned())) - }; - let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?; + let dtor_ptr = args[1].read_ptr(&self.memory)?; // TODO: The null-pointer case here is entirely untested let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; @@ -699,8 +709,10 @@ fn call_c_abi( 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); + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; }, _ => { diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs index 1fd2c4b2bd6a..d15f1d6ae3e7 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=20)] fn main() { let _x = [42; 10]; - //~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory + //~^ERROR tried to allocate 40 more bytes, but only }