Skip to content

Commit e6eaf20

Browse files
committed
interpret StorageLive & StorageDead, and check dead stack slots are not used
1 parent b946351 commit e6eaf20

File tree

4 files changed

+128
-50
lines changed

4 files changed

+128
-50
lines changed

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub enum EvalError<'tcx> {
2424
ReadPointerAsBytes,
2525
InvalidPointerMath,
2626
ReadUndefBytes,
27+
DeadLocal,
2728
InvalidBoolOp(mir::BinOp),
2829
Unimplemented(String),
2930
DerefFunctionPointer,
@@ -83,6 +84,8 @@ impl<'tcx> Error for EvalError<'tcx> {
8384
"attempted to do math or a comparison on pointers into different allocations",
8485
EvalError::ReadUndefBytes =>
8586
"attempted to read undefined bytes",
87+
EvalError::DeadLocal =>
88+
"tried to access a dead local variable",
8689
EvalError::InvalidBoolOp(_) =>
8790
"invalid boolean operation",
8891
EvalError::Unimplemented(ref msg) => msg,

src/eval_context.rs

Lines changed: 107 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{HashMap, HashSet};
22
use std::fmt::Write;
33

44
use rustc::hir::def_id::DefId;
@@ -74,11 +74,12 @@ pub struct Frame<'tcx> {
7474
pub return_lvalue: Lvalue<'tcx>,
7575

7676
/// The list of locals for this stack frame, stored in order as
77-
/// `[arguments..., variables..., temporaries...]`. The locals are stored as `Value`s, which
77+
/// `[arguments..., variables..., temporaries...]`. The locals are stored as `Option<Value>`s.
78+
/// `None` represents a local that is currently dead, while a live local
7879
/// can either directly contain `PrimVal` or refer to some part of an `Allocation`.
7980
///
80-
/// Before being initialized, all locals are `Value::ByVal(PrimVal::Undef)`.
81-
pub locals: Vec<Value>,
81+
/// Before being initialized, arguments are `Value::ByVal(PrimVal::Undef)` and other locals are `None`.
82+
pub locals: Vec<Option<Value>>,
8283

8384
////////////////////////////////////////////////////////////////////////////////
8485
// Current position within the function
@@ -452,10 +453,33 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
452453
) -> EvalResult<'tcx> {
453454
::log_settings::settings().indentation += 1;
454455

456+
/// Return the set of locals that have a stroage annotation anywhere
457+
fn collect_storage_annotations<'tcx>(mir: &'tcx mir::Mir<'tcx>) -> HashSet<mir::Local> {
458+
use rustc::mir::StatementKind::*;
459+
460+
let mut set = HashSet::new();
461+
for block in mir.basic_blocks() {
462+
for stmt in block.statements.iter() {
463+
match stmt.kind {
464+
StorageLive(mir::Lvalue::Local(local)) | StorageDead(mir::Lvalue::Local(local)) => {
465+
set.insert(local);
466+
}
467+
_ => {}
468+
}
469+
}
470+
};
471+
set
472+
}
473+
455474
// Subtract 1 because `local_decls` includes the ReturnPointer, but we don't store a local
456475
// `Value` for that.
476+
let annotated_locals = collect_storage_annotations(mir);
457477
let num_locals = mir.local_decls.len() - 1;
458-
let locals = vec![Value::ByVal(PrimVal::Undef); num_locals];
478+
let mut locals = Vec::with_capacity(num_locals);
479+
for i in 0..num_locals {
480+
let local = mir::Local::new(i+1);
481+
locals.push(if annotated_locals.contains(&local) { None } else { Some(Value::ByVal(PrimVal::Undef)) });
482+
}
459483

460484
self.stack.push(Frame {
461485
mir,
@@ -509,21 +533,26 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
509533
}
510534
// deallocate all locals that are backed by an allocation
511535
for local in frame.locals {
512-
if let Value::ByRef(ptr) = local {
513-
trace!("deallocating local");
514-
self.memory.dump_alloc(ptr.alloc_id);
515-
match self.memory.deallocate(ptr) {
516-
// We could alternatively check whether the alloc_id is static before calling
517-
// deallocate, but this is much simpler and is probably the rare case.
518-
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
519-
other => return other,
520-
}
521-
}
536+
self.deallocate_local(local)?;
522537
}
523538

524539
Ok(())
525540
}
526541

