Skip to content

Commit cd6e3e6

Browse files
committed
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
1 parent 720c5f8 commit cd6e3e6

File tree

5 files changed

+136
-67
lines changed

5 files changed

+136
-67
lines changed

src/bin/miri.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
8484
if i.attrs.iter().any(|attr| attr.name().map_or(false, |n| n == "test")) {
8585
let did = self.1.hir.body_owner_def_id(body_id);
8686
println!("running test: {}", self.1.hir.def_path(did).to_string(self.1));
87-
miri::eval_main(self.1, did, self.0);
87+
miri::eval_main(self.1, did, None, self.0);
8888
self.2.session.abort_if_errors();
8989
}
9090
}
@@ -95,7 +95,9 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
9595
state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(limits, tcx, state));
9696
} else if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() {
9797
let entry_def_id = tcx.hir.local_def_id(entry_node_id);
98-
miri::eval_main(tcx, entry_def_id, limits);
98+
let start_wrapper = tcx.lang_items.start_fn()
99+
.and_then(|start_fn| if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None });
100+
miri::eval_main(tcx, entry_def_id, start_wrapper, limits);
99101

100102
state.session.abort_if_errors();
101103
} else {

src/eval_context.rs

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ impl Default for ResourceLimits {
126126

127127
impl<'a, 'tcx> EvalContext<'a, 'tcx> {
128128
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, limits: ResourceLimits) -> Self {
129+
// Register array drop glue code
129130
let source_info = mir::SourceInfo {
130131
span: DUMMY_SP,
131132
scope: mir::ARGUMENT_VISIBILITY_SCOPE
@@ -852,7 +853,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
852853
let fn_ptr = self.memory.create_fn_alloc(instance);
853854
self.write_value(Value::ByVal(PrimVal::Ptr(fn_ptr)), dest, dest_ty)?;
854855
},
855-
ref other => bug!("reify fn pointer on {:?}", other),
856+
ref other => bug!("closure fn pointer on {:?}", other),
856857
},
857858
}
858859
}
@@ -1676,62 +1677,120 @@ impl<'tcx> Frame<'tcx> {
16761677

16771678
pub fn eval_main<'a, 'tcx: 'a>(
16781679
tcx: TyCtxt<'a, 'tcx, 'tcx>,
1679-
def_id: DefId,
1680+
main_id: DefId,
1681+
start_wrapper: Option<DefId>,
16801682
limits: ResourceLimits,
16811683
) {
1682-
let mut ecx = EvalContext::new(tcx, limits);
1683-
let instance = ty::Instance::mono(tcx, def_id);
1684-
let mir = ecx.load_mir(instance.def).expect("main function's MIR not found");
1685-
1686-
if !mir.return_ty.is_nil() || mir.arg_count != 0 {
1687-
let msg = "miri does not support main functions without `fn()` type signatures";
1688-
tcx.sess.err(&EvalError::Unimplemented(String::from(msg)).to_string());
1689-
return;
1690-
}
1691-
1692-
ecx.push_stack_frame(
1693-
instance,
1694-
DUMMY_SP,
1695-
mir,
1696-
Lvalue::from_ptr(Pointer::zst_ptr()),
1697-
StackPopCleanup::None,
1698-
).expect("could not allocate first stack frame");
1699-
1700-
loop {
1701-
match ecx.step() {
1702-
Ok(true) => {}
1703-
Ok(false) => {
1704-
let leaks = ecx.memory.leak_report();
1705-
if leaks != 0 {
1706-
tcx.sess.err("the evaluated program leaked memory");
1707-
}
1708-
return;
1684+
fn run_main<'a, 'tcx: 'a>(
1685+
ecx: &mut EvalContext<'a, 'tcx>,
1686+
main_id: DefId,
1687+
start_wrapper: Option<DefId>,
1688+
) -> EvalResult<'tcx> {
1689+
let main_instance = ty::Instance::mono(ecx.tcx, main_id);
1690+
let main_mir = ecx.load_mir(main_instance.def)?;
1691+
1692+
if !main_mir.return_ty.is_nil() || main_mir.arg_count != 0 {
1693+
return Err(EvalError::Unimplemented("miri does not support main functions without `fn()` type signatures".to_owned()));
1694+
}
1695+
1696+
if let Some(start_id) = start_wrapper {
1697+
let start_instance = ty::Instance::mono(ecx.tcx, start_id);
1698+
let start_mir = ecx.load_mir(start_instance.def)?;
1699+
1700+
if start_mir.arg_count != 3 {
1701+
return Err(EvalError::AbiViolation(format!("'start' lang item should have three arguments, but has {}", start_mir.arg_count)));
17091702
}
1710-
Err(e) => {
1711-
report(tcx, &ecx, e);
1712-
return;
1703+
1704+
// Push our stack frame
1705+
ecx.push_stack_frame(
1706+
start_instance,
1707+
start_mir.span,
1708+
start_mir,
1709+
Lvalue::from_ptr(Pointer::zst_ptr()), // we'll fix the return lvalue later
1710+
StackPopCleanup::None,
1711+
)?;
1712+
1713+
let mut args = ecx.frame().mir.args_iter();
1714+
1715+
// First argument: pointer to main()
1716+
let main_ptr = ecx.memory.create_fn_alloc(main_instance);
1717+
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
1718+
let main_ty = main_instance.def.def_ty(ecx.tcx);
1719+
let main_ptr_ty = ecx.tcx.mk_fn_ptr(main_ty.fn_sig());
1720+
ecx.write_value(Value::ByVal(PrimVal::Ptr(main_ptr)), dest, main_ptr_ty)?;
1721+
1722+
// Second argument (argc): 0
1723+
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
1724+
let ty = ecx.tcx.types.isize;
1725+
ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?;
1726+
1727+
// Third argument (argv): 0
1728+
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
1729+
let ty = ecx.tcx.mk_imm_ptr(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8));
1730+
ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?;
1731+
} else {
1732+
ecx.push_stack_frame(
1733+
main_instance,
1734+
main_mir.span,
1735+
main_mir,
1736+
Lvalue::from_ptr(Pointer::zst_ptr()),
1737+
StackPopCleanup::None,
1738+
)?;
1739+
}
1740+
1741+
// Allocate memory for the return value. We have to do this when a stack frame was already pushed as the type code below
1742+
// calls EvalContext::substs, which needs a frame to be allocated (?!?)
1743+
let ret_ptr = {
1744+
let ty = ecx.tcx.types.isize;
1745+
let layout = ecx.type_layout(ty)?;
1746+
let size = layout.size(&ecx.tcx.data_layout).bytes();
1747+
let align = layout.align(&ecx.tcx.data_layout).pref(); // FIXME is this right?
1748+
ecx.memory.allocate(size, align)?
1749+
};
1750+
ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr);
1751+
1752+
loop {
1753+
if !ecx.step()? {
1754+
ecx.memory.deallocate(ret_ptr)?;
1755+
return Ok(());
1756+
}
1757+
}
1758+
}
1759+
1760+
let mut ecx = EvalContext::new(tcx, limits);
1761+
match run_main(&mut ecx, main_id, start_wrapper) {
1762+
Ok(()) => {
1763+
let leaks = ecx.memory.leak_report();
1764+
if leaks != 0 {
1765+
tcx.sess.err("the evaluated program leaked memory");
17131766
}
17141767
}
1768+
Err(e) => {
1769+
report(tcx, &ecx, e);
1770+
}
17151771
}
17161772
}
17171773

