Skip to main content

miri/borrow_tracker/
mod.rs

1use std::cell::RefCell;
2use std::fmt;
3use std::num::NonZero;
4
5use rustc_abi::Size;
6use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7use rustc_middle::mir::RetagKind;
8use smallvec::SmallVec;
9
10use crate::*;
11pub mod stacked_borrows;
12pub mod tree_borrows;
13
14/// Indicates which kind of access is being performed.
15#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
16pub enum AccessKind {
17    Read,
18    Write,
19}
20
21impl fmt::Display for AccessKind {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            AccessKind::Read => write!(f, "read access"),
25            AccessKind::Write => write!(f, "write access"),
26        }
27    }
28}
29
30/// Tracking pointer provenance
31#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
32pub struct BorTag(NonZero<u64>);
33
34impl BorTag {
35    pub fn new(i: u64) -> Option<Self> {
36        NonZero::new(i).map(BorTag)
37    }
38
39    pub fn get(&self) -> u64 {
40        self.0.get()
41    }
42
43    pub fn inner(&self) -> NonZero<u64> {
44        self.0
45    }
46
47    pub fn succ(self) -> Option<Self> {
48        self.0.checked_add(1).map(Self)
49    }
50
51    /// The minimum representable tag
52    pub fn one() -> Self {
53        Self::new(1).unwrap()
54    }
55}
56
57impl std::default::Default for BorTag {
58    /// The default to be used when borrow tracking is disabled
59    fn default() -> Self {
60        Self::one()
61    }
62}
63
64impl fmt::Debug for BorTag {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "<{}>", self.0)
67    }
68}
69
70/// Per-call-stack-frame data for borrow tracking
71#[derive(Debug)]
72pub struct FrameState {
73    /// If this frame is protecting any tags, they are listed here. We use this list to do
74    /// incremental updates of the global list of protected tags stored in the
75    /// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
76    /// tag, to identify which call is responsible for protecting the tag.
77    /// See `Stack::item_invalidated` for more explanation.
78    /// Tree Borrows also needs to know which allocation these tags
79    /// belong to so that it can perform a read through them immediately before
80    /// the frame gets popped.
81    ///
82    /// This will contain one tag per reference passed to the function, so
83    /// a size of 2 is enough for the vast majority of functions.
84    protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
85}
86
87impl VisitProvenance for FrameState {
88    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
89        // Visit all protected tags. At least in Tree Borrows,
90        // protected tags can not be GC'd because they still have
91        // an access coming when the protector ends. Additionally,
92        // the tree compacting mechanism of TB's GC relies on the fact
93        // that all protected tags are marked as live for correctness,
94        // so we _have_ to visit them here.
95        for (id, tag) in &self.protected_tags {
96            visit(Some(*id), Some(*tag));
97        }
98    }
99}
100
101/// Extra global state, available to the memory access hooks.
102#[derive(Debug)]
103pub struct GlobalStateInner {
104    /// Borrow tracker method currently in use.
105    borrow_tracker_method: BorrowTrackerMethod,
106    /// Next unused pointer ID (tag).
107    next_ptr_tag: BorTag,
108    /// Table storing the "root" tag for each allocation.
109    /// The root tag is the one used for the initial pointer.
110    /// We need this in a separate table to handle cyclic statics.
111    root_ptr_tags: FxHashMap<AllocId, BorTag>,
112    /// All currently protected tags.
113    /// We add tags to this when they are created with a protector in `reborrow`, and
114    /// we remove tags from this when the call which is protecting them returns, in
115    /// `GlobalStateInner::end_call`. See `Stack::item_invalidated` for more details.
116    protected_tags: FxHashMap<BorTag, ProtectorKind>,
117    /// The pointer ids to trace
118    tracked_pointer_tags: FxHashSet<BorTag>,
119}
120
121impl VisitProvenance for GlobalStateInner {
122    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
123        // All the provenance in protected_tags is also stored in FrameState, and visited there.
124        // The only other candidate is base_ptr_tags, and that does not need visiting since we don't ever
125        // GC the bottommost/root tag.
126    }
127}
128
129/// We need interior mutable access to the global state.
130pub type GlobalState = RefCell<GlobalStateInner>;
131
132/// The flavor of the protector.
133#[derive(Copy, Clone, Debug, PartialEq, Eq)]
134pub enum ProtectorKind {
135    /// Protected against aliasing violations from other pointers.
136    ///
137    /// Items protected like this cause UB when they are invalidated, *but* the pointer itself may
138    /// still be used to issue a deallocation.
139    ///
140    /// This is required for LLVM IR pointers that are `noalias` but *not* `dereferenceable`.
141    WeakProtector,
142
143    /// Protected against any kind of invalidation.
144    ///
145    /// Items protected like this cause UB when they are invalidated or the memory is deallocated.
146    /// This is strictly stronger protection than `WeakProtector`.
147    ///
148    /// This is required for LLVM IR pointers that are `dereferenceable` (and also allows `noalias`).
149    StrongProtector,
150}
151
152/// Utilities for initialization and ID generation
153impl GlobalStateInner {
154    pub fn new(
155        borrow_tracker_method: BorrowTrackerMethod,
156        tracked_pointer_tags: FxHashSet<BorTag>,
157    ) -> Self {
158        GlobalStateInner {
159            borrow_tracker_method,
160            next_ptr_tag: BorTag::one(),
161            root_ptr_tags: FxHashMap::default(),
162            protected_tags: FxHashMap::default(),
163            tracked_pointer_tags,
164        }
165    }
166
167    /// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation!
168    fn new_ptr(&mut self) -> BorTag {
169        let id = self.next_ptr_tag;
170        self.next_ptr_tag = id.succ().unwrap();
171        id
172    }
173
174    pub fn new_frame(&mut self) -> FrameState {
175        FrameState { protected_tags: SmallVec::new() }
176    }
177
178    fn end_call(&mut self, frame: &machine::FrameExtra<'_>) {
179        for (_, tag) in &frame
180            .borrow_tracker
181            .as_ref()
182            .expect("we should have borrow tracking data")
183            .protected_tags
184        {
185            self.protected_tags.remove(tag);
186        }
187    }
188
189    pub fn root_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_>) -> BorTag {
190        self.root_ptr_tags.get(&id).copied().unwrap_or_else(|| {
191            let tag = self.new_ptr();
192            if self.tracked_pointer_tags.contains(&tag) {
193                machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
194                    tag.inner(),
195                    None,
196                    None,
197                ));
198            }
199            trace!("New allocation {:?} has rpot tag {:?}", id, tag);
200            self.root_ptr_tags.try_insert(id, tag).unwrap();
201            tag
202        })
203    }
204
205    pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_>) {
206        self.root_ptr_tags.retain(|id, _| allocs.is_live(*id));
207    }
208
209    pub fn borrow_tracker_method(&self) -> BorrowTrackerMethod {
210        self.borrow_tracker_method
211    }
212}
213
214/// Which borrow tracking method to use
215#[derive(Debug, Copy, Clone, PartialEq, Eq)]
216pub enum BorrowTrackerMethod {
217    /// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
218    StackedBorrows,
219    /// Tree borrows, as implemented in borrow_tracker/tree_borrows
220    TreeBorrows(TreeBorrowsParams),
221}
222
223/// Parameters that Tree Borrows can take.
224#[derive(Debug, Copy, Clone, PartialEq, Eq)]
225pub struct TreeBorrowsParams {
226    pub precise_interior_mut: bool,
227    /// Controls whether `&mut` function arguments are immediately activated with an implicit write.
228    pub implicit_writes: bool,
229}
230
231impl BorrowTrackerMethod {
232    pub fn instantiate_global_state(self, config: &MiriConfig) -> GlobalState {
233        RefCell::new(GlobalStateInner::new(self, config.tracked_pointer_tags.clone()))
234    }
235
236    pub fn get_tree_borrows_params(self) -> TreeBorrowsParams {
237        match self {
238            BorrowTrackerMethod::TreeBorrows(params) => params,
239            _ => panic!("can only be called when `BorrowTrackerMethod` is `TreeBorrows`"),
240        }
241    }
242}
243
244impl GlobalStateInner {
245    pub fn new_allocation(
246        &mut self,
247        id: AllocId,
248        alloc_size: Size,
249        kind: MemoryKind,
250        machine: &MiriMachine<'_>,
251    ) -> AllocState {
252        let _trace = enter_trace_span!(borrow_tracker::new_allocation, ?id, ?alloc_size, ?kind);
253        match self.borrow_tracker_method {
254            BorrowTrackerMethod::StackedBorrows =>
255                AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
256                    id, alloc_size, self, kind, machine,
257                )))),
258            BorrowTrackerMethod::TreeBorrows { .. } =>
259                AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
260                    id, alloc_size, self, kind, machine,
261                )))),
262        }
263    }
264}
265
266impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
267pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
268    fn retag_ptr_value(
269        &mut self,
270        kind: RetagKind,
271        val: &ImmTy<'tcx>,
272    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
273        let _trace = enter_trace_span!(borrow_tracker::retag_ptr_value, ?kind, ?val.layout);
274        let this = self.eval_context_mut();
275        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
276        match method {
277            BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
278            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_ptr_value(kind, val),
279        }
280    }
281
282    fn retag_place_contents(
283        &mut self,
284        kind: RetagKind,
285        place: &PlaceTy<'tcx>,
286    ) -> InterpResult<'tcx> {
287        let _trace = enter_trace_span!(borrow_tracker::retag_place_contents, ?kind, ?place);
288        let this = self.eval_context_mut();
289        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
290        match method {
291            BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
292            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_place_contents(kind, place),
293        }
294    }
295
296    fn protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
297        let _trace = enter_trace_span!(borrow_tracker::protect_place, ?place);
298        let this = self.eval_context_mut();
299        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
300        match method {
301            BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place),
302            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_protect_place(place),
303        }
304    }
305
306    fn expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
307        let _trace =
308            enter_trace_span!(borrow_tracker::expose_tag, alloc_id = alloc_id.0, tag = tag.0);
309        let this = self.eval_context_ref();
310        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
311        match method {
312            BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
313            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_expose_tag(alloc_id, tag),
314        }
315    }
316
317    fn give_pointer_debug_name(
318        &mut self,
319        ptr: Pointer,
320        nth_parent: u8,
321        name: &str,
322    ) -> InterpResult<'tcx> {
323        let this = self.eval_context_mut();
324        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
325        match method {
326            BorrowTrackerMethod::StackedBorrows => {
327                this.tcx.tcx.dcx().warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
328                interp_ok(())
329            }
330            BorrowTrackerMethod::TreeBorrows { .. } =>
331                this.tb_give_pointer_debug_name(ptr, nth_parent, name),
332        }
333    }
334
335    fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
336        let this = self.eval_context_mut();
337        let Some(borrow_tracker) = &this.machine.borrow_tracker else {
338            eprintln!("attempted to print borrow state, but no borrow state is being tracked");
339            return interp_ok(());
340        };
341        let method = borrow_tracker.borrow().borrow_tracker_method;
342        match method {
343            BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
344            BorrowTrackerMethod::TreeBorrows { .. } => this.print_tree(alloc_id, show_unnamed),
345        }
346    }
347
348    fn on_stack_pop(
349        &self,
350        frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>,
351    ) -> InterpResult<'tcx> {
352        let _trace = enter_trace_span!(borrow_tracker::on_stack_pop);
353        let this = self.eval_context_ref();
354        let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap();
355        // The body of this loop needs `borrow_tracker` immutably
356        // so we can't move this code inside the following `end_call`.
357
358        for (alloc_id, tag) in &frame
359            .extra
360            .borrow_tracker
361            .as_ref()
362            .expect("we should have borrow tracking data")
363            .protected_tags
364        {
365            // Just because the tag is protected doesn't guarantee that
366            // the allocation still exists (weak protectors allow deallocations)
367            // so we must check that the allocation exists.
368            // If it does exist, then we have the guarantee that the
369            // pointer is readable, and the implicit read access inserted
370            // will never cause UB on the pointer itself.
371            let kind = this.get_alloc_info(*alloc_id).kind;
372            if matches!(kind, AllocKind::LiveData) {
373                let alloc_extra = this.get_alloc_extra(*alloc_id)?; // can still fail for `extern static`
374                let alloc_borrow_tracker = &alloc_extra.borrow_tracker.as_ref().unwrap();
375                alloc_borrow_tracker.release_protector(
376                    &this.machine,
377                    borrow_tracker,
378                    *tag,
379                    *alloc_id,
380                )?;
381            }
382        }
383        borrow_tracker.borrow_mut().end_call(&frame.extra);
384
385        interp_ok(())
386    }
387}
388
389/// Extra per-allocation data for borrow tracking
390#[derive(Debug, Clone)]
391pub enum AllocState {
392    /// Data corresponding to Stacked Borrows
393    StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
394    /// Data corresponding to Tree Borrows
395    TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
396}
397
398impl machine::AllocExtra<'_> {
399    #[track_caller]
400    pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
401        match self.borrow_tracker {
402            Some(AllocState::StackedBorrows(ref sb)) => sb,
403            _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
404        }
405    }
406
407    #[track_caller]
408    pub fn borrow_tracker_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocState> {
409        match self.borrow_tracker {
410            Some(AllocState::StackedBorrows(ref mut sb)) => sb,
411            _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
412        }
413    }
414
415    #[track_caller]
416    pub fn borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState> {
417        match self.borrow_tracker {
418            Some(AllocState::TreeBorrows(ref tb)) => tb,
419            _ => panic!("expected Tree Borrows borrow tracking, got something else"),
420        }
421    }
422}
423
424impl AllocState {
425    pub fn before_memory_read<'tcx>(
426        &self,
427        alloc_id: AllocId,
428        prov_extra: ProvenanceExtra,
429        range: AllocRange,
430        machine: &MiriMachine<'tcx>,
431    ) -> InterpResult<'tcx> {
432        let _trace = enter_trace_span!(borrow_tracker::before_memory_read, alloc_id = alloc_id.0);
433        match self {
434            AllocState::StackedBorrows(sb) =>
435                sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
436            AllocState::TreeBorrows(tb) =>
437                tb.borrow_mut().before_memory_access(
438                    AccessKind::Read,
439                    alloc_id,
440                    prov_extra,
441                    range,
442                    machine,
443                ),
444        }
445    }
446
447    pub fn before_memory_write<'tcx>(
448        &mut self,
449        alloc_id: AllocId,
450        prov_extra: ProvenanceExtra,
451        range: AllocRange,
452        machine: &MiriMachine<'tcx>,
453    ) -> InterpResult<'tcx> {
454        let _trace = enter_trace_span!(borrow_tracker::before_memory_write, alloc_id = alloc_id.0);
455        match self {
456            AllocState::StackedBorrows(sb) =>
457                sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
458            AllocState::TreeBorrows(tb) =>
459                tb.get_mut().before_memory_access(
460                    AccessKind::Write,
461                    alloc_id,
462                    prov_extra,
463                    range,
464                    machine,
465                ),
466        }
467    }
468
469    pub fn before_memory_deallocation<'tcx>(
470        &mut self,
471        alloc_id: AllocId,
472        prov_extra: ProvenanceExtra,
473        size: Size,
474        machine: &MiriMachine<'tcx>,
475    ) -> InterpResult<'tcx> {
476        let _trace =
477            enter_trace_span!(borrow_tracker::before_memory_deallocation, alloc_id = alloc_id.0);
478        match self {
479            AllocState::StackedBorrows(sb) =>
480                sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
481            AllocState::TreeBorrows(tb) =>
482                tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
483        }
484    }
485
486    pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
487        let _trace = enter_trace_span!(borrow_tracker::remove_unreachable_tags);
488        match self {
489            AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
490            AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
491        }
492    }
493
494    /// Tree Borrows needs to be told when a tag stops being protected.
495    pub fn release_protector<'tcx>(
496        &self,
497        machine: &MiriMachine<'tcx>,
498        global: &GlobalState,
499        tag: BorTag,
500        alloc_id: AllocId, // diagnostics
501    ) -> InterpResult<'tcx> {
502        let _trace = enter_trace_span!(
503            borrow_tracker::release_protector,
504            alloc_id = alloc_id.0,
505            tag = tag.0
506        );
507        match self {
508            AllocState::StackedBorrows(_sb) => interp_ok(()),
509            AllocState::TreeBorrows(tb) =>
510                tb.borrow_mut().release_protector(machine, global, tag, alloc_id),
511        }
512    }
513}
514
515impl VisitProvenance for AllocState {
516    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
517        let _trace = enter_trace_span!(borrow_tracker::visit_provenance);
518        match self {
519            AllocState::StackedBorrows(sb) => sb.visit_provenance(visit),
520            AllocState::TreeBorrows(tb) => tb.visit_provenance(visit),
521        }
522    }
523}