miri/borrow_tracker/tree_borrows/
mod.rs1use rustc_abi::Size;
2use rustc_middle::mir::{Mutability, RetagKind};
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;
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 freeze_access: bool,
114 nonfreeze_perm: Permission,
116 nonfreeze_access: bool,
119 outside_perm: Permission,
121 protector: Option<ProtectorKind>,
124}
125
126impl<'tcx> NewPermission {
127 fn new(
131 pointee: Ty<'tcx>,
132 ref_mutability: Option<Mutability>,
133 retag_kind: RetagKind,
134 cx: &crate::MiriInterpCx<'tcx>,
135 ) -> Option<Self> {
136 let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
137 let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
138 let is_protected = retag_kind == RetagKind::FnEntry;
139
140 if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) {
141 return None;
144 }
145
146 let freeze_perm = match ref_mutability {
147 Some(Mutability::Not) => Permission::new_frozen(),
149 _ => Permission::new_reserved_frz(),
151 };
152 let nonfreeze_perm = match ref_mutability {
153 Some(Mutability::Not) => Permission::new_cell(),
155 _ if is_protected => Permission::new_reserved_frz(),
157 _ => Permission::new_reserved_im(),
159 };
160
161 let initial_access = |perm: &Permission| !perm.is_cell();
163
164 Some(NewPermission {
165 freeze_perm,
166 freeze_access: initial_access(&freeze_perm),
167 nonfreeze_perm,
168 nonfreeze_access: initial_access(&nonfreeze_perm),
169 outside_perm: if ty_is_freeze { freeze_perm } else { nonfreeze_perm },
170 protector: is_protected.then_some(if ref_mutability.is_some() {
171 ProtectorKind::StrongProtector
173 } else {
174 ProtectorKind::WeakProtector
176 }),
177 })
178 }
179}
180
181impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
185trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
186 fn tb_reborrow(
188 &mut self,
189 place: &MPlaceTy<'tcx>, ptr_size: Size,
191 new_perm: NewPermission,
192 new_tag: BorTag,
193 ) -> InterpResult<'tcx, Option<Provenance>> {
194 let this = self.eval_context_mut();
195 this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;
197
198 let log_creation = |this: &MiriInterpCx<'tcx>,
200 loc: Option<(AllocId, Size, ProvenanceExtra)>| -> InterpResult<'tcx> {
202 let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
203 let ty = place.layout.ty;
204 if global.tracked_pointer_tags.contains(&new_tag) {
205 let ty_is_freeze = ty.is_freeze(*this.tcx, this.typing_env());
206 let kind_str =
207 if ty_is_freeze {
208 format!("initial state {} (pointee type {ty})", new_perm.freeze_perm)
209 } else {
210 format!("initial state {}/{} outside/inside UnsafeCell (pointee type {ty})", new_perm.freeze_perm, new_perm.nonfreeze_perm)
211 };
212 this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
213 new_tag.inner(),
214 Some(kind_str),
215 loc.map(|(alloc_id, base_offset, orig_tag)| (alloc_id, alloc_range(base_offset, ptr_size), orig_tag)),
216 ));
217 }
218 drop(global); interp_ok(())
220 };
221
222 trace!("Reborrow of size {:?}", ptr_size);
223 let Ok((alloc_id, base_offset, parent_prov)) = this.ptr_try_get_alloc_id(place.ptr(), 0)
226 else {
227 assert_eq!(ptr_size, Size::ZERO); let new_prov = place.ptr().provenance;
231 trace!("reborrow of size 0: reusing {:?} (pointee {})", place.ptr(), place.layout.ty,);
232 log_creation(this, None)?;
233 return interp_ok(new_prov);
235 };
236 let new_prov = Provenance::Concrete { alloc_id, tag: new_tag };
237
238 log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
239
240 trace!(
241 "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
242 new_tag,
243 parent_prov,
244 place.layout.ty,
245 interpret::Pointer::new(alloc_id, base_offset),
246 ptr_size.bytes()
247 );
248
249 if let Some(protect) = new_perm.protector {
250 this.frame_mut()
254 .extra
255 .borrow_tracker
256 .as_mut()
257 .unwrap()
258 .protected_tags
259 .push((alloc_id, new_tag));
260 this.machine
261 .borrow_tracker
262 .as_mut()
263 .expect("We should have borrow tracking data")
264 .get_mut()
265 .protected_tags
266 .insert(new_tag, protect);
267 }
268
269 let alloc_kind = this.get_alloc_info(alloc_id).kind;
270 if !matches!(alloc_kind, AllocKind::LiveData) {
271 assert_eq!(ptr_size, Size::ZERO); return interp_ok(Some(new_prov));
275 }
276
277 let protected = new_perm.protector.is_some();
278 let precise_interior_mut = this
279 .machine
280 .borrow_tracker
281 .as_mut()
282 .unwrap()
283 .get_mut()
284 .borrow_tracker_method
285 .get_tree_borrows_params()
286 .precise_interior_mut;
287
288 let loc_state = |frozen: bool| -> LocationState {
290 let (perm, access) = if frozen {
291 (new_perm.freeze_perm, new_perm.freeze_access)
292 } else {
293 (new_perm.nonfreeze_perm, new_perm.nonfreeze_access)
294 };
295 let sifa = perm.strongest_idempotent_foreign_access(protected);
296 if access {
297 LocationState::new_accessed(perm, sifa)
298 } else {
299 LocationState::new_non_accessed(perm, sifa)
300 }
301 };
302 let inside_perms = if !precise_interior_mut {
303 let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env());
305 let state = loc_state(ty_is_freeze);
306 DedupRangeMap::new(ptr_size, state)
307 } else {
308 let mut perms_map: DedupRangeMap<LocationState> = DedupRangeMap::new(
310 ptr_size,
311 LocationState::new_accessed(
312 Permission::new_disabled(),
313 IdempotentForeignAccess::None,
314 ),
315 );
316 this.visit_freeze_sensitive(place, ptr_size, |range, frozen| {
317 let state = loc_state(frozen);
318 for (_loc_range, loc) in perms_map.iter_mut(range.start, range.size) {
319 *loc = state;
320 }
321 interp_ok(())
322 })?;
323 perms_map
324 };
325
326 let alloc_extra = this.get_alloc_extra(alloc_id)?;
327 let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
328
329 for (perm_range, perm) in inside_perms.iter_all() {
330 if perm.accessed() {
331 let range_in_alloc = AllocRange {
334 start: Size::from_bytes(perm_range.start) + base_offset,
335 size: Size::from_bytes(perm_range.end - perm_range.start),
336 };
337
338 tree_borrows.perform_access(
339 parent_prov,
340 range_in_alloc,
341 AccessKind::Read,
342 diagnostics::AccessCause::Reborrow,
343 this.machine.borrow_tracker.as_ref().unwrap(),
344 alloc_id,
345 this.machine.current_user_relevant_span(),
346 )?;
347
348 if range_in_alloc.size.bytes() > 0 {
350 if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() {
351 data_race.read_non_atomic(
352 alloc_id,
353 range_in_alloc,
354 NaReadType::Retag,
355 Some(place.layout.ty),
356 &this.machine,
357 )?
358 }
359 }
360 }
361 }
362 tree_borrows.new_child(
364 base_offset,
365 parent_prov,
366 new_tag,
367 inside_perms,
368 new_perm.outside_perm,
369 protected,
370 this.machine.current_user_relevant_span(),
371 )?;
372 drop(tree_borrows);
373
374 interp_ok(Some(new_prov))
375 }
376
377 fn tb_retag_place(
378 &mut self,
379 place: &MPlaceTy<'tcx>,
380 new_perm: NewPermission,
381 ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
382 let this = self.eval_context_mut();
383
384 let reborrow_size =
390 this.size_and_align_of_val(place)?.map(|(size, _)| size).unwrap_or(place.layout.size);
391 trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
392
393 let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
400
401 let new_prov = this.tb_reborrow(place, reborrow_size, new_perm, new_tag)?;
403
404 interp_ok(place.clone().map_provenance(|_| new_prov.unwrap()))
408 }
409
410 fn tb_retag_reference(
412 &mut self,
413 val: &ImmTy<'tcx>,
414 new_perm: NewPermission,
415 ) -> InterpResult<'tcx, ImmTy<'tcx>> {
416 let this = self.eval_context_mut();
417 let place = this.ref_to_mplace(val)?;
418 let new_place = this.tb_retag_place(&place, new_perm)?;
419 interp_ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))
420 }
421}
422
423impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
424pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
425 fn tb_retag_ptr_value(
428 &mut self,
429 kind: RetagKind,
430 val: &ImmTy<'tcx>,
431 ) -> InterpResult<'tcx, ImmTy<'tcx>> {
432 let this = self.eval_context_mut();
433 let new_perm = match val.layout.ty.kind() {
434 &ty::Ref(_, pointee, mutability) =>
435 NewPermission::new(pointee, Some(mutability), kind, this),
436 _ => None,
437 };
438 if let Some(new_perm) = new_perm {
439 this.tb_retag_reference(val, new_perm)
440 } else {
441 interp_ok(val.clone())
442 }
443 }
444
445 fn tb_retag_place_contents(
447 &mut self,
448 kind: RetagKind,
449 place: &PlaceTy<'tcx>,
450 ) -> InterpResult<'tcx> {
451 let this = self.eval_context_mut();
452 let mut visitor = RetagVisitor { ecx: this, kind };
453 return visitor.visit_value(place);
454
455 struct RetagVisitor<'ecx, 'tcx> {
457 ecx: &'ecx mut MiriInterpCx<'tcx>,
458 kind: RetagKind,
459 }
460 impl<'ecx, 'tcx> RetagVisitor<'ecx, 'tcx> {
461 #[inline(always)] fn retag_ptr_inplace(
463 &mut self,
464 place: &PlaceTy<'tcx>,
465 new_perm: Option<NewPermission>,
466 ) -> InterpResult<'tcx> {
467 if let Some(new_perm) = new_perm {
468 let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
469 let val = self.ecx.tb_retag_reference(&val, new_perm)?;
470 self.ecx.write_immediate(*val, place)?;
471 }
472 interp_ok(())
473 }
474 }
475 impl<'ecx, 'tcx> ValueVisitor<'tcx, MiriMachine<'tcx>> for RetagVisitor<'ecx, 'tcx> {
476 type V = PlaceTy<'tcx>;
477
478 #[inline(always)]
479 fn ecx(&self) -> &MiriInterpCx<'tcx> {
480 self.ecx
481 }
482
483 fn visit_box(&mut self, box_ty: Ty<'tcx>, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
487 if box_ty.is_box_global(*self.ecx.tcx) {
489 let pointee = place.layout.ty.builtin_deref(true).unwrap();
490 let new_perm =
491 NewPermission::new(pointee, None, self.kind, self.ecx);
492 self.retag_ptr_inplace(place, new_perm)?;
493 }
494 interp_ok(())
495 }
496
497 fn visit_value(&mut self, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
498 if place.layout.is_sized() && place.layout.size < self.ecx.pointer_size() {
503 return interp_ok(());
504 }
505
506 match place.layout.ty.kind() {
508 &ty::Ref(_, pointee, mutability) => {
509 let new_perm =
510 NewPermission::new(pointee, Some(mutability), self.kind, self.ecx);
511 self.retag_ptr_inplace(place, new_perm)?;
512 }
513 ty::RawPtr(_, _) => {
514 }
519 ty::Adt(adt, _) if adt.is_box() => {
520 self.walk_value(place)?;
524 }
525 _ => {
526 self.walk_value(place)?;
528 }
529 }
530 interp_ok(())
531 }
532 }
533 }
534
535 fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx>) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
540 let this = self.eval_context_mut();
541
542 let new_perm = NewPermission {
544 freeze_perm: Permission::new_reserved_frz(),
549 freeze_access: true,
550 nonfreeze_perm: Permission::new_reserved_frz(),
551 nonfreeze_access: true,
552 outside_perm: Permission::new_reserved_frz(),
553 protector: Some(ProtectorKind::StrongProtector),
554 };
555 this.tb_retag_place(place, new_perm)
556 }
557
558 fn tb_expose_tag(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
560 let this = self.eval_context_ref();
561
562 let kind = this.get_alloc_info(alloc_id).kind;
566 match kind {
567 AllocKind::LiveData => {
568 let alloc_extra = this.get_alloc_extra(alloc_id)?;
572 trace!("Tree Borrows tag {tag:?} exposed in {alloc_id:?}");
573
574 let global = this.machine.borrow_tracker.as_ref().unwrap();
575 let protected_tags = &global.borrow().protected_tags;
576 let protected = protected_tags.contains_key(&tag);
577 alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
578 }
579 AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
580 }
582 }
583 interp_ok(())
584 }
585
586 fn print_tree(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
588 let this = self.eval_context_mut();
589 let alloc_extra = this.get_alloc_extra(alloc_id)?;
590 let tree_borrows = alloc_extra.borrow_tracker_tb().borrow();
591 let borrow_tracker = &this.machine.borrow_tracker.as_ref().unwrap().borrow();
592 tree_borrows.print_tree(&borrow_tracker.protected_tags, show_unnamed)
593 }
594
595 fn tb_give_pointer_debug_name(
599 &mut self,
600 ptr: Pointer,
601 nth_parent: u8,
602 name: &str,
603 ) -> InterpResult<'tcx> {
604 let this = self.eval_context_mut();
605 let (tag, alloc_id) = match ptr.provenance {
606 Some(Provenance::Concrete { tag, alloc_id }) => (tag, alloc_id),
607 Some(Provenance::Wildcard) => {
608 eprintln!("Can't give the name {name} to wildcard pointer");
609 return interp_ok(());
610 }
611 None => {
612 eprintln!("Can't give the name {name} to pointer without provenance");
613 return interp_ok(());
614 }
615 };
616 let alloc_extra = this.get_alloc_extra(alloc_id)?;
617 let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
618 tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
619 }
620}