17181774
fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) {
1719-
let frame = ecx.stack().last().expect("stackframe was empty");
1720-
let block = &frame.mir.basic_blocks()[frame.block];
1721-
let span = if frame.stmt < block.statements.len() {
1722-
block.statements[frame.stmt].source_info.span
1723-
} else {
1724-
block.terminator().source_info.span
1725-
};
1726-
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
1727-
for &Frame { instance, span, .. } in ecx.stack().iter().rev() {
1728-
if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
1729-
err.span_note(span, "inside call to closure");
1730-
continue;
1775+
if let Some(frame) = ecx.stack().last() {
1776+
let block = &frame.mir.basic_blocks()[frame.block];
1777+
let span = if frame.stmt < block.statements.len() {
1778+
block.statements[frame.stmt].source_info.span
1779+
} else {
1780+
block.terminator().source_info.span
1781+
};
1782+
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
1783+
for &Frame { instance, span, .. } in ecx.stack().iter().rev() {
1784+
if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
1785+
err.span_note(span, "inside call to closure");
1786+
continue;
1787+
}
1788+
err.span_note(span, &format!("inside call to {}", instance));
17311789
}
1732-
err.span_note(span, &format!("inside call to {}", instance));
1790+
err.emit();
1791+
} else {
1792+
tcx.sess.err(&e.to_string());
17331793
}
1734-
err.emit();
17351794
}
17361795