542+
pub fn deallocate_local(&mut self, local: Option<Value>) -> EvalResult<'tcx> {
543+
if let Some(Value::ByRef(ptr)) = local {
544+
trace!("deallocating local");
545+
self.memory.dump_alloc(ptr.alloc_id);
546+
match self.memory.deallocate(ptr) {
547+
// We could alternatively check whether the alloc_id is static before calling
548+
// deallocate, but this is much simpler and is probably the rare case.
549+
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
550+
other => return other,
551+
}
552+
};
553+
Ok(())
554+
}
555+
527556
pub fn assign_discr_and_fields<
528557
V: IntoValTyPair<'tcx>,
529558
J: IntoIterator<Item = V>,
@@ -1047,16 +1076,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
10471076
Lvalue::Local { frame, local, field } => {
10481077
// -1 since we don't store the return value
10491078
match self.stack[frame].locals[local.index() - 1] {
1050-
Value::ByRef(ptr) => {
1079+
None => return Err(EvalError::DeadLocal),
1080+
Some(Value::ByRef(ptr)) => {
10511081
assert!(field.is_none());
10521082
Lvalue::from_ptr(ptr)
10531083
},
1054-
val => {
1084+
Some(val) => {
10551085
let ty = self.stack[frame].mir.local_decls[local].ty;
10561086
let ty = self.monomorphize(ty, self.stack[frame].instance.substs);
10571087
let substs = self.stack[frame].instance.substs;
10581088
let ptr = self.alloc_ptr_with_substs(ty, substs)?;
1059-
self.stack[frame].locals[local.index() - 1] = Value::ByRef(ptr);
1089+
self.stack[frame].locals[local.index() - 1] = Some(Value::ByRef(ptr)); // it stays live
10601090
self.write_value_to_ptr(val, ptr, ty)?;
10611091
let lval = Lvalue::from_ptr(ptr);
10621092
if let Some((field, field_ty)) = field {
@@ -1139,7 +1169,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11391169
*this.globals.get_mut(&cid).expect("already checked") = Global {
11401170
value: val,
11411171
..dest
1142-
}
1172+
};
1173+
Ok(())
11431174
};
11441175
self.write_value_possibly_by_val(src_val, write_dest, dest.value, dest_ty)
11451176
},
@@ -1150,7 +1181,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11501181
}
11511182

11521183
Lvalue::Local { frame, local, field } => {
1153-
let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i));
1184+
let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i))?;
11541185
self.write_value_possibly_by_val(
11551186
src_val,
11561187
|this, val| this.stack[frame].set_local(local, field.map(|(i, _)| i), val),
@@ -1162,7 +1193,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11621193
}
11631194

11641195
// The cases here can be a bit subtle. Read carefully!
1165-
fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value)>(
1196+
fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value) -> EvalResult<'tcx>>(
11661197
&mut self,
11671198
src_val: Value,
11681199
write_dest: F,
@@ -1192,17 +1223,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11921223
// source and write that into the destination without making an allocation, so
11931224
// we do so here.
11941225
if let Ok(Some(src_val)) = self.try_read_value(src_ptr, dest_ty) {
1195-
write_dest(self, src_val);
1226+
write_dest(self, src_val)?;
11961227
} else {
11971228
let dest_ptr = self.alloc_ptr(dest_ty)?;
11981229
self.copy(src_ptr, dest_ptr, dest_ty)?;
1199-
write_dest(self, Value::ByRef(dest_ptr));
1230+
write_dest(self, Value::ByRef(dest_ptr))?;
12001231
}
12011232

12021233
} else {
12031234
// Finally, we have the simple case where neither source nor destination are
12041235
// `ByRef`. We may simply copy the source value over the the destintion.
1205-
write_dest(self, src_val);
1236+
write_dest(self, src_val)?;
12061237
}
12071238
Ok(())
12081239
}
@@ -1572,14 +1603,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
15721603
write!(msg, ":").unwrap();
15731604

15741605
match self.stack[frame].get_local(local, field.map(|(i, _)| i)) {
1575-
Value::ByRef(ptr) => {
1606+
Err(EvalError::DeadLocal) => {
1607+
write!(msg, " is dead").unwrap();
1608+
}
1609+
Err(err) => {
1610+
panic!("Failed to access local: {:?}", err);
1611+
}
1612+
Ok(Value::ByRef(ptr)) => {
15761613
allocs.push(ptr.alloc_id);
15771614
}
1578-
Value::ByVal(val) => {
1615+
Ok(Value::ByVal(val)) => {
15791616
write!(msg, " {:?}", val).unwrap();
15801617
if let PrimVal::Ptr(ptr) = val { allocs.push(ptr.alloc_id); }
15811618
}
1582-
Value::ByValPair(val1, val2) => {
1619+
Ok(Value::ByValPair(val1, val2)) => {
15831620
write!(msg, " ({:?}, {:?})", val1, val2).unwrap();
15841621
if let PrimVal::Ptr(ptr) = val1 { allocs.push(ptr.alloc_id); }
15851622
if let PrimVal::Ptr(ptr) = val2 { allocs.push(ptr.alloc_id); }
@@ -1614,9 +1651,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
16141651
) -> EvalResult<'tcx>
16151652
where F: FnOnce(&mut Self, Value) -> EvalResult<'tcx, Value>,
16161653
{
1617-
let val = self.stack[frame].get_local(local, field);
1654+
let val = self.stack[frame].get_local(local, field)?;
16181655
let new_val = f(self, val)?;
1619-
self.stack[frame].set_local(local, field, new_val);
1656+
self.stack[frame].set_local(local, field, new_val)?;
16201657
// FIXME(solson): Run this when setting to Undef? (See previous version of this code.)
16211658
// if let Value::ByRef(ptr) = self.stack[frame].get_local(local) {
16221659
// self.memory.deallocate(ptr)?;
@@ -1626,53 +1663,79 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
16261663
}
16271664

16281665
impl<'tcx> Frame<'tcx> {
1629-
pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> Value {
1666+
pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> EvalResult<'tcx, Value> {
16301667
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
16311668
if let Some(field) = field {
1632-
match self.locals[local.index() - 1] {
1633-
Value::ByRef(_) => bug!("can't have lvalue fields for ByRef"),
1634-
val @ Value::ByVal(_) => {
1669+
Ok(match self.locals[local.index() - 1] {
1670+
None => return Err(EvalError::DeadLocal),
1671+
Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
1672+
Some(val @ Value::ByVal(_)) => {
16351673
assert_eq!(field, 0);
16361674
val
16371675
},
1638-
Value::ByValPair(a, b) => {
1676+
Some(Value::ByValPair(a, b)) => {
16391677
match field {
16401678
0 => Value::ByVal(a),
16411679
1 => Value::ByVal(b),
16421680
_ => bug!("ByValPair has only two fields, tried to access {}", field),
16431681
}
16441682
},
1645-
}
1683+
})
16461684
} else {
1647-
self.locals[local.index() - 1]
1685+
self.locals[local.index() - 1].ok_or(EvalError::DeadLocal)
16481686
}
16491687
}
16501688

1651-
fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) {
1689+
fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) -> EvalResult<'tcx> {
16521690
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
16531691
if let Some(field) = field {
16541692
match self.locals[local.index() - 1] {
1655-
Value::ByRef(_) => bug!("can't have lvalue fields for ByRef"),
1656-
Value::ByVal(_) => {
1693+
None => return Err(EvalError::DeadLocal),
1694+
Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
1695+
Some(Value::ByVal(_)) => {
16571696
assert_eq!(field, 0);
1658-
self.set_local(local, None, value);
1697+
self.set_local(local, None, value)?;
16591698
},
1660-
Value::ByValPair(a, b) => {
1699+
Some(Value::ByValPair(a, b)) => {
16611700
let prim = match value {
16621701
Value::ByRef(_) => bug!("can't set ValPair field to ByRef"),
16631702
Value::ByVal(val) => val,
16641703
Value::ByValPair(_, _) => bug!("can't set ValPair field to ValPair"),
16651704
};
16661705
match field {
1667-
0 => self.set_local(local, None, Value::ByValPair(prim, b)),
1668-
1 => self.set_local(local, None, Value::ByValPair(a, prim)),
1706+
0 => self.set_local(local, None, Value::ByValPair(prim, b))?,
1707+
1 => self.set_local(local, None, Value::ByValPair(a, prim))?,
16691708
_ => bug!("ByValPair has only two fields, tried to access {}", field),
16701709
}
16711710
},
16721711
}
16731712
} else {
1674-
self.locals[local.index() - 1] = value;
1713+
match self.locals[local.index() - 1] {
1714+
None => return Err(EvalError::DeadLocal),
1715+
Some(ref mut local) => { *local = value; }
1716+
}
1717+
}
1718+
return Ok(());
1719+
}
1720+
1721+
pub fn storage_live(&mut self, local: mir::Local) -> EvalResult<'tcx> {
1722+
trace!("{:?} is now live", local);
1723+
if self.locals[local.index() - 1].is_some() {
1724+
// The variables comes live now, but was already accessed previously, when it was still dead
1725+
return Err(EvalError::DeadLocal);
1726+
} else {
1727+
self.locals[local.index() - 1] = Some(Value::ByVal(PrimVal::Undef));
16751728
}
1729+
return Ok(());
1730+
}
1731+
1732+
/// Returns the old value of the local
1733+
pub fn storage_dead(&mut self, local: mir::Local) -> EvalResult<'tcx, Option<Value>> {
1734+
trace!("{:?} is now dead", local);
1735+
1736+
let old = self.locals[local.index() - 1];
1737+
self.locals[local.index() - 1] = None;
1738+
return Ok(old);
16761739
}
16771740
}
16781741

src/lvalue.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
130130
Ok(Value::ByRef(ptr))
131131
}
132132
Lvalue::Local { frame, local, field } => {
133-
Ok(self.stack[frame].get_local(local, field.map(|(i, _)| i)))
133+
Ok(self.stack[frame].get_local(local, field.map(|(i, _)| i))?)
134134
}
135135
Lvalue::Global(cid) => {
136136
Ok(self.globals.get(&cid).expect("global not cached").value)
@@ -226,7 +226,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
226226

227227
let (base_ptr, base_extra) = match base {
228228
Lvalue::Ptr { ptr, extra } => (ptr, extra),
229-
Lvalue::Local { frame, local, field } => match self.stack[frame].get_local(local, field.map(|(i, _)| i)) {
229+
Lvalue::Local { frame, local, field } => match self.stack[frame].get_local(local, field.map(|(i, _)| i))? {
230230
Value::ByRef(ptr) => {
231231
assert!(field.is_none(), "local can't be ByRef and have a field offset");
232232
(ptr, LvalueExtra::None)

src/step.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
126126
}
127127
}
128128

129-
// Miri can safely ignore these. Only translation needs it.
130-
StorageLive(_) |
131-
StorageDead(_) => {}
129+
// Mark locals as dead or alive.
130+
StorageLive(ref lvalue) | StorageDead(ref lvalue)=> {
131+
let (frame, local) = match self.eval_lvalue(lvalue)? {
132+
Lvalue::Local{ frame, local, field: None } if self.stack.len() == frame+1 => (frame, local),
133+
_ => 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
134+
};
135+
match stmt.kind {
136+
StorageLive(_) => self.stack[frame].storage_live(local)?,
137+
_ => {
138+
let old_val = self.stack[frame].storage_dead(local)?;
139+
self.deallocate_local(old_val)?;
140+
}
141+
};
142+
}
132143

133144
// Defined to do nothing. These are added by optimization passes, to avoid changing the
134145
// size of MIR constantly.
@@ -240,7 +251,8 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
240251
constant.span,
241252
mir,
242253
Lvalue::Global(cid),
243-
StackPopCleanup::MarkStatic(false))
254+
StackPopCleanup::MarkStatic(false),
255+
)
244256
});
245257
}
246258
}

0 commit comments

Comments
 (0)