Skip to main content

miri/borrow_tracker/tree_borrows/
mod.rs

1use rustc_abi::Size;
2use rustc_hir::find_attr;
3use rustc_middle::mir::Mutability;
4use rustc_middle::ty::layout::HasTypingEnv;
5use rustc_middle::ty::{self, Ty};
6
7use self::foreign_access_skipping::IdempotentForeignAccess;
8use self::tree::LocationState;
9use crate::borrow_tracker::{AccessKind, GlobalState, GlobalStateInner, ProtectorKind};
10use crate::concurrency::data_race::{NaReadType, NaWriteType};
11use crate::*;
12
13pub mod diagnostics;
14mod foreign_access_skipping;
15mod perms;
16mod tree;
17mod tree_visitor;
18mod unimap;
19mod wildcard;
20
21#[cfg(test)]
22mod exhaustive;
23
24use self::perms::Permission;
25pub use self::tree::Tree;
26
27pub type AllocState = Tree;
28
29impl<'tcx> Tree {
30    /// Create a new allocation, i.e. a new tree
31    pub fn new_allocation(
32        id: AllocId,
33        size: Size,
34        state: &mut GlobalStateInner,
35        _kind: MemoryKind,
36        machine: &MiriMachine<'tcx>,
37    ) -> Self {
38        let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root
39        let span = machine.current_user_relevant_span();
40        Tree::new(tag, size, span)
41    }
42
43    /// Check that an access on the entire range is permitted, and update
44    /// the tree.
45    pub fn before_memory_access(
46        &mut self,
47        access_kind: AccessKind,
48        alloc_id: AllocId,
49        prov: ProvenanceExtra,
50        range: AllocRange,
51        machine: &MiriMachine<'tcx>,
52    ) -> InterpResult<'tcx> {
53        trace!(
54            "{} with tag {:?}: {:?}, size {}",
55            access_kind,
56            prov,
57            interpret::Pointer::new(alloc_id, range.start),
58            range.size.bytes(),
59        );
60        let global = machine.borrow_tracker.as_ref().unwrap();
61        let span = machine.current_user_relevant_span();
62        self.perform_access(
63            prov,
64            range,
65            access_kind,
66            diagnostics::AccessCause::Explicit(access_kind),
67            global,
68            alloc_id,
69            span,
70        )
71    }
72
73    /// Check that this pointer has permission to deallocate this range.
74    pub fn before_memory_deallocation(
75        &mut self,
76        alloc_id: AllocId,
77        prov: ProvenanceExtra,
78        size: Size,
79        machine: &MiriMachine<'tcx>,
80    ) -> InterpResult<'tcx> {
81        let global = machine.borrow_tracker.as_ref().unwrap();
82        let span = machine.current_user_relevant_span();
83        self.dealloc(prov, alloc_range(Size::ZERO, size), global, alloc_id, span)
84    }
85
86    /// A tag just lost its protector.
87    ///
88    /// This emits a special kind of access that is only applied
89    /// to accessed locations, as a protection against other
90    /// tags not having been made aware of the existence of this
91    /// protector.
92    pub fn release_protector(
93        &mut self,
94        machine: &MiriMachine<'tcx>,
95        global: &GlobalState,
96        tag: BorTag,
97        alloc_id: AllocId, // diagnostics
98    ) -> InterpResult<'tcx> {
99        let span = machine.current_user_relevant_span();
100        self.perform_protector_end_access(tag, global, alloc_id, span)?;
101
102        self.update_exposure_for_protector_release(tag);
103
104        interp_ok(())
105    }
106}
107
108/// Policy for a new borrow.
109#[derive(Debug, Clone, Copy)]
110pub struct NewPermission {
111    /// Permission for the frozen part of the range.
112    freeze_perm: Permission,
113    /// Permission for the non-frozen part of the range.
114    nonfreeze_perm: Permission,
115    /// Permission for memory outside the range.
116    outside_perm: Permission,
117    /// Whether this pointer is part of the arguments of a function call.
118    /// `protector` is `Some(_)` for all pointers marked `noalias`.
119    protector: Option<ProtectorKind>,
120}
121
122impl<'tcx> NewPermission {
123    /// Determine NewPermission of the reference/Box from the type of the pointee.
124    ///
125    /// A `ref_mutability` of `None` indicates a `Box` type.
126    fn new(
127        pointee: Ty<'tcx>,
128        ref_mutability: Option<Mutability>,
129        mode: RetagMode,
130        cx: &MiriInterpCx<'tcx>,
131    ) -> Option<Self> {
132        if mode == RetagMode::None {
133            return None;
134        }
135
136        let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env())
137            && pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env());
138        let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
139        let is_protected = mode == RetagMode::FnEntry;
140
141        // Check if the implicit writes feature is globally enabled, using the
142        // `-Zmiri-tree-borrows-implicit-writes` flag, and not locally disabled using the
143        // `#[rustc_no_writable]` attribute. For performance reasons, only performs the lookup if
144        // is_protected is true as implicit writes are only performed for protected references.
145        let implicit_writes_enabled = is_protected && {
146            let implicit_writes = cx.get_tree_borrows_params().implicit_writes;
147            let def_id = cx.frame().instance().def_id();
148            implicit_writes && !find_attr!(cx.tcx, def_id, RustcNoWritable)
149        };
150
151        if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
152            // Mutable reference / Box to pinning type: retagging is a NOP.
153            // FIXME: with `UnsafePinned`, this should do proper per-byte tracking.
154            return None;
155        }
156
157        enum Part {
158            InsideFrozen,
159            InsideUnsafeCell,
160            Outside,
161        }
162        use Part::*;
163
164        let perm = |part: Part| {
165            // Whether we should consider this byte to be frozen.
166            // Outside bytes are frozen only if the entire type is frozen.
167            let frozen = match part {
168                InsideFrozen => true,
169                InsideUnsafeCell => false,
170                Outside => ty_is_freeze,
171            };
172            match ref_mutability {
173                // Shared references
174                Some(Mutability::Not) =>
175                    if frozen {
176                        Permission::new_frozen()
177                    } else {
178                        Permission::new_cell()
179                    },
180                // Mutable references
181                Some(Mutability::Mut) => {
182                    if implicit_writes_enabled && !matches!(part, Outside) {
183                        // We cannot use `Unique` for the outside part.
184                        Permission::new_unique()
185                    } else if is_protected || frozen {
186                        // We also use this for protected `&mut UnsafeCell` as otherwise adding
187                        // `noalias` would not be sound.
188                        Permission::new_reserved_frz()
189                    } else {
190                        Permission::new_reserved_im()
191                    }
192                }
193                // Boxes
194                None => {
195                    if implicit_writes_enabled && !matches!(part, Outside) {
196                        // Boxes are treated the same as mutable references.
197                        Permission::new_unique()
198                    } else if is_protected || frozen {
199                        // We also use this for protected `Box<UnsafeCell>` as otherwise adding
200                        // `noalias` would not be sound.
201                        Permission::new_reserved_frz()
202                    } else {
203                        Permission::new_reserved_im()
204                    }
205                }
206            }
207        };
208
209        Some(NewPermission {
210            freeze_perm: perm(InsideFrozen),
211            nonfreeze_perm: perm(InsideUnsafeCell),
212            outside_perm: perm(Outside),
213            protector: is_protected.then_some(if ref_mutability.is_some() {
214                // Strong protector for references
215                ProtectorKind::StrongProtector
216            } else {
217                // Weak protector for boxes
218                ProtectorKind::WeakProtector
219            }),
220        })
221    }
222}
223
224/// Retagging/reborrowing.
225/// Policy on which permission to grant to each pointer should be left to
226/// the implementation of NewPermission.
227impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
228trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
229    #[track_caller]
230    #[inline]
231    fn get_tree_borrows_params(&self) -> TreeBorrowsParams {
232        let this = self.eval_context_ref();
233        let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap().borrow();
234        borrow_tracker.borrow_tracker_method.get_tree_borrows_params()
235    }
236
237    /// Returns the provenance that should be used henceforth.
238    fn tb_reborrow(
239        &mut self,
240        place: &MPlaceTy<'tcx>, // parent tag extracted from here
241        ptr_size: Size,
242        new_perm: NewPermission,
243        new_tag: BorTag,
244    ) -> InterpResult<'tcx, Option<Provenance>> {
245        let this = self.eval_context_mut();
246        // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
247        this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;
248
249        // It is crucial that this gets called on all code paths, to ensure we track tag creation.
250        let log_creation = |this: &MiriInterpCx<'tcx>,
251                            loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
252         -> InterpResult<'tcx> {
253            let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
254            let ty = place.layout.ty;
255            if global.tracked_pointer_tags.contains(&new_tag) {
256                 let ty_is_freeze = ty.is_freeze(*this.tcx, this.typing_env());
257                 let kind_str =
258                     if ty_is_freeze {
259                         format!("initial state {} (pointee type {ty})", new_perm.freeze_perm)
260                     } else {
261                         format!("initial state {}/{} outside/inside UnsafeCell (pointee type {ty})", new_perm.freeze_perm, new_perm.nonfreeze_perm)
262                     };
263                this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
264                    new_tag.inner(),
265                    Some(kind_str),
266                    loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
267                ));
268            }
269            drop(global); // don't hold that reference any longer than we have to
270            interp_ok(())
271        };
272
273        trace!("Reborrow of size {:?}", ptr_size);
274        // Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
275        // After all, the pointer may be lazily initialized outside this initial range.
276        let Ok((alloc_id, base_offset, parent_prov)) = this.ptr_try_get_alloc_id(place.ptr(), 0)
277        else {
278            assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
279            // This pointer doesn't come with an AllocId, so there's no
280            // memory to do retagging in.
281            let new_prov = place.ptr().provenance;
282            trace!("reborrow of size 0: reusing {:?} (pointee {})", place.ptr(), place.layout.ty,);
283            log_creation(this, None)?;
284            // Keep original provenance.
285            return interp_ok(new_prov);
286        };
287        let new_prov = Provenance::Concrete { alloc_id, tag: new_tag };
288
289        log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
290
291        trace!(
292            "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
293            new_tag,
294            parent_prov,
295            place.layout.ty,
296            interpret::Pointer::new(alloc_id, base_offset),
297            ptr_size.bytes()
298        );
299
300        if let Some(protect) = new_perm.protector {
301            // We register the protection in two different places.
302            // This makes creating a protector slower, but checking whether a tag
303            // is protected faster.
304            this.frame_mut()
305                .extra
306                .borrow_tracker
307                .as_mut()
308                .unwrap()
309                .protected_tags
310                .push((alloc_id, new_tag));
311            this.machine
312                .borrow_tracker
313                .as_mut()
314                .expect("We should have borrow tracking data")
315                .get_mut()
316                .protected_tags
317                .insert(new_tag, protect);
318        }
319
320        let alloc_kind = this.get_alloc_info(alloc_id).kind;
321        if !matches!(alloc_kind, AllocKind::LiveData) {
322            assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
323            // There's not actually any bytes here where accesses could even be tracked.
324            // Just produce the new provenance, nothing else to do.
325            return interp_ok(Some(new_prov));
326        }
327
328        let protected = new_perm.protector.is_some();
329        let precise_interior_mut = this.get_tree_borrows_params().precise_interior_mut;
330
331        // Compute initial "inside" permissions.
332        let loc_state = |frozen: bool| -> LocationState {
333            let perm = if frozen { new_perm.freeze_perm } else { new_perm.nonfreeze_perm };
334            let sifa = perm.strongest_idempotent_foreign_access(protected);
335
336            if perm.associated_access().is_some() {
337                LocationState::new_accessed(perm, sifa)
338            } else {
339                LocationState::new_non_accessed(perm, sifa)
340            }
341        };
342        let inside_perms = if !precise_interior_mut {
343            // For `!Freeze` types, just pretend the entire thing is an `UnsafeCell`.
344            let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
345            DedupRangeMap::new(ptr_size, loc_state(ty_is_freeze))
346        } else {
347            // The initial state will be overwritten by the visitor below.
348            let mut perms_map = DedupRangeMap::new(
349                ptr_size,
350                LocationState::new_accessed(
351                    Permission::new_disabled(),
352                    IdempotentForeignAccess::None,
353                ),
354            );
355            this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
356                let state = loc_state(frozen);
357                for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
358                    *loc = state;
359                }
360                interp_ok(())
361            })?;
362            perms_map
363        };
364
365        let alloc_extra = this.get_alloc_extra(alloc_id)?;
366        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
367
368        for (perm_range, loc_state) in inside_perms.iter_all() {
369            if let Some(access) = loc_state.permission().associated_access() {
370                // Some reborrows incur a read/write access to the parent.
371                // As a write also implies a read, a single write is performed instead of a read and a write.
372
373                // writing to an immutable allocation (static variables) is UB, check this here
374                if access == AccessKind::Write
375                    && this.get_alloc_mutability(alloc_id).unwrap().is_not()
376                {
377                    throw_ub!(WriteToReadOnly(alloc_id))
378                }
379
380                // Adjust range to be relative to allocation start (rather than to `place`).
381                let range_in_alloc = AllocRange {
382                    start: Size::from_bytes(perm_range.start) + base_offset,
383                    size: Size::from_bytes(perm_range.end - perm_range.start),
384                };
385
386                tree_borrows.perform_access(
387                    parent_prov,
388                    range_in_alloc,
389                    access,
390                    diagnostics::AccessCause::Reborrow(access),
391                    this.machine.borrow_tracker.as_ref().unwrap(),
392                    alloc_id,
393                    this.machine.current_user_relevant_span(),
394                )?;
395
396                // Also inform the data race model (but only if any bytes are actually affected).
397                if range_in_alloc.size.bytes() > 0 {
398                    if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
399                        match access {
400                            AccessKind::Read =>
401                                data_race.read_non_atomic(
402                                    alloc_id,
403                                    range_in_alloc,
404                                    NaReadType::Retag,
405                                    Some(place.layout.ty),
406                                    &this.machine,
407                                )?,
408                            AccessKind::Write =>
409                                data_race.write_non_atomic(
410                                    alloc_id,
411                                    range_in_alloc,
412                                    NaWriteType::Retag,
413                                    Some(place.layout.ty),
414                                    &this.machine,
415                                )?,
416                        };
417                    }
418                }
419            }
420        }
421
422        // Record the parent-child pair in the tree.
423        tree_borrows.new_child(
424            base_offset,
425            parent_prov,
426            new_tag,
427            inside_perms,
428            new_perm.outside_perm,
429            protected,
430            this.machine.current_user_relevant_span(),
431        )?;
432
433        interp_ok(Some(new_prov))
434    }
435
436    fn tb_retag_place(
437        &mut self,
438        place: &MPlaceTy<'tcx>,
439        new_perm: NewPermission,
440    ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
441        let this = self.eval_context_mut();
442
443        // Determine the size of the reborrow.
444        // For most types this is the entire size of the place, however
445        // - when `extern type` is involved we use the size of the known prefix,
446        // - if the pointer is not reborrowed (raw pointer) then we override the size
447        //   to do a zero-length reborrow.
448        let reborrow_size =
449            this.size_and_align_of_val(place)?.map(|(size, _)| size).unwrap_or(place.layout.size);
450        trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
451
452        // This new tag is not guaranteed to actually be used.
453        //
454        // If you run out of tags, consider the following optimization: adjust `tb_reborrow`
455        // so that rather than taking as input a fresh tag and deciding whether it uses this
456        // one or the parent it instead just returns whether a new tag should be created.
457        // This will avoid creating tags than end up never being used.
458        let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
459
460        // Compute the actual reborrow.
461        let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
462
463        // Adjust place.
464        // (If the closure gets called, that means the old provenance was `Some`, and hence the new
465        // one must also be `Some`.)
466        interp_ok(place.clone().map_provenance(|_| new_prov.unwrap()))
467    }
468}
469
470impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
471pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
472    /// Retag a pointer. References are passed to `from_ref_ty` and
473    /// raw pointers are never reborrowed.
474    fn tb_retag_ptr_value(
475        &mut self,
476        val: &ImmTy<'tcx>,
477        ty: Ty<'tcx>,
478        mode: RetagMode,
479    ) -> InterpResult<'tcx, Option<ImmTy<'tcx>>> {
480        let this = self.eval_context_mut();
481        let new_perm = match *ty.kind() {
482            ty::Ref(_, pointee, mutability) =>
483                NewPermission::new(pointee, Some(mutability), mode, this),
484            _ if ty.is_box() => {
485                let box_custom_allocator_unique =
486                    this.get_tree_borrows_params().box_custom_allocator_unique;
487                if box_custom_allocator_unique || ty.is_box_global(*this.tcx) {
488                    // The `None` marks this as a Box.
489                    NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this)
490                } else {
491                    // No retagging for boxes with custom allocators.
492                    None
493                }
494            }
495
496            ty::RawPtr(..) => {
497                assert!(mode == RetagMode::Raw);
498                // We don't give new tags to raw pointers.
499                None
500            }
501            _ => panic!("tb_retag_ptr_value: invalid type {ty}"),
502        };
503        if let Some(new_perm) = new_perm {
504            let place = this.imm_ptr_to_mplace(val)?;
505            let new_place = this.tb_retag_place(&place, new_perm)?;
506            interp_ok(Some(ImmTy::from_immediate(new_place.to_ref(this), val.layout)))
507        } else {
508            interp_ok(None)
509        }
510    }
511
512    /// Protect a place so that it cannot be used any more for the duration of the current function
513    /// call.
514    ///
515    /// This is used to ensure soundness of in-place function argument/return passing.
516    fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
517        let this = self.eval_context_mut();
518
519        // Retag it. With protection! That is the entire point.
520        let new_perm = NewPermission {
521            // Note: If we are creating a protected Reserved, which can
522            // never be ReservedIM, the value of the `ty_is_freeze`
523            // argument doesn't matter
524            // (`ty_is_freeze || true` in `new_reserved` will always be `true`).
525            freeze_perm: Permission::new_reserved_frz(),
526            nonfreeze_perm: Permission::new_reserved_frz(),
527            outside_perm: Permission::new_reserved_frz(),
528            protector: Some(ProtectorKind::StrongProtector),
529        };
530        this.tb_retag_place(place, new_perm)
531    }
532
533    /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
534    fn tb_expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
535        let this = self.eval_context_ref();
536
537        // Function pointers and dead objects don't have an alloc_extra so we ignore them.
538        // This is okay because accessing them is UB anyway, no need for any Tree Borrows checks.
539        // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
540        let kind = this.get_alloc_info(alloc_id).kind;
541        match kind {
542            AllocKind::LiveData => {
543                // This should have alloc_extra data, but `get_alloc_extra` can still fail
544                // if converting this alloc_id from a global to a local one
545                // uncovers a non-supported `extern static`.
546                let alloc_extra = this.get_alloc_extra(alloc_id)?;
547                trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
548
549                let global = this.machine.borrow_tracker.as_ref().unwrap();
550                let protected_tags = &global.borrow().protected_tags;
551                let protected = protected_tags.contains_key(&tag);
552                alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
553            }
554            AllocKind::Function
555            | AllocKind::VTable
556            | AllocKind::TypeId
557            | AllocKind::Dead
558            | AllocKind::VaList => {
559                // No tree borrows on these allocations.
560            }
561        }
562        interp_ok(())
563    }
564
565    /// Display the tree.
566    fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
567        let this = self.eval_context_mut();
568        let alloc_extra = this.get_alloc_extra(alloc_id)?;
569        let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
570        let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
571        tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
572    }
573
574    /// Give a name to the pointer, usually the name it has in the source code (for debugging).
575    /// The name given is `name` and the pointer that receives it is the `nth_parent`
576    /// of `ptr` (with 0 representing `ptr` itself)
577    fn tb_give_pointer_debug_name(
578        &mut self,
579        ptr: Pointer,
580        nth_parent: u8,
581        name: &str,
582    ) -> InterpResult<'tcx> {
583        let this = self.eval_context_mut();
584        let (tag, alloc_id) = match ptr.provenance {
585            Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
586            Some(Provenance::Wildcard) => {
587                eprintln!("Can't give the name {name} to wildcard pointer");
588                return interp_ok(());
589            }
590            None => {
591                eprintln!("Can't give the name {name} to pointer without provenance");
592                return interp_ok(());
593            }
594        };
595        let alloc_extra = this.get_alloc_extra(alloc_id)?;
596        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
597        tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
598    }
599}