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}
228
229impl BorrowTrackerMethod {
230    pub fn instantiate_global_state(self, config: &MiriConfig) -> GlobalState {
231        RefCell::new(GlobalStateInner::new(self, config.tracked_pointer_tags.clone()))
232    }
233
234    pub fn get_tree_borrows_params(self) -> TreeBorrowsParams {
235        match self {
236            BorrowTrackerMethod::TreeBorrows(params) => params,
237            _ => panic!("can only be called when `BorrowTrackerMethod` is `TreeBorrows`"),
238        }
239    }
240}
241
242impl GlobalStateInner {
243    pub fn new_allocation(
244        &mut self,
245        id: AllocId,
246        alloc_size: Size,
247        kind: MemoryKind,
248        machine: &MiriMachine<'_>,
249    ) -> AllocState {
250        let _trace = enter_trace_span!(borrow_tracker::new_allocation, ?id, ?alloc_size, ?kind);
251        match self.borrow_tracker_method {
252            BorrowTrackerMethod::StackedBorrows =>
253                AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
254                    id, alloc_size, self, kind, machine,
255                )))),
256            BorrowTrackerMethod::TreeBorrows { .. } =>
257                AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
258                    id, alloc_size, self, kind, machine,
259                )))),
260        }
261    }
262}
263
264impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
265pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
266    fn retag_ptr_value(
267        &mut self,
268        kind: RetagKind,
269        val: &ImmTy<'tcx>,
270    ) -> InterpResult<'tcx, ImmTy<'tcx>> {
271        let _trace = enter_trace_span!(borrow_tracker::retag_ptr_value, ?kind, ?val.layout);
272        let this = self.eval_context_mut();
273        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
274        match method {
275            BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
276            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_ptr_value(kind, val),
277        }
278    }
279
280    fn retag_place_contents(
281        &mut self,
282        kind: RetagKind,
283        place: &PlaceTy<'tcx>,
284    ) -> InterpResult<'tcx> {
285        let _trace = enter_trace_span!(borrow_tracker::retag_place_contents, ?kind, ?place);
286        let this = self.eval_context_mut();
287        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
288        match method {
289            BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
290            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_retag_place_contents(kind, place),
291        }
292    }
293
294    fn protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
295        let _trace = enter_trace_span!(borrow_tracker::protect_place, ?place);
296        let this = self.eval_context_mut();
297        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
298        match method {
299            BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place),
300            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_protect_place(place),
301        }
302    }
303
304    fn expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
305        let _trace =
306            enter_trace_span!(borrow_tracker::expose_tag, alloc_id = alloc_id.0, tag = tag.0);
307        let this = self.eval_context_ref();
308        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
309        match method {
310            BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
311            BorrowTrackerMethod::TreeBorrows { .. } => this.tb_expose_tag(alloc_id, tag),
312        }
313    }
314
315    fn give_pointer_debug_name(
316        &mut self,
317        ptr: Pointer,
318        nth_parent: u8,
319        name: &str,
320    ) -> InterpResult<'tcx> {
321        let this = self.eval_context_mut();
322        let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
323        match method {
324            BorrowTrackerMethod::StackedBorrows => {
325                this.tcx.tcx.dcx().warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
326                interp_ok(())
327            }
328            BorrowTrackerMethod::TreeBorrows { .. } =>
329                this.tb_give_pointer_debug_name(ptr, nth_parent, name),
330        }
331    }
332
333    fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
334        let this = self.eval_context_mut();
335        let Some(borrow_tracker) = &this.machine.borrow_tracker else {
336            eprintln!("attempted to print borrow state, but no borrow state is being tracked");
337            return interp_ok(());
338        };
339        let method = borrow_tracker.borrow().borrow_tracker_method;
340        match method {
341            BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
342            BorrowTrackerMethod::TreeBorrows { .. } => this.print_tree(alloc_id, show_unnamed),
343        }
344    }
345
346    fn on_stack_pop(
347        &self,
348        frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>,
349    ) -> InterpResult<'tcx> {
350        let _trace = enter_trace_span!(borrow_tracker::on_stack_pop);
351        let this = self.eval_context_ref();
352        let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap();
353        // The body of this loop needs `borrow_tracker` immutably
354        // so we can't move this code inside the following `end_call`.
355
356        for (alloc_id, tag) in &frame
357            .extra
358            .borrow_tracker
359            .as_ref()
360            .expect("we should have borrow tracking data")
361            .protected_tags
362        {
363            // Just because the tag is protected doesn't guarantee that
364            // the allocation still exists (weak protectors allow deallocations)
365            // so we must check that the allocation exists.
366            // If it does exist, then we have the guarantee that the
367            // pointer is readable, and the implicit read access inserted
368            // will never cause UB on the pointer itself.
369            let kind = this.get_alloc_info(*alloc_id).kind;
370            if matches!(kind, AllocKind::LiveData) {
371                let alloc_extra = this.get_alloc_extra(*alloc_id)?; // can still fail for `extern static`
372                let alloc_borrow_tracker = &alloc_extra.borrow_tracker.as_ref().unwrap();
373                alloc_borrow_tracker.release_protector(
374                    &this.machine,
375                    borrow_tracker,
376                    *tag,
377                    *alloc_id,
378                )?;
379            }
380        }
381        borrow_tracker.borrow_mut().end_call(&frame.extra);
382
383        interp_ok(())
384    }
385}
386
387/// Extra per-allocation data for borrow tracking
388#[derive(Debug, Clone)]
389pub enum AllocState {
390    /// Data corresponding to Stacked Borrows
391    StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
392    /// Data corresponding to Tree Borrows
393    TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
394}
395
396impl machine::AllocExtra<'_> {
397    #[track_caller]
398    pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
399        match self.borrow_tracker {
400            Some(AllocState::StackedBorrows(ref sb)) => sb,
401            _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
402        }
403    }
404
405    #[track_caller]
406    pub fn borrow_tracker_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocState> {
407        match self.borrow_tracker {
408            Some(AllocState::StackedBorrows(ref mut sb)) => sb,
409            _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
410        }
411    }
412
413    #[track_caller]
414    pub fn borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState> {
415        match self.borrow_tracker {
416            Some(AllocState::TreeBorrows(ref tb)) => tb,
417            _ => panic!("expected Tree Borrows borrow tracking, got something else"),
418        }
419    }
420}
421
422impl AllocState {
423    pub fn before_memory_read<'tcx>(
424        &self,
425        alloc_id: AllocId,
426        prov_extra: ProvenanceExtra,
427        range: AllocRange,
428        machine: &MiriMachine<'tcx>,
429    ) -> InterpResult<'tcx> {
430        let _trace = enter_trace_span!(borrow_tracker::before_memory_read, alloc_id = alloc_id.0);
431        match self {
432            AllocState::StackedBorrows(sb) =>
433                sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
434            AllocState::TreeBorrows(tb) =>
435                tb.borrow_mut().before_memory_access(
436                    AccessKind::Read,
437                    alloc_id,
438                    prov_extra,
439                    range,
440                    machine,
441                ),
442        }
443    }
444
445    pub fn before_memory_write<'tcx>(
446        &mut self,
447        alloc_id: AllocId,
448        prov_extra: ProvenanceExtra,
449        range: AllocRange,
450        machine: &MiriMachine<'tcx>,
451    ) -> InterpResult<'tcx> {
452        let _trace = enter_trace_span!(borrow_tracker::before_memory_write, alloc_id = alloc_id.0);
453        match self {
454            AllocState::StackedBorrows(sb) =>
455                sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
456            AllocState::TreeBorrows(tb) =>
457                tb.get_mut().before_memory_access(
458                    AccessKind::Write,
459                    alloc_id,
460                    prov_extra,
461                    range,
462                    machine,
463                ),
464        }
465    }
466
467    pub fn before_memory_deallocation<'tcx>(
468        &mut self,
469        alloc_id: AllocId,
470        prov_extra: ProvenanceExtra,
471        size: Size,
472        machine: &MiriMachine<'tcx>,
473    ) -> InterpResult<'tcx> {
474        let _trace =
475            enter_trace_span!(borrow_tracker::before_memory_deallocation, alloc_id = alloc_id.0);
476        match self {
477            AllocState::StackedBorrows(sb) =>
478                sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
479            AllocState::TreeBorrows(tb) =>
480                tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, size, machine),
481        }
482    }
483
484    pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
485        let _trace = enter_trace_span!(borrow_tracker::remove_unreachable_tags);
486        match self {
487            AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
488            AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
489        }
490    }
491
492    /// Tree Borrows needs to be told when a tag stops being protected.
493    pub fn release_protector<'tcx>(
494        &self,
495        machine: &MiriMachine<'tcx>,
496        global: &GlobalState,
497        tag: BorTag,
498        alloc_id: AllocId, // diagnostics
499    ) -> InterpResult<'tcx> {
500        let _trace = enter_trace_span!(
501            borrow_tracker::release_protector,
502            alloc_id = alloc_id.0,
503            tag = tag.0
504        );
505        match self {
506            AllocState::StackedBorrows(_sb) => interp_ok(()),
507            AllocState::TreeBorrows(tb) =>
508                tb.borrow_mut().release_protector(machine, global, tag, alloc_id),
509        }
510    }
511}
512
513impl VisitProvenance for AllocState {
514    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
515        let _trace = enter_trace_span!(borrow_tracker::visit_provenance);
516        match self {
517            AllocState::StackedBorrows(sb) => sb.visit_provenance(visit),
518            AllocState::TreeBorrows(tb) => tb.visit_provenance(visit),
519        }
520    }
521}