• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::cell::RefCell;
2 use std::fmt;
3 use std::num::NonZeroU64;
4 
5 use log::trace;
6 use smallvec::SmallVec;
7 
8 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
9 use rustc_middle::mir::RetagKind;
10 use rustc_target::abi::Size;
11 
12 use crate::*;
13 pub mod stacked_borrows;
14 pub mod tree_borrows;
15 
16 pub type CallId = NonZeroU64;
17 
18 /// Tracking pointer provenance
19 #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
20 pub struct BorTag(NonZeroU64);
21 
22 impl BorTag {
new(i: u64) -> Option<Self>23     pub fn new(i: u64) -> Option<Self> {
24         NonZeroU64::new(i).map(BorTag)
25     }
26 
get(&self) -> u6427     pub fn get(&self) -> u64 {
28         self.0.get()
29     }
30 
inner(&self) -> NonZeroU6431     pub fn inner(&self) -> NonZeroU64 {
32         self.0
33     }
34 
succ(self) -> Option<Self>35     pub fn succ(self) -> Option<Self> {
36         self.0.checked_add(1).map(Self)
37     }
38 
39     /// The minimum representable tag
one() -> Self40     pub fn one() -> Self {
41         Self::new(1).unwrap()
42     }
43 }
44 
45 impl std::default::Default for BorTag {
46     /// The default to be used when borrow tracking is disabled
default() -> Self47     fn default() -> Self {
48         Self::one()
49     }
50 }
51 
52 impl fmt::Debug for BorTag {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result53     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54         write!(f, "<{}>", self.0)
55     }
56 }
57 
58 /// Per-call-stack-frame data for borrow tracking
59 #[derive(Debug)]
60 pub struct FrameState {
61     /// The ID of the call this frame corresponds to.
62     pub call_id: CallId,
63 
64     /// If this frame is protecting any tags, they are listed here. We use this list to do
65     /// incremental updates of the global list of protected tags stored in the
66     /// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
67     /// tag, to identify which call is responsible for protecting the tag.
68     /// See `Stack::item_popped` for more explanation.
69     ///
70     /// This will contain one tag per reference passed to the function, so
71     /// a size of 2 is enough for the vast majority of functions.
72     pub protected_tags: SmallVec<[BorTag; 2]>,
73 }
74 
75 impl VisitTags for FrameState {
visit_tags(&self, _visit: &mut dyn FnMut(BorTag))76     fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
77         // `protected_tags` are fine to GC.
78     }
79 }
80 
81 /// Extra global state, available to the memory access hooks.
82 #[derive(Debug)]
83 pub struct GlobalStateInner {
84     /// Borrow tracker method currently in use.
85     pub borrow_tracker_method: BorrowTrackerMethod,
86     /// Next unused pointer ID (tag).
87     pub next_ptr_tag: BorTag,
88     /// Table storing the "base" tag for each allocation.
89     /// The base tag is the one used for the initial pointer.
90     /// We need this in a separate table to handle cyclic statics.
91     pub base_ptr_tags: FxHashMap<AllocId, BorTag>,
92     /// Next unused call ID (for protectors).
93     pub next_call_id: CallId,
94     /// All currently protected tags.
95     /// An item is protected if its tag is in this set, *and* it has the "protected" bit set.
96     /// We add tags to this when they are created with a protector in `reborrow`, and
97     /// we remove tags from this when the call which is protecting them returns, in
98     /// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details.
99     pub protected_tags: FxHashMap<BorTag, ProtectorKind>,
100     /// The pointer ids to trace
101     pub tracked_pointer_tags: FxHashSet<BorTag>,
102     /// The call ids to trace
103     pub tracked_call_ids: FxHashSet<CallId>,
104     /// Whether to recurse into datatypes when searching for pointers to retag.
105     pub retag_fields: RetagFields,
106     /// Whether `core::ptr::Unique` gets special (`Box`-like) handling.
107     pub unique_is_unique: bool,
108 }
109 
110 impl VisitTags for GlobalStateInner {
visit_tags(&self, _visit: &mut dyn FnMut(BorTag))111     fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
112         // The only candidate is base_ptr_tags, and that does not need visiting since we don't ever
113         // GC the bottommost tag.
114     }
115 }
116 
117 /// We need interior mutable access to the global state.
118 pub type GlobalState = RefCell<GlobalStateInner>;
119 
120 /// Indicates which kind of access is being performed.
121 #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
122 pub enum AccessKind {
123     Read,
124     Write,
125 }
126 
127 impl fmt::Display for AccessKind {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result128     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129         match self {
130             AccessKind::Read => write!(f, "read access"),
131             AccessKind::Write => write!(f, "write access"),
132         }
133     }
134 }
135 
136 /// Policy on whether to recurse into fields to retag
137 #[derive(Copy, Clone, Debug)]
138 pub enum RetagFields {
139     /// Don't retag any fields.
140     No,
141     /// Retag all fields.
142     Yes,
143     /// Only retag fields of types with Scalar and ScalarPair layout,
144     /// to match the LLVM `noalias` we generate.
145     OnlyScalar,
146 }
147 
148 /// The flavor of the protector.
149 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
150 pub enum ProtectorKind {
151     /// Protected against aliasing violations from other pointers.
152     ///
153     /// Items protected like this cause UB when they are invalidated, *but* the pointer itself may
154     /// still be used to issue a deallocation.
155     ///
156     /// This is required for LLVM IR pointers that are `noalias` but *not* `dereferenceable`.
157     WeakProtector,
158 
159     /// Protected against any kind of invalidation.
160     ///
161     /// Items protected like this cause UB when they are invalidated or the memory is deallocated.
162     /// This is strictly stronger protection than `WeakProtector`.
163     ///
164     /// This is required for LLVM IR pointers that are `dereferenceable` (and also allows `noalias`).
165     StrongProtector,
166 }
167 
168 /// Utilities for initialization and ID generation
169 impl GlobalStateInner {
new( borrow_tracker_method: BorrowTrackerMethod, tracked_pointer_tags: FxHashSet<BorTag>, tracked_call_ids: FxHashSet<CallId>, retag_fields: RetagFields, unique_is_unique: bool, ) -> Self170     pub fn new(
171         borrow_tracker_method: BorrowTrackerMethod,
172         tracked_pointer_tags: FxHashSet<BorTag>,
173         tracked_call_ids: FxHashSet<CallId>,
174         retag_fields: RetagFields,
175         unique_is_unique: bool,
176     ) -> Self {
177         GlobalStateInner {
178             borrow_tracker_method,
179             next_ptr_tag: BorTag::one(),
180             base_ptr_tags: FxHashMap::default(),
181             next_call_id: NonZeroU64::new(1).unwrap(),
182             protected_tags: FxHashMap::default(),
183             tracked_pointer_tags,
184             tracked_call_ids,
185             retag_fields,
186             unique_is_unique,
187         }
188     }
189 
190     /// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation!
new_ptr(&mut self) -> BorTag191     pub fn new_ptr(&mut self) -> BorTag {
192         let id = self.next_ptr_tag;
193         self.next_ptr_tag = id.succ().unwrap();
194         id
195     }
196 
new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameState197     pub fn new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameState {
198         let call_id = self.next_call_id;
199         trace!("new_frame: Assigning call ID {}", call_id);
200         if self.tracked_call_ids.contains(&call_id) {
201             machine.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
202         }
203         self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
204         FrameState { call_id, protected_tags: SmallVec::new() }
205     }
206 
end_call(&mut self, frame: &machine::FrameExtra<'_>)207     pub fn end_call(&mut self, frame: &machine::FrameExtra<'_>) {
208         for tag in &frame
209             .borrow_tracker
210             .as_ref()
211             .expect("we should have borrow tracking data")
212             .protected_tags
213         {
214             self.protected_tags.remove(tag);
215         }
216     }
217 
base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag218     pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag {
219         self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
220             let tag = self.new_ptr();
221             if self.tracked_pointer_tags.contains(&tag) {
222                 machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
223                     tag.inner(),
224                     None,
225                     None,
226                 ));
227             }
228             trace!("New allocation {:?} has base tag {:?}", id, tag);
229             self.base_ptr_tags.try_insert(id, tag).unwrap();
230             tag
231         })
232     }
233 }
234 
235 /// Which borrow tracking method to use
236 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
237 pub enum BorrowTrackerMethod {
238     /// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
239     StackedBorrows,
240     /// Tree borrows, as implemented in borrow_tracker/tree_borrows
241     TreeBorrows,
242 }
243 
244 impl BorrowTrackerMethod {
instantiate_global_state(self, config: &MiriConfig) -> GlobalState245     pub fn instantiate_global_state(self, config: &MiriConfig) -> GlobalState {
246         RefCell::new(GlobalStateInner::new(
247             self,
248             config.tracked_pointer_tags.clone(),
249             config.tracked_call_ids.clone(),
250             config.retag_fields,
251             config.unique_is_unique,
252         ))
253     }
254 }
255 
256 impl GlobalStateInner {
new_allocation( &mut self, id: AllocId, alloc_size: Size, kind: MemoryKind<machine::MiriMemoryKind>, machine: &MiriMachine<'_, '_>, ) -> AllocState257     pub fn new_allocation(
258         &mut self,
259         id: AllocId,
260         alloc_size: Size,
261         kind: MemoryKind<machine::MiriMemoryKind>,
262         machine: &MiriMachine<'_, '_>,
263     ) -> AllocState {
264         match self.borrow_tracker_method {
265             BorrowTrackerMethod::StackedBorrows =>
266                 AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
267                     id, alloc_size, self, kind, machine,
268                 )))),
269             BorrowTrackerMethod::TreeBorrows =>
270                 AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
271                     id, alloc_size, self, kind, machine,
272                 )))),
273         }
274     }
275 }
276 
277 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
278 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
retag_ptr_value( &mut self, kind: RetagKind, val: &ImmTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>>279     fn retag_ptr_value(
280         &mut self,
281         kind: RetagKind,
282         val: &ImmTy<'tcx, Provenance>,
283     ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
284         let this = self.eval_context_mut();
285         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
286         match method {
287             BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
288             BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val),
289         }
290     }
291 
retag_place_contents( &mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx>292     fn retag_place_contents(
293         &mut self,
294         kind: RetagKind,
295         place: &PlaceTy<'tcx, Provenance>,
296     ) -> InterpResult<'tcx> {
297         let this = self.eval_context_mut();
298         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
299         match method {
300             BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
301             BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place),
302         }
303     }
304 
retag_return_place(&mut self) -> InterpResult<'tcx>305     fn retag_return_place(&mut self) -> InterpResult<'tcx> {
306         let this = self.eval_context_mut();
307         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
308         match method {
309             BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
310             BorrowTrackerMethod::TreeBorrows => this.tb_retag_return_place(),
311         }
312     }
313 
expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx>314     fn expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
315         let this = self.eval_context_mut();
316         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
317         match method {
318             BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
319             BorrowTrackerMethod::TreeBorrows => this.tb_expose_tag(alloc_id, tag),
320         }
321     }
322 
give_pointer_debug_name( &mut self, ptr: Pointer<Option<Provenance>>, nth_parent: u8, name: &str, ) -> InterpResult<'tcx>323     fn give_pointer_debug_name(
324         &mut self,
325         ptr: Pointer<Option<Provenance>>,
326         nth_parent: u8,
327         name: &str,
328     ) -> InterpResult<'tcx> {
329         let this = self.eval_context_mut();
330         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
331         match method {
332             BorrowTrackerMethod::StackedBorrows => {
333                 this.tcx.tcx.sess.warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
334                 Ok(())
335             }
336             BorrowTrackerMethod::TreeBorrows =>
337                 this.tb_give_pointer_debug_name(ptr, nth_parent, name),
338         }
339     }
340 
print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx>341     fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
342         let this = self.eval_context_mut();
343         let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
344         match method {
345             BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
346             BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),
347         }
348     }
349 }
350 
351 /// Extra per-allocation data for borrow tracking
352 #[derive(Debug, Clone)]
353 pub enum AllocState {
354     /// Data corresponding to Stacked Borrows
355     StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
356     /// Data corresponding to Tree Borrows
357     TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
358 }
359 
360 impl machine::AllocExtra<'_> {
361     #[track_caller]
borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState>362     pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
363         match self.borrow_tracker {
364             Some(AllocState::StackedBorrows(ref sb)) => sb,
365             _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
366         }
367     }
368 
369     #[track_caller]
borrow_tracker_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocState>370     pub fn borrow_tracker_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocState> {
371         match self.borrow_tracker {
372             Some(AllocState::StackedBorrows(ref mut sb)) => sb,
373             _ => panic!("expected Stacked Borrows borrow tracking, got something else"),
374         }
375     }
376 
377     #[track_caller]
borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState>378     pub fn borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState> {
379         match self.borrow_tracker {
380             Some(AllocState::TreeBorrows(ref tb)) => tb,
381             _ => panic!("expected Tree Borrows borrow tracking, got something else"),
382         }
383     }
384 }
385 
386 impl AllocState {
before_memory_read<'tcx>( &self, alloc_id: AllocId, prov_extra: ProvenanceExtra, range: AllocRange, machine: &MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx>387     pub fn before_memory_read<'tcx>(
388         &self,
389         alloc_id: AllocId,
390         prov_extra: ProvenanceExtra,
391         range: AllocRange,
392         machine: &MiriMachine<'_, 'tcx>,
393     ) -> InterpResult<'tcx> {
394         match self {
395             AllocState::StackedBorrows(sb) =>
396                 sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
397             AllocState::TreeBorrows(tb) =>
398                 tb.borrow_mut().before_memory_access(
399                     AccessKind::Read,
400                     alloc_id,
401                     prov_extra,
402                     range,
403                     machine,
404                 ),
405         }
406     }
407 
before_memory_write<'tcx>( &mut self, alloc_id: AllocId, prov_extra: ProvenanceExtra, range: AllocRange, machine: &mut MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx>408     pub fn before_memory_write<'tcx>(
409         &mut self,
410         alloc_id: AllocId,
411         prov_extra: ProvenanceExtra,
412         range: AllocRange,
413         machine: &mut MiriMachine<'_, 'tcx>,
414     ) -> InterpResult<'tcx> {
415         match self {
416             AllocState::StackedBorrows(sb) =>
417                 sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
418             AllocState::TreeBorrows(tb) =>
419                 tb.get_mut().before_memory_access(
420                     AccessKind::Write,
421                     alloc_id,
422                     prov_extra,
423                     range,
424                     machine,
425                 ),
426         }
427     }
428 
before_memory_deallocation<'tcx>( &mut self, alloc_id: AllocId, prov_extra: ProvenanceExtra, range: AllocRange, machine: &mut MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx>429     pub fn before_memory_deallocation<'tcx>(
430         &mut self,
431         alloc_id: AllocId,
432         prov_extra: ProvenanceExtra,
433         range: AllocRange,
434         machine: &mut MiriMachine<'_, 'tcx>,
435     ) -> InterpResult<'tcx> {
436         match self {
437             AllocState::StackedBorrows(sb) =>
438                 sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
439             AllocState::TreeBorrows(tb) =>
440                 tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
441         }
442     }
443 
remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>)444     pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
445         match self {
446             AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
447             AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
448         }
449     }
450 }
451 
452 impl VisitTags for AllocState {
visit_tags(&self, visit: &mut dyn FnMut(BorTag))453     fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
454         match self {
455             AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
456             AllocState::TreeBorrows(tb) => tb.visit_tags(visit),
457         }
458     }
459 }
460