miri/borrow_tracker/tree_borrows/
mod.rs

1use rustc_abi::{BackendRepr, Size};
2use rustc_middle::mir::{Mutability, RetagKind};
3use rustc_middle::ty::layout::HasTypingEnv;
4use rustc_middle::ty::{self, Ty};
5
6use crate::borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind};
7use crate::concurrency::data_race::NaReadType;
8use crate::*;
9
10pub mod diagnostics;
11mod foreign_access_skipping;
12mod perms;
13mod tree;
14mod unimap;
15
16#[cfg(test)]
17mod exhaustive;
18
19use self::perms::Permission;
20pub use self::tree::Tree;
21
22pub type AllocState = Tree;
23
24impl<'tcx> Tree {
25    /// Create a new allocation, i.e. a new tree
26    pub fn new_allocation(
27        id: AllocId,
28        size: Size,
29        state: &mut GlobalStateInner,
30        _kind: MemoryKind,
31        machine: &MiriMachine<'tcx>,
32    ) -> Self {
33        let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root
34        let span = machine.current_span();
35        Tree::new(tag, size, span)
36    }
37
38    /// Check that an access on the entire range is permitted, and update
39    /// the tree.
40    pub fn before_memory_access(
41        &mut self,
42        access_kind: AccessKind,
43        alloc_id: AllocId,
44        prov: ProvenanceExtra,
45        range: AllocRange,
46        machine: &MiriMachine<'tcx>,
47    ) -> InterpResult<'tcx> {
48        trace!(
49            "{} with tag {:?}: {:?}, size {}",
50            access_kind,
51            prov,
52            interpret::Pointer::new(alloc_id, range.start),
53            range.size.bytes(),
54        );
55        // TODO: for now we bail out on wildcard pointers. Eventually we should
56        // handle them as much as we can.
57        let tag = match prov {
58            ProvenanceExtra::Concrete(tag) => tag,
59            ProvenanceExtra::Wildcard => return interp_ok(()),
60        };
61        let global = machine.borrow_tracker.as_ref().unwrap();
62        let span = machine.current_span();
63        self.perform_access(
64            tag,
65            Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))),
66            global,
67            alloc_id,
68            span,
69        )
70    }
71
72    /// Check that this pointer has permission to deallocate this range.
73    pub fn before_memory_deallocation(
74        &mut self,
75        alloc_id: AllocId,
76        prov: ProvenanceExtra,
77        size: Size,
78        machine: &MiriMachine<'tcx>,
79    ) -> InterpResult<'tcx> {
80        // TODO: for now we bail out on wildcard pointers. Eventually we should
81        // handle them as much as we can.
82        let tag = match prov {
83            ProvenanceExtra::Concrete(tag) => tag,
84            ProvenanceExtra::Wildcard => return interp_ok(()),
85        };
86        let global = machine.borrow_tracker.as_ref().unwrap();
87        let span = machine.current_span();
88        self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span)
89    }
90
91    pub fn expose_tag(&mut self, _tag: BorTag) {
92        // TODO
93    }
94
95    /// A tag just lost its protector.
96    ///
97    /// This emits a special kind of access that is only applied
98    /// to initialized locations, as a protection against other
99    /// tags not having been made aware of the existence of this
100    /// protector.
101    pub fn release_protector(
102        &mut self,
103        machine: &MiriMachine<'tcx>,
104        global: &GlobalState,
105        tag: BorTag,
106        alloc_id: AllocId, // diagnostics
107    ) -> InterpResult<'tcx> {
108        let span = machine.current_span();
109        // `None` makes it the magic on-protector-end operation
110        self.perform_access(tag, None, global, alloc_id, span)
111    }
112}
113
114/// Policy for a new borrow.
115#[derive(Debug, Clone, Copy)]
116struct NewPermission {
117    /// Which permission should the pointer start with.
118    initial_state: Permission,
119    /// Whether this pointer is part of the arguments of a function call.
120    /// `protector` is `Some(_)` for all pointers marked `noalias`.
121    protector: Option<ProtectorKind>,
122    /// Whether a read should be performed on a retag.  This should be `false`
123    /// for `Cell` because this could cause data races when using thread-safe
124    /// data types like `Mutex<T>`.
125    initial_read: bool,
126}
127
128impl<'tcx> NewPermission {
129    /// Determine NewPermission of the reference from the type of the pointee.
130    fn from_ref_ty(
131        pointee: Ty<'tcx>,
132        mutability: Mutability,
133        kind: RetagKind,
134        cx: &crate::MiriInterpCx<'tcx>,
135    ) -> Option<Self> {
136        let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
137        let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
138        let is_protected = kind == RetagKind::FnEntry;
139        // As demonstrated by `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
140        // interior mutability and protectors interact poorly.
141        // To eliminate the case of Protected Reserved IM we override interior mutability
142        // in the case of a protected reference: protected references are always considered
143        // "freeze" in their reservation phase.
144        let (initial_state, initial_read) = match mutability {
145            Mutability::Mut if ty_is_unpin =>
146                (Permission::new_reserved(ty_is_freeze, is_protected), true),
147            Mutability::Not if ty_is_freeze => (Permission::new_frozen(), true),
148            Mutability::Not if !ty_is_freeze => (Permission::new_cell(), false),
149            // Raw pointers never enter this function so they are not handled.
150            // However raw pointers are not the only pointers that take the parent
151            // tag, this also happens for `!Unpin` `&mut`s, which are excluded above.
152            _ => return None,
153        };
154
155        let protector = is_protected.then_some(ProtectorKind::StrongProtector);
156        Some(Self { initial_state, protector, initial_read })
157    }
158
159    /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
160    /// These pointers allow deallocation so need a different kind of protector not handled
161    /// by `from_ref_ty`.
162    fn from_unique_ty(
163        ty: Ty<'tcx>,
164        kind: RetagKind,
165        cx: &crate::MiriInterpCx<'tcx>,
166    ) -> Option<Self> {
167        let pointee = ty.builtin_deref(true).unwrap();
168        pointee.is_unpin(*cx.tcx, cx.typing_env()).then_some(()).map(|()| {
169            // Regular `Unpin` box, give it `noalias` but only a weak protector
170            // because it is valid to deallocate it within the function.
171            let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
172            let protected = kind == RetagKind::FnEntry;
173            let initial_state = Permission::new_reserved(ty_is_freeze, protected);
174            Self {
175                initial_state,
176                protector: protected.then_some(ProtectorKind::WeakProtector),
177                initial_read: true,
178            }
179        })
180    }
181}
182
183/// Retagging/reborrowing.
184/// Policy on which permission to grant to each pointer should be left to
185/// the implementation of NewPermission.
186impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
187trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
188    /// Returns the provenance that should be used henceforth.
189    fn tb_reborrow(
190        &mut self,
191        place: &MPlaceTy<'tcx>, // parent tag extracted from here
192        ptr_size: Size,
193        new_perm: NewPermission,
194        new_tag: BorTag,
195    ) -> InterpResult<'tcx, Option<Provenance>> {
196        let this = self.eval_context_mut();
197        // Make sure the new permission makes sense as the initial permission of a fresh tag.
198        assert!(new_perm.initial_state.is_initial());
199        // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
200        this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;
201
202        // It is crucial that this gets called on all code paths, to ensure we track tag creation.
203        let log_creation = |this: &MiriInterpCx<'tcx>,
204                            loc: Option<(AllocId, Size, ProvenanceExtra)>| // alloc_id, base_offset, orig_tag
205         -> InterpResult<'tcx> {
206            let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
207            let ty = place.layout.ty;
208            if global.tracked_pointer_tags.contains(&new_tag) {
209                let kind_str = format!("initial state {} (pointee type {ty})", new_perm.initial_state);
210                this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
211                    new_tag.inner(),
212                    Some(kind_str),
213                    loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
214                ));
215            }
216            drop(global); // don't hold that reference any longer than we have to
217            interp_ok(())
218        };
219
220        trace!("Reborrow of size {:?}", ptr_size);
221        let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr(), 0) {
222            Ok(data) => {
223                // Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
224                // After all, the pointer may be lazily initialized outside this initial range.
225                data
226            }
227            Err(_) => {
228                assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
229                // This pointer doesn't come with an AllocId, so there's no
230                // memory to do retagging in.
231                trace!(
232                    "reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
233                    new_tag,
234                    place.ptr(),
235                    place.layout.ty,
236                );
237                log_creation(this, None)?;
238                // Keep original provenance.
239                return interp_ok(place.ptr().provenance);
240            }
241        };
242        log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
243
244        let orig_tag = match parent_prov {
245            ProvenanceExtra::Wildcard => return interp_ok(place.ptr().provenance), // TODO: handle wildcard pointers
246            ProvenanceExtra::Concrete(tag) => tag,
247        };
248
249        trace!(
250            "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
251            new_tag,
252            orig_tag,
253            place.layout.ty,
254            interpret::Pointer::new(alloc_id, base_offset),
255            ptr_size.bytes()
256        );
257
258        if let Some(protect) = new_perm.protector {
259            // We register the protection in two different places.
260            // This makes creating a protector slower, but checking whether a tag
261            // is protected faster.
262            this.frame_mut()
263                .extra
264                .borrow_tracker
265                .as_mut()
266                .unwrap()
267                .protected_tags
268                .push((alloc_id, new_tag));
269            this.machine
270                .borrow_tracker
271                .as_mut()
272                .expect("We should have borrow tracking data")
273                .get_mut()
274                .protected_tags
275                .insert(new_tag, protect);
276        }
277
278        let alloc_kind = this.get_alloc_info(alloc_id).kind;
279        if !matches!(alloc_kind, AllocKind::LiveData) {
280            assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
281            // There's not actually any bytes here where accesses could even be tracked.
282            // Just produce the new provenance, nothing else to do.
283            return interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
284        }
285
286        let span = this.machine.current_span();
287        let alloc_extra = this.get_alloc_extra(alloc_id)?;
288        let range = alloc_range(base_offset, ptr_size);
289        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
290
291        // All reborrows incur a (possibly zero-sized) read access to the parent
292        if new_perm.initial_read {
293            tree_borrows.perform_access(
294                orig_tag,
295                Some((range, AccessKind::Read, diagnostics::AccessCause::Reborrow)),
296                this.machine.borrow_tracker.as_ref().unwrap(),
297                alloc_id,
298                this.machine.current_span(),
299            )?;
300        }
301        // Record the parent-child pair in the tree.
302        tree_borrows.new_child(
303            orig_tag,
304            new_tag,
305            new_perm.initial_state,
306            range,
307            span,
308            new_perm.protector.is_some(),
309        )?;
310        drop(tree_borrows);
311
312        // Also inform the data race model (but only if any bytes are actually affected).
313        if range.size.bytes() > 0 && new_perm.initial_read {
314            if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
315                data_race.read(
316                    alloc_id,
317                    range,
318                    NaReadType::Retag,
319                    Some(place.layout.ty),
320                    &this.machine,
321                )?;
322            }
323        }
324
325        interp_ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }))
326    }
327
328    fn tb_retag_place(
329        &mut self,
330        place: &MPlaceTy<'tcx>,
331        new_perm: NewPermission,
332    ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
333        let this = self.eval_context_mut();
334
335        // Determine the size of the reborrow.
336        // For most types this is the entire size of the place, however
337        // - when `extern type` is involved we use the size of the known prefix,
338        // - if the pointer is not reborrowed (raw pointer) then we override the size
339        //   to do a zero-length reborrow.
340        let reborrow_size = this
341            .size_and_align_of_mplace(place)?
342            .map(|(size, _)| size)
343            .unwrap_or(place.layout.size);
344        trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
345
346        // This new tag is not guaranteed to actually be used.
347        //
348        // If you run out of tags, consider the following optimization: adjust `tb_reborrow`
349        // so that rather than taking as input a fresh tag and deciding whether it uses this
350        // one or the parent it instead just returns whether a new tag should be created.
351        // This will avoid creating tags than end up never being used.
352        let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
353
354        // Compute the actual reborrow.
355        let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
356
357        // Adjust place.
358        // (If the closure gets called, that means the old provenance was `Some`, and hence the new
359        // one must also be `Some`.)
360        interp_ok(place.clone().map_provenance(|_| new_prov.unwrap()))
361    }
362
363    /// Retags an individual pointer, returning the retagged version.
364    fn tb_retag_reference(
365        &mut self,
366        val: &ImmTy<'tcx>,
367        new_perm: NewPermission,
368    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
369        let this = self.eval_context_mut();
370        let place = this.ref_to_mplace(val)?;
371        let new_place = this.tb_retag_place(&place, new_perm)?;
372        interp_ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
373    }
374}
375
376impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
377pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
378    /// Retag a pointer. References are passed to `from_ref_ty` and
379    /// raw pointers are never reborrowed.
380    fn tb_retag_ptr_value(
381        &mut self,
382        kind: RetagKind,
383        val: &ImmTy<'tcx>,
384    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
385        let this = self.eval_context_mut();
386        let new_perm = match val.layout.ty.kind() {
387            &ty::Ref(_, pointee, mutability) =>
388                NewPermission::from_ref_ty(pointee, mutability, kind, this),
389            _ => None,
390        };
391        if let Some(new_perm) = new_perm {
392            this.tb_retag_reference(val, new_perm)
393        } else {
394            interp_ok(val.clone())
395        }
396    }
397
398    /// Retag all pointers that are stored in this place.
399    fn tb_retag_place_contents(
400        &mut self,
401        kind: RetagKind,
402        place: &PlaceTy<'tcx>,
403    ) -> InterpResult<'tcx> {
404        let this = self.eval_context_mut();
405        let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
406        let retag_fields = options.retag_fields;
407        let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
408        return visitor.visit_value(place);
409
410        // The actual visitor.
411        struct RetagVisitor<'ecx, 'tcx> {
412            ecx: &'ecx mut MiriInterpCx<'tcx>,
413            kind: RetagKind,
414            retag_fields: RetagFields,
415        }
416        impl<'ecx, 'tcx> RetagVisitor<'ecx, 'tcx> {
417            #[inline(always)] // yes this helps in our benchmarks
418            fn retag_ptr_inplace(
419                &mut self,
420                place: &PlaceTy<'tcx>,
421                new_perm: Option<NewPermission>,
422            ) -> InterpResult<'tcx> {
423                if let Some(new_perm) = new_perm {
424                    let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
425                    let val = self.ecx.tb_retag_reference(&val, new_perm)?;
426                    self.ecx.write_immediate(*val, place)?;
427                }
428                interp_ok(())
429            }
430        }
431        impl<'ecx, 'tcx> ValueVisitor<'tcx, MiriMachine<'tcx>> for RetagVisitor<'ecx, 'tcx> {
432            type V = PlaceTy<'tcx>;
433
434            #[inline(always)]
435            fn ecx(&self) -> &MiriInterpCx<'tcx> {
436                self.ecx
437            }
438
439            /// Regardless of how `Unique` is handled, Boxes are always reborrowed.
440            /// When `Unique` is also reborrowed, then it behaves exactly like `Box`
441            /// except for the fact that `Box` has a non-zero-sized reborrow.
442            fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
443                // Only boxes for the global allocator get any special treatment.
444                if box_ty.is_box_global(*self.ecx.tcx) {
445                    let new_perm =
446                        NewPermission::from_unique_ty(place.layout.ty, self.kind, self.ecx);
447                    self.retag_ptr_inplace(place, new_perm)?;
448                }
449                interp_ok(())
450            }
451
452            fn visit_value(&mut self, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
453                // If this place is smaller than a pointer, we know that it can't contain any
454                // pointers we need to retag, so we can stop recursion early.
455                // This optimization is crucial for ZSTs, because they can contain way more fields
456                // than we can ever visit.
457                if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
458                    return interp_ok(());
459                }
460
461                // Check the type of this value to see what to do with it (retag, or recurse).
462                match place.layout.ty.kind() {
463                    &ty::Ref(_, pointee, mutability) => {
464                        let new_perm =
465                            NewPermission::from_ref_ty(pointee, mutability, self.kind, self.ecx);
466                        self.retag_ptr_inplace(place, new_perm)?;
467                    }
468                    ty::RawPtr(_, _) => {
469                        // We definitely do *not* want to recurse into raw pointers -- wide raw
470                        // pointers have fields, and for dyn Trait pointees those can have reference
471                        // type!
472                        // We also do not want to reborrow them.
473                    }
474                    ty::Adt(adt, _) if adt.is_box() => {
475                        // Recurse for boxes, they require some tricky handling and will end up in `visit_box` above.
476                        // (Yes this means we technically also recursively retag the allocator itself
477                        // even if field retagging is not enabled. *shrug*)
478                        self.walk_value(place)?;
479                    }
480                    _ => {
481                        // Not a reference/pointer/box. Only recurse if configured appropriately.
482                        let recurse = match self.retag_fields {
483                            RetagFields::No => false,
484                            RetagFields::Yes => true,
485                            RetagFields::OnlyScalar => {
486                                // Matching `ArgAbi::new` at the time of writing, only fields of
487                                // `Scalar` and `ScalarPair` ABI are considered.
488                                matches!(
489                                    place.layout.backend_repr,
490                                    BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)
491                                )
492                            }
493                        };
494                        if recurse {
495                            self.walk_value(place)?;
496                        }
497                    }
498                }
499                interp_ok(())
500            }
501        }
502    }
503
504    /// Protect a place so that it cannot be used any more for the duration of the current function
505    /// call.
506    ///
507    /// This is used to ensure soundness of in-place function argument/return passing.
508    fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
509        let this = self.eval_context_mut();
510
511        // Note: if we were to inline `new_reserved` below we would find out that
512        // `ty_is_freeze` is eventually unused because it appears in a `ty_is_freeze || true`.
513        // We are nevertheless including it here for clarity.
514        let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
515        // Retag it. With protection! That is the entire point.
516        let new_perm = NewPermission {
517            initial_state: Permission::new_reserved(ty_is_freeze, /* protected */ true),
518            protector: Some(ProtectorKind::StrongProtector),
519            initial_read: true,
520        };
521        this.tb_retag_place(place, new_perm)
522    }
523
524    /// Mark the given tag as exposed. It was found on a pointer with the given AllocId.
525    fn tb_expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
526        let this = self.eval_context_ref();
527
528        // Function pointers and dead objects don't have an alloc_extra so we ignore them.
529        // This is okay because accessing them is UB anyway, no need for any Tree Borrows checks.
530        // NOT using `get_alloc_extra_mut` since this might be a read-only allocation!
531        let kind = this.get_alloc_info(alloc_id).kind;
532        match kind {
533            AllocKind::LiveData => {
534                // This should have alloc_extra data, but `get_alloc_extra` can still fail
535                // if converting this alloc_id from a global to a local one
536                // uncovers a non-supported `extern static`.
537                let alloc_extra = this.get_alloc_extra(alloc_id)?;
538                trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
539                alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag);
540            }
541            AllocKind::Function | AllocKind::VTable | AllocKind::Dead => {
542                // No tree borrows on these allocations.
543            }
544        }
545        interp_ok(())
546    }
547
548    /// Display the tree.
549    fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
550        let this = self.eval_context_mut();
551        let alloc_extra = this.get_alloc_extra(alloc_id)?;
552        let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
553        let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
554        tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
555    }
556
557    /// Give a name to the pointer, usually the name it has in the source code (for debugging).
558    /// The name given is `name` and the pointer that receives it is the `nth_parent`
559    /// of `ptr` (with 0 representing `ptr` itself)
560    fn tb_give_pointer_debug_name(
561        &mut self,
562        ptr: Pointer,
563        nth_parent: u8,
564        name: &str,
565    ) -> InterpResult<'tcx> {
566        let this = self.eval_context_mut();
567        let (tag, alloc_id) = match ptr.provenance {
568            Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
569            _ => {
570                eprintln!("Can't give the name {name} to Wildcard pointer");
571                return interp_ok(());
572            }
573        };
574        let alloc_extra = this.get_alloc_extra(alloc_id)?;
575        let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
576        tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
577    }
578}