Skip to content

Commit cec5ec4

Browse files
committed
Auto merge of rust-lang#2936 - Vanille-N:unique, r=RalfJung
Optional semantics for `Unique` Use with `-Zmiri-unique-is-unique`, makes `core::ptr::Unique` get a reborrow with its own permissions.
2 parents 1ffe627 + 0671f14 commit cec5ec4

25 files changed

+524
-39
lines changed

src/tools/miri/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,9 @@ to Miri failing to detect cases of undefined behavior in a program.
435435
so with this flag.
436436
* `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k.
437437
`4` is default for most targets. This value should always be a power of 2 and nonzero.
438+
* `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure
439+
that it could theoretically be considered `noalias`. This flag is experimental and has
440+
an effect only when used with `-Zmiri-tree-borrows`.
438441

439442
[function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
440443

src/tools/miri/src/bin/miri.rs

+10
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ fn main() {
343343
miri_config.borrow_tracker = None;
344344
} else if arg == "-Zmiri-tree-borrows" {
345345
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
346+
} else if arg == "-Zmiri-unique-is-unique" {
347+
miri_config.unique_is_unique = true;
346348
} else if arg == "-Zmiri-disable-data-race-detector" {
347349
miri_config.data_race_detector = false;
348350
miri_config.weak_memory_emulation = false;
@@ -560,6 +562,14 @@ fn main() {
560562
rustc_args.push(arg);
561563
}
562564
}
565+
// `-Zmiri-unique-is-unique` should only be used with `-Zmiri-tree-borrows`
566+
if miri_config.unique_is_unique
567+
&& !matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
568+
{
569+
show_error!(
570+
"-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
571+
);
572+
}
563573

564574
debug!("rustc arguments: {:?}", rustc_args);
565575
debug!("crate arguments: {:?}", miri_config.args);

src/tools/miri/src/borrow_tracker/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ pub struct GlobalStateInner {
103103
pub tracked_call_ids: FxHashSet<CallId>,
104104
/// Whether to recurse into datatypes when searching for pointers to retag.
105105
pub retag_fields: RetagFields,
106+
/// Whether `core::ptr::Unique` gets special (`Box`-like) handling.
107+
pub unique_is_unique: bool,
106108
}
107109

108110
impl VisitTags for GlobalStateInner {
@@ -170,6 +172,7 @@ impl GlobalStateInner {
170172
tracked_pointer_tags: FxHashSet<BorTag>,
171173
tracked_call_ids: FxHashSet<CallId>,
172174
retag_fields: RetagFields,
175+
unique_is_unique: bool,
173176
) -> Self {
174177
GlobalStateInner {
175178
borrow_tracker_method,
@@ -180,6 +183,7 @@ impl GlobalStateInner {
180183
tracked_pointer_tags,
181184
tracked_call_ids,
182185
retag_fields,
186+
unique_is_unique,
183187
}
184188
}
185189

@@ -244,6 +248,7 @@ impl BorrowTrackerMethod {
244248
config.tracked_pointer_tags.clone(),
245249
config.tracked_call_ids.clone(),
246250
config.retag_fields,
251+
config.unique_is_unique,
247252
))
248253
}
249254
}

src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs

+82-29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_middle::{
1111
Ty,
1212
},
1313
};
14+
use rustc_span::def_id::DefId;
1415

1516
use crate::*;
1617

@@ -99,9 +100,9 @@ impl<'tcx> Tree {
99100
/// Policy for a new borrow.
100101
#[derive(Debug, Clone, Copy)]
101102
struct NewPermission {
102-
/// Whether this borrow requires a read access on its parent.
103-
/// `perform_read_access` is `true` for all pointers marked `dereferenceable`.
104-
perform_read_access: bool,
103+
/// Optionally ignore the actual size to do a zero-size reborrow.
104+
/// If this is set then `dereferenceable` is not enforced.
105+
zero_size: bool,
105106
/// Which permission should the pointer start with.
106107
initial_state: Permission,
107108
/// Whether this pointer is part of the arguments of a function call.
@@ -128,28 +129,27 @@ impl<'tcx> NewPermission {
128129
// `&`s, which are excluded above.
129130
_ => return None,
130131
};
131-
// This field happens to be redundant since right now we always do a read,
132-
// but it could be useful in the future.
133-
let perform_read_access = true;
134132

