Skip to content

Commit c502bb8

Browse files
authored
Merge pull request #142 from oli-obk/memleak
Memleak
2 parents cd2f34a + 31c81ac commit c502bb8

File tree

7 files changed

+62
-43
lines changed

7 files changed

+62
-43
lines changed

src/eval_context.rs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ pub struct Frame<'tcx> {
8181
/// Temporary allocations introduced to save stackframes
8282
/// This is pure interpreter magic and has nothing to do with how rustc does it
8383
/// An example is calling an FnMut closure that has been converted to a FnOnce closure
84-
/// The memory will be freed when the stackframe finishes
85-
pub interpreter_temporaries: Vec<Pointer>,
84+
/// The value's destructor will be called and the memory freed when the stackframe finishes
85+
pub interpreter_temporaries: Vec<(Pointer, Ty<'tcx>)>,
8686

8787
////////////////////////////////////////////////////////////////////////////////
8888
// Current position within the function
@@ -273,7 +273,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
273273
substs: &'tcx Substs<'tcx>,
274274
return_lvalue: Lvalue<'tcx>,
275275
return_to_block: StackPopCleanup,
276-
temporaries: Vec<Pointer>,
276+
temporaries: Vec<(Pointer, Ty<'tcx>)>,
277277
) -> EvalResult<'tcx> {
278278
::log_settings::settings().indentation += 1;
279279

@@ -347,11 +347,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
347347
}
348348
}
349349
}
350-
// deallocate all temporary allocations
351-
for ptr in frame.interpreter_temporaries {
352-
trace!("deallocating temporary allocation");
353-
self.memory.dump_alloc(ptr.alloc_id);
354-
self.memory.deallocate(ptr)?;
350+
// drop and deallocate all temporary allocations
351+
for (ptr, ty) in frame.interpreter_temporaries {
352+
trace!("dropping temporary allocation");
353+
let mut drops = Vec::new();
354+
self.drop(Lvalue::from_ptr(ptr), ty, &mut drops)?;
355+
self.eval_drop_impls(drops, frame.span)?;
355356
}
356357
Ok(())
357358
}
@@ -928,26 +929,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
928929
val: PrimVal,
929930
dest_ty: Ty<'tcx>,
930931
) -> EvalResult<'tcx> {
931-
match dest {
932-
Lvalue::Ptr { ptr, extra } => {
933-
assert_eq!(extra, LvalueExtra::None);
934-
let size = self.type_size(dest_ty)?.expect("dest type must be sized");
935-
self.memory.write_primval(ptr, val, size)
936-
}
937-
Lvalue::Local { frame, local, field } => {
938-
self.stack[frame].set_local(local, field.map(|(i, _)| i), Value::ByVal(val));
939-
Ok(())
940-
}
941-
Lvalue::Global(cid) => {
942-
let global_val = self.globals.get_mut(&cid).expect("global not cached");
943-
if global_val.mutable {
944-
global_val.value = Value::ByVal(val);
945-
Ok(())
946-
} else {
947-
Err(EvalError::ModifiedConstantMemory)
948-
}
949-
}
950-
}
932+
self.write_value(Value::ByVal(val), dest, dest_ty)
951933
}
952934

953935
pub(super) fn write_value(
@@ -1509,7 +1491,13 @@ pub fn eval_main<'a, 'tcx: 'a>(
15091491
loop {
15101492
match ecx.step() {
15111493
Ok(true) => {}
1512-
Ok(false) => return,
1494+
Ok(false) => {
1495+
let leaks = ecx.memory.leak_report();
1496+
if leaks != 0 {
1497+
tcx.sess.err("the evaluated program leaked memory");
1498+
}
1499+
return;
1500+
}
15131501
Err(e) => {
15141502
report(tcx, &ecx, e);
15151503
return;

src/memory.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,23 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
585585
}
586586
}
587587
}
588+
589+
pub fn leak_report(&self) -> usize {
590+
trace!("### LEAK REPORT ###");
591+
let leaks: Vec<_> = self.alloc_map
592+
.iter()
593+
.filter_map(|(&key, val)| {
594+
if val.static_kind == StaticKind::NotStatic {
595+
Some(key)
596+
} else {
597+
None
598+
}
599+
})
600+
.collect();
601+
let n = leaks.len();
602+
self.dump_allocs(leaks);
603+
n
604+
}
588605
}
589606

