Skip to content

limit stack size, memory size and execution time #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 7, 2016
37 changes: 35 additions & 2 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use miri::{eval_main, run_mir_passes};
use rustc::session::Session;
use rustc::mir::mir_map::MirMap;
use rustc_driver::{driver, CompilerCalls, Compilation};
use syntax::ast::MetaItemKind;

struct MiriCompilerCalls;

Expand All @@ -23,7 +24,9 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
_: &getopts::Matches
) -> driver::CompileController<'a> {
let mut control = driver::CompileController::basic();

control.after_hir_lowering.callback = Box::new(|state| {
state.session.plugin_attributes.borrow_mut().push(("miri".to_owned(), syntax::feature_gate::AttributeType::Whitelisted));
});
control.after_analysis.stop = Compilation::Stop;
control.after_analysis.callback = Box::new(|state| {
state.session.abort_if_errors();
Expand All @@ -33,9 +36,39 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
let (node_id, _) = state.session.entry_fn.borrow()
.expect("no main or start function found");

let krate = state.hir_crate.as_ref().unwrap();
let mut memory_size = 100*1024*1024; // 100MB
let mut step_limit = 1000_000;
let mut stack_limit = 100;
fn extract_str(lit: &syntax::ast::Lit) -> syntax::parse::token::InternedString {
match lit.node {
syntax::ast::LitKind::Str(ref s, _) => s.clone(),
_ => panic!("attribute values need to be strings"),
}
}
for attr in krate.attrs.iter() {
match attr.node.value.node {
MetaItemKind::List(ref name, _) if name != "miri" => {}
MetaItemKind::List(_, ref items) => for item in items {
match item.node {
MetaItemKind::NameValue(ref name, ref value) => {
match &**name {
"memory_size" => memory_size = extract_str(value).parse().expect("not a number"),
"step_limit" => step_limit = extract_str(value).parse().expect("not a number"),
"stack_limit" => stack_limit = extract_str(value).parse().expect("not a number"),
_ => state.session.span_err(item.span, "unknown miri attribute"),
}
}
_ => state.session.span_err(item.span, "miri attributes need to be of key = value kind"),
}
},
_ => {},
}
}

let mut mir_map = MirMap { map: mir_map.map.clone() };
run_mir_passes(tcx, &mut mir_map);
eval_main(tcx, &mir_map, node_id);
eval_main(tcx, &mir_map, node_id, memory_size, step_limit, stack_limit);

state.session.abort_if_errors();
});
Expand Down
16 changes: 16 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ pub enum EvalError<'tcx> {
ArrayIndexOutOfBounds(Span, u64, u64),
Math(Span, ConstMathErr),
InvalidChar(u32),
OutOfMemory {
allocation_size: usize,
memory_size: usize,
memory_usage: usize,
},
ExecutionTimeLimitReached,
StackFrameLimitReached,
}

pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
Expand Down Expand Up @@ -69,6 +76,12 @@ impl<'tcx> Error for EvalError<'tcx> {
"mathematical operation failed",
EvalError::InvalidChar(..) =>
"tried to interpret an invalid 32-bit value as a char",
EvalError::OutOfMemory{..} =>
"could not allocate more memory",
EvalError::ExecutionTimeLimitReached =>
"reached the configured maximum execution time",
EvalError::StackFrameLimitReached =>
"reached the configured maximum number of stack frames",
}
}

Expand All @@ -90,6 +103,9 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
write!(f, "{:?} at {:?}", err, span),
EvalError::InvalidChar(c) =>
write!(f, "tried to interpret an invalid 32-bit value as a char: {}", c),
EvalError::OutOfMemory { allocation_size, memory_size, memory_usage } =>
write!(f, "tried to allocate {} more bytes, but only {} bytes are free of the {} byte memory",
allocation_size, memory_size - memory_usage, memory_size),
_ => write!(f, "{}", self.description()),
}
}
Expand Down
79 changes: 50 additions & 29 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct EvalContext<'a, 'tcx: 'a> {

