Skip to content

Commit d7d11c1

Browse files
authored
Merge pull request #176 from RalfJung/storage
interpret StorageLive & StorageDead, and check dead stack slots are not used
2 parents b946351 + ec7f1d5 commit d7d11c1

File tree

4 files changed

+126
-50
lines changed

4 files changed

+126
-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: 106 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,35 @@ 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 storage 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![None; num_locals];
479+
for i in 0..num_locals {
480+
let local = mir::Local::new(i+1);
481+
if !annotated_locals.contains(&local) {
482+
locals[i] = Some(Value::ByVal(PrimVal::Undef));
483+
}
484+
}
459485

460486
self.stack.push(Frame {
461487
mir,
@@ -509,21 +535,26 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
509535
}
510536
// deallocate all locals that are backed by an allocation
511537
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-
}
538+
self.deallocate_local(local)?;
522539
}
523540

524541
Ok(())
525542
}
526543

544+
pub fn deallocate_local(&mut self, local: Option<Value>) -> EvalResult<'tcx> {
545+
if let Some(Value::ByRef(ptr)) = local {
546+
trace!("deallocating local");
547+
self.memory.dump_alloc(ptr.alloc_id);
548+
match self.memory.deallocate(ptr) {
549+
// We could alternatively check whether the alloc_id is static before calling
550+
// deallocate, but this is much simpler and is probably the rare case.
551+
Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {},
552+
other => return other,
553+
}
554+
};
555+
Ok(())
556+
}
557+
527558
pub fn assign_discr_and_fields<
528559
V: IntoValTyPair<'tcx>,
529560
J: IntoIterator<Item = V>,
@@ -1047,16 +1078,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
10471078
Lvalue::Local { frame, local, field } => {
10481079
// -1 since we don't store the return value
10491080
match self.stack[frame].locals[local.index() - 1] {
1050-
Value::ByRef(ptr) => {
1081+
None => return Err(EvalError::DeadLocal),
1082+
Some(Value::ByRef(ptr)) => {
10511083
assert!(field.is_none());
10521084
Lvalue::from_ptr(ptr)
10531085
},
1054-
val => {
1086+
Some(val) => {
10551087
let ty = self.stack[frame].mir.local_decls[local].ty;
10561088
let ty = self.monomorphize(ty, self.stack[frame].instance.substs);
10571089
let substs = self.stack[frame].instance.substs;
10581090
let ptr = self.alloc_ptr_with_substs(ty, substs)?;
1059-
self.stack[frame].locals[local.index() - 1] = Value::ByRef(ptr);
1091+
self.stack[frame].locals[local.index() - 1] = Some(Value::ByRef(ptr)); // it stays live
10601092
self.write_value_to_ptr(val, ptr, ty)?;
10611093
let lval = Lvalue::from_ptr(ptr);
10621094
if let Some((field, field_ty)) = field {
@@ -1139,7 +1171,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11391171
*this.globals.get_mut(&cid).expect("already checked") = Global {
11401172
value: val,
11411173
..dest
1142-
}
1174+
};
1175+
Ok(())
11431176
};
11441177
self.write_value_possibly_by_val(src_val, write_dest, dest.value, dest_ty)
11451178
},
@@ -1150,7 +1183,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11501183
}
11511184

11521185
Lvalue::Local { frame, local, field } => {
1153-
let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i));
1186+
let dest = self.stack[frame].get_local(local, field.map(|(i, _)| i))?;
11541187
self.write_value_possibly_by_val(
11551188
src_val,
11561189
|this, val| this.stack[frame].set_local(local, field.map(|(i, _)| i), val),
@@ -1162,7 +1195,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11621195
}
11631196

11641197
// The cases here can be a bit subtle. Read carefully!
1165-
fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value)>(
1198+
fn write_value_possibly_by_val<F: FnOnce(&mut Self, Value) -> EvalResult<'tcx>>(
11661199
&mut self,
11671200
src_val: Value,
11681201
write_dest: F,
@@ -1192,17 +1225,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
11921225
// source and write that into the destination without making an allocation, so
11931226
// we do so here.
11941227
if let Ok(Some(src_val)) = self.try_read_value(src_ptr, dest_ty) {
1195-
write_dest(self, src_val);
1228+
write_dest(self, src_val)?;
11961229
} else {
11971230
let dest_ptr = self.alloc_ptr(dest_ty)?;
11981231
self.copy(src_ptr, dest_ptr, dest_ty)?;
1199-
write_dest(self, Value::ByRef(dest_ptr));
1232+
write_dest(self, Value::ByRef(dest_ptr))?;
12001233
}
12011234

12021235
} else {
12031236
// Finally, we have the simple case where neither source nor destination are
12041237
// `ByRef`. We may simply copy the source value over the the destintion.
1205-
write_dest(self, src_val);
1238+
write_dest(self, src_val)?;
12061239
}
12071240
Ok(())
12081241
}
@@ -1572,14 +1605,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
15721605
write!(msg, ":").unwrap();
15731606

