From 756fbcce480c3ab93a735436f7f27bf38a0dfb10 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 10:47:10 +0200 Subject: [PATCH 01/10] add a memory limit --- src/error.rs | 10 +++++++ src/interpreter/mod.rs | 53 ++++++++++++++++++++--------------- src/interpreter/step.rs | 41 +++++++++++++++++++-------- src/interpreter/terminator.rs | 4 +-- src/memory.rs | 36 ++++++++++++++++++------ 5 files changed, 101 insertions(+), 43 deletions(-) diff --git a/src/error.rs b/src/error.rs index da5d8cf787f4..4467347b49bc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,6 +29,11 @@ pub enum EvalError<'tcx> { ArrayIndexOutOfBounds(Span, u64, u64), Math(Span, ConstMathErr), InvalidChar(u32), + OutOfMemory { + allocation_size: u64, + memory_size: u64, + memory_usage: u64, + } } pub type EvalResult<'tcx, T> = Result>; @@ -69,6 +74,8 @@ 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" } } @@ -90,6 +97,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()), } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 4e564005f23f..66d147e2d8b4 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -138,19 +138,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { tcx: tcx, mir_map: mir_map, mir_cache: RefCell::new(DefIdMap()), - memory: Memory::new(&tcx.data_layout), + memory: Memory::new(&tcx.data_layout, 100*1024*1024 /* 100MB */), statics: HashMap::new(), stack: Vec::new(), } } - pub fn alloc_ret_ptr(&mut self, output_ty: ty::FnOutput<'tcx>, substs: &'tcx Substs<'tcx>) -> Option { + pub fn alloc_ret_ptr(&mut self, output_ty: ty::FnOutput<'tcx>, substs: &'tcx Substs<'tcx>) -> EvalResult<'tcx, Option> { 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), } } @@ -172,7 +172,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { use rustc_const_math::{ConstInt, ConstIsize, ConstUsize}; 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) }} @@ -197,8 +197,8 @@ 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)?; @@ -206,19 +206,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } 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) }, @@ -282,9 +282,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) - { + pub fn push_stack_frame( + &mut self, + def_id: DefId, + span: codemap::Span, + mir: CachedMir<'a, 'tcx>, + substs: &'tcx Substs<'tcx>, + return_ptr: Option, + ) -> 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); @@ -294,7 +299,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ::log_settings::settings().indentation += 1; - let locals: Vec = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| { + let locals: EvalResult<'tcx, Vec> = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| { let size = self.type_size_with_substs(ty, substs); self.memory.allocate(size) }).collect(); @@ -303,7 +308,7 @@ 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, @@ -311,6 +316,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { substs: substs, stmt: 0, }); + Ok(()) } fn pop_stack_frame(&mut self) { @@ -538,7 +544,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)?; } @@ -686,7 +692,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, @@ -927,16 +933,19 @@ pub fn eval_main<'a, 'tcx: 'a>( let def_id = tcx.map.local_def_id(node_id); let mut ecx = EvalContext::new(tcx, mir_map); 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; diff --git a/src/interpreter/step.rs b/src/interpreter/step.rs index 6d4d8aad2cc0..5bfe85fff92f 100644 --- a/src/interpreter/step.rs +++ b/src/interpreter/step.rs @@ -29,15 +29,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let basic_block = &mir.basic_blocks()[block]; if let Some(ref stmt) = basic_block.statements.get(stmt) { - let current_stack = self.stack.len(); + let mut new = Ok(0); ConstantExtractor { span: stmt.source_info.span, substs: self.substs(), def_id: self.frame().def_id, ecx: self, mir: &mir, + new_constants: &mut new, }.visit_statement(block, stmt); - if current_stack == self.stack.len() { + if new? == 0 { self.statement(stmt)?; } // if ConstantExtractor added new frames, we don't execute anything here @@ -46,15 +47,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } let terminator = basic_block.terminator(); - let current_stack = self.stack.len(); + let mut new = Ok(0); ConstantExtractor { span: terminator.source_info.span, substs: self.substs(), def_id: self.frame().def_id, ecx: self, mir: &mir, + new_constants: &mut new, }.visit_terminator(block, terminator); - if current_stack == self.stack.len() { + if new? == 0 { self.terminator(terminator)?; } // if ConstantExtractor added new frames, we don't execute anything here @@ -92,6 +94,7 @@ struct ConstantExtractor<'a, 'b: 'a, 'tcx: 'b> { mir: &'a mir::Mir<'tcx>, def_id: DefId, substs: &'tcx subst::Substs<'tcx>, + new_constants: &'a mut EvalResult<'tcx, u64>, } impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { @@ -105,9 +108,22 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { return; } let mir = self.ecx.load_mir(def_id); - let ptr = self.ecx.alloc_ret_ptr(mir.return_ty, substs).expect("there's no such thing as an unreachable static"); - self.ecx.statics.insert(cid.clone(), ptr); - self.ecx.push_stack_frame(def_id, span, mir, substs, Some(ptr)); + self.try(|this| { + let ptr = this.ecx.alloc_ret_ptr(mir.return_ty, substs)?; + let ptr = ptr.expect("there's no such thing as an unreachable static"); + this.ecx.statics.insert(cid.clone(), ptr); + this.ecx.push_stack_frame(def_id, span, mir, substs, Some(ptr)) + }); + } + fn try EvalResult<'tcx, ()>>(&mut self, f: F) { + if let Ok(ref mut n) = *self.new_constants { + *n += 1; + } else { + return; + } + if let Err(e) = f(self) { + *self.new_constants = Err(e); + } } } @@ -137,10 +153,13 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> { } let mir = self.mir.promoted[index].clone(); let return_ty = mir.return_ty; - let return_ptr = self.ecx.alloc_ret_ptr(return_ty, cid.substs).expect("there's no such thing as an unreachable static"); - let mir = CachedMir::Owned(Rc::new(mir)); - self.ecx.statics.insert(cid.clone(), return_ptr); - self.ecx.push_stack_frame(self.def_id, constant.span, mir, self.substs, Some(return_ptr)); + self.try(|this| { + let return_ptr = this.ecx.alloc_ret_ptr(return_ty, cid.substs)?; + let return_ptr = return_ptr.expect("there's no such thing as an unreachable static"); + let mir = CachedMir::Owned(Rc::new(mir)); + this.ecx.statics.insert(cid.clone(), return_ptr); + this.ecx.push_stack_frame(this.def_id, constant.span, mir, this.substs, Some(return_ptr)) + }); } } } diff --git a/src/interpreter/terminator.rs b/src/interpreter/terminator.rs index 8f5ec0e1b597..2fbc9a63ef68 100644 --- a/src/interpreter/terminator.rs +++ b/src/interpreter/terminator.rs @@ -212,7 +212,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } let mir = self.load_mir(resolved_def_id); - self.push_stack_frame(def_id, span, mir, resolved_substs, return_ptr); + self.push_stack_frame(def_id, span, mir, resolved_substs, return_ptr)?; for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() { let dest = self.frame().locals[i]; @@ -416,7 +416,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { match &link_name[..] { "__rust_allocate" => { let size = self.memory.read_usize(args[0])?; - let ptr = self.memory.allocate(size as usize); + let ptr = self.memory.allocate(size as usize)?; self.memory.write_ptr(dest, ptr)?; } diff --git a/src/memory.rs b/src/memory.rs index f3072b886b81..2453b1f6e67a 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -66,6 +66,10 @@ pub struct FunctionDefinition<'tcx> { pub struct Memory<'a, 'tcx> { /// Actual memory allocations (arbitrary bytes, may contain pointers into other allocations) alloc_map: HashMap, + /// Number of virtual bytes allocated + memory_usage: u64, + /// Maximum number of virtual bytes that may be allocated + memory_size: u64, /// Function "allocations". They exist solely so pointers have something to point to, and /// we can figure out what they point to. functions: HashMap>, @@ -78,13 +82,15 @@ pub struct Memory<'a, 'tcx> { const ZST_ALLOC_ID: AllocId = AllocId(0); impl<'a, 'tcx> Memory<'a, 'tcx> { - pub fn new(layout: &'a TargetDataLayout) -> Self { + pub fn new(layout: &'a TargetDataLayout, max_memory: u64) -> Self { let mut mem = Memory { alloc_map: HashMap::new(), functions: HashMap::new(), function_alloc_cache: HashMap::new(), next_id: AllocId(1), layout: layout, + memory_size: max_memory, + memory_usage: 0, }; // alloc id 0 is reserved for ZSTs, this is an optimization to prevent ZST // (e.g. function items, (), [], ...) from requiring memory @@ -95,7 +101,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { }; mem.alloc_map.insert(ZST_ALLOC_ID, alloc); // check that additional zst allocs work - debug_assert!(mem.allocate(0).points_to_zst()); + debug_assert!(mem.allocate(0).unwrap().points_to_zst()); debug_assert!(mem.get(ZST_ALLOC_ID).is_ok()); mem } @@ -127,10 +133,18 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } - pub fn allocate(&mut self, size: usize) -> Pointer { + pub fn allocate(&mut self, size: usize) -> EvalResult<'tcx, Pointer> { if size == 0 { - return Pointer::zst_ptr(); + return Ok(Pointer::zst_ptr()); } + if self.memory_size - self.memory_usage < size as u64 { + return Err(EvalError::OutOfMemory { + allocation_size: size as u64, + memory_size: self.memory_size, + memory_usage: self.memory_usage, + }); + } + self.memory_usage += size as u64; let alloc = Allocation { bytes: vec![0; size], relocations: BTreeMap::new(), @@ -139,10 +153,10 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let id = self.next_id; self.next_id.0 += 1; self.alloc_map.insert(id, alloc); - Pointer { + Ok(Pointer { alloc_id: id, offset: 0, - } + }) } // TODO(solson): Track which allocations were returned from __rust_allocate and report an error @@ -153,17 +167,21 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); } if ptr.points_to_zst() { - return Ok(self.allocate(new_size)); + return self.allocate(new_size); } let size = self.get_mut(ptr.alloc_id)?.bytes.len(); if new_size > size { let amount = new_size - size; + self.memory_usage += amount as u64; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.extend(iter::repeat(0).take(amount)); alloc.undef_mask.grow(amount, false); } else if size > new_size { + // it's possible to cause miri to use arbitrary amounts of memory that aren't detectable + // through the memory_usage value, by allocating a lot and reallocating to zero + self.memory_usage -= (size - new_size) as u64; self.clear_relocations(ptr.offset(new_size as isize), size - new_size)?; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.truncate(new_size); @@ -183,7 +201,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { return Err(EvalError::Unimplemented(format!("bad pointer offset: {}", ptr.offset))); } - if self.alloc_map.remove(&ptr.alloc_id).is_none() { + if let Some(alloc) = self.alloc_map.remove(&ptr.alloc_id) { + self.memory_usage -= alloc.bytes.len() as u64; + } else { debug!("deallocated a pointer twice: {}", ptr.alloc_id); // TODO(solson): Report error about erroneous free. This is blocked on properly tracking // already-dropped state since this if-statement is entered even in safe code without From 1444cabc081f67ef30fbb86d6088754ec76163ac Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 13:04:46 +0200 Subject: [PATCH 02/10] make the memory limit configurable --- src/bin/miri.rs | 34 ++++++++++++++++++++++++++++++++-- src/interpreter/mod.rs | 7 ++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 4340729d7e02..087da20ac8be 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -4,6 +4,7 @@ extern crate getopts; extern crate miri; extern crate rustc; extern crate rustc_driver; +extern crate rustc_plugin; extern crate env_logger; extern crate log_settings; extern crate syntax; @@ -13,6 +14,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; @@ -23,7 +25,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(); @@ -33,9 +37,35 @@ 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 + 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"), + _ => 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); state.session.abort_if_errors(); }); diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 66d147e2d8b4..ea1f82f5f62a 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -133,12 +133,12 @@ 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: u64) -> Self { EvalContext { tcx: tcx, mir_map: mir_map, mir_cache: RefCell::new(DefIdMap()), - memory: Memory::new(&tcx.data_layout, 100*1024*1024 /* 100MB */), + memory: Memory::new(&tcx.data_layout, memory_size), statics: HashMap::new(), stack: Vec::new(), } @@ -928,10 +928,11 @@ pub fn eval_main<'a, 'tcx: 'a>( tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, node_id: ast::NodeId, + memory_size: u64, ) { 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); let substs = tcx.mk_substs(subst::Substs::empty()); 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") From 3e5d86bb0809cdc84e565880284965f1b06cf73f Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 13:04:53 +0200 Subject: [PATCH 03/10] test the memory limit --- tests/compile-fail/oom.rs | 11 +++++++++++ tests/compile-fail/oom2.rs | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/compile-fail/oom.rs create mode 100644 tests/compile-fail/oom2.rs diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs new file mode 100644 index 000000000000..83109a77e620 --- /dev/null +++ b/tests/compile-fail/oom.rs @@ -0,0 +1,11 @@ +#![feature(custom_attribute)] +#![miri(memory_size="0")] + +fn bar() { + let x = 5; + assert_eq!(x, 6); +} + +fn main() { //~ ERROR tried to allocate 4 more bytes, but only 0 bytes are free of the 0 byte memory + bar(); +} diff --git a/tests/compile-fail/oom2.rs b/tests/compile-fail/oom2.rs new file mode 100644 index 000000000000..63c51dbaa7d2 --- /dev/null +++ b/tests/compile-fail/oom2.rs @@ -0,0 +1,39 @@ +#![feature(custom_attribute)] +#![miri(memory_size="1000")] + +fn bar(i: i32) { + if i < 1000 { + bar(i + 1) //~ ERROR tried to allocate 4 more bytes, but only 1 bytes are free of the 1000 byte memory + //~^NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + //~|NOTE inside call to bar + } +} + +fn main() { //~NOTE inside call to main + bar(1); + //~^NOTE inside call to bar +} From 88d98998e1cfdfce8ce7e02a020cc9d19f318bd7 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 13:17:40 +0200 Subject: [PATCH 04/10] add execution time limit --- src/bin/miri.rs | 4 +++- src/error.rs | 7 +++++-- src/interpreter/mod.rs | 8 +++++--- tests/compile-fail/timeout.rs | 9 +++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 tests/compile-fail/timeout.rs diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 087da20ac8be..6997bbd011df 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -39,6 +39,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { let krate = state.hir_crate.as_ref().unwrap(); let mut memory_size = 100*1024*1024; // 100MB + let mut step_limit = 1000_000; fn extract_str(lit: &syntax::ast::Lit) -> syntax::parse::token::InternedString { match lit.node { syntax::ast::LitKind::Str(ref s, _) => s.clone(), @@ -53,6 +54,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { 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"), _ => state.session.span_err(item.span, "unknown miri attribute"), } } @@ -65,7 +67,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { let mut mir_map = MirMap { map: mir_map.map.clone() }; run_mir_passes(tcx, &mut mir_map); - eval_main(tcx, &mir_map, node_id, memory_size); + eval_main(tcx, &mir_map, node_id, memory_size, step_limit); state.session.abort_if_errors(); }); diff --git a/src/error.rs b/src/error.rs index 4467347b49bc..5c2410ed840a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,7 +33,8 @@ pub enum EvalError<'tcx> { allocation_size: u64, memory_size: u64, memory_usage: u64, - } + }, + ExecutionTimeLimitReached, } pub type EvalResult<'tcx, T> = Result>; @@ -75,7 +76,9 @@ impl<'tcx> Error for EvalError<'tcx> { EvalError::InvalidChar(..) => "tried to interpret an invalid 32-bit value as a char", EvalError::OutOfMemory{..} => - "could not allocate more memory" + "could not allocate more memory", + EvalError::ExecutionTimeLimitReached => + "reached the configured maximum execution time", } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index ea1f82f5f62a..5b8947ec7353 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -929,6 +929,7 @@ pub fn eval_main<'a, 'tcx: 'a>( mir_map: &'a MirMap<'tcx>, node_id: ast::NodeId, memory_size: u64, + step_limit: u64, ) { let mir = mir_map.map.get(&node_id).expect("no mir for main function"); let def_id = tcx.map.local_def_id(node_id); @@ -952,17 +953,18 @@ pub fn eval_main<'a, 'tcx: 'a>( 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) { diff --git a/tests/compile-fail/timeout.rs b/tests/compile-fail/timeout.rs new file mode 100644 index 000000000000..bcb6c993089a --- /dev/null +++ b/tests/compile-fail/timeout.rs @@ -0,0 +1,9 @@ +//error-pattern: reached the configured maximum execution time +#![feature(custom_attribute)] +#![miri(step_limit="1000")] + +fn main() { + for i in 0..1000000 { + assert!(i < 1000); + } +} From 4781a6ba5431e00b3bb6a5c085e4811f3b4d988f Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 5 Jul 2016 13:23:58 +0200 Subject: [PATCH 05/10] add attribute to limit the stack size --- src/bin/miri.rs | 4 +++- src/error.rs | 3 +++ src/interpreter/mod.rs | 16 +++++++++++++--- tests/compile-fail/stack_limit.rs | 20 ++++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/compile-fail/stack_limit.rs diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 6997bbd011df..e11ab5a86099 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -40,6 +40,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { 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(), @@ -55,6 +56,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { 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"), } } @@ -67,7 +69,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { let mut mir_map = MirMap { map: mir_map.map.clone() }; run_mir_passes(tcx, &mut mir_map); - eval_main(tcx, &mir_map, node_id, memory_size, step_limit); + eval_main(tcx, &mir_map, node_id, memory_size, step_limit, stack_limit); state.session.abort_if_errors(); }); diff --git a/src/error.rs b/src/error.rs index 5c2410ed840a..9952cfd9a7e8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,6 +35,7 @@ pub enum EvalError<'tcx> { memory_usage: u64, }, ExecutionTimeLimitReached, + StackFrameLimitReached, } pub type EvalResult<'tcx, T> = Result>; @@ -79,6 +80,8 @@ impl<'tcx> Error for EvalError<'tcx> { "could not allocate more memory", EvalError::ExecutionTimeLimitReached => "reached the configured maximum execution time", + EvalError::StackFrameLimitReached => + "reached the configured maximum number of stack frames", } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 5b8947ec7353..34fa9d8de6fd 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -42,6 +42,9 @@ pub struct EvalContext<'a, 'tcx: 'a> { /// The virtual call stack. stack: Vec>, + + /// The maximum number of stack frames allowed + stack_limit: usize, } /// A stack frame. @@ -133,7 +136,8 @@ enum ConstantKind { } impl<'a, 'tcx> EvalContext<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, memory_size: u64) -> Self { + pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, memory_size: u64, stack_limit: u64) -> Self { + assert_eq!(stack_limit as usize as u64, stack_limit); EvalContext { tcx: tcx, mir_map: mir_map, @@ -141,6 +145,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { memory: Memory::new(&tcx.data_layout, memory_size), statics: HashMap::new(), stack: Vec::new(), + stack_limit: stack_limit as usize, } } @@ -316,7 +321,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { substs: substs, stmt: 0, }); - Ok(()) + if self.stack.len() > self.stack_limit { + Err(EvalError::StackFrameLimitReached) + } else { + Ok(()) + } } fn pop_stack_frame(&mut self) { @@ -930,10 +939,11 @@ pub fn eval_main<'a, 'tcx: 'a>( node_id: ast::NodeId, memory_size: u64, step_limit: u64, + stack_limit: u64, ) { 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, memory_size); + 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("should at least be able to allocate space for the main function's return value") diff --git a/tests/compile-fail/stack_limit.rs b/tests/compile-fail/stack_limit.rs new file mode 100644 index 000000000000..3b6d4186dc90 --- /dev/null +++ b/tests/compile-fail/stack_limit.rs @@ -0,0 +1,20 @@ +#![feature(custom_attribute)] +#![miri(stack_limit="2")] + +fn bar() { + foo(); +} + +fn foo() { + cake(); //~ ERROR reached the configured maximum number of stack frames +} + +fn cake() { + flubber(); +} + +fn flubber() {} + +fn main() { + bar(); +} From 7d2803ae3fa1d5aed6efa56d9d60776fd62d644c Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 7 Jul 2016 11:19:55 +0200 Subject: [PATCH 06/10] remove unused extern crate --- src/bin/miri.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index e11ab5a86099..f5b0c6fbf679 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -4,7 +4,6 @@ extern crate getopts; extern crate miri; extern crate rustc; extern crate rustc_driver; -extern crate rustc_plugin; extern crate env_logger; extern crate log_settings; extern crate syntax; From a7d3a85d9e22e978e8156c2ad57b9d3c4a67dd13 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 7 Jul 2016 11:20:09 +0200 Subject: [PATCH 07/10] infer type of the various limits --- src/bin/miri.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index f5b0c6fbf679..14a947302c65 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -53,9 +53,9 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls { 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"), + "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"), } } From 8d3817cfc63674db308e56371ed8f1cc32af4d92 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 7 Jul 2016 11:20:46 +0200 Subject: [PATCH 08/10] use usize instead of u64 for memory limits --- src/error.rs | 6 +++--- src/interpreter/mod.rs | 9 ++++----- src/memory.rs | 18 +++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9952cfd9a7e8..2d723e692352 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,9 +30,9 @@ pub enum EvalError<'tcx> { Math(Span, ConstMathErr), InvalidChar(u32), OutOfMemory { - allocation_size: u64, - memory_size: u64, - memory_usage: u64, + allocation_size: usize, + memory_size: usize, + memory_usage: usize, }, ExecutionTimeLimitReached, StackFrameLimitReached, diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 34fa9d8de6fd..04d7d9f9d050 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -136,8 +136,7 @@ enum ConstantKind { } impl<'a, 'tcx> EvalContext<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, memory_size: u64, stack_limit: u64) -> Self { - assert_eq!(stack_limit as usize as u64, stack_limit); + 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, @@ -145,7 +144,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { memory: Memory::new(&tcx.data_layout, memory_size), statics: HashMap::new(), stack: Vec::new(), - stack_limit: stack_limit as usize, + stack_limit: stack_limit, } } @@ -937,9 +936,9 @@ pub fn eval_main<'a, 'tcx: 'a>( tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>, node_id: ast::NodeId, - memory_size: u64, + memory_size: usize, step_limit: u64, - stack_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); diff --git a/src/memory.rs b/src/memory.rs index 2453b1f6e67a..79130b4e24b1 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -67,9 +67,9 @@ pub struct Memory<'a, 'tcx> { /// Actual memory allocations (arbitrary bytes, may contain pointers into other allocations) alloc_map: HashMap, /// Number of virtual bytes allocated - memory_usage: u64, + memory_usage: usize, /// Maximum number of virtual bytes that may be allocated - memory_size: u64, + memory_size: usize, /// Function "allocations". They exist solely so pointers have something to point to, and /// we can figure out what they point to. functions: HashMap>, @@ -82,7 +82,7 @@ pub struct Memory<'a, 'tcx> { const ZST_ALLOC_ID: AllocId = AllocId(0); impl<'a, 'tcx> Memory<'a, 'tcx> { - pub fn new(layout: &'a TargetDataLayout, max_memory: u64) -> Self { + pub fn new(layout: &'a TargetDataLayout, max_memory: usize) -> Self { let mut mem = Memory { alloc_map: HashMap::new(), functions: HashMap::new(), @@ -137,14 +137,14 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { if size == 0 { return Ok(Pointer::zst_ptr()); } - if self.memory_size - self.memory_usage < size as u64 { + if self.memory_size - self.memory_usage < size { return Err(EvalError::OutOfMemory { - allocation_size: size as u64, + allocation_size: size, memory_size: self.memory_size, memory_usage: self.memory_usage, }); } - self.memory_usage += size as u64; + self.memory_usage += size; let alloc = Allocation { bytes: vec![0; size], relocations: BTreeMap::new(), @@ -174,14 +174,14 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { if new_size > size { let amount = new_size - size; - self.memory_usage += amount as u64; + self.memory_usage += amount; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.extend(iter::repeat(0).take(amount)); alloc.undef_mask.grow(amount, false); } else if size > new_size { // it's possible to cause miri to use arbitrary amounts of memory that aren't detectable // through the memory_usage value, by allocating a lot and reallocating to zero - self.memory_usage -= (size - new_size) as u64; + self.memory_usage -= size - new_size; self.clear_relocations(ptr.offset(new_size as isize), size - new_size)?; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.truncate(new_size); @@ -202,7 +202,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } if let Some(alloc) = self.alloc_map.remove(&ptr.alloc_id) { - self.memory_usage -= alloc.bytes.len() as u64; + self.memory_usage -= alloc.bytes.len(); } else { debug!("deallocated a pointer twice: {}", ptr.alloc_id); // TODO(solson): Report error about erroneous free. This is blocked on properly tracking From 5381981446320107536a6058e20f2e52283dd8c5 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 7 Jul 2016 11:21:18 +0200 Subject: [PATCH 09/10] shrink_to_fit some vectors to prevent interpreted code from passing the memory limits --- src/memory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index 79130b4e24b1..f7ee6b61e5ac 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -179,12 +179,11 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { alloc.bytes.extend(iter::repeat(0).take(amount)); alloc.undef_mask.grow(amount, false); } else if size > new_size { - // it's possible to cause miri to use arbitrary amounts of memory that aren't detectable - // through the memory_usage value, by allocating a lot and reallocating to zero self.memory_usage -= size - new_size; self.clear_relocations(ptr.offset(new_size as isize), size - new_size)?; let alloc = self.get_mut(ptr.alloc_id)?; alloc.bytes.truncate(new_size); + alloc.bytes.shrink_to_fit(); alloc.undef_mask.truncate(new_size); } @@ -676,6 +675,7 @@ impl UndefMask { fn truncate(&mut self, length: usize) { self.len = length; self.blocks.truncate(self.len / BLOCK_SIZE + 1); + self.blocks.shrink_to_fit(); } } From 44bef2523552b3764506d027f4afc0d7996e376c Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 7 Jul 2016 11:30:00 +0200 Subject: [PATCH 10/10] allocating memory for floats can fail, too --- src/interpreter/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 7bef32b1d561..4200340e6a9c 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -183,12 +183,12 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } 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) },