Skip to content

Commit 237590a

Browse files
authored
Merge pull request #336 from RalfJung/mir-validate
Valiation: Identify write locks using an abstract lvalue
2 parents 02a943b + 5d2ed4d commit 237590a

File tree

14 files changed

+426
-302
lines changed

14 files changed

+426
-302
lines changed

src/librustc_mir/interpret/eval_context.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
618618
}
619619
for (field_index, operand) in operands.iter().enumerate() {
620620
let value = self.eval_operand(operand)?;
621-
let field_dest = self.lvalue_field(dest, field_index, dest_ty, value.ty)?;
621+
let field_dest = self.lvalue_field(dest, mir::Field::new(field_index), dest_ty, value.ty)?;
622622
self.write_value(value, field_dest)?;
623623
}
624624
Ok(())
@@ -1466,7 +1466,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
14661466

14671467
/// ensures this Value is not a ByRef
14681468
pub(super) fn follow_by_ref_value(
1469-
&mut self,
1469+
&self,
14701470
value: Value,
14711471
ty: Ty<'tcx>,
14721472
) -> EvalResult<'tcx, Value> {
@@ -1479,7 +1479,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
14791479
}
14801480

14811481
pub fn value_to_primval(
1482-
&mut self,
1482+
&self,
14831483
ValTy { value, ty } : ValTy<'tcx>,
14841484
) -> EvalResult<'tcx, PrimVal> {
14851485
match self.follow_by_ref_value(value, ty)? {

src/librustc_mir/interpret/lvalue.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,14 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
218218
pub fn lvalue_field(
219219
&mut self,
220220
base: Lvalue,
221-
field_index: usize,
221+
field: mir::Field,
222222
base_ty: Ty<'tcx>,
223223
field_ty: Ty<'tcx>,
224224
) -> EvalResult<'tcx, Lvalue> {
225-
let base_layout = self.type_layout(base_ty)?;
226225
use rustc::ty::layout::Layout::*;
226+
227+
let base_layout = self.type_layout(base_ty)?;
228+
let field_index = field.index();
227229
let (offset, packed) = match *base_layout {
228230
Univariant { ref variant, .. } => (variant.offsets[field_index], variant.packed),
229231

@@ -405,7 +407,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
405407
use rustc::mir::ProjectionElem::*;
406408
let (ptr, extra) = match *proj_elem {
407409
Field(field, field_ty) => {
408-
return self.lvalue_field(base, field.index(), base_ty, field_ty);
410+
return self.lvalue_field(base, field, base_ty, field_ty);
409411
}
410412

411413
Downcast(_, variant) => {

src/librustc_mir/interpret/memory.rs

+86-53
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use syntax::ast::Mutability;
99
use rustc::middle::region;
1010

1111
use super::{EvalResult, EvalErrorKind, PrimVal, Pointer, EvalContext, DynamicLifetime, Machine,
12-
RangeMap};
12+
RangeMap, AbsLvalue};
1313

1414
////////////////////////////////////////////////////////////////////////////////
1515
// Locks
@@ -23,14 +23,29 @@ pub enum AccessKind {
2323

2424
/// Information about a lock that is currently held.
2525
#[derive(Clone, Debug)]
26-
struct LockInfo {
26+
struct LockInfo<'tcx> {
2727
/// Stores for which lifetimes (of the original write lock) we got
2828
/// which suspensions.
29-
suspended: HashMap<DynamicLifetime, Vec<region::Scope>>,
29+
suspended: HashMap<WriteLockId<'tcx>, Vec<region::Scope>>,
3030
/// The current state of the lock that's actually effective.
3131
active: Lock,
3232
}
3333

34+
/// Write locks are identified by a stack frame and an "abstract" (untyped) lvalue.
35+
/// It may be tempting to use the lifetime as identifier, but that does not work
36+
/// for two reasons:
37+
/// * First of all, due to subtyping, the same lock may be referred to with different
38+
/// lifetimes.
39+
/// * Secondly, different write locks may actually have the same lifetime. See `test2`
40+
/// in `run-pass/many_shr_bor.rs`.
41+
/// The Id is "captured" when the lock is first suspended; at that point, the borrow checker
42+
/// considers the path frozen and hence the Id remains stable.
43+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
44+
struct WriteLockId<'tcx> {
45+
frame: usize,
46+
path: AbsLvalue<'tcx>,
47+
}
48+
3449
#[derive(Clone, Debug, PartialEq)]
3550
pub enum Lock {
3651
NoLock,
@@ -39,14 +54,14 @@ pub enum Lock {
3954
}
4055
use self::Lock::*;
4156

42-
impl Default for LockInfo {
57+
impl<'tcx> Default for LockInfo<'tcx> {
4358
fn default() -> Self {
4459
LockInfo::new(NoLock)
4560
}
4661
}
4762

48-
impl LockInfo {
49-
fn new(lock: Lock) -> LockInfo {
63+
impl<'tcx> LockInfo<'tcx> {
64+
fn new(lock: Lock) -> LockInfo<'tcx> {
5065
LockInfo {
5166
suspended: HashMap::new(),
5267
active: lock,
@@ -128,7 +143,7 @@ impl fmt::Debug for AllocId {
128143
}
129144

130145
#[derive(Debug)]
131-
pub struct Allocation<M> {
146+
pub struct Allocation<'tcx, M> {
132147
/// The actual bytes of the allocation.
133148
/// Note that the bytes of a pointer represent the offset of the pointer
134149
pub bytes: Vec<u8>,
@@ -146,17 +161,17 @@ pub struct Allocation<M> {
146161
/// Helps guarantee that stack allocations aren't deallocated via `rust_deallocate`
147162
pub kind: MemoryKind<M>,
148163
/// Memory regions that are locked by some function
149-
locks: RangeMap<LockInfo>,
164+
locks: RangeMap<LockInfo<'tcx>>,
150165
}
151166

152-
impl<M> Allocation<M> {
153-
fn check_locks<'tcx>(
167+
impl<'tcx, M> Allocation<'tcx, M> {
168+
fn check_locks(
154169
&self,
155170
frame: Option<usize>,
156171
offset: u64,
157172
len: u64,
158173
access: AccessKind,
159-
) -> Result<(), LockInfo> {
174+
) -> Result<(), LockInfo<'tcx>> {
160175
if len == 0 {
161176
return Ok(());
162177
}
@@ -237,7 +252,7 @@ pub struct Memory<'a, 'tcx, M: Machine<'tcx>> {
237252
pub data: M::MemoryData,
238253

239254
/// Actual memory allocations (arbitrary bytes, may contain pointers into other allocations).
240-
alloc_map: HashMap<u64, Allocation<M::MemoryKinds>>,
255+
alloc_map: HashMap<u64, Allocation<'tcx, M::MemoryKinds>>,
241256

242257
/// The AllocId to assign to the next new regular allocation. Always incremented, never gets smaller.
243258
next_alloc_id: u64,
@@ -610,62 +625,72 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
610625

611626
/// Release or suspend a write lock of the given lifetime prematurely.
612627
/// When releasing, if there is a read lock or someone else's write lock, that's an error.
613-
/// We *do* accept relasing a NoLock, as this can happen when a local is first acquired and later force_allocate'd.
628+
/// If no lock is held, that's fine. This can happen when e.g. a local is initialized
629+
/// from a constant, and then suspended.
614630
/// When suspending, the same cases are fine; we just register an additional suspension.
615631
pub(crate) fn suspend_write_lock(
616632
&mut self,
617633
ptr: MemoryPointer,
618634
len: u64,
619-
lock_region: Option<region::Scope>,
635+
lock_path: &AbsLvalue<'tcx>,
620636
suspend: Option<region::Scope>,
621637
) -> EvalResult<'tcx> {
622638
assert!(len > 0);
623639
let cur_frame = self.cur_frame;
624-
let lock_lft = DynamicLifetime {
625-
frame: cur_frame,
626-
region: lock_region,
627-
};
628640
let alloc = self.get_mut_unchecked(ptr.alloc_id)?;
629641

630642
'locks: for lock in alloc.locks.iter_mut(ptr.offset, len) {
631643
let is_our_lock = match lock.active {
632-
WriteLock(lft) => lft == lock_lft,
644+
WriteLock(lft) =>
645+
// Double-check that we are holding the lock.
646+
// (Due to subtyping, checking the region would not make any sense.)
647+
lft.frame == cur_frame,
633648
ReadLock(_) | NoLock => false,
634649
};
635650
if is_our_lock {
636-
trace!("Releasing {:?} at {:?}", lock.active, lock_lft);
651+
trace!("Releasing {:?}", lock.active);
637652
// Disable the lock
638653
lock.active = NoLock;
639654
} else {
640655
trace!(
641-
"Not touching {:?} at {:?} as its not our lock",
656+
"Not touching {:?} as it is not our lock",
642657
lock.active,
643-
lock_lft
644658
);
645659
}
646-
match suspend {
647-
Some(suspend_region) => {
648-
trace!("Adding suspension to {:?} at {:?}", lock.active, lock_lft);
649-
// We just released this lock, so add a new suspension.
650-
// FIXME: Really, if there ever already is a suspension when is_our_lock, or if there is no suspension when !is_our_lock, something is amiss.
651-
// But this model is not good enough yet to prevent that.
652-
lock.suspended
653-
.entry(lock_lft)
654-
.or_insert_with(|| Vec::new())
655-
.push(suspend_region);
660+
// Check if we want to register a suspension
661+
if let Some(suspend_region) = suspend {
662+
let lock_id = WriteLockId {
663+
frame: cur_frame,
664+
path: lock_path.clone(),
665+
};
666+
trace!("Adding suspension to {:?}", lock_id);
667+
let mut new_suspension = false;
668+
lock.suspended
669+
.entry(lock_id)
670+
// Remember whether we added a new suspension or not
671+
.or_insert_with(|| { new_suspension = true; Vec::new() })
672+
.push(suspend_region);
673+
// If the suspension is new, we should have owned this.
674+
// If there already was a suspension, we should NOT have owned this.
675+
if new_suspension == is_our_lock {
676+
// All is well
677+
continue 'locks;
656678
}
657-
None => {
658-
// Make sure we did not try to release someone else's lock.
659-
if !is_our_lock && lock.active != NoLock {
660-
return err!(InvalidMemoryLockRelease {
661-
ptr,
662-
len,
663-
frame: cur_frame,
664-
lock: lock.active.clone(),
665-
});
666-
}
679+
} else {
680+
if !is_our_lock {
681+
// All is well.
682+
continue 'locks;
667683
}
668684
}
685+
// If we get here, releasing this is an error except for NoLock.
686+
if lock.active != NoLock {
687+
return err!(InvalidMemoryLockRelease {
688+
ptr,
689+
len,
690+
frame: cur_frame,
691+
lock: lock.active.clone(),
692+
});
693+
}
669694
}
670695

671696
Ok(())
@@ -676,26 +701,27 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
676701
&mut self,
677702
ptr: MemoryPointer,
678703
len: u64,
704+
lock_path: &AbsLvalue<'tcx>,
679705
lock_region: Option<region::Scope>,
680706
suspended_region: region::Scope,
681707
) -> EvalResult<'tcx> {
682708
assert!(len > 0);
683709
let cur_frame = self.cur_frame;
684-
let lock_lft = DynamicLifetime {
710+
let lock_id = WriteLockId {
685711
frame: cur_frame,
686-
region: lock_region,
712+
path: lock_path.clone(),
687713
};
688714
let alloc = self.get_mut_unchecked(ptr.alloc_id)?;
689715

690716
for lock in alloc.locks.iter_mut(ptr.offset, len) {
691717
// Check if we have a suspension here
692-
let (got_the_lock, remove_suspension) = match lock.suspended.get_mut(&lock_lft) {
718+
let (got_the_lock, remove_suspension) = match lock.suspended.get_mut(&lock_id) {
693719
None => {
694720
trace!("No suspension around, we can just acquire");
695721
(true, false)
696722
}
697723
Some(suspensions) => {
698-
trace!("Found suspension of {:?}, removing it", lock_lft);
724+
trace!("Found suspension of {:?}, removing it", lock_id);
699725
// That's us! Remove suspension (it should be in there). The same suspension can
700726
// occur multiple times (when there are multiple shared borrows of this that have the same
701727
// lifetime); only remove one of them.
@@ -715,12 +741,17 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
715741
if remove_suspension {
716742
// with NLL, we could do that up in the match above...
717743
assert!(got_the_lock);
718-
lock.suspended.remove(&lock_lft);
744+
lock.suspended.remove(&lock_id);
719745
}
720746
if got_the_lock {
721747
match lock.active {
722748
ref mut active @ NoLock => {
723-
*active = WriteLock(lock_lft);
749+
*active = WriteLock(
750+
DynamicLifetime {
751+
frame: cur_frame,
752+
region: lock_region,
753+
}
754+
);
724755
}
725756
_ => {
726757
return err!(MemoryAcquireConflict {
@@ -770,8 +801,10 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
770801
if lock_ended {
771802
lock.active = NoLock;
772803
}
773-
// Also clean up suspended write locks
774-
lock.suspended.retain(|lft, _suspensions| !has_ended(lft));
804+
// Also clean up suspended write locks when the function returns
805+
if ending_region.is_none() {
806+
lock.suspended.retain(|id, _suspensions| id.frame != cur_frame);
807+
}
775808
}
776809
// Clean up the map
777810
alloc.locks.retain(|lock| match lock.active {
@@ -784,7 +817,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
784817

785818
/// Allocation accessors
786819
impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
787-
pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation<M::MemoryKinds>> {
820+
pub fn get(&self, id: AllocId) -> EvalResult<'tcx, &Allocation<'tcx, M::MemoryKinds>> {
788821
match id.into_alloc_id_kind() {
789822
AllocIdKind::Function(_) => err!(DerefFunctionPointer),
790823
AllocIdKind::Runtime(id) => {
@@ -799,7 +832,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
799832
fn get_mut_unchecked(
800833
&mut self,
801834
id: AllocId,
802-
) -> EvalResult<'tcx, &mut Allocation<M::MemoryKinds>> {
835+
) -> EvalResult<'tcx, &mut Allocation<'tcx, M::MemoryKinds>> {
803836
match id.into_alloc_id_kind() {
804837
AllocIdKind::Function(_) => err!(DerefFunctionPointer),
805838
AllocIdKind::Runtime(id) => {
@@ -811,7 +844,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
811844
}
812845
}
813846

814-
fn get_mut(&mut self, id: AllocId) -> EvalResult<'tcx, &mut Allocation<M::MemoryKinds>> {
847+
fn get_mut(&mut self, id: AllocId) -> EvalResult<'tcx, &mut Allocation<'tcx, M::MemoryKinds>> {
815848
let alloc = self.get_mut_unchecked(id)?;
816849
if alloc.mutable == Mutability::Mutable {
817850
Ok(alloc)

src/librustc_mir/interpret/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ pub use self::const_eval::{eval_body_as_integer, eval_body_as_primval};
3939

4040
pub use self::machine::Machine;
4141

42-
pub use self::validation::ValidationQuery;
42+
pub use self::validation::{ValidationQuery, AbsLvalue};

0 commit comments

Comments
 (0)