590607
fn dump_fn_def<'tcx>(fn_def: FunctionDefinition<'tcx>) -> String {

src/traits.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
2323
def_id: DefId,
2424
substs: &'tcx Substs<'tcx>,
2525
args: &mut Vec<(Value, Ty<'tcx>)>,
26-
) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>, Vec<Pointer>)> {
26+
) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>, Vec<(Pointer, Ty<'tcx>)>)> {
2727
let trait_ref = ty::TraitRef::from_method(self.tcx, trait_id, substs);
2828
let trait_ref = self.tcx.normalize_associated_type(&ty::Binder(trait_ref));
2929

@@ -72,16 +72,15 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
7272
let ptr = self.alloc_ptr(args[0].1)?;
7373
let size = self.type_size(args[0].1)?.expect("closures are sized");
7474
self.memory.write_primval(ptr, primval, size)?;
75-
temporaries.push(ptr);
7675
ptr
7776
},
7877
Value::ByValPair(a, b) => {
7978
let ptr = self.alloc_ptr(args[0].1)?;
8079
self.write_pair_to_ptr(a, b, ptr, args[0].1)?;
81-
temporaries.push(ptr);
8280
ptr
8381
},
8482
};
83+
temporaries.push((ptr, args[0].1));
8584
args[0].0 = Value::ByVal(PrimVal::Ptr(ptr));
8685
args[0].1 = self.tcx.mk_mut_ptr(args[0].1);
8786
}

tests/compile-fail/memleak.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//error-pattern: the evaluated program leaked memory
2+
3+
fn main() {
4+
std::mem::forget(Box::new(42));
5+
}

tests/compile-fail/memleak_rc.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//error-pattern: the evaluated program leaked memory
2+
3+
use std::rc::Rc;
4+
use std::cell::RefCell;
5+
6+
struct Dummy(Rc<RefCell<Option<Dummy>>>);
7+
8+
fn main() {
9+
let x = Dummy(Rc::new(RefCell::new(None)));
10+
let y = Dummy(x.0.clone());
11+
*x.0.borrow_mut() = Some(y);
12+
}

tests/run-pass/closure-drop.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@ fn f<T: FnOnce()>(t: T) {
1313
fn main() {
1414
let mut ran_drop = false;
1515
{
16-
// FIXME: v is a temporary hack to force the below closure to be a FnOnce-only closure
17-
// (with sig fn(self)). Without it, the closure sig would be fn(&self) which requires a
18-
// shim to call via FnOnce::call_once, and Miri's current shim doesn't correctly call
19-
// destructors.
20-
let v = vec![1];
2116
let x = Foo(&mut ran_drop);
22-
let g = move || {
23-
let _ = x;
24-
drop(v); // Force the closure to be FnOnce-only by using a capture by-value.
25-
};
26-
f(g);
17+
// this closure never by val uses its captures
18+
// so it's basically a fn(&self)
19+
// the shim used to not drop the `x`
20+
let x = move || { let _ = x; };
21+
f(x);
2722
}
2823
assert!(ran_drop);
2924
}

tests/run-pass/option_box_transmute_ptr.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ fn option_box_deref() -> i32 {
33
let val = Some(Box::new(42));
44
unsafe {
55
let ptr: *const i32 = std::mem::transmute::<Option<Box<i32>>, *const i32>(val);
6-
*ptr
6+
let ret = *ptr;
7+
// unleak memory
8+
std::mem::transmute::<*const i32, Option<Box<i32>>>(ptr);
9+
ret
710
}
811
}
912

0 commit comments

Comments
 (0)