15741607
match self.stack[frame].get_local(local, field.map(|(i, _)| i)) {
1575-
Value::ByRef(ptr) => {
1608+
Err(EvalError::DeadLocal) => {
1609+
write!(msg, " is dead").unwrap();
1610+
}
1611+
Err(err) => {
1612+
panic!("Failed to access local: {:?}", err);
1613+
}
1614+
Ok(Value::ByRef(ptr)) => {
15761615
allocs.push(ptr.alloc_id);
15771616
}
1578-
Value::ByVal(val) => {
1617+
Ok(Value::ByVal(val)) => {
15791618
write!(msg, " {:?}", val).unwrap();
15801619
if let PrimVal::Ptr(ptr) = val { allocs.push(ptr.alloc_id); }
15811620
}
1582-
Value::ByValPair(val1, val2) => {
1621+
Ok(Value::ByValPair(val1, val2)) => {
15831622
write!(msg, " ({:?}, {:?})", val1, val2).unwrap();
15841623
if let PrimVal::Ptr(ptr) = val1 { allocs.push(ptr.alloc_id); }
15851624
if let PrimVal::Ptr(ptr) = val2 { allocs.push(ptr.alloc_id); }
@@ -1614,9 +1653,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
16141653
) -> EvalResult<'tcx>
16151654
where F: FnOnce(&mut Self, Value) -> EvalResult<'tcx, Value>,
16161655
{
1617-
let val = self.stack[frame].get_local(local, field);
1656+
let val = self.stack[frame].get_local(local, field)?;
16181657
let new_val = f(self, val)?;
1619-
self.stack[frame].set_local(local, field, new_val);
1658+
self.stack[frame].set_local(local, field, new_val)?;
16201659
// FIXME(solson): Run this when setting to Undef? (See previous version of this code.)
16211660
// if let Value::ByRef(ptr) = self.stack[frame].get_local(local) {
16221661
// self.memory.deallocate(ptr)?;
@@ -1626,53 +1665,76 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
16261665
}
16271666

16281667
impl<'tcx> Frame<'tcx> {
1629-
pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> Value {
1668+
pub fn get_local(&self, local: mir::Local, field: Option<usize>) -> EvalResult<'tcx, Value> {
16301669
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
16311670
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(_) => {
1671+
Ok(match self.locals[local.index() - 1] {
1672+
None => return Err(EvalError::DeadLocal),
1673+
Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
1674+
Some(val @ Value::ByVal(_)) => {
16351675
assert_eq!(field, 0);
16361676
val
16371677
},
1638-
Value::ByValPair(a, b) => {
1678+
Some(Value::ByValPair(a, b)) => {
16391679
match field {
16401680
0 => Value::ByVal(a),
16411681
1 => Value::ByVal(b),
16421682
_ => bug!("ByValPair has only two fields, tried to access {}", field),
16431683
}
16441684
},
1645-
}
1685+
})
16461686
} else {
1647-
self.locals[local.index() - 1]
1687+
self.locals[local.index() - 1].ok_or(EvalError::DeadLocal)
16481688
}
16491689
}
16501690

1651-
fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) {
1691+
fn set_local(&mut self, local: mir::Local, field: Option<usize>, value: Value) -> EvalResult<'tcx> {
16521692
// Subtract 1 because we don't store a value for the ReturnPointer, the local with index 0.
16531693
if let Some(field) = field {
16541694
match self.locals[local.index() - 1] {
1655-
Value::ByRef(_) => bug!("can't have lvalue fields for ByRef"),
1656-
Value::ByVal(_) => {
1695+
None => return Err(EvalError::DeadLocal),
1696+
Some(Value::ByRef(_)) => bug!("can't have lvalue fields for ByRef"),
1697+
Some(Value::ByVal(_)) => {
16571698
assert_eq!(field, 0);
1658-
self.set_local(local, None, value);
1699+
self.set_local(local, None, value)?;
16591700
},
1660-
Value::ByValPair(a, b) => {
1701+
Some(Value::ByValPair(a, b)) => {
16611702
let prim = match value {
16621703
Value::ByRef(_) => bug!("can't set ValPair field to ByRef"),
16631704
Value::ByVal(val) => val,
16641705
Value::ByValPair(_, _) => bug!("can't set ValPair field to ValPair"),
16651706
};
16661707
match field {
1667-
0 => self.set_local(local, None, Value::ByValPair(prim, b)),
1668-
1 => self.set_local(local, None, Value::ByValPair(a, prim)),
1708+
0 => self.set_local(local, None, Value::ByValPair(prim, b))?,
1709+
1 => self.set_local(local, None, Value::ByValPair(a, prim))?,
16691710
_ => bug!("ByValPair has only two fields, tried to access {}", field),
16701711
}
16711712
},
16721713
}
16731714
} else {
1674-
self.locals[local.index() - 1] = value;
1715+
match self.locals[local.index() - 1] {
1716+
None => return Err(EvalError::DeadLocal),
1717+
Some(ref mut local) => { *local = value; }
1718+
}
16751719
}
1720+
return Ok(());
1721+
}
1722+
1723+
pub fn storage_live(&mut self, local: mir::Local) -> EvalResult<'tcx, Option<Value>> {
1724+
trace!("{:?} is now live", local);
1725+
1726+
let old = self.locals[local.index() - 1];
1727+
self.locals[local.index() - 1] = Some(Value::ByVal(PrimVal::Undef)); // StorageLive *always* kills the value that's currently stored
1728+
return Ok(old);
1729+
}
1730+
1731+
/// Returns the old value of the local
1732+
pub fn storage_dead(&mut self, local: mir::Local) -> EvalResult<'tcx, Option<Value>> {
1733+
trace!("{:?} is now dead", local);
1734+
1735+
let old = self.locals[local.index() - 1];
1736+
self.locals[local.index() - 1] = None;
1737+
return Ok(old);
16761738
}
16771739
}
16781740

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+
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: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,19 @@ 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("Storage annotations must refer to locals of the topmost stack frame.".to_owned())) // FIXME maybe this should get its own error type
134+
};
135+
let old_val = match stmt.kind {
136+
StorageLive(_) => self.stack[frame].storage_live(local)?,
137+
StorageDead(_) => self.stack[frame].storage_dead(local)?,
138+
_ => bug!("We already checked that we are a storage stmt")
139+
};
140+
self.deallocate_local(old_val)?;
141+
}
132142

133143
// Defined to do nothing. These are added by optimization passes, to avoid changing the
134144
// size of MIR constantly.
@@ -240,7 +250,8 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
240250
constant.span,
241251
mir,
242252
Lvalue::Global(cid),
243-
StackPopCleanup::MarkStatic(false))
253+
StackPopCleanup::MarkStatic(false),
254+
)
244255
});
245256
}
246257
}

0 commit comments

Comments
 (0)