miri/borrow_tracker/tree_borrows/
mod.rs1use rustc_abi::Size;
2use rustc_middle::mir::Mutability;
3use rustc_middle::ty::layout::HasTypingEnv;
4use rustc_middle::ty::{self, Ty};
5
6use self::foreign_access_skipping::IdempotentForeignAccess;
7use self::tree::LocationState;
8use crate::borrow_tracker::{AccessKind, GlobalState, GlobalStateInner, ProtectorKind};
9use crate::concurrency::data_race::{NaReadType, NaWriteType};
10use crate::*;
11
12pub mod diagnostics;
13mod foreign_access_skipping;
14mod perms;
15mod tree;
16mod tree_visitor;
17mod unimap;
18mod wildcard;
19
20#[cfg(test)]
21mod exhaustive;
22
23use self::perms::Permission;
24pub use self::tree::Tree;
25
26pub type AllocState = Tree;
27
28impl<'tcx> Tree {
29 pub fn new_allocation(
31 id: AllocId,
32 size: Size,
33 state: &mut GlobalStateInner,
34 _kind: MemoryKind,
35 machine: &MiriMachine<'tcx>,
36 ) -> Self {
37 let tag = state.root_ptr_tag(id, machine); let span = machine.current_user_relevant_span();
39 Tree::new(tag, size, span)
40 }
41
42 pub fn before_memory_access(
45 &mut self,
46 access_kind: AccessKind,
47 alloc_id: AllocId,
48 prov: ProvenanceExtra,
49 range: AllocRange,
50 machine: &MiriMachine<'tcx>,
51 ) -> InterpResult<'tcx> {
52 trace!(
53 "{} with tag {:?}: {:?}, size {}",
54 access_kind,
55 prov,
56 interpret::Pointer::new(alloc_id, range.start),
57 range.size.bytes(),
58 );
59 let global = machine.borrow_tracker.as_ref().unwrap();
60 let span = machine.current_user_relevant_span();
61 self.perform_access(
62 prov,
63 range,
64 access_kind,
65 diagnostics::AccessCause::Explicit(access_kind),
66 global,
67 alloc_id,
68 span,
69 )
70 }
71
72 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 let global = machine.borrow_tracker.as_ref().unwrap();
81 let span = machine.current_user_relevant_span();
82 self.dealloc(prov, alloc_range(Size::ZERO, size), global, alloc_id, span)
83 }
84
85 pub fn release_protector(
92 &mut self,
93 machine: &MiriMachine<'tcx>,
94 global: &GlobalState,
95 tag: BorTag,
96 alloc_id: AllocId, ) -> InterpResult<'tcx> {
98 let span = machine.current_user_relevant_span();
99 self.perform_protector_end_access(tag, global, alloc_id, span)?;
100
101 self.update_exposure_for_protector_release(tag);
102
103 interp_ok(())
104 }
105}
106
107#[derive(Debug, Clone, Copy)]
109pub struct NewPermission {
110 freeze_perm: Permission,
112 nonfreeze_perm: Permission,
114 outside_perm: Permission,
116 protector: Option<ProtectorKind>,
119}
120
121impl<'tcx> NewPermission {
122 fn new(
126 pointee: Ty<'tcx>,
127 ref_mutability: Option<Mutability>,
128 mode: RetagMode,
129 cx: &crate::MiriInterpCx<'tcx>,
130 ) -> Option<Self> {
131 if mode == RetagMode::None {
132 return None;
133 }
134
135 let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env())
136 && pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env());
137 let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
138 let is_protected = mode == RetagMode::FnEntry;
139
140 let implicit_writes = cx
142 .machine
143 .borrow_tracker
144 .as_ref()
145 .unwrap()
146 .borrow()
147 .borrow_tracker_method
148 .get_tree_borrows_params()
149 .implicit_writes;
150
151 if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
152 return None;
155 }
156
157 enum Part {
158 InsideFrozen,
159 InsideUnsafeCell,
160 Outside,
161 }
162 use Part::*;
163
164 let perm = |part: Part| {
165 let frozen = match part {
168 InsideFrozen => true,
169 InsideUnsafeCell => false,
170 Outside => ty_is_freeze,
171 };
172 match ref_mutability {
173 Some(Mutability::Not) =>
175 if frozen {
176 Permission::new_frozen()
177 } else {
178 Permission::new_cell()
179 },
180 Some(Mutability::Mut) => {
182 if is_protected && implicit_writes && !matches!(part, Outside) {
183 Permission::new_unique()
185 } else if is_protected || frozen {
186 Permission::new_reserved_frz()
189 } else {
190 Permission::new_reserved_im()
191 }
192 }
193 None =>
195 if is_protected && implicit_writes && !matches!(part, Outside) {
196 Permission::new_unique()
198 } else if is_protected || frozen {
199 Permission::new_reserved_frz()
202 } else {
203 Permission::new_reserved_im()
204 },
205 }
206 };
207
208 Some(NewPermission {
209 freeze_perm: perm(InsideFrozen),
210 nonfreeze_perm: perm(InsideUnsafeCell),
211 outside_perm: perm(Outside),
212 protector: is_protected.then_some(if ref_mutability.is_some() {
213 ProtectorKind::StrongProtector
215 } else {
216 ProtectorKind::WeakProtector
218 }),
219 })
220 }
221}
222
223impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
227trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
228 fn tb_reborrow(
230 &mut self,
231 place: &MPlaceTy<'tcx>, ptr_size: Size,
233 new_perm: NewPermission,
234 new_tag: BorTag,
235 ) -> InterpResult<'tcx, Option<Provenance>> {
236 let this = self.eval_context_mut();
237 this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;
239
240 let log_creation = |this: &MiriInterpCx<'tcx>,
242 loc: Option<(AllocId, Size, ProvenanceExtra)>| -> InterpResult<'tcx> {
244 let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
245 let ty = place.layout.ty;
246 if global.tracked_pointer_tags.contains(&new_tag) {
247 let ty_is_freeze = ty.is_freeze(*this.tcx, this.typing_env());
248 let kind_str =
249 if ty_is_freeze {
250 format!("initial state {} (pointee type {ty})", new_perm.freeze_perm)
251 } else {
252 format!("initial state {}/{} outside/inside UnsafeCell (pointee type {ty})", new_perm.freeze_perm, new_perm.nonfreeze_perm)
253 };
254 this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
255 new_tag.inner(),
256 Some(kind_str),
257 loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
258 ));
259 }
260 drop(global); interp_ok(())
262 };
263
264 trace!("Reborrow of size {:?}", ptr_size);
265 let Ok((alloc_id, base_offset, parent_prov)) = this.ptr_try_get_alloc_id(place.ptr(), 0)
268 else {
269 assert_eq!(ptr_size, Size::ZERO); let new_prov = place.ptr().provenance;
273 trace!("reborrow of size 0: reusing {:?} (pointee {})", place.ptr(), place.layout.ty,);
274 log_creation(this, None)?;
275 return interp_ok(new_prov);
277 };
278 let new_prov = Provenance::Concrete { alloc_id, tag: new_tag };
279
280 log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
281
282 trace!(
283 "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
284 new_tag,
285 parent_prov,
286 place.layout.ty,
287 interpret::Pointer::new(alloc_id, base_offset),
288 ptr_size.bytes()
289 );
290
291 if let Some(protect) = new_perm.protector {
292 this.frame_mut()
296 .extra
297 .borrow_tracker
298 .as_mut()
299 .unwrap()
300 .protected_tags
301 .push((alloc_id, new_tag));
302 this.machine
303 .borrow_tracker
304 .as_mut()
305 .expect("We should have borrow tracking data")
306 .get_mut()
307 .protected_tags
308 .insert(new_tag, protect);
309 }
310
311 let alloc_kind = this.get_alloc_info(alloc_id).kind;
312 if !matches!(alloc_kind, AllocKind::LiveData) {
313 assert_eq!(ptr_size, Size::ZERO); return interp_ok(Some(new_prov));
317 }
318
319 let protected = new_perm.protector.is_some();
320 let precise_interior_mut = this
321 .machine
322 .borrow_tracker
323 .as_mut()
324 .unwrap()
325 .get_mut()
326 .borrow_tracker_method
327 .get_tree_borrows_params()
328 .precise_interior_mut;
329
330 let loc_state = |frozen: bool| -> LocationState {
332 let perm = if frozen { new_perm.freeze_perm } else { new_perm.nonfreeze_perm };
333 let sifa = perm.strongest_idempotent_foreign_access(protected);
334
335 if perm.associated_access().is_some() {
336 LocationState::new_accessed(perm, sifa)
337 } else {
338 LocationState::new_non_accessed(perm, sifa)
339 }
340 };
341 let inside_perms = if !precise_interior_mut {
342 let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
344 DedupRangeMap::new(ptr_size, loc_state(ty_is_freeze))
345 } else {
346 let mut perms_map = DedupRangeMap::new(
348 ptr_size,
349 LocationState::new_accessed(
350 Permission::new_disabled(),
351 IdempotentForeignAccess::None,
352 ),
353 );
354 this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
355 let state = loc_state(frozen);
356 for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
357 *loc = state;
358 }
359 interp_ok(())
360 })?;
361 perms_map
362 };
363
364 let alloc_extra = this.get_alloc_extra(alloc_id)?;
365 let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
366
367 for (perm_range, loc_state) in inside_perms.iter_all() {
368 if let Some(access) = loc_state.permission().associated_access() {
369 if access == AccessKind::Write
374 && this.get_alloc_mutability(alloc_id).unwrap().is_not()
375 {
376 throw_ub!(WriteToReadOnly(alloc_id))
377 }
378
379 let range_in_alloc = AllocRange {
381 start: Size::from_bytes(perm_range.start) + base_offset,
382 size: Size::from_bytes(perm_range.end - perm_range.start),
383 };
384
385 tree_borrows.perform_access(
386 parent_prov,
387 range_in_alloc,
388 access,
389 diagnostics::AccessCause::Reborrow(access),
390 this.machine.borrow_tracker.as_ref().unwrap(),
391 alloc_id,
392 this.machine.current_user_relevant_span(),
393 )?;
394
395 if range_in_alloc.size.bytes() > 0 {
397 if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
398 match access {
399 AccessKind::Read =>
400 data_race.read_non_atomic(
401 alloc_id,
402 range_in_alloc,
403 NaReadType::Retag,
404 Some(place.layout.ty),
405 &this.machine,
406 )?,
407 AccessKind::Write =>
408 data_race.write_non_atomic(
409 alloc_id,
410 range_in_alloc,
411 NaWriteType::Retag,
412 Some(place.layout.ty),
413 &this.machine,
414 )?,
415 };
416 }
417 }
418 }
419 }
420
421 tree_borrows.new_child(
423 base_offset,
424 parent_prov,
425 new_tag,
426 inside_perms,
427 new_perm.outside_perm,
428 protected,
429 this.machine.current_user_relevant_span(),
430 )?;
431
432 interp_ok(Some(new_prov))
433 }
434
435 fn tb_retag_place(
436 &mut self,
437 place: &MPlaceTy<'tcx>,
438 new_perm: NewPermission,
439 ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
440 let this = self.eval_context_mut();
441
442 let reborrow_size =
448 this.size_and_align_of_val(place)?.map(|(size, _)| size).unwrap_or(place.layout.size);
449 trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
450
451 let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
458
459 let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
461
462 interp_ok(place.clone().map_provenance(|_| new_prov.unwrap()))
466 }
467}
468
469impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
470pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
471 fn tb_retag_ptr_value(
474 &mut self,
475 val: &ImmTy<'tcx>,
476 ty: Ty<'tcx>,
477 mode: RetagMode,
478 ) -> InterpResult<'tcx, Option<ImmTy<'tcx>>> {
479 let this = self.eval_context_mut();
480 let new_perm = match ty.kind() {
481 _ if ty.is_box_global(*this.tcx) => {
482 NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this)
484 }
485 &ty::Ref(_, pointee, mutability) =>
486 NewPermission::new(pointee, Some(mutability), mode, this),
487
488 &ty::RawPtr(..) => {
489 assert!(mode == RetagMode::Raw);
490 None
492 }
493 _ if ty.is_box() => {
494 None
496 }
497 _ => panic!("tb_retag_ptr_value: invalid type {ty}"),
498 };
499 if let Some(new_perm) = new_perm {
500 let place = this.imm_ptr_to_mplace(val)?;
501 let new_place = this.tb_retag_place(&place, new_perm)?;
502 interp_ok(Some(ImmTy::from_immediate(new_place.to_ref(this), val.layout)))
503 } else {
504 interp_ok(None)
505 }
506 }
507
508 fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
513 let this = self.eval_context_mut();
514
515 let new_perm = NewPermission {
517 freeze_perm: Permission::new_reserved_frz(),
522 nonfreeze_perm: Permission::new_reserved_frz(),
523 outside_perm: Permission::new_reserved_frz(),
524 protector: Some(ProtectorKind::StrongProtector),
525 };
526 this.tb_retag_place(place, new_perm)
527 }
528
529 fn tb_expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
531 let this = self.eval_context_ref();
532
533 let kind = this.get_alloc_info(alloc_id).kind;
537 match kind {
538 AllocKind::LiveData => {
539 let alloc_extra = this.get_alloc_extra(alloc_id)?;
543 trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
544
545 let global = this.machine.borrow_tracker.as_ref().unwrap();
546 let protected_tags = &global.borrow().protected_tags;
547 let protected = protected_tags.contains_key(&tag);
548 alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
549 }
550 AllocKind::Function
551 | AllocKind::VTable
552 | AllocKind::TypeId
553 | AllocKind::Dead
554 | AllocKind::VaList => {
555 }
557 }
558 interp_ok(())
559 }
560
561 fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
563 let this = self.eval_context_mut();
564 let alloc_extra = this.get_alloc_extra(alloc_id)?;
565 let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
566 let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
567 tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
568 }
569
570 fn tb_give_pointer_debug_name(
574 &mut self,
575 ptr: Pointer,
576 nth_parent: u8,
577 name: &str,
578 ) -> InterpResult<'tcx> {
579 let this = self.eval_context_mut();
580 let (tag, alloc_id) = match ptr.provenance {
581 Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
582 Some(Provenance::Wildcard) => {
583 eprintln!("Can't give the name {name} to wildcard pointer");
584 return interp_ok(());
585 }
586 None => {
587 eprintln!("Can't give the name {name} to pointer without provenance");
588 return interp_ok(());
589 }
590 };
591 let alloc_extra = this.get_alloc_extra(alloc_id)?;
592 let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
593 tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
594 }
595}