17371796
// TODO(solson): Upstream these methods into rustc::ty::layout.

src/step.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
4343
Lvalue::from_ptr(Pointer::zst_ptr()),
4444
StackPopCleanup::None,
4545
)?;
46-
if let Some(arg_local) = self.frame().mir.args_iter().next() {
47-
let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?;
48-
let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8);
49-
self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?;
50-
} else {
51-
return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()));
52-
}
53-
46+
let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()))?;
47+
let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?;
48+
let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8);
49+
self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?;
5450
return Ok(true);
5551
}
5652
return Ok(false);

src/terminator/mod.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -637,23 +637,33 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
637637
self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?;
638638
}
639639

640-
// unix panic code inside libstd will read the return value of this function
641-
"pthread_rwlock_rdlock" => {
640+
// Some things needed for sys::thread initialization to go through
641+
"signal" | "sigaction" | "sigaltstack" => {
642642
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
643643
}
644644

645+
"sysconf" => {
646+
let name = self.value_to_primval(args[0], usize)?.to_u64()?;
647+
trace!("sysconf() called with name {}", name);
648+
let result = match name {
649+
30 => 4096, // _SC_PAGESIZE
650+
_ => return Err(EvalError::Unimplemented(format!("Unimplemented sysconf name: {}", name)))
651+
};
652+
self.write_primval(dest, PrimVal::Bytes(result), dest_ty)?;
653+
}
654+
655+
"mmap" => {
656+
// 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
657+
let addr = args[0].read_ptr(&self.memory)?;
658+
self.write_primval(dest, PrimVal::Ptr(addr), dest_ty)?;
659+
}
660+
645661
// Hook pthread calls that go to the thread-local storage memory subsystem
646662
"pthread_key_create" => {
647663
let key_ptr = args[0].read_ptr(&self.memory)?;
648664

649665
// Extract the function type out of the signature (that seems easier than constructing it ourselves...)
650-
let dtor_fn_ty = match self.operand_ty(&arg_operands[1]).sty {
651-
TypeVariants::TyAdt(_, ref substs) => {
652-
substs.type_at(0)
653-
}
654-
_ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned()))
655-
};
656-
let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?;
666+
let dtor_ptr = args[1].read_ptr(&self.memory)?;
657667
// TODO: The null-pointer case here is entirely untested
658668
let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) };
659669

@@ -699,8 +709,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
699709
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
700710
}
701711

712+
// Stub out all the other pthread calls to just return 0
702713
link_name if link_name.starts_with("pthread_") => {
703714
warn!("ignoring C ABI call: {}", link_name);
715+
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
704716
},
705717

706718
_ => {

tests/compile-fail/oom.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![feature(custom_attribute, attr_literals)]
2-
#![miri(memory_size=0)]
2+
#![miri(memory_size=20)]
33

44
fn main() {
55
let _x = [42; 10];
6-
//~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory
6+
//~^ERROR tried to allocate 40 more bytes, but only
77
}

0 commit comments

Comments
 (0)