Skip to content

Commit 0671f14

Browse files
committed
Unique gets special treatment when -Zmiri-unique-is-unique
1 parent 65d60f9 commit 0671f14

25 files changed

+486
-13
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
@@ -340,6 +340,8 @@ fn main() {
340340
miri_config.borrow_tracker = None;
341341
} else if arg == "-Zmiri-tree-borrows" {
342342
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
343+
} else if arg == "-Zmiri-unique-is-unique" {
344+
miri_config.unique_is_unique = true;
343345
} else if arg == "-Zmiri-disable-data-race-detector" {
344346
miri_config.data_race_detector = false;
345347
miri_config.weak_memory_emulation = false;
@@ -557,6 +559,14 @@ fn main() {
557559
rustc_args.push(arg);
558560
}
559561
}
562+
// `-Zmiri-unique-is-unique` should only be used with `-Zmiri-tree-borrows`
563+
if miri_config.unique_is_unique
564+
&& !matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
565+
{
566+
show_error!(
567+
"-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
568+
);
569+
}
560570

561571
debug!("rustc arguments: {:?}", rustc_args);
562572
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

+44-3
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

@@ -381,15 +382,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
381382
place: &PlaceTy<'tcx, Provenance>,
382383
) -> InterpResult<'tcx> {
383384
let this = self.eval_context_mut();
384-
let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
385-
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 };
386390
return visitor.visit_value(place);
387391

388392
// The actual visitor.
389393
struct RetagVisitor<'ecx, 'mir, 'tcx> {
390394
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
391395
kind: RetagKind,
392396
retag_fields: RetagFields,
397+
unique_did: Option<DefId>,
393398
}
394399
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
395400
#[inline(always)] // yes this helps in our benchmarks
@@ -414,6 +419,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
414419
self.ecx
415420
}
416421

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.
417425
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
418426
let new_perm = NewPermission::from_unique_ty(
419427
place.layout.ty,
@@ -452,6 +460,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
452460
// even if field retagging is not enabled. *shrug*)
453461
self.walk_value(place)?;
454462
}
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+
}
455473
_ => {
456474
// Not a reference/pointer/box. Only recurse if configured appropriately.
457475
let recurse = match self.retag_fields {
@@ -468,7 +486,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
468486
}
469487
}
470488
}
471-
472489
Ok(())
473490
}
474491
}
@@ -564,3 +581,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
564581
tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
565582
}
566583
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
error: Undefined Behavior: write access through <TAG> is forbidden
2+
--> $DIR/children-can-alias.rs:LL:CC
3+
|
4+
LL | child2.write(2);
5+
| ^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
8+
= help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
9+
= help: the conflicting tag <TAG> has state Disabled which forbids this child write access
10+
help: the accessed tag <TAG> was created here
11+
--> $DIR/children-can-alias.rs:LL:CC
12+
|
13+
LL | let child2 = x.as_ptr();
14+
| ^^^^^^^^^^
15+
help: the conflicting tag <TAG> was created here, in the initial state Reserved
16+
--> $DIR/children-can-alias.rs:LL:CC
17+
|
18+
LL | let child2 = x.as_ptr();
19+
| ^
20+
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x1]
21+
--> $DIR/children-can-alias.rs:LL:CC
22+
|
23+
LL | child1.write(1);
24+
| ^^^^^^^^^^^^^^^
25+
= help: this transition corresponds to a loss of read and write permissions
26+
= note: BACKTRACE (of the first span):
27+
= note: inside `raw_children_of_unique_can_alias` at $DIR/children-can-alias.rs:LL:CC
28+
note: inside `main`
29+
--> $DIR/children-can-alias.rs:LL:CC
30+
|
31+
LL | raw_children_of_unique_can_alias(Unique::new_unchecked(raw));
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
34+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
35+
36+
error: aborting due to previous error
37+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: Undefined Behavior: write access through <TAG> is forbidden
2+
--> $DIR/unique.rs:LL:CC
3+
|
4+
LL | *uniq.as_ptr() = 3;
5+
| ^^^^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
8+
= help: the accessed tag <TAG> has state Frozen which forbids this child write access
9+
help: the accessed tag <TAG> was created here, in the initial state Reserved
10+
--> $DIR/unique.rs:LL:CC
11+
|
12+
LL | let refmut = &mut data;
13+
| ^^^^^^^^^
14+
help: the accessed tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
15+
--> $DIR/unique.rs:LL:CC
16+
|
17+
LL | *uniq.as_ptr() = 1; // activation
18+
| ^^^^^^^^^^^^^^^^^^
19+
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
20+
help: the accessed tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
21+
--> $DIR/unique.rs:LL:CC
22+
|
23+
LL | let _definitely_parent = data; // definitely Frozen by now
24+
| ^^^^
25+
= help: this transition corresponds to a loss of write permissions
26+
= note: BACKTRACE (of the first span):
27+
= note: inside `main` at $DIR/unique.rs:LL:CC
28+
29+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
30+
31+
error: aborting due to previous error
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//@revisions: default uniq
2+
//@compile-flags: -Zmiri-tree-borrows
3+
//@[uniq]compile-flags: -Zmiri-unique-is-unique
4+
5+
// A pattern that detects if `Unique` is treated as exclusive or not:
6+
// activate the pointer behind a `Unique` then do a read that is parent
7+
// iff `Unique` was specially reborrowed.
8+
9+
#![feature(ptr_internals)]
10+
use core::ptr::Unique;
11+
12+
fn main() {
13+
let mut data = 0u8;
14+
let refmut = &mut data;
15+
let rawptr = refmut as *mut u8;
16+
17+
unsafe {
18+
let uniq = Unique::new_unchecked(rawptr);
19+
*uniq.as_ptr() = 1; // activation
20+
let _maybe_parent = *rawptr; // maybe becomes Frozen
21+
*uniq.as_ptr() = 2;
22+
//~[uniq]^ ERROR: /write access through .* is forbidden/
23+
let _definitely_parent = data; // definitely Frozen by now
24+
*uniq.as_ptr() = 3;
25+
//~[default]^ ERROR: /write access through .* is forbidden/
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: Undefined Behavior: write access through <TAG> is forbidden
2+
--> $DIR/unique.rs:LL:CC
3+
|
4+
LL | *uniq.as_ptr() = 2;
5+
| ^^^^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
8+
= help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
9+
= help: the conflicting tag <TAG> has state Frozen which forbids this child write access
10+
help: the accessed tag <TAG> was created here
11+
--> $DIR/unique.rs:LL:CC
12+
|
13+
LL | *uniq.as_ptr() = 2;
14+
| ^^^^^^^^^^^^^
15+
help: the conflicting tag <TAG> was created here, in the initial state Reserved
16+
--> $DIR/unique.rs:LL:CC
17+
|
18+
LL | let uniq = Unique::new_unchecked(rawptr);
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
help: the conflicting tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
21+
--> $DIR/unique.rs:LL:CC
22+
|
23+
LL | *uniq.as_ptr() = 1; // activation
24+
| ^^^^^^^^^^^^^^^^^^
25+
= help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
26+
help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
27+
--> $DIR/unique.rs:LL:CC
28+
|
29+
LL | let _maybe_parent = *rawptr; // maybe becomes Frozen
30+
| ^^^^^^^
31+
= help: this transition corresponds to a loss of write permissions
32+
= note: BACKTRACE (of the first span):
33+
= note: inside `main` at $DIR/unique.rs:LL:CC
34+
35+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
36+
37+
error: aborting due to previous error
38+

0 commit comments

Comments
 (0)