Skip to content

Commit c0eca2e

Browse files
authored
Rollup merge of #122233 - RalfJung:custom-alloc-box, r=oli-obk
miri: do not apply aliasing restrictions to Box with custom allocator This is the Miri side of rust-lang/rust#122018. The "intrinsics with body" made this much more pleasant. :) Fixes #3341. r? `@oli-obk`
2 parents 5345dde + fcb8d60 commit c0eca2e

File tree

5 files changed

+205
-13
lines changed

5 files changed

+205
-13
lines changed

src/borrow_tracker/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::num::NonZero;
55
use smallvec::SmallVec;
66

77
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8-
use rustc_middle::mir::RetagKind;
8+
use rustc_middle::{mir::RetagKind, ty::Ty};
99
use rustc_target::abi::Size;
1010

1111
use crate::*;
@@ -291,6 +291,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
291291
}
292292
}
293293

294+
fn retag_box_to_raw(
295+
&mut self,
296+
val: &ImmTy<'tcx, Provenance>,
297+
alloc_ty: Ty<'tcx>,
298+
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
299+
let this = self.eval_context_mut();
300+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
301+
match method {
302+
BorrowTrackerMethod::StackedBorrows => this.sb_retag_box_to_raw(val, alloc_ty),
303+
BorrowTrackerMethod::TreeBorrows => this.tb_retag_box_to_raw(val, alloc_ty),
304+
}
305+
}
306+
294307
fn retag_place_contents(
295308
&mut self,
296309
kind: RetagKind,

src/borrow_tracker/stacked_borrows/mod.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
865865
this.sb_retag_reference(val, new_perm, RetagInfo { cause, in_field: false })
866866
}
867867

868+
fn sb_retag_box_to_raw(
869+
&mut self,
870+
val: &ImmTy<'tcx, Provenance>,
871+
alloc_ty: Ty<'tcx>,
872+
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
873+
let this = self.eval_context_mut();
874+
let is_global_alloc = alloc_ty.ty_adt_def().is_some_and(|adt| {
875+
let global_alloc = this.tcx.require_lang_item(rustc_hir::LangItem::GlobalAlloc, None);
876+
adt.did() == global_alloc
877+
});
878+
if is_global_alloc {
879+
// Retag this as-if it was a mutable reference.
880+
this.sb_retag_ptr_value(RetagKind::Raw, val)
881+
} else {
882+
Ok(val.clone())
883+
}
884+
}
885+
868886
fn sb_retag_place_contents(
869887
&mut self,
870888
kind: RetagKind,
@@ -916,10 +934,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
916934
self.ecx
917935
}
918936

919-
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
920-
// Boxes get a weak protectors, since they may be deallocated.
921-
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
922-
self.retag_ptr_inplace(place, new_perm)
937+
fn visit_box(
938+
&mut self,
939+
box_ty: Ty<'tcx>,
940+
place: &PlaceTy<'tcx, Provenance>,
941+
) -> InterpResult<'tcx> {
942+
// Only boxes for the global allocator get any special treatment.
943+
if box_ty.is_box_global(*self.ecx.tcx) {
944+
// Boxes get a weak protectors, since they may be deallocated.
945+
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
946+
self.retag_ptr_inplace(place, new_perm)?;
947+
}
948+
Ok(())
923949
}
924950

925951
fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {

src/borrow_tracker/tree_borrows/mod.rs

+25-8
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
392392
}
393393
}
394394

395+
fn tb_retag_box_to_raw(
396+
&mut self,
397+
val: &ImmTy<'tcx, Provenance>,
398+
_alloc_ty: Ty<'tcx>,
399+
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
400+
// Casts to raw pointers are NOPs in Tree Borrows.
401+
Ok(val.clone())
402+
}
403+
395404
/// Retag all pointers that are stored in this place.
396405
fn tb_retag_place_contents(
397406
&mut self,
@@ -441,14 +450,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
441450
/// Regardless of how `Unique` is handled, Boxes are always reborrowed.
442451
/// When `Unique` is also reborrowed, then it behaves exactly like `Box`
443452
/// except for the fact that `Box` has a non-zero-sized reborrow.
444-
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
445-
let new_perm = NewPermission::from_unique_ty(
446-
place.layout.ty,
447-
self.kind,
448-
self.ecx,
449-
/* zero_size */ false,
450-
);
451-
self.retag_ptr_inplace(place, new_perm)
453+
fn visit_box(
454+
&mut self,
455+
box_ty: Ty<'tcx>,
456+
place: &PlaceTy<'tcx, Provenance>,
457+
) -> InterpResult<'tcx> {
458+
// Only boxes for the global allocator get any special treatment.
459+
if box_ty.is_box_global(*self.ecx.tcx) {
460+
let new_perm = NewPermission::from_unique_ty(
461+
place.layout.ty,
462+
self.kind,
463+
self.ecx,
464+
/* zero_size */ false,
465+
);
466+
self.retag_ptr_inplace(place, new_perm)?;
467+
}
468+
Ok(())
452469
}
453470

454471
fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {

src/shims/intrinsics/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
129129
this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?;
130130
}
131131

132+
// Memory model / provenance manipulation
132133
"ptr_mask" => {
133134
let [ptr, mask] = check_arg_count(args)?;
134135

@@ -139,6 +140,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
139140

140141
this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?;
141142
}
143+
"retag_box_to_raw" => {
144+
let [ptr] = check_arg_count(args)?;
145+
let alloc_ty = generic_args[1].expect_ty();
146+
147+
let val = this.read_immediate(ptr)?;
148+
let new_val = if this.machine.borrow_tracker.is_some() {
149+
this.retag_box_to_raw(&val, alloc_ty)?
150+
} else {
151+
val
152+
};
153+
this.write_immediate(*new_val, dest)?;
154+
}
142155

143156
// We want to return either `true` or `false` at random, or else something like
144157
// ```
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Regression test for <https://github.com/rust-lang/miri/issues/3341>:
2+
//! If `Box` has a local allocator, then it can't be `noalias` as the allocator
3+
//! may want to access allocator state based on the data pointer.
4+
5+
//@revisions: stack tree
6+
//@[tree]compile-flags: -Zmiri-tree-borrows
7+
#![feature(allocator_api)]
8+
#![feature(strict_provenance)]
9+
10+
use std::{
11+
alloc::{AllocError, Allocator, Layout},
12+
cell::{Cell, UnsafeCell},
13+
ptr::{self, addr_of, NonNull},
14+
thread::{self, ThreadId},
15+
mem,
16+
};
17+
18+
const BIN_SIZE: usize = 8;
19+
20+
// A bin represents a collection of blocks of a specific layout.
21+
#[repr(align(128))]
22+
struct MyBin {
23+
top: Cell<usize>,
24+
thread_id: ThreadId,
25+
memory: UnsafeCell<[usize; BIN_SIZE]>,
26+
}
27+
28+
impl MyBin {
29+
fn pop(&self) -> Option<NonNull<u8>> {
30+
let top = self.top.get();
31+
if top == BIN_SIZE {
32+
return None;
33+
}
34+
// Cast the *entire* thing to a raw pointer to not restrict its provenance.
35+
let bin = self as *const MyBin;
36+
let base_ptr = UnsafeCell::raw_get(unsafe{ addr_of!((*bin).memory )}).cast::<usize>();
37+
let ptr = unsafe { NonNull::new_unchecked(base_ptr.add(top)) };
38+
self.top.set(top + 1);
39+
Some(ptr.cast())
40+
}
41+
42+
// Pretends to not be a throwaway allocation method like this. A more realistic
43+
// substitute is using intrusive linked lists, which requires access to the
44+
// metadata of this bin as well.
45+
unsafe fn push(&self, ptr: NonNull<u8>) {
46+
// For now just check that this really is in this bin.
47+
let start = self.memory.get().addr();
48+
let end = start + BIN_SIZE * mem::size_of::<usize>();
49+
let addr = ptr.addr().get();
50+
assert!((start..end).contains(&addr));
51+
}
52+
}
53+
54+
// A collection of bins.
55+
struct MyAllocator {
56+
thread_id: ThreadId,
57+
// Pretends to be some complex collection of bins, such as an array of linked lists.
58+
bins: Box<[MyBin; 1]>,
59+
}
60+
61+
impl MyAllocator {
62+
fn new() -> Self {
63+
let thread_id = thread::current().id();
64+
MyAllocator {
65+
thread_id,
66+
bins: Box::new(
67+
[MyBin {
68+
top: Cell::new(0),
69+
thread_id,
70+
memory: UnsafeCell::default(),
71+
}; 1],
72+
),
73+
}
74+
}
75+
76+
// Pretends to be expensive finding a suitable bin for the layout.
77+
fn find_bin(&self, layout: Layout) -> Option<&MyBin> {
78+
if layout == Layout::new::<usize>() {
79+
Some(&self.bins[0])
80+
} else {
81+
None
82+
}
83+
}
84+
}
85+
86+
unsafe impl Allocator for MyAllocator {
87+
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
88+
// Expensive bin search.
89+
let bin = self.find_bin(layout).ok_or(AllocError)?;
90+
let ptr = bin.pop().ok_or(AllocError)?;
91+
Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
92+
}
93+
94+
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
95+
// Since manually finding the corresponding bin of `ptr` is very expensive,
96+
// doing pointer arithmetics is preferred.
97+
// But this means we access `top` via `ptr` rather than `self`!
98+
// That is fundamentally the source of the aliasing trouble in this example.
99+
let their_bin = ptr.as_ptr().map_addr(|addr| addr & !127).cast::<MyBin>();
100+
let thread_id = ptr::read(ptr::addr_of!((*their_bin).thread_id));
101+
if self.thread_id == thread_id {
102+
unsafe { (*their_bin).push(ptr) };
103+
} else {
104+
todo!("Deallocating from another thread")
105+
}
106+
}
107+
}
108+
109+
// Make sure to involve `Box` in allocating these,
110+
// as that's where `noalias` may come from.
111+
fn v<T, A: Allocator>(t: T, a: A) -> Vec<T, A> {
112+
(Box::new_in([t], a) as Box<[T], A>).into_vec()
113+
}
114+
115+
fn main() {
116+
assert!(mem::size_of::<MyBin>() <= 128); // if it grows bigger, the trick to access the "header" no longer works
117+
let my_alloc = MyAllocator::new();
118+
let a = v(1usize, &my_alloc);
119+
let b = v(2usize, &my_alloc);
120+
assert_eq!(a[0] + 1, b[0]);
121+
assert_eq!(addr_of!(a[0]).wrapping_add(1), addr_of!(b[0]));
122+
drop((a, b));
123+
}

0 commit comments

Comments
 (0)