1 //! Implements "Stacked Borrows". See <https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md>
2 //! for further information.
12 use rustc_data_structures::fx::FxHashSet;
13 use rustc_middle::mir::{Mutability, RetagKind};
14 use rustc_middle::ty::{
16 layout::{HasParamEnv, LayoutOf},
18 use rustc_target::abi::{Abi, Size};
20 use crate::borrow_tracker::{
21 stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, TagHistory},
22 AccessKind, GlobalStateInner, ProtectorKind, RetagFields,
26 use diagnostics::RetagCause;
27 pub use item::{Item, Permission};
30 pub type AllocState = Stacks;
32 /// Extra per-allocation state.
33 #[derive(Clone, Debug)]
35 // Even reading memory can have effects on the stack, so we need a `RefCell` here.
36 stacks: RangeMap<Stack>,
37 /// Stores past operations on this allocation
38 history: AllocHistory,
39 /// The set of tags that have been exposed inside this allocation.
40 exposed_tags: FxHashSet<BorTag>,
41 /// Whether this memory has been modified since the last time the tag GC ran
42 modified_since_last_gc: bool,
45 /// Indicates which permissions to grant to the retagged pointer.
46 #[derive(Clone, Debug)]
50 access: Option<AccessKind>,
51 protector: Option<ProtectorKind>,
54 freeze_perm: Permission,
55 freeze_access: Option<AccessKind>,
56 freeze_protector: Option<ProtectorKind>,
57 nonfreeze_perm: Permission,
58 nonfreeze_access: Option<AccessKind>,
59 // nonfreeze_protector must always be None
64 /// A key function: determine the permissions to grant at a retag for the given kind of
65 /// reference/pointer.
69 cx: &crate::MiriInterpCx<'_, 'tcx>,
71 let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector);
73 ty::Ref(_, pointee, Mutability::Mut) => {
74 if kind == RetagKind::TwoPhase {
75 // We mostly just give up on 2phase-borrows, and treat these exactly like raw pointers.
76 assert!(protector.is_none()); // RetagKind can't be both FnEntry and TwoPhase.
77 NewPermission::Uniform {
78 perm: Permission::SharedReadWrite,
82 } else if pointee.is_unpin(*cx.tcx, cx.param_env()) {
83 // A regular full mutable reference.
84 NewPermission::Uniform {
85 perm: Permission::Unique,
86 access: Some(AccessKind::Write),
90 NewPermission::Uniform {
91 perm: Permission::SharedReadWrite,
92 // FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we
93 // should do fake accesses here. But then we run into
94 // <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now
101 ty::RawPtr(ty::TypeAndMut { mutbl: Mutability::Mut, .. }) => {
102 assert!(protector.is_none()); // RetagKind can't be both FnEntry and Raw.
103 // Mutable raw pointer. No access, not protected.
104 NewPermission::Uniform {
105 perm: Permission::SharedReadWrite,
110 ty::Ref(_, _pointee, Mutability::Not) => {
111 NewPermission::FreezeSensitive {
112 freeze_perm: Permission::SharedReadOnly,
113 freeze_access: Some(AccessKind::Read),
114 freeze_protector: protector,
115 nonfreeze_perm: Permission::SharedReadWrite,
116 // Inside UnsafeCell, this does *not* count as an access, as there
117 // might actually be mutable references further up the stack that
118 // we have to keep alive.
119 nonfreeze_access: None,
120 // We do not protect inside UnsafeCell.
121 // This fixes https://github.com/rust-lang/rust/issues/55005.
124 ty::RawPtr(ty::TypeAndMut { mutbl: Mutability::Not, .. }) => {
125 assert!(protector.is_none()); // RetagKind can't be both FnEntry and Raw.
126 // `*const T`, when freshly created, are read-only in the frozen part.
127 NewPermission::FreezeSensitive {
128 freeze_perm: Permission::SharedReadOnly,
129 freeze_access: Some(AccessKind::Read),
130 freeze_protector: None,
131 nonfreeze_perm: Permission::SharedReadWrite,
132 nonfreeze_access: None,
139 fn protector(&self) -> Option<ProtectorKind> {
141 NewPermission::Uniform { protector, .. } => *protector,
142 NewPermission::FreezeSensitive { freeze_protector, .. } => *freeze_protector,
148 pub fn err_sb_ub<'tcx>(
150 help: Option<String>,
151 history: Option<TagHistory>,
152 ) -> InterpError<'tcx> {
153 err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
156 // # Stacked Borrows Core Begin
158 /// We need to make at least the following things true:
160 /// U1: After creating a `Uniq`, it is at the top.
161 /// U2: If the top is `Uniq`, accesses must be through that `Uniq` or remove it.
162 /// U3: If an access happens with a `Uniq`, it requires the `Uniq` to be in the stack.
164 /// F1: After creating a `&`, the parts outside `UnsafeCell` have our `SharedReadOnly` on top.
165 /// F2: If a write access happens, it pops the `SharedReadOnly`. This has three pieces:
166 /// F2a: If a write happens granted by an item below our `SharedReadOnly`, the `SharedReadOnly`
168 /// F2b: No `SharedReadWrite` or `Unique` will ever be added on top of our `SharedReadOnly`.
169 /// F3: If an access happens with an `&` outside `UnsafeCell`,
170 /// it requires the `SharedReadOnly` to still be in the stack.
172 /// Core relation on `Permission` to define which accesses are allowed
174 /// This defines for a given permission, whether it permits the given kind of access.
175 fn grants(self, access: AccessKind) -> bool {
176 // Disabled grants nothing. Otherwise, all items grant read access, and except for SharedReadOnly they grant write access.
177 self != Permission::Disabled
178 && (access == AccessKind::Read || self != Permission::SharedReadOnly)
182 /// Determines whether an item was invalidated by a conflicting access, or by deallocation.
183 #[derive(Copy, Clone, Debug)]
184 enum ItemInvalidationCause {
189 /// Core per-location operations: access, dealloc, reborrow.
191 /// Find the first write-incompatible item above the given one --
192 /// i.e, find the height to which the stack will be truncated when writing to `granting`.
193 fn find_first_write_incompatible(&self, granting: usize) -> usize {
194 let perm = self.get(granting).unwrap().perm();
196 Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
197 Permission::Disabled => bug!("Cannot use Disabled for anything"),
198 Permission::Unique => {
199 // On a write, everything above us is incompatible.
202 Permission::SharedReadWrite => {
203 // The SharedReadWrite *just* above us are compatible, to skip those.
204 let mut idx = granting + 1;
205 while let Some(item) = self.get(idx) {
206 if item.perm() == Permission::SharedReadWrite {
210 // Found first incompatible!
219 /// The given item was invalidated -- check its protectors for whether that will cause UB.
222 global: &GlobalStateInner,
223 dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
224 cause: ItemInvalidationCause,
225 ) -> InterpResult<'tcx> {
226 if !global.tracked_pointer_tags.is_empty() {
227 dcx.check_tracked_tag_popped(item, global);
230 if !item.protected() {
234 // We store tags twice, once in global.protected_tags and once in each call frame.
235 // We do this because consulting a single global set in this function is faster
236 // than attempting to search all call frames in the program for the `FrameExtra`
237 // (if any) which is protecting the popped tag.
239 // This duplication trades off making `end_call` slower to make this function faster. This
240 // trade-off is profitable in practice for a combination of two reasons.
241 // 1. A single protected tag can (and does in some programs) protect thousands of `Item`s.
242 // Therefore, adding overhead in function call/return is profitable even if it only
243 // saves a little work in this function.
244 // 2. Most frames protect only one or two tags. So this duplicative global turns a search
245 // which ends up about linear in the number of protected tags in the program into a
246 // constant time check (and a slow linear, because the tags in the frames aren't contiguous).
247 if let Some(&protector_kind) = global.protected_tags.get(&item.tag()) {
248 // The only way this is okay is if the protector is weak and we are deallocating with
249 // the right pointer.
250 let allowed = matches!(cause, ItemInvalidationCause::Dealloc)
251 && matches!(protector_kind, ProtectorKind::WeakProtector);
253 return Err(dcx.protector_error(item, protector_kind).into());
259 /// Test if a memory `access` using pointer tagged `tag` is granted.
260 /// If yes, return the index of the item that granted it.
261 /// `range` refers the entire operation, and `offset` refers to the specific offset into the
262 /// allocation that we are currently checking.
266 tag: ProvenanceExtra,
267 global: &GlobalStateInner,
268 dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
269 exposed_tags: &FxHashSet<BorTag>,
270 ) -> InterpResult<'tcx> {
271 // Two main steps: Find granting item, remove incompatible items above.
273 // Step 1: Find granting item.
275 self.find_granting(access, tag, exposed_tags).map_err(|()| dcx.access_error(self))?;
277 // Step 2: Remove incompatible items above them. Make sure we do not remove protected
278 // items. Behavior differs for reads and writes.
279 // In case of wildcards/unknown matches, we remove everything that is *definitely* gone.
280 if access == AccessKind::Write {
281 // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
282 // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
283 let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
284 // The granting_idx *might* be approximate, but any lower idx would remove more
285 // things. Even if this is a Unique and the lower idx is an SRW (which removes
286 // less), there is an SRW group boundary here so strictly more would get removed.
287 self.find_first_write_incompatible(granting_idx)
289 // We are writing to something in the unknown part.
290 // There is a SRW group boundary between the unknown and the known, so everything is incompatible.
293 self.pop_items_after(first_incompatible_idx, |item| {
294 Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
295 dcx.log_invalidation(item.tag());
299 // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses.
300 // The reason this is not following the stack discipline (by removing the first Unique and
301 // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
302 // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
303 // `SharedReadWrite` for `raw`.
304 // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
305 // reference and use that.
306 // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
307 let first_incompatible_idx = if let Some(granting_idx) = granting_idx {
308 // The granting_idx *might* be approximate, but any lower idx would disable more things.
311 // We are reading from something in the unknown part. That means *all* `Unique` we know about are dead now.
314 self.disable_uniques_starting_at(first_incompatible_idx, |item| {
315 Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Conflict)?;
316 dcx.log_invalidation(item.tag());
321 // If this was an approximate action, we now collapse everything into an unknown.
322 if granting_idx.is_none() || matches!(tag, ProvenanceExtra::Wildcard) {
323 // Compute the upper bound of the items that remain.
324 // (This is why we did all the work above: to reduce the items we have to consider here.)
325 let mut max = BorTag::one();
326 for i in 0..self.len() {
327 let item = self.get(i).unwrap();
328 // Skip disabled items, they cannot be matched anyway.
329 if !matches!(item.perm(), Permission::Disabled) {
330 // We are looking for a strict upper bound, so add 1 to this tag.
331 max = cmp::max(item.tag().succ().unwrap(), max);
334 if let Some(unk) = self.unknown_bottom() {
335 max = cmp::max(unk, max);
337 // Use `max` as new strict upper bound for everything.
339 "access: forgetting stack to upper bound {max} due to wildcard or unknown access",
342 self.set_unknown_bottom(max);
349 /// Deallocate a location: Like a write access, but also there must be no
350 /// active protectors at all because we will remove all items.
353 tag: ProvenanceExtra,
354 global: &GlobalStateInner,
355 dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
356 exposed_tags: &FxHashSet<BorTag>,
357 ) -> InterpResult<'tcx> {
358 // Step 1: Make a write access.
359 // As part of this we do regular protector checking, i.e. even weakly protected items cause UB when popped.
360 self.access(AccessKind::Write, tag, global, dcx, exposed_tags)?;
362 // Step 2: Pretend we remove the remaining items, checking if any are strongly protected.
363 for idx in (0..self.len()).rev() {
364 let item = self.get(idx).unwrap();
365 Stack::item_invalidated(&item, global, dcx, ItemInvalidationCause::Dealloc)?;
371 /// Derive a new pointer from one with the given tag.
373 /// `access` indicates which kind of memory access this retag itself should correspond to.
376 derived_from: ProvenanceExtra,
378 access: Option<AccessKind>,
379 global: &GlobalStateInner,
380 dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>,
381 exposed_tags: &FxHashSet<BorTag>,
382 ) -> InterpResult<'tcx> {
383 dcx.start_grant(new.perm());
385 // Compute where to put the new item.
386 // Either way, we ensure that we insert the new item in a way such that between
387 // `derived_from` and the new one, there are only items *compatible with* `derived_from`.
388 let new_idx = if let Some(access) = access {
389 // Simple case: We are just a regular memory access, and then push our thing on top,
390 // like a regular stack.
391 // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
392 self.access(access, derived_from, global, dcx, exposed_tags)?;
394 // We insert "as far up as possible": We know only compatible items are remaining
395 // on top of `derived_from`, and we want the new item at the top so that we
396 // get the strongest possible guarantees.
397 // This ensures U1 and F1.
400 // The tricky case: creating a new SRW permission without actually being an access.
401 assert!(new.perm() == Permission::SharedReadWrite);
403 // First we figure out which item grants our parent (`derived_from`) this kind of access.
404 // We use that to determine where to put the new item.
405 let granting_idx = self
406 .find_granting(AccessKind::Write, derived_from, exposed_tags)
407 .map_err(|()| dcx.grant_error(self))?;
409 let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) else {
410 // The parent is a wildcard pointer or matched the unknown bottom.
411 // This is approximate. Nobody knows what happened, so forget everything.
412 // The new thing is SRW anyway, so we cannot push it "on top of the unkown part"
413 // (for all we know, it might join an SRW group inside the unknown).
414 trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown");
415 self.set_unknown_bottom(global.next_ptr_tag);
419 // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
420 // access. Instead of popping the stack, we insert the item at the place the stack would
421 // be popped to (i.e., we insert it above all the write-compatible items).
422 // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
423 self.find_first_write_incompatible(granting_idx)
426 // Put the new item there.
427 trace!("reborrow: adding item {:?}", new);
428 self.insert(new_idx, new);
432 // # Stacked Borrows Core End
434 /// Integration with the BorTag garbage collector
436 pub fn remove_unreachable_tags(&mut self, live_tags: &FxHashSet<BorTag>) {
437 if self.modified_since_last_gc {
438 for stack in self.stacks.iter_mut_all() {
439 if stack.len() > 64 {
440 stack.retain(live_tags);
443 self.modified_since_last_gc = false;
448 impl VisitTags for Stacks {
449 fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
450 for tag in self.exposed_tags.iter().copied() {
456 /// Map per-stack operations to higher-level per-location-range operations.
458 /// Creates a new stack with an initial tag. For diagnostic purposes, we also need to know
459 /// the [`AllocId`] of the allocation this is associated with.
465 machine: &MiriMachine<'_, '_>,
467 let item = Item::new(tag, perm, false);
468 let stack = Stack::new(item);
471 stacks: RangeMap::new(size, stack),
472 history: AllocHistory::new(id, item, machine),
473 exposed_tags: FxHashSet::default(),
474 modified_since_last_gc: false,
478 /// Call `f` on every stack in the range.
482 mut dcx_builder: DiagnosticCxBuilder<'_, '_, 'tcx>,
485 &mut DiagnosticCx<'_, '_, '_, 'tcx>,
486 &mut FxHashSet<BorTag>,
487 ) -> InterpResult<'tcx>,
488 ) -> InterpResult<'tcx> {
489 self.modified_since_last_gc = true;
490 for (offset, stack) in self.stacks.iter_mut(range.start, range.size) {
491 let mut dcx = dcx_builder.build(&mut self.history, offset);
492 f(stack, &mut dcx, &mut self.exposed_tags)?;
493 dcx_builder = dcx.unbuild();
499 /// Glue code to connect with Miri Machine Hooks
501 pub fn new_allocation(
504 state: &mut GlobalStateInner,
505 kind: MemoryKind<MiriMemoryKind>,
506 machine: &MiriMachine<'_, '_>,
508 let (base_tag, perm) = match kind {
509 // New unique borrow. This tag is not accessible by the program,
510 // so it will only ever be used when using the local directly (i.e.,
511 // not through a pointer). That is, whenever we directly write to a local, this will pop
512 // everything else off the stack, invalidating all previous pointers,
513 // and in particular, *all* raw pointers.
514 MemoryKind::Stack => (state.base_ptr_tag(id, machine), Permission::Unique),
515 // Everything else is shared by default.
516 _ => (state.base_ptr_tag(id, machine), Permission::SharedReadWrite),
518 Stacks::new(size, perm, base_tag, id, machine)
522 pub fn before_memory_read<'tcx, 'mir, 'ecx>(
525 tag: ProvenanceExtra,
527 machine: &'ecx MiriMachine<'mir, 'tcx>,
528 ) -> InterpResult<'tcx>
533 "read access with tag {:?}: {:?}, size {}",
535 Pointer::new(alloc_id, range.start),
538 let dcx = DiagnosticCxBuilder::read(machine, tag, range);
539 let state = machine.borrow_tracker.as_ref().unwrap().borrow();
540 self.for_each(range, dcx, |stack, dcx, exposed_tags| {
541 stack.access(AccessKind::Read, tag, &state, dcx, exposed_tags)
546 pub fn before_memory_write<'tcx>(
549 tag: ProvenanceExtra,
551 machine: &mut MiriMachine<'_, 'tcx>,
552 ) -> InterpResult<'tcx> {
554 "write access with tag {:?}: {:?}, size {}",
556 Pointer::new(alloc_id, range.start),
559 let dcx = DiagnosticCxBuilder::write(machine, tag, range);
560 let state = machine.borrow_tracker.as_ref().unwrap().borrow();
561 self.for_each(range, dcx, |stack, dcx, exposed_tags| {
562 stack.access(AccessKind::Write, tag, &state, dcx, exposed_tags)
567 pub fn before_memory_deallocation<'tcx>(
570 tag: ProvenanceExtra,
572 machine: &mut MiriMachine<'_, 'tcx>,
573 ) -> InterpResult<'tcx> {
574 trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes());
575 let dcx = DiagnosticCxBuilder::dealloc(machine, tag);
576 let state = machine.borrow_tracker.as_ref().unwrap().borrow();
577 self.for_each(range, dcx, |stack, dcx, exposed_tags| {
578 stack.dealloc(tag, &state, dcx, exposed_tags)
584 /// Retagging/reborrowing. There is some policy in here, such as which permissions
585 /// to grant for which references, and when to add protectors.
586 impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx>
587 for crate::MiriInterpCx<'mir, 'tcx>
590 trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> {
591 /// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation
595 place: &MPlaceTy<'tcx, Provenance>,
597 new_perm: NewPermission,
599 retag_cause: RetagCause, // What caused this retag, for diagnostics only
600 ) -> InterpResult<'tcx, Option<AllocId>> {
601 let this = self.eval_context_mut();
603 // It is crucial that this gets called on all code paths, to ensure we track tag creation.
604 let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
605 loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
606 -> InterpResult<'tcx> {
607 let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
608 let ty = place.layout.ty;
609 if global.tracked_pointer_tags.contains(&new_tag) {
610 let mut kind_str = String::new();
612 NewPermission::Uniform { perm, .. } =>
613 write!(kind_str, "{perm:?} permission").unwrap(),
614 NewPermission::FreezeSensitive { freeze_perm, .. } if ty.is_freeze(*this.tcx, this.param_env()) =>
615 write!(kind_str, "{freeze_perm:?} permission").unwrap(),
616 NewPermission::FreezeSensitive { freeze_perm, nonfreeze_perm, .. } =>
617 write!(kind_str, "{freeze_perm:?}/{nonfreeze_perm:?} permission for frozen/non-frozen parts").unwrap(),
619 write!(kind_str, " (pointee type {ty})").unwrap();
620 this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
623 loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, size), orig_tag)),
626 drop(global); // don't hold that reference any longer than we have to
628 let Some((alloc_id, base_offset, orig_tag)) = loc else {
632 let (_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
634 AllocKind::LiveData => {
635 // This should have alloc_extra data, but `get_alloc_extra` can still fail
636 // if converting this alloc_id from a global to a local one
637 // uncovers a non-supported `extern static`.
638 let extra = this.get_alloc_extra(alloc_id)?;
639 let mut stacked_borrows = extra
642 // Note that we create a *second* `DiagnosticCxBuilder` below for the actual retag.
643 // FIXME: can this be done cleaner?
644 let dcx = DiagnosticCxBuilder::retag(
649 alloc_range(base_offset, size),
651 let mut dcx = dcx.build(&mut stacked_borrows.history, base_offset);
653 if new_perm.protector().is_some() {
657 AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
658 // No stacked borrows on these allocations.
664 if size == Size::ZERO {
666 "reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
671 // Don't update any stacks for a zero-sized access; borrow stacks are per-byte and this
672 // touches no bytes so there is no stack to put this tag in.
673 // However, if the pointer for this operation points at a real allocation we still
674 // record where it was created so that we can issue a helpful diagnostic if there is an
675 // attempt to use it for a non-zero-sized access.
676 // Dangling slices are a common case here; it's valid to get their length but with raw
677 // pointer tagging for example all calls to get_unchecked on them are invalid.
678 if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) {
679 log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
680 return Ok(Some(alloc_id));
682 // This pointer doesn't come with an AllocId. :shrug:
683 log_creation(this, None)?;
687 let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
688 log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
690 // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
691 let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?;
692 if base_offset + size > alloc_size {
693 throw_ub!(PointerOutOfBounds {
696 ptr_offset: this.machine_usize_to_isize(base_offset.bytes()),
698 msg: CheckInAllocMsg::InboundsTest
703 "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
707 Pointer::new(alloc_id, base_offset),
711 if let Some(protect) = new_perm.protector() {
712 // See comment in `Stack::item_invalidated` for why we store the tag twice.
713 this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag);
720 .insert(new_tag, protect);
723 // Update the stacks, according to the new permission information we are given.
725 NewPermission::Uniform { perm, access, protector } => {
726 assert!(perm != Permission::SharedReadOnly);
727 // Here we can avoid `borrow()` calls because we have mutable references.
728 // Note that this asserts that the allocation is mutable -- but since we are creating a
729 // mutable pointer, that seems reasonable.
730 let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?;
731 let stacked_borrows = alloc_extra.borrow_tracker_sb_mut().get_mut();
732 let item = Item::new(new_tag, perm, protector.is_some());
733 let range = alloc_range(base_offset, size);
734 let global = machine.borrow_tracker.as_ref().unwrap().borrow();
735 let dcx = DiagnosticCxBuilder::retag(
740 alloc_range(base_offset, size),
742 stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
743 stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
746 if let Some(access) = access {
747 assert_eq!(access, AccessKind::Write);
748 // Make sure the data race model also knows about this.
749 if let Some(data_race) = alloc_extra.data_race.as_mut() {
750 data_race.write(alloc_id, range, machine)?;
754 NewPermission::FreezeSensitive {
761 // The permission is not uniform across the entire range!
762 // We need a frozen-sensitive reborrow.
763 // We have to use shared references to alloc/memory_extra here since
764 // `visit_freeze_sensitive` needs to access the global state.
765 let alloc_extra = this.get_alloc_extra(alloc_id)?;
766 let mut stacked_borrows = alloc_extra.borrow_tracker_sb().borrow_mut();
767 this.visit_freeze_sensitive(place, size, |mut range, frozen| {
769 range.start += base_offset;
770 // We are only ever `SharedReadOnly` inside the frozen bits.
771 let (perm, access, protector) = if frozen {
772 (freeze_perm, freeze_access, freeze_protector)
774 (nonfreeze_perm, nonfreeze_access, None)
776 let item = Item::new(new_tag, perm, protector.is_some());
777 let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
778 let dcx = DiagnosticCxBuilder::retag(
783 alloc_range(base_offset, size),
785 stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| {
786 stack.grant(orig_tag, item, access, &global, dcx, exposed_tags)
789 if let Some(access) = access {
790 assert_eq!(access, AccessKind::Read);
791 // Make sure the data race model also knows about this.
792 if let Some(data_race) = alloc_extra.data_race.as_ref() {
793 data_race.read(alloc_id, range, &this.machine)?;
804 /// Retags an indidual pointer, returning the retagged version.
805 /// `kind` indicates what kind of reference is being created.
806 fn sb_retag_reference(
808 val: &ImmTy<'tcx, Provenance>,
809 new_perm: NewPermission,
810 cause: RetagCause, // What caused this retag, for diagnostics only
811 ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
812 let this = self.eval_context_mut();
813 // We want a place for where the ptr *points to*, so we get one.
814 let place = this.ref_to_mplace(val)?;
815 let size = this.size_and_align_of_mplace(&place)?.map(|(size, _)| size);
816 // FIXME: If we cannot determine the size (because the unsized tail is an `extern type`),
817 // bail out -- we cannot reasonably figure out which memory range to reborrow.
818 // See https://github.com/rust-lang/unsafe-code-guidelines/issues/276.
819 let size = match size {
821 None => return Ok(val.clone()),
824 // Compute new borrow.
825 let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
828 let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, cause)?;
831 let new_place = place.map_provenance(|p| {
835 // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one.
836 // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation.
837 Provenance::Concrete { alloc_id, tag: new_tag }
840 // Looks like this has to stay a wildcard pointer.
841 assert!(matches!(prov, Provenance::Wildcard));
848 // Return new pointer.
849 Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
853 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
854 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
855 fn sb_retag_ptr_value(
858 val: &ImmTy<'tcx, Provenance>,
859 ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
860 let this = self.eval_context_mut();
861 let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this);
862 let retag_cause = match kind {
863 RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
864 RetagKind::FnEntry => unreachable!(),
865 RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
867 this.sb_retag_reference(&val, new_perm, retag_cause)
870 fn sb_retag_place_contents(
873 place: &PlaceTy<'tcx, Provenance>,
874 ) -> InterpResult<'tcx> {
875 let this = self.eval_context_mut();
876 let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
877 let retag_cause = match kind {
878 RetagKind::Raw | RetagKind::TwoPhase { .. } => unreachable!(), // these can only happen in `retag_ptr_value`
879 RetagKind::FnEntry => RetagCause::FnEntry,
880 RetagKind::Default => RetagCause::Normal,
882 let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields };
883 return visitor.visit_value(place);
885 // The actual visitor.
886 struct RetagVisitor<'ecx, 'mir, 'tcx> {
887 ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
889 retag_cause: RetagCause,
890 retag_fields: RetagFields,
892 impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
893 #[inline(always)] // yes this helps in our benchmarks
894 fn retag_ptr_inplace(
896 place: &PlaceTy<'tcx, Provenance>,
897 new_perm: NewPermission,
898 retag_cause: RetagCause,
899 ) -> InterpResult<'tcx> {
900 let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
901 let val = self.ecx.sb_retag_reference(&val, new_perm, retag_cause)?;
902 self.ecx.write_immediate(*val, place)?;
906 impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>>
907 for RetagVisitor<'ecx, 'mir, 'tcx>
909 type V = PlaceTy<'tcx, Provenance>;
912 fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> {
916 fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
917 // Boxes get a weak protectors, since they may be deallocated.
918 let new_perm = NewPermission::Uniform {
919 perm: Permission::Unique,
920 access: Some(AccessKind::Write),
921 protector: (self.kind == RetagKind::FnEntry)
922 .then_some(ProtectorKind::WeakProtector),
924 self.retag_ptr_inplace(place, new_perm, self.retag_cause)
927 fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
928 // If this place is smaller than a pointer, we know that it can't contain any
929 // pointers we need to retag, so we can stop recursion early.
930 // This optimization is crucial for ZSTs, because they can contain way more fields
931 // than we can ever visit.
932 if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
936 // Check the type of this value to see what to do with it (retag, or recurse).
937 match place.layout.ty.kind() {
940 NewPermission::from_ref_ty(place.layout.ty, self.kind, self.ecx);
941 self.retag_ptr_inplace(place, new_perm, self.retag_cause)?;
944 // We do *not* want to recurse into raw pointers -- wide raw pointers have
945 // fields, and for dyn Trait pointees those can have reference type!
947 ty::Adt(adt, _) if adt.is_box() => {
948 // Recurse for boxes, they require some tricky handling and will end up in `visit_box` above.
949 // (Yes this means we technically also recursively retag the allocator itself
950 // even if field retagging is not enabled. *shrug*)
951 self.walk_value(place)?;
954 // Not a reference/pointer/box. Only recurse if configured appropriately.
955 let recurse = match self.retag_fields {
956 RetagFields::No => false,
957 RetagFields::Yes => true,
958 RetagFields::OnlyScalar => {
959 // Matching `ArgAbi::new` at the time of writing, only fields of
960 // `Scalar` and `ScalarPair` ABI are considered.
961 matches!(place.layout.abi, Abi::Scalar(..) | Abi::ScalarPair(..))
965 self.walk_value(place)?;
975 /// After a stack frame got pushed, retag the return place so that we are sure
976 /// it does not alias with anything.
978 /// This is a HACK because there is nothing in MIR that would make the retag
979 /// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
980 fn sb_retag_return_place(&mut self) -> InterpResult<'tcx> {
981 let this = self.eval_context_mut();
982 let return_place = &this.frame().return_place;
983 if return_place.layout.is_zst() {
984 // There may not be any memory here, nothing to do.
987 // We need this to be in-memory to use tagged pointers.
988 let return_place = this.force_allocation(&return_place.clone())?;
990 // We have to turn the place into a pointer to use the existing code.
991 // (The pointer type does not matter, so we use a raw pointer.)
992 let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?;
993 let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
994 // Reborrow it. With protection! That is part of the point.
995 let new_perm = NewPermission::Uniform {
996 perm: Permission::Unique,
997 access: Some(AccessKind::Write),
998 protector: Some(ProtectorKind::StrongProtector),
1000 let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturn)?;
1001 // And use reborrowed pointer for return place.
1002 let return_place = this.ref_to_mplace(&val)?;
1003 this.frame_mut().return_place = return_place.into();
1008 /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
1009 fn sb_expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
1010 let this = self.eval_context_mut();
1012 // Function pointers and dead objects don't have an alloc_extra so we ignore them.
1013 // This is okay because accessing them is UB anyway, no need for any Stacked Borrows checks.
1014 // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
1015 let (_size, _align, kind) = this.get_alloc_info(alloc_id);
1017 AllocKind::LiveData => {
1018 // This should have alloc_extra data, but `get_alloc_extra` can still fail
1019 // if converting this alloc_id from a global to a local one
1020 // uncovers a non-supported `extern static`.
1021 let alloc_extra = this.get_alloc_extra(alloc_id)?;
1022 trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}");
1023 alloc_extra.borrow_tracker_sb().borrow_mut().exposed_tags.insert(tag);
1025 AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
1026 // No stacked borrows on these allocations.
1032 fn print_stacks(&mut self, alloc_id: AllocId) -> InterpResult<'tcx> {
1033 let this = self.eval_context_mut();
1034 let alloc_extra = this.get_alloc_extra(alloc_id)?;
1035 let stacks = alloc_extra.borrow_tracker_sb().borrow();
1036 for (range, stack) in stacks.stacks.iter_all() {
1037 print!("{range:?}: [");
1038 if let Some(bottom) = stack.unknown_bottom() {
1039 print!(" unknown-bottom(..{bottom:?})");
1041 for i in 0..stack.len() {
1042 let item = stack.get(i).unwrap();
1043 print!(" {:?}{:?}", item.perm(), item.tag());