interpret StorageLive & StorageDead, and check dead stack slots are not used

This commit is contained in:
Ralf Jung 2017-05-31 17:41:33 -07:00
parent b946351083
commit e6eaf2083a
4 changed files with 128 additions and 50 deletions

View File

@ -24,6 +24,7 @@ pub enum EvalError<'tcx> {
ReadPointerAsBytes, ReadPointerAsBytes,
InvalidPointerMath, InvalidPointerMath,
ReadUndefBytes, ReadUndefBytes,
DeadLocal,
InvalidBoolOp(mir::BinOp), InvalidBoolOp(mir::BinOp),
Unimplemented(String), Unimplemented(String),
DerefFunctionPointer, DerefFunctionPointer,
@ -83,6 +84,8 @@ impl<'tcx> Error for EvalError<'tcx> {
"attempted to do math or a comparison on pointers into different allocations", "attempted to do math or a comparison on pointers into different allocations",
EvalError::ReadUndefBytes => EvalError::ReadUndefBytes =>
"attempted to read undefined bytes", "attempted to read undefined bytes",
EvalError::DeadLocal =>
"tried to access a dead local variable",
EvalError::InvalidBoolOp(_) => EvalError::InvalidBoolOp(_) =>
"invalid boolean operation", "invalid boolean operation",
EvalError::Unimplemented(ref msg) => msg, EvalError::Unimplemented(ref msg) => msg,

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::fmt::Write; use std::fmt::Write;
use rustc::hir::def_id::DefId; use rustc::hir::def_id::DefId;
@ -74,11 +74,12 @@ pub struct Frame<'tcx> {
pub return_lvalue: Lvalue<'tcx>, pub return_lvalue: Lvalue<'tcx>,
/// The list of locals for this stack frame, stored in order as /// The list of locals for this stack frame, stored in order as
/// `[arguments..., variables..., temporaries...]`. The locals are stored as `Value`s, which /// `[arguments..., variables..., temporaries...]`. The locals are stored as `Option<Value>`s.
/// `None` represents a local that is currently dead, while a live local
/// can either directly contain `PrimVal` or refer to some part of an `Allocation`. /// can either directly contain `PrimVal` or refer to some part of an `Allocation`.
/// ///
/// Before being initialized, all locals are `Value::ByVal(PrimVal::Undef)`. /// Before being initialized, arguments are `Value::ByVal(PrimVal::Undef)` and other locals are `None`.
pub locals: Vec<Value>, pub locals: Vec<Option<Value>>,
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Current position within the function // Current position within the function
@ -452,10 +453,33 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
) -> EvalResult<'tcx> { ) -> EvalResult<'tcx> {
::log_settings::settings().indentation += 1; ::log_settings::settings().indentation += 1;
/// Return the set of locals that have a stroage annotation anywhere
fn collect_storage_annotations<'tcx>(mir: &'tcx mir::Mir<'tcx>) -> HashSet<mir::Local> {
use rustc::mir::StatementKind::*;
let mut set = HashSet::new();
for block in mir.basic_blocks() {
for stmt in block.statements.iter() {
match stmt.kind {
StorageLive(mir::Lvalue::Local(local)) | StorageDead(mir::Lvalue::Local(local)) => {
set.insert(local);
}
_ => {}
}
}
};
set
}
// Subtract 1 because `local_decls` includes the ReturnPointer, but we don't store a local // Subtract 1 because `local_decls` includes the ReturnPointer, but we don't store a local
// `Value` for that. // `Value` for that.
let annotated_locals = collect_storage_annotations(mir);
let num_locals = mir.local_decls.len() - 1; let num_locals = mir.local_decls.len() - 1;
let locals = vec![Value::ByVal(PrimVal::Undef); num_locals]; let mut locals = Vec::with_capacity(num_locals);
for i in 0..num_locals {
let local = mir::Local::new(i+1);
locals.push(if annotated_locals.contains(&local) { None } else { Some(Value::ByVal(PrimVal::Undef)) });
}
self.stack.push(Frame { self.stack.push(Frame {
mir, mir,
@ -509,21 +533,26 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
// deallocate all locals that are backed by an allocation // deallocate all locals that are backed by an allocation
for local in frame.locals { for local in frame.locals {
if let Value::ByRef(ptr) = local { self.deallocate_local(local)?;
trace!("deallocating local");
self.memory.dump_alloc(ptr.alloc_id);
match self.memory.deallocate(ptr) {
// We could alternatively check whether the alloc_id is static before calling
// deallocate, but this is much simpler and is probably the rare case.
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
other => return other,
}
}
} }
Ok(()) Ok(())
} }
pub fn deallocate_local(&mut self, local: Option<Value>) -> EvalResult<'tcx> {
if let Some(Value::ByRef(ptr)) = local {
trace!("deallocating local");
self.memory.dump_alloc(ptr.alloc_id);
match self.memory.deallocate(ptr) {
// We could alternatively check whether the alloc_id is static before calling
// deallocate, but this is much simpler and is probably the rare case.
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
other => return other,
}
};
Ok(())
}
pub fn assign_discr_and_fields< pub fn assign_discr_and_fields<
V: IntoValTyPair<'tcx>, V: IntoValTyPair<'tcx>,
J: IntoIterator<Item = V>, J: IntoIterator<Item = V>,
@ -1047,16 +1076,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Lvalue::Local { frame, local, field } => { Lvalue::Local { frame, local, field } => {
// -1 since we don't store the return value // -1 since we don't store the return value
match self.stack[frame].locals[local.index() - 1] { match self.stack[frame].locals[local.index() - 1] {
Value::ByRef(ptr) => { None => return Err(EvalError::DeadLocal),
Some(Value::ByRef(ptr)) => {
assert!(field.is_none()); assert!(field.is_none());
Lvalue::from_ptr(ptr) Lvalue::from_ptr(ptr)
}, },
val => { Some(val) => {
let ty = self.stack[frame].mir.local_decls[local].ty; let ty = self.stack[frame].mir.local_decls[local].ty;
let ty = self.monomorphize(ty, self.stack[frame].instance.substs); let ty = self.monomorphize(ty, self.stack[frame].instance.substs);
let substs = self.stack[frame].instance.substs; let substs = self.stack[frame].instance.substs;
let ptr = self.alloc_ptr_with_substs(ty, substs)?; let ptr = self.alloc_ptr_with_substs(ty, substs)?;
self.stack[frame].locals[local.index() - 1] = Value::ByRef(ptr); self.stack[frame].locals[local.index() - 1] = Some(Value::ByRef(ptr)); // it stays live
self.write_value_to_ptr(val, ptr, ty)?; self.write_value_to_ptr(val, ptr, ty)?;
let lval = Lvalue::from_ptr(ptr); let lval = Lvalue::from_ptr(ptr);
if let Some((field, field_ty)) = field { if let Some((field, field_ty)) = field {
@ -1139,7 +1169,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
*this.globals.get_mut(&cid).expect("already checked") = Global { *this.globals.get_mut(&cid).expect("already checked") = Global {
value: val, value: val,
..dest ..dest
} };
Ok(())
}; };
self.write_value_possibly_by_val(src_val, write_dest, dest.value, dest_ty) self.write_value_possibly_by_val(src_val, write_dest, dest.value, dest_ty)
}, },
@ -1150,7 +1181,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
Lvalue::Local { frame, local, field } => { Lvalue::Local { frame, local, field } => {
let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i)); let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i))?;
self.write_value_possibly_by_val( self.write_value_possibly_by_val(
src_val, src_val,
|this, val| this.stack[frame].set_local(local, field.map(|(i, _)| i), val), |this, val| this.stack[frame].set_local(local, field.map(|(i, _)| i), val),
@ -1162,7 +1193,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
// The cases here can be a bit subtle. Read carefully! // The cases here can be a bit subtle. Read carefully!
fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value)>( fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value) -> EvalResult<'tcx>>(
&mut self, &mut self,
src_val: Value, src_val: Value,
write_dest: F, write_dest: F,
@ -1192,17 +1223,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
// source and write that into the destination without making an allocation, so // source and write that into the destination without making an allocation, so
// we do so here. // we do so here.
if let Ok(Some(src_val)) = self.try_read_value(src_ptr, dest_ty) { if let Ok(Some(src_val)) = self.try_read_value(src_ptr, dest_ty) {
write_dest(self, src_val); write_dest(self, src_val)?;
} else { } else {
let dest_ptr = self.alloc_ptr(dest_ty)?; let dest_ptr = self.alloc_ptr(dest_ty)?;
self.copy(src_ptr, dest_ptr, dest_ty)?; self.copy(src_ptr, dest_ptr, dest_ty)?;
write_dest(self, Value::ByRef(dest_ptr)); write_dest(self, Value::ByRef(dest_ptr))?;
} }
} else { } else {
// Finally, we have the simple case where neither source nor destination are // Finally, we have the simple case where neither source nor destination are
// `ByRef`. We may simply copy the source value over the the destintion. // `ByRef`. We may simply copy the source value over the the destintion.
write_dest(self, src_val); write_dest(self, src_val)?;
} }
Ok(()) Ok(())
} }
@ -1572,14 +1603,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
write!(msg, ":").unwrap(); write!(msg, ":").unwrap();
match self.stack[frame].get_local(local, field.map(|(i, _)| i)) { match self.stack[frame].get_local(local, field.map(|(i, _)| i)) {
Value::ByRef(ptr) => { Err(EvalError::DeadLocal) => {
write!(msg, " is dead").unwrap();
}
Err(err) => {
panic!("Failed to access local: {:?}", err);
}
Ok(Value::ByRef(ptr)) => {
allocs.push(ptr.alloc_id); allocs.push(ptr.alloc_id);
} }
Value::ByVal(val) => { Ok(Value::ByVal(val)) => {
write!(msg, " {:?}", val).unwrap(); write!(msg, " {:?}", val).unwrap();
if let PrimVal::Ptr(ptr) = val { allocs.push(ptr.alloc_id); } if let PrimVal::Ptr(ptr) = val { allocs.push(ptr.alloc_id); }
} }
Value::ByValPair(val1, val2) => { Ok(Value::ByValPair(val1, val2)) => {
write!(msg, " ({:?}, {:?})", val1, val2).unwrap(); write!(msg, " ({:?}, {:?})", val1, val2).unwrap();
if let PrimVal::Ptr(ptr) = val1 { allocs.push(ptr.alloc_id); } if let PrimVal::Ptr(ptr) = val1 { allocs.push(ptr.alloc_id); }
if let PrimVal::Ptr(ptr) = val2 { allocs.push(ptr.alloc_id); } if let PrimVal::Ptr(ptr) = val2 { allocs.push(ptr.alloc_id); }
@ -1614,9 +1651,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
) -> EvalResult<'tcx> ) -> EvalResult<'tcx>
where F: FnOnce(&mut Self, Value) -> EvalResult<'tcx, Value>, where F: FnOnce(&mut Self, Value) -> EvalResult<'tcx, Value>,
{ {
let val = self.stack[frame].get_local(local, field); let val = self.stack[frame].get_local(local, field)?;
let new_val = f(self, val)?; let new_val = f(self, val)?;
self.stack[frame].set_local(local, field, new_val); self.stack[frame].set_local(local, field, new_val)?;
// FIXME(solson): Run this when setting to Undef? (See previous version of this code.) // FIXME(solson): Run this when setting to Undef? (See previous version of this code.)
// if let Value::ByRef(ptr) = self.stack[frame].get_local(local) { // if let Value::ByRef(ptr) = self.stack[frame].get_local(local) {
// self.memory.deallocate(ptr)?; // self.memory.deallocate(ptr)?;
@ -1626,53 +1663,79 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
impl<'tcx> Frame<'tcx> { impl<'tcx> Frame<'tcx> {
pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> Value { pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> EvalResult<'tcx, Value> {
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0. // Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
if let Some(field) = field { if let Some(field) = field {
match self.locals[local.index() - 1] { Ok(match self.locals[local.index() - 1] {
Value::ByRef(_) => bug!("can't have lvalue fields for ByRef"), None => return Err(EvalError::DeadLocal),
val @ Value::ByVal(_) => { Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
Some(val @ Value::ByVal(_)) => {
assert_eq!(field, 0); assert_eq!(field, 0);
val val
}, },
Value::ByValPair(a, b) => { Some(Value::ByValPair(a, b)) => {
match field { match field {
0 => Value::ByVal(a), 0 => Value::ByVal(a),
1 => Value::ByVal(b), 1 => Value::ByVal(b),
_ => bug!("ByValPair has only two fields, tried to access {}", field), _ => bug!("ByValPair has only two fields, tried to access {}", field),
} }
}, },
} })
} else { } else {
self.locals[local.index() - 1] self.locals[local.index() - 1].ok_or(EvalError::DeadLocal)
} }
} }
fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) { fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) -> EvalResult<'tcx> {
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0. // Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
if let Some(field) = field { if let Some(field) = field {
match self.locals[local.index() - 1] { match self.locals[local.index() - 1] {
Value::ByRef(_) => bug!("can't have lvalue fields for ByRef"), None => return Err(EvalError::DeadLocal),
Value::ByVal(_) => { Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
Some(Value::ByVal(_)) => {
assert_eq!(field, 0); assert_eq!(field, 0);
self.set_local(local, None, value); self.set_local(local, None, value)?;
}, },
Value::ByValPair(a, b) => { Some(Value::ByValPair(a, b)) => {
let prim = match value { let prim = match value {
Value::ByRef(_) => bug!("can't set ValPair field to ByRef"), Value::ByRef(_) => bug!("can't set ValPair field to ByRef"),
Value::ByVal(val) => val, Value::ByVal(val) => val,
Value::ByValPair(_, _) => bug!("can't set ValPair field to ValPair"), Value::ByValPair(_, _) => bug!("can't set ValPair field to ValPair"),
}; };
match field { match field {
0 => self.set_local(local, None, Value::ByValPair(prim, b)), 0 => self.set_local(local, None, Value::ByValPair(prim, b))?,
1 => self.set_local(local, None, Value::ByValPair(a, prim)), 1 => self.set_local(local, None, Value::ByValPair(a, prim))?,
_ => bug!("ByValPair has only two fields, tried to access {}", field), _ => bug!("ByValPair has only two fields, tried to access {}", field),
} }
}, },
} }
} else { } else {
self.locals[local.index() - 1] = value; match self.locals[local.index() - 1] {
None => return Err(EvalError::DeadLocal),
Some(ref mut local) => { *local = value; }
}
} }
return Ok(());
}
pub fn storage_live(&mut self, local: mir::Local) -> EvalResult<'tcx> {
trace!("{:?} is now live", local);
if self.locals[local.index() - 1].is_some() {
// The variables comes live now, but was already accessed previously, when it was still dead
return Err(EvalError::DeadLocal);
} else {
self.locals[local.index() - 1] = Some(Value::ByVal(PrimVal::Undef));
}
return Ok(());
}
/// Returns the old value of the local
pub fn storage_dead(&mut self, local: mir::Local) -> EvalResult<'tcx, Option<Value>> {
trace!("{:?} is now dead", local);
let old = self.locals[local.index() - 1];
self.locals[local.index() - 1] = None;
return Ok(old);
} }
} }

View File

@ -130,7 +130,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Ok(Value::ByRef(ptr)) Ok(Value::ByRef(ptr))
} }
Lvalue::Local { frame, local, field } => { Lvalue::Local { frame, local, field } => {
Ok(self.stack[frame].get_local(local, field.map(|(i, _)| i))) Ok(self.stack[frame].get_local(local, field.map(|(i, _)| i))?)
} }
Lvalue::Global(cid) => { Lvalue::Global(cid) => {
Ok(self.globals.get(&cid).expect("global not cached").value) Ok(self.globals.get(&cid).expect("global not cached").value)
@ -226,7 +226,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let (base_ptr, base_extra) = match base { let (base_ptr, base_extra) = match base {
Lvalue::Ptr { ptr, extra } => (ptr, extra), Lvalue::Ptr { ptr, extra } => (ptr, extra),
Lvalue::Local { frame, local, field } => match self.stack[frame].get_local(local, field.map(|(i, _)| i)) { Lvalue::Local { frame, local, field } => match self.stack[frame].get_local(local, field.map(|(i, _)| i))? {
Value::ByRef(ptr) => { Value::ByRef(ptr) => {
assert!(field.is_none(), "local can't be ByRef and have a field offset"); assert!(field.is_none(), "local can't be ByRef and have a field offset");
(ptr, LvalueExtra::None) (ptr, LvalueExtra::None)

View File

@ -126,9 +126,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
} }
// Miri can safely ignore these. Only translation needs it. // Mark locals as dead or alive.
StorageLive(_) | StorageLive(ref lvalue) | StorageDead(ref lvalue)=> {
StorageDead(_) => {} let (frame, local) = match self.eval_lvalue(lvalue)? {
Lvalue::Local{ frame, local, field: None } if self.stack.len() == frame+1 => (frame, local),
_ => return Err(EvalError::Unimplemented("Stroage annotations must refer to locals of the topmost stack frame.".to_owned())) // FIXME maybe this should get its own error type
};
match stmt.kind {
StorageLive(_) => self.stack[frame].storage_live(local)?,
_ => {
let old_val = self.stack[frame].storage_dead(local)?;
self.deallocate_local(old_val)?;
}
};
}
// Defined to do nothing. These are added by optimization passes, to avoid changing the // Defined to do nothing. These are added by optimization passes, to avoid changing the
// size of MIR constantly. // size of MIR constantly.
@ -240,7 +251,8 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
constant.span, constant.span,
mir, mir,
Lvalue::Global(cid), Lvalue::Global(cid),
StackPopCleanup::MarkStatic(false)) StackPopCleanup::MarkStatic(false),
)
}); });
} }
} }