Skip to main content

miri/borrow_tracker/
mod.rs

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