diff --git a/src/eval_context.rs b/src/eval_context.rs index 13fad7b464..0ab6e85e38 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -81,8 +81,8 @@ pub struct Frame<'tcx> { /// Temporary allocations introduced to save stackframes /// This is pure interpreter magic and has nothing to do with how rustc does it /// An example is calling an FnMut closure that has been converted to a FnOnce closure - /// The memory will be freed when the stackframe finishes - pub interpreter_temporaries: Vec, + /// The value's destructor will be called and the memory freed when the stackframe finishes + pub interpreter_temporaries: Vec<(Pointer, Ty<'tcx>)>, //////////////////////////////////////////////////////////////////////////////// // Current position within the function @@ -273,7 +273,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { substs: &'tcx Substs<'tcx>, return_lvalue: Lvalue<'tcx>, return_to_block: StackPopCleanup, - temporaries: Vec, + temporaries: Vec<(Pointer, Ty<'tcx>)>, ) -> EvalResult<'tcx> { ::log_settings::settings().indentation += 1; @@ -347,11 +347,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } } } - // deallocate all temporary allocations - for ptr in frame.interpreter_temporaries { - trace!("deallocating temporary allocation"); - self.memory.dump_alloc(ptr.alloc_id); - self.memory.deallocate(ptr)?; + // drop and deallocate all temporary allocations + for (ptr, ty) in frame.interpreter_temporaries { + trace!("dropping temporary allocation"); + let mut drops = Vec::new(); + self.drop(Lvalue::from_ptr(ptr), ty, &mut drops)?; + self.eval_drop_impls(drops, frame.span)?; } Ok(()) } @@ -456,7 +457,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { General { discr, ref variants, .. } => { if let mir::AggregateKind::Adt(adt_def, variant, _, _) = *kind { - let discr_val = adt_def.variants[variant].disr_val.to_u128_unchecked(); + let discr_val = adt_def.variants[variant].disr_val; let discr_size = discr.size().bytes(); if variants[variant].packed { let ptr = self.force_allocation(dest)?.to_ptr_and_extra().0; @@ -529,7 +530,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { CEnum { .. } => { assert_eq!(operands.len(), 0); if let mir::AggregateKind::Adt(adt_def, variant, _, _) = *kind { - let n = adt_def.variants[variant].disr_val.to_u128_unchecked(); + let n = adt_def.variants[variant].disr_val; self.write_primval(dest, PrimVal::Bytes(n), dest_ty)?; } else { bug!("tried to assign {:?} to Layout::CEnum", kind); @@ -661,7 +662,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } } - InlineAsm { .. } => return Err(EvalError::InlineAsm), + Discriminant(ref lvalue) => { + let lval = self.eval_lvalue(lvalue)?; + let ty = self.lvalue_ty(lvalue); + let ptr = self.force_allocation(lval)?.to_ptr(); + let discr_val = self.read_discriminant_value(ptr, ty)?; + if let ty::TyAdt(adt_def, _) = ty.sty { + if adt_def.variants.iter().all(|v| discr_val != v.disr_val) { + return Err(EvalError::InvalidDiscriminant); + } + } else { + bug!("rustc only generates Rvalue::Discriminant for enums"); + } + self.write_primval(dest, PrimVal::Bytes(discr_val), dest_ty)?; + }, } if log_enabled!(::log::LogLevel::Trace) { @@ -915,26 +929,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { val: PrimVal, dest_ty: Ty<'tcx>, ) -> EvalResult<'tcx> { - match dest { - Lvalue::Ptr { ptr, extra } => { - assert_eq!(extra, LvalueExtra::None); - let size = self.type_size(dest_ty)?.expect("dest type must be sized"); - self.memory.write_primval(ptr, val, size) - } - Lvalue::Local { frame, local, field } => { - self.stack[frame].set_local(local, field.map(|(i, _)| i), Value::ByVal(val)); - Ok(()) - } - Lvalue::Global(cid) => { - let global_val = self.globals.get_mut(&cid).expect("global not cached"); - if global_val.mutable { - global_val.value = Value::ByVal(val); - Ok(()) - } else { - Err(EvalError::ModifiedConstantMemory) - } - } - } + self.write_value(Value::ByVal(val), dest, dest_ty) } pub(super) fn write_value( @@ -1496,7 +1491,13 @@ pub fn eval_main<'a, 'tcx: 'a>( loop { match ecx.step() { Ok(true) => {} - Ok(false) => return, + 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; diff --git a/src/lib.rs b/src/lib.rs index 290b478eb6..d1d8e8cf22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #![feature( btree_range, collections, - field_init_shorthand, i128_type, pub_restricted, rustc_private, diff --git a/src/memory.rs b/src/memory.rs index 0c7b4971e1..459a9bed41 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -585,6 +585,23 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } } + + pub fn leak_report(&self) -> usize { + trace!("### LEAK REPORT ###"); + let leaks: Vec<_> = self.alloc_map + .iter() + .filter_map(|(&key, val)| { + if val.static_kind == StaticKind::NotStatic { + Some(key) + } else { + None + } + }) + .collect(); + let n = leaks.len(); + self.dump_allocs(leaks); + n + } } fn dump_fn_def<'tcx>(fn_def: FunctionDefinition<'tcx>) -> String { diff --git a/src/step.rs b/src/step.rs index 5c957f0cdc..c08ac9693a 100644 --- a/src/step.rs +++ b/src/step.rs @@ -119,6 +119,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Defined to do nothing. These are added by optimization passes, to avoid changing the // size of MIR constantly. Nop => {} + + InlineAsm { .. } => return Err(EvalError::InlineAsm), } self.frame_mut().stmt += 1; diff --git a/src/terminator/drop.rs b/src/terminator/drop.rs index 71f3d46736..dd199a4266 100644 --- a/src/terminator/drop.rs +++ b/src/terminator/drop.rs @@ -121,7 +121,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Layout::General { .. } => { let discr_val = self.read_discriminant_value(adt_ptr, ty)? as u128; let ptr = self.force_allocation(lval)?.to_ptr(); - match adt_def.variants.iter().position(|v| discr_val == v.disr_val.to_u128_unchecked()) { + match adt_def.variants.iter().position(|v| discr_val == v.disr_val) { Some(i) => { lval = Lvalue::Ptr { ptr, diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 4fb3968161..183a3f54fb 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -35,22 +35,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Goto { target } => self.goto_block(target), - If { ref cond, targets: (then_target, else_target) } => { - let cond_val = self.eval_operand_to_primval(cond)?.to_bool()?; - self.goto_block(if cond_val { then_target } else { else_target }); - } - SwitchInt { ref discr, ref values, ref targets, .. } => { - let discr_val = self.eval_and_read_lvalue(discr)?; - let discr_ty = self.lvalue_ty(discr); + let discr_val = self.eval_operand(discr)?; + let discr_ty = self.operand_ty(discr); let discr_prim = self.value_to_primval(discr_val, discr_ty)?; // Branch to the `otherwise` case by default, if no match is found. let mut target_block = targets[targets.len() - 1]; - for (index, const_val) in values.iter().enumerate() { - let val = self.const_to_value(const_val)?; - let prim = self.value_to_primval(val, discr_ty)?; + for (index, const_int) in values.iter().enumerate() { + let prim = PrimVal::Bytes(const_int.to_u128_unchecked()); if discr_prim.to_bytes()? == prim.to_bytes()? { target_block = targets[index]; break; @@ -60,23 +54,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.goto_block(target_block); } - Switch { ref discr, ref targets, adt_def } => { - // FIXME(solson) - let lvalue = self.eval_lvalue(discr)?; - let lvalue = self.force_allocation(lvalue)?; - - let adt_ptr = lvalue.to_ptr(); - let adt_ty = self.lvalue_ty(discr); - let discr_val = self.read_discriminant_value(adt_ptr, adt_ty)?; - let matching = adt_def.variants.iter() - .position(|v| discr_val == v.disr_val.to_u128_unchecked()); - - match matching { - Some(i) => self.goto_block(targets[i]), - None => return Err(EvalError::InvalidDiscriminant), - } - } - Call { ref func, ref args, ref destination, .. } => { let destination = match *destination { Some((ref lv, target)) => Some((self.eval_lvalue(lv)?, target)), @@ -216,12 +193,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { trace!("layout({:?}) = {:#?}", dest_ty, dest_layout); match *dest_layout { Layout::Univariant { .. } => { - let disr_val = v.disr_val.to_u128_unchecked(); + let disr_val = v.disr_val; assert_eq!(disr_val, 0); self.assign_fields(lvalue, dest_ty, args)?; }, Layout::General { discr, ref variants, .. } => { - let disr_val = v.disr_val.to_u128_unchecked(); + let disr_val = v.disr_val; let discr_size = discr.size().bytes(); self.assign_discr_and_fields( lvalue, @@ -234,7 +211,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { )?; }, Layout::StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => { - let disr_val = v.disr_val.to_u128_unchecked(); + let disr_val = v.disr_val; if nndiscr as u128 == disr_val { self.assign_fields(lvalue, dest_ty, args)?; } else { @@ -325,7 +302,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } } - fn read_discriminant_value(&self, adt_ptr: Pointer, adt_ty: Ty<'tcx>) -> EvalResult<'tcx, u128> { + pub fn read_discriminant_value(&self, adt_ptr: Pointer, adt_ty: Ty<'tcx>) -> EvalResult<'tcx, u128> { use rustc::ty::layout::Layout::*; let adt_layout = self.type_layout(adt_ty)?; trace!("read_discriminant_value {:#?}", adt_layout); diff --git a/src/traits.rs b/src/traits.rs index b892efbf56..a46d6096df 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -23,7 +23,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { def_id: DefId, substs: &'tcx Substs<'tcx>, args: &mut Vec<(Value, Ty<'tcx>)>, - ) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>, Vec)> { + ) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>, Vec<(Pointer, Ty<'tcx>)>)> { let trait_ref = ty::TraitRef::from_method(self.tcx, trait_id, substs); let trait_ref = self.tcx.normalize_associated_type(&ty::Binder(trait_ref)); @@ -72,16 +72,15 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ptr = self.alloc_ptr(args[0].1)?; let size = self.type_size(args[0].1)?.expect("closures are sized"); self.memory.write_primval(ptr, primval, size)?; - temporaries.push(ptr); ptr }, Value::ByValPair(a, b) => { let ptr = self.alloc_ptr(args[0].1)?; self.write_pair_to_ptr(a, b, ptr, args[0].1)?; - temporaries.push(ptr); ptr }, }; + temporaries.push((ptr, args[0].1)); args[0].0 = Value::ByVal(PrimVal::Ptr(ptr)); args[0].1 = self.tcx.mk_mut_ptr(args[0].1); } diff --git a/tests/compile-fail/memleak.rs b/tests/compile-fail/memleak.rs new file mode 100644 index 0000000000..71b4e2f442 --- /dev/null +++ b/tests/compile-fail/memleak.rs @@ -0,0 +1,5 @@ +//error-pattern: the evaluated program leaked memory + +fn main() { + std::mem::forget(Box::new(42)); +} diff --git a/tests/compile-fail/memleak_rc.rs b/tests/compile-fail/memleak_rc.rs new file mode 100644 index 0000000000..b2bc6722af --- /dev/null +++ b/tests/compile-fail/memleak_rc.rs @@ -0,0 +1,12 @@ +//error-pattern: the evaluated program leaked memory + +use std::rc::Rc; +use std::cell::RefCell; + +struct Dummy(Rc>>); + +fn main() { + let x = Dummy(Rc::new(RefCell::new(None))); + let y = Dummy(x.0.clone()); + *x.0.borrow_mut() = Some(y); +} diff --git a/tests/run-pass/closure-drop.rs b/tests/run-pass/closure-drop.rs index 913df06f15..f1bdafaeb1 100644 --- a/tests/run-pass/closure-drop.rs +++ b/tests/run-pass/closure-drop.rs @@ -13,17 +13,12 @@ fn f(t: T) { fn main() { let mut ran_drop = false; { - // FIXME: v is a temporary hack to force the below closure to be a FnOnce-only closure - // (with sig fn(self)). Without it, the closure sig would be fn(&self) which requires a - // shim to call via FnOnce::call_once, and Miri's current shim doesn't correctly call - // destructors. - let v = vec![1]; let x = Foo(&mut ran_drop); - let g = move || { - let _ = x; - drop(v); // Force the closure to be FnOnce-only by using a capture by-value. - }; - f(g); + // this closure never by val uses its captures + // so it's basically a fn(&self) + // the shim used to not drop the `x` + let x = move || { let _ = x; }; + f(x); } assert!(ran_drop); } diff --git a/tests/run-pass/option_box_transmute_ptr.rs b/tests/run-pass/option_box_transmute_ptr.rs index c81db8e8b2..0786db1ef8 100644 --- a/tests/run-pass/option_box_transmute_ptr.rs +++ b/tests/run-pass/option_box_transmute_ptr.rs @@ -3,7 +3,10 @@ fn option_box_deref() -> i32 { let val = Some(Box::new(42)); unsafe { let ptr: *const i32 = std::mem::transmute::>, *const i32>(val); - *ptr + let ret = *ptr; + // unleak memory + std::mem::transmute::<*const i32, Option>>(ptr); + ret } }