/// The virtual call stack.
stack: Vec<Frame<'a, 'tcx>>,

/// The maximum number of stack frames allowed
stack_limit: usize,
}

/// A stack frame.
Expand Down Expand Up @@ -133,24 +136,25 @@ enum ConstantKind {
}

impl<'a, 'tcx> EvalContext<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>) -> Self {
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, memory_size: usize, stack_limit: usize) -> Self {
EvalContext {
tcx: tcx,
mir_map: mir_map,
mir_cache: RefCell::new(DefIdMap()),
memory: Memory::new(&tcx.data_layout),
memory: Memory::new(&tcx.data_layout, memory_size),
statics: HashMap::new(),
stack: Vec::new(),
stack_limit: stack_limit,
}
}

pub fn alloc_ret_ptr(&mut self, output_ty: ty::FnOutput<'tcx>, substs: &'tcx Substs<'tcx>) -> Option<Pointer> {
pub fn alloc_ret_ptr(&mut self, output_ty: ty::FnOutput<'tcx>, substs: &'tcx Substs<'tcx>) -> EvalResult<'tcx, Option<Pointer>> {
match output_ty {
ty::FnConverging(ty) => {
let size = self.type_size_with_substs(ty, substs);
Some(self.memory.allocate(size))
self.memory.allocate(size).map(Some)
}
ty::FnDiverging => None,
ty::FnDiverging => Ok(None),
}
}

Expand All @@ -172,19 +176,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
use rustc_const_math::{ConstInt, ConstIsize, ConstUsize, ConstFloat};
macro_rules! i2p {
($i:ident, $n:expr) => {{
let ptr = self.memory.allocate($n);
let ptr = self.memory.allocate($n)?;
self.memory.write_int(ptr, $i as i64, $n)?;
Ok(ptr)
}}
}
match *const_val {
Float(ConstFloat::F32(f)) => {
let ptr = self.memory.allocate(4);
let ptr = self.memory.allocate(4)?;
self.memory.write_f32(ptr, f)?;
Ok(ptr)
},
Float(ConstFloat::F64(f)) => {
let ptr = self.memory.allocate(8);
let ptr = self.memory.allocate(8)?;
self.memory.write_f64(ptr, f)?;
Ok(ptr)
},
Expand All @@ -207,28 +211,28 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Integral(ConstInt::Usize(ConstUsize::Us64(i))) => i2p!(i, 8),
Str(ref s) => {
let psize = self.memory.pointer_size();
let static_ptr = self.memory.allocate(s.len());
let ptr = self.memory.allocate(psize * 2);
let static_ptr = self.memory.allocate(s.len())?;
let ptr = self.memory.allocate(psize * 2)?;
self.memory.write_bytes(static_ptr, s.as_bytes())?;
self.memory.write_ptr(ptr, static_ptr)?;
self.memory.write_usize(ptr.offset(psize as isize), s.len() as u64)?;
Ok(ptr)
}
ByteStr(ref bs) => {
let psize = self.memory.pointer_size();
let static_ptr = self.memory.allocate(bs.len());
let ptr = self.memory.allocate(psize);
let static_ptr = self.memory.allocate(bs.len())?;
let ptr = self.memory.allocate(psize)?;
self.memory.write_bytes(static_ptr, bs)?;
self.memory.write_ptr(ptr, static_ptr)?;
Ok(ptr)
}
Bool(b) => {
let ptr = self.memory.allocate(1);
let ptr = self.memory.allocate(1)?;
self.memory.write_bool(ptr, b)?;
Ok(ptr)
}
Char(c) => {
let ptr = self.memory.allocate(4);
let ptr = self.memory.allocate(4)?;
self.memory.write_uint(ptr, c as u64, 4)?;
Ok(ptr)
},
Expand Down Expand Up @@ -292,9 +296,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
})
}

pub fn push_stack_frame(&mut self, def_id: DefId, span: codemap::Span, mir: CachedMir<'a, 'tcx>, substs: &'tcx Substs<'tcx>,
return_ptr: Option<Pointer>)
{
pub fn push_stack_frame(
&mut self,
def_id: DefId,
span: codemap::Span,
mir: CachedMir<'a, 'tcx>,
substs: &'tcx Substs<'tcx>,
return_ptr: Option<Pointer>,
) -> EvalResult<'tcx, ()> {
let arg_tys = mir.arg_decls.iter().map(|a| a.ty);
let var_tys = mir.var_decls.iter().map(|v| v.ty);
let temp_tys = mir.temp_decls.iter().map(|t| t.ty);
Expand All @@ -304,7 +313,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {

::log_settings::settings().indentation += 1;

let locals: Vec<Pointer> = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| {
let locals: EvalResult<'tcx, Vec<Pointer>> = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| {
let size = self.type_size_with_substs(ty, substs);
self.memory.allocate(size)
}).collect();
Expand All @@ -313,14 +322,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
mir: mir.clone(),
block: mir::START_BLOCK,
return_ptr: return_ptr,
locals: locals,
locals: locals?,
var_offset: num_args,
temp_offset: num_args + num_vars,
span: span,
def_id: def_id,
substs: substs,
stmt: 0,
});
if self.stack.len() > self.stack_limit {
Err(EvalError::StackFrameLimitReached)
} else {
Ok(())
}
}

fn pop_stack_frame(&mut self) {
Expand Down Expand Up @@ -548,7 +562,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {

Box(ty) => {
let size = self.type_size(ty);
let ptr = self.memory.allocate(size);
let ptr = self.memory.allocate(size)?;
self.memory.write_ptr(dest, ptr)?;
}

Expand Down Expand Up @@ -696,7 +710,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Item { def_id, substs } => {
if let ty::TyFnDef(..) = ty.sty {
// function items are zero sized
Ok(self.memory.allocate(0))
Ok(self.memory.allocate(0)?)
} else {
let cid = ConstantId {
def_id: def_id,
Expand Down Expand Up @@ -935,37 +949,44 @@ pub fn eval_main<'a, 'tcx: 'a>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
mir_map: &'a MirMap<'tcx>,
node_id: ast::NodeId,
memory_size: usize,
step_limit: u64,
stack_limit: usize,
) {
let mir = mir_map.map.get(&node_id).expect("no mir for main function");
let def_id = tcx.map.local_def_id(node_id);
let mut ecx = EvalContext::new(tcx, mir_map);
let mut ecx = EvalContext::new(tcx, mir_map, memory_size, stack_limit);
let substs = tcx.mk_substs(subst::Substs::empty());
let return_ptr = ecx.alloc_ret_ptr(mir.return_ty, substs).expect("main function should not be diverging");
let return_ptr = ecx.alloc_ret_ptr(mir.return_ty, substs)
.expect("should at least be able to allocate space for the main function's return value")
.expect("main function should not be diverging");

ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr));
ecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, Some(return_ptr))
.expect("could not allocate first stack frame");

if mir.arg_decls.len() == 2 {
// start function
let ptr_size = ecx.memory().pointer_size();
let nargs = ecx.memory_mut().allocate(ptr_size);
let nargs = ecx.memory_mut().allocate(ptr_size).expect("can't allocate memory for nargs");
ecx.memory_mut().write_usize(nargs, 0).unwrap();
let args = ecx.memory_mut().allocate(ptr_size);
let args = ecx.memory_mut().allocate(ptr_size).expect("can't allocate memory for arg pointer");
ecx.memory_mut().write_usize(args, 0).unwrap();
ecx.frame_mut().locals[0] = nargs;
ecx.frame_mut().locals[1] = args;
}

loop {
for _ in 0..step_limit {
match ecx.step() {
Ok(true) => {}
Ok(false) => break,
Ok(false) => return,
// FIXME: diverging functions can end up here in some future miri
Err(e) => {
report(tcx, &ecx, e);
break;
return;
}
}
}
report(tcx, &ecx, EvalError::ExecutionTimeLimitReached);
}

fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) {
Expand Down
Loading