135133
let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector);
136-
Some(Self { perform_read_access, initial_state, protector })
134+
Some(Self { zero_size: false, initial_state, protector })
137135
}
138136

139-
// Boxes are not handled by `from_ref_ty`, they need special behavior
140-
// implemented here.
141-
fn from_box_ty(
137+
/// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled).
138+
/// These pointers allow deallocation so need a different kind of protector not handled
139+
/// by `from_ref_ty`.
140+
fn from_unique_ty(
142141
ty: Ty<'tcx>,
143142
kind: RetagKind,
144143
cx: &crate::MiriInterpCx<'_, 'tcx>,
144+
zero_size: bool,
145145
) -> Option<Self> {
146146
let pointee = ty.builtin_deref(true).unwrap().ty;
147147
pointee.is_unpin(*cx.tcx, cx.param_env()).then_some(()).map(|()| {
148148
// Regular `Unpin` box, give it `noalias` but only a weak protector
149149
// because it is valid to deallocate it within the function.
150150
let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env());
151151
Self {
152-
perform_read_access: true,
152+
zero_size,
153153
initial_state: Permission::new_unique_2phase(ty_is_freeze),
154154
protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector),
155155
}
@@ -201,6 +201,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
201201
Ok(())
202202
};
203203

204+
trace!("Reborrow of size {:?}", ptr_size);
204205
let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO {
205206
this.ptr_get_alloc_id(place.ptr)?
206207
} else {
@@ -276,8 +277,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
276277
let range = alloc_range(base_offset, ptr_size);
277278
let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut();
278279

279-
if new_perm.perform_read_access {
280-
// Count this reborrow as a read access
280+
// All reborrows incur a (possibly zero-sized) read access to the parent
281+
{
281282
let global = &this.machine.borrow_tracker.as_ref().unwrap();
282283
let span = this.machine.current_span();
283284
tree_borrows.perform_access(
@@ -308,12 +309,19 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
308309
// We want a place for where the ptr *points to*, so we get one.
309310
let place = this.ref_to_mplace(val)?;
310311

311-
// Get a lower bound of the size of this place.
312-
// (When `extern type` are involved, use the size of the known prefix.)
313-
let size = this
314-
.size_and_align_of_mplace(&place)?
315-
.map(|(size, _)| size)
316-
.unwrap_or(place.layout.size);
312+
// Determine the size of the reborrow.
313+
// For most types this is the entire size of the place, however
314+
// - when `extern type` is involved we use the size of the known prefix,
315+
// - if the pointer is not reborrowed (raw pointer) or if `zero_size` is set
316+
// then we override the size to do a zero-length reborrow.
317+
let reborrow_size = match new_perm {
318+
Some(NewPermission { zero_size: false, .. }) =>
319+
this.size_and_align_of_mplace(&place)?
320+
.map(|(size, _)| size)
321+
.unwrap_or(place.layout.size),
322+
_ => Size::from_bytes(0),
323+
};
324+
trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size);
317325

318326
// This new tag is not guaranteed to actually be used.
319327
//
@@ -324,7 +332,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
324332
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
325333

326334
// Compute the actual reborrow.
327-
let reborrowed = this.tb_reborrow(&place, size, new_perm, new_tag)?;
335+
let reborrowed = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?;
328336

329337
// Adjust pointer.
330338
let new_place = place.map_provenance(|p| {
@@ -359,10 +367,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
359367
val: &ImmTy<'tcx, Provenance>,
360368
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
361369
let this = self.eval_context_mut();
362-
let new_perm = if let &ty::Ref(_, pointee, mutability) = val.layout.ty.kind() {
363-
NewPermission::from_ref_ty(pointee, mutability, kind, this)
364-
} else {
365-
None
370+
let new_perm = match val.layout.ty.kind() {
371+
&ty::Ref(_, pointee, mutability) =>
372+
NewPermission::from_ref_ty(pointee, mutability, kind, this),
373+
_ => None,
366374
};
367375
this.tb_retag_reference(val, new_perm)
368376
}
@@ -374,15 +382,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
374382
place: &PlaceTy<'tcx, Provenance>,
375383
) -> InterpResult<'tcx> {
376384
let this = self.eval_context_mut();
377-
let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
378-
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
385+
let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
386+
let retag_fields = options.retag_fields;
387+
let unique_did =
388+
options.unique_is_unique.then(|| this.tcx.lang_items().ptr_unique()).flatten();
389+
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields, unique_did };
379390
return visitor.visit_value(place);
380391

381392
// The actual visitor.
382393
struct RetagVisitor<'ecx, 'mir, 'tcx> {
383394
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
384395
kind: RetagKind,
385396
retag_fields: RetagFields,
397+
unique_did: Option<DefId>,
386398
}
387399
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
388400
#[inline(always)] // yes this helps in our benchmarks
@@ -407,8 +419,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
407419
self.ecx
408420
}
409421

422+
/// Regardless of how `Unique` is handled, Boxes are always reborrowed.
423+
/// When `Unique` is also reborrowed, then it behaves exactly like `Box`
424+
/// except for the fact that `Box` has a non-zero-sized reborrow.
410425
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
411-
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
426+
let new_perm = NewPermission::from_unique_ty(
427+
place.layout.ty,
428+
self.kind,
429+
self.ecx,
430+
/* zero_size */ false,
431+
);
412432
self.retag_ptr_inplace(place, new_perm)
413433
}
414434

@@ -440,6 +460,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
440460
// even if field retagging is not enabled. *shrug*)
441461
self.walk_value(place)?;
442462
}
463+
ty::Adt(adt, _) if self.unique_did == Some(adt.did()) => {
464+
let place = inner_ptr_of_unique(self.ecx, place)?;
465+
let new_perm = NewPermission::from_unique_ty(
466+
place.layout.ty,
467+
self.kind,
468+
self.ecx,
469+
/* zero_size */ true,
470+
);
471+
self.retag_ptr_inplace(&place, new_perm)?;
472+
}
443473
_ => {
444474
// Not a reference/pointer/box. Only recurse if configured appropriately.
445475
let recurse = match self.retag_fields {
@@ -456,7 +486,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
456486
}
457487
}
458488
}
459-
460489
Ok(())
461490
}
462491
}
@@ -486,7 +515,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
486515
// FIXME: do we truly want a 2phase borrow here?
487516
let new_perm = Some(NewPermission {
488517
initial_state: Permission::new_unique_2phase(/*freeze*/ false),
489-
perform_read_access: true,
518+
zero_size: false,
490519
protector: Some(ProtectorKind::StrongProtector),
491520
});
492521
let val = this.tb_retag_reference(&val, new_perm)?;
@@ -552,3 +581,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
552581
tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
553582
}
554583
}
584+
585+
/// Takes a place for a `Unique` and turns it into a place with the inner raw pointer.
586+
/// I.e. input is what you get from the visitor upon encountering an `adt` that is `Unique`,
587+
/// and output can be used by `retag_ptr_inplace`.
588+
fn inner_ptr_of_unique<'tcx>(
589+
ecx: &mut MiriInterpCx<'_, 'tcx>,
590+
place: &PlaceTy<'tcx, Provenance>,
591+
) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> {
592+
// Follows the same layout as `interpret/visitor.rs:walk_value` for `Box` in
593+
// `rustc_const_eval`, just with one fewer layer.
594+
// Here we have a `Unique(NonNull(*mut), PhantomData)`
595+
assert_eq!(place.layout.fields.count(), 2, "Unique must have exactly 2 fields");
596+
let (nonnull, phantom) = (ecx.place_field(place, 0)?, ecx.place_field(place, 1)?);
597+
assert!(
598+
phantom.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
599+
"2nd field of `Unique` should be `PhantomData` but is `{:?}`",
600+
phantom.layout.ty,
601+
);
602+
// Now down to `NonNull(*mut)`
603+
assert_eq!(nonnull.layout.fields.count(), 1, "NonNull must have exactly 1 field");
604+
let ptr = ecx.place_field(&nonnull, 0)?;
605+
// Finally a plain `*mut`
606+
Ok(ptr)
607+
}

src/tools/miri/src/eval.rs

+5
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ pub struct MiriConfig {
9090
pub validate: bool,
9191
/// Determines if Stacked Borrows or Tree Borrows is enabled.
9292
pub borrow_tracker: Option<BorrowTrackerMethod>,
93+
/// Whether `core::ptr::Unique` receives special treatment.
94+
/// If `true` then `Unique` is reborrowed with its own new tag and permission,
95+
/// otherwise `Unique` is just another raw pointer.
96+
pub unique_is_unique: bool,
9397
/// Controls alignment checking.
9498
pub check_alignment: AlignmentCheck,
9599
/// Controls function [ABI](Abi) checking.
@@ -156,6 +160,7 @@ impl Default for MiriConfig {
156160
env: vec![],
157161
validate: true,
158162
borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
163+
unique_is_unique: false,
159164
check_alignment: AlignmentCheck::Int,
160165
check_abi: true,
161166
isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: entering unreachable code
2+
--> $DIR/children-can-alias.rs:LL:CC
3+
|
4+
LL | std::hint::unreachable_unchecked();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/children-can-alias.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to previous error
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//@revisions: default uniq
2+
//@compile-flags: -Zmiri-tree-borrows
3+
//@[uniq]compile-flags: -Zmiri-unique-is-unique
4+
5+
//! This is NOT intended behavior.
6+
//! We should eventually find a solution so that the version with `Unique` passes too,
7+
//! otherwise `Unique` is more strict than `&mut`!
8+
9+
#![feature(ptr_internals)]
10+
11+
use core::ptr::addr_of_mut;
12+
use core::ptr::Unique;
13+
14+
fn main() {
15+
let mut data = 0u8;
16+
let raw = addr_of_mut!(data);
17+
unsafe {
18+
raw_children_of_refmut_can_alias(&mut *raw);
19+
raw_children_of_unique_can_alias(Unique::new_unchecked(raw));
20+
21+
// Ultimately the intended behavior is that both above tests would
22+
// succeed.
23+
std::hint::unreachable_unchecked();
24+
//~[default]^ ERROR: entering unreachable code
25+
}
26+
}
27+
28+
unsafe fn raw_children_of_refmut_can_alias(x: &mut u8) {
29+
let child1 = addr_of_mut!(*x);
30+
let child2 = addr_of_mut!(*x);
31+
// We create two raw aliases of `x`: they have the exact same
32+
// tag and can be used interchangeably.
33+
child1.write(1);
34+
child2.write(2);
35+
child1.write(1);
36+
child2.write(2);
37+
}
38+
39+
unsafe fn raw_children_of_unique_can_alias(x: Unique<u8>) {
40+
let child1 = x.as_ptr();
41+
let child2 = x.as_ptr();
42+
// Under `-Zmiri-unique-is-unique`, `Unique` accidentally offers more guarantees
43+
// than `&mut`. Not because it responds differently to accesses but because
44+
// there is no easy way to obtain a copy with the same tag.
45+
//
46+
// The closest (non-hack) attempt is two calls to `as_ptr`.
47+
// - Without `-Zmiri-unique-is-unique`, independent `as_ptr` calls return pointers
48+
// with the same tag that can thus be used interchangeably.
49+
// - With the current implementation of `-Zmiri-unique-is-unique`, they return cousin
50+
// tags with permissions that do not tolerate aliasing.
51+
// Eventually we should make such aliasing allowed in some situations
52+
// (e.g. when there is no protector), which will probably involve
53+
// introducing a new kind of permission.
54+
child1.write(1);
55+
child2.write(2);
56+
//~[uniq]^ ERROR: /write access through .* is forbidden/
57+
child1.write(1);
58+
child2.write(2);
59+
}

0 commit comments

Comments
 (0)