1
Fork 0

add a memory limit

This commit is contained in:
Oliver Schneider 2016-07-05 10:47:10 +02:00
parent a7cc77a010
commit 756fbcce48
No known key found for this signature in database
GPG key ID: 56D6EEA0FC67AC46
5 changed files with 101 additions and 43 deletions

View file

@ -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<T, EvalError<'tcx>>;
@ -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()),
}
}

View file

@ -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<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),
}
}
@ -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<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);
@ -294,7 +299,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();
@ -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;

View file

@ -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<F: FnOnce(&mut Self) -> 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))
});
}
}
}

View file

@ -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)?;
}

View file

@ -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<AllocId, Allocation>,
/// 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<AllocId, FunctionDefinition<'tcx>>,
@ -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