about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNeven Villani <vanille@crans.org>2023-06-19 12:07:23 +0200
committerNeven Villani <vanille@crans.org>2023-06-28 18:42:13 +0200
commit0671f14733390f59631c363807f23344950f07f1 (patch)
tree114cc7cd30e827a252bfc79f2993497f78b5e651
parent65d60f9f11179bf0faa33f948131d454069b2be8 (diff)
downloadrust-0671f14733390f59631c363807f23344950f07f1.tar.gz
rust-0671f14733390f59631c363807f23344950f07f1.zip
Unique gets special treatment when -Zmiri-unique-is-unique
-rw-r--r--src/tools/miri/README.md3
-rw-r--r--src/tools/miri/src/bin/miri.rs10
-rw-r--r--src/tools/miri/src/borrow_tracker/mod.rs5
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs47
-rw-r--r--src/tools/miri/src/eval.rs5
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr15
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs59
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr37
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/unique.default.stderr32
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/unique.rs27
-rw-r--r--src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr38
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/formatting.rs1
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/formatting.stderr2
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs2
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/unique.default.stderr21
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/unique.rs66
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr24
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr6
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/vec_unique.rs68
-rw-r--r--src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr8
-rw-r--r--src/tools/miri/tests/pass/vec.rs5
-rw-r--r--src/tools/miri/tests/pass/vecdeque.rs6
-rw-r--r--src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout2
-rw-r--r--src/tools/miri/tests/utils/macros.rs8
-rw-r--r--src/tools/miri/tests/utils/miri_extern.rs2
25 files changed, 486 insertions, 13 deletions
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index 79b0daf9e82..daf13984fb7 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -435,6 +435,9 @@ to Miri failing to detect cases of undefined behavior in a program.
   so with this flag.
 * `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k.
   `4` is default for most targets. This value should always be a power of 2 and nonzero.
+* `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure
+  that it could theoretically be considered `noalias`. This flag is experimental and has
+  an effect only when used with `-Zmiri-tree-borrows`.
 
 [function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
 
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 083dd4800d9..8a51a7e6634 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -340,6 +340,8 @@ fn main() {
             miri_config.borrow_tracker = None;
         } else if arg == "-Zmiri-tree-borrows" {
             miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
+        } else if arg == "-Zmiri-unique-is-unique" {
+            miri_config.unique_is_unique = true;
         } else if arg == "-Zmiri-disable-data-race-detector" {
             miri_config.data_race_detector = false;
             miri_config.weak_memory_emulation = false;
@@ -557,6 +559,14 @@ fn main() {
             rustc_args.push(arg);
         }
     }
+    // `-Zmiri-unique-is-unique` should only be used with `-Zmiri-tree-borrows`
+    if miri_config.unique_is_unique
+        && !matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
+    {
+        show_error!(
+            "-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
+        );
+    }
 
     debug!("rustc arguments: {:?}", rustc_args);
     debug!("crate arguments: {:?}", miri_config.args);
diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs
index ffc49eedb5a..faa23fd2620 100644
--- a/src/tools/miri/src/borrow_tracker/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/mod.rs
@@ -103,6 +103,8 @@ pub struct GlobalStateInner {
     pub tracked_call_ids: FxHashSet<CallId>,
     /// Whether to recurse into datatypes when searching for pointers to retag.
     pub retag_fields: RetagFields,
+    /// Whether `core::ptr::Unique` gets special (`Box`-like) handling.
+    pub unique_is_unique: bool,
 }
 
 impl VisitTags for GlobalStateInner {
@@ -170,6 +172,7 @@ impl GlobalStateInner {
         tracked_pointer_tags: FxHashSet<BorTag>,
         tracked_call_ids: FxHashSet<CallId>,
         retag_fields: RetagFields,
+        unique_is_unique: bool,
     ) -> Self {
         GlobalStateInner {
             borrow_tracker_method,
@@ -180,6 +183,7 @@ impl GlobalStateInner {
             tracked_pointer_tags,
             tracked_call_ids,
             retag_fields,
+            unique_is_unique,
         }
     }
 
@@ -244,6 +248,7 @@ impl BorrowTrackerMethod {
             config.tracked_pointer_tags.clone(),
             config.tracked_call_ids.clone(),
             config.retag_fields,
+            config.unique_is_unique,
         ))
     }
 }
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
index 31cc03828f9..c4fc2fea74c 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
@@ -11,6 +11,7 @@ use rustc_middle::{
         Ty,
     },
 };
+use rustc_span::def_id::DefId;
 
 use crate::*;
 
@@ -381,8 +382,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         place: &PlaceTy<'tcx, Provenance>,
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
-        let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
+        let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
+        let retag_fields = options.retag_fields;
+        let unique_did =
+            options.unique_is_unique.then(|| this.tcx.lang_items().ptr_unique()).flatten();
+        let mut visitor = RetagVisitor { ecx: this, kind, retag_fields, unique_did };
         return visitor.visit_value(place);
 
         // The actual visitor.
@@ -390,6 +394,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
             kind: RetagKind,
             retag_fields: RetagFields,
+            unique_did: Option<DefId>,
         }
         impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
             #[inline(always)] // yes this helps in our benchmarks
@@ -414,6 +419,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 self.ecx
             }
 
+            /// Regardless of how `Unique` is handled, Boxes are always reborrowed.
+            /// When `Unique` is also reborrowed, then it behaves exactly like `Box`
+            /// except for the fact that `Box` has a non-zero-sized reborrow.
             fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
                 let new_perm = NewPermission::from_unique_ty(
                     place.layout.ty,
@@ -452,6 +460,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                         // even if field retagging is not enabled. *shrug*)
                         self.walk_value(place)?;
                     }
+                    ty::Adt(adt, _) if self.unique_did == Some(adt.did()) => {
+                        let place = inner_ptr_of_unique(self.ecx, place)?;
+                        let new_perm = NewPermission::from_unique_ty(
+                            place.layout.ty,
+                            self.kind,
+                            self.ecx,
+                            /* zero_size */ true,
+                        );
+                        self.retag_ptr_inplace(&place, new_perm)?;
+                    }
                     _ => {
                         // Not a reference/pointer/box. Only recurse if configured appropriately.
                         let recurse = match self.retag_fields {
@@ -468,7 +486,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                         }
                     }
                 }
-
                 Ok(())
             }
         }
@@ -564,3 +581,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         tree_borrows.give_pointer_debug_name(tag, nth_parent, name)
     }
 }
+
+/// Takes a place for a `Unique` and turns it into a place with the inner raw pointer.
+/// I.e. input is what you get from the visitor upon encountering an `adt` that is `Unique`,
+/// and output can be used by `retag_ptr_inplace`.
+fn inner_ptr_of_unique<'tcx>(
+    ecx: &mut MiriInterpCx<'_, 'tcx>,
+    place: &PlaceTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> {
+    // Follows the same layout as `interpret/visitor.rs:walk_value` for `Box` in
+    // `rustc_const_eval`, just with one fewer layer.
+    // Here we have a `Unique(NonNull(*mut), PhantomData)`
+    assert_eq!(place.layout.fields.count(), 2, "Unique must have exactly 2 fields");
+    let (nonnull, phantom) = (ecx.place_field(place, 0)?, ecx.place_field(place, 1)?);
+    assert!(
+        phantom.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
+        "2nd field of `Unique` should be `PhantomData` but is `{:?}`",
+        phantom.layout.ty,
+    );
+    // Now down to `NonNull(*mut)`
+    assert_eq!(nonnull.layout.fields.count(), 1, "NonNull must have exactly 1 field");
+    let ptr = ecx.place_field(&nonnull, 0)?;
+    // Finally a plain `*mut`
+    Ok(ptr)
+}
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index 1e9d48be65e..91e004e0106 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -90,6 +90,10 @@ pub struct MiriConfig {
     pub validate: bool,
     /// Determines if Stacked Borrows or Tree Borrows is enabled.
     pub borrow_tracker: Option<BorrowTrackerMethod>,
+    /// Whether `core::ptr::Unique` receives special treatment.
+    /// If `true` then `Unique` is reborrowed with its own new tag and permission,
+    /// otherwise `Unique` is just another raw pointer.
+    pub unique_is_unique: bool,
     /// Controls alignment checking.
     pub check_alignment: AlignmentCheck,
     /// Controls function [ABI](Abi) checking.
@@ -156,6 +160,7 @@ impl Default for MiriConfig {
             env: vec![],
             validate: true,
             borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
+            unique_is_unique: false,
             check_alignment: AlignmentCheck::Int,
             check_abi: true,
             isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr
new file mode 100644
index 00000000000..1b05be9c54d
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: entering unreachable code
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |         std::hint::unreachable_unchecked();
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/children-can-alias.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs
new file mode 100644
index 00000000000..b5a01cd4324
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs
@@ -0,0 +1,59 @@
+//@revisions: default uniq
+//@compile-flags: -Zmiri-tree-borrows
+//@[uniq]compile-flags: -Zmiri-unique-is-unique
+
+//! This is NOT intended behavior.
+//! We should eventually find a solution so that the version with `Unique` passes too,
+//! otherwise `Unique` is more strict than `&mut`!
+
+#![feature(ptr_internals)]
+
+use core::ptr::addr_of_mut;
+use core::ptr::Unique;
+
+fn main() {
+    let mut data = 0u8;
+    let raw = addr_of_mut!(data);
+    unsafe {
+        raw_children_of_refmut_can_alias(&mut *raw);
+        raw_children_of_unique_can_alias(Unique::new_unchecked(raw));
+
+        // Ultimately the intended behavior is that both above tests would
+        // succeed.
+        std::hint::unreachable_unchecked();
+        //~[default]^ ERROR: entering unreachable code
+    }
+}
+
+unsafe fn raw_children_of_refmut_can_alias(x: &mut u8) {
+    let child1 = addr_of_mut!(*x);
+    let child2 = addr_of_mut!(*x);
+    // We create two raw aliases of `x`: they have the exact same
+    // tag and can be used interchangeably.
+    child1.write(1);
+    child2.write(2);
+    child1.write(1);
+    child2.write(2);
+}
+
+unsafe fn raw_children_of_unique_can_alias(x: Unique<u8>) {
+    let child1 = x.as_ptr();
+    let child2 = x.as_ptr();
+    // Under `-Zmiri-unique-is-unique`, `Unique` accidentally offers more guarantees
+    // than `&mut`. Not because it responds differently to accesses but because
+    // there is no easy way to obtain a copy with the same tag.
+    //
+    // The closest (non-hack) attempt is two calls to `as_ptr`.
+    // - Without `-Zmiri-unique-is-unique`, independent `as_ptr` calls return pointers
+    //   with the same tag that can thus be used interchangeably.
+    // - With the current implementation of `-Zmiri-unique-is-unique`, they return cousin
+    //   tags with permissions that do not tolerate aliasing.
+    // Eventually we should make such aliasing allowed in some situations
+    // (e.g. when there is no protector), which will probably involve
+    // introducing a new kind of permission.
+    child1.write(1);
+    child2.write(2);
+    //~[uniq]^ ERROR: /write access through .* is forbidden/
+    child1.write(1);
+    child2.write(2);
+}
diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr
new file mode 100644
index 00000000000..2f77b27729e
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr
@@ -0,0 +1,37 @@
+error: Undefined Behavior: write access through <TAG> is forbidden
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |     child2.write(2);
+   |     ^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
+   = help: the conflicting tag <TAG> has state Disabled which forbids this child write access
+help: the accessed tag <TAG> was created here
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |     let child2 = x.as_ptr();
+   |                  ^^^^^^^^^^
+help: the conflicting tag <TAG> was created here, in the initial state Reserved
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |     let child2 = x.as_ptr();
+   |                  ^
+help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x0..0x1]
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |     child1.write(1);
+   |     ^^^^^^^^^^^^^^^
+   = help: this transition corresponds to a loss of read and write permissions
+   = note: BACKTRACE (of the first span):
+   = note: inside `raw_children_of_unique_can_alias` at $DIR/children-can-alias.rs:LL:CC
+note: inside `main`
+  --> $DIR/children-can-alias.rs:LL:CC
+   |
+LL |         raw_children_of_unique_can_alias(Unique::new_unchecked(raw));
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr b/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr
new file mode 100644
index 00000000000..eb4d5c10c25
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr
@@ -0,0 +1,32 @@
+error: Undefined Behavior: write access through <TAG> is forbidden
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         *uniq.as_ptr() = 3;
+   |         ^^^^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: the accessed tag <TAG> has state Frozen which forbids this child write access
+help: the accessed tag <TAG> was created here, in the initial state Reserved
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |     let refmut = &mut data;
+   |                  ^^^^^^^^^
+help: the accessed tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         *uniq.as_ptr() = 1; // activation
+   |         ^^^^^^^^^^^^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
+help: the accessed tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         let _definitely_parent = data; // definitely Frozen by now
+   |                                  ^^^^
+   = help: this transition corresponds to a loss of write permissions
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at $DIR/unique.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.rs b/src/tools/miri/tests/fail/tree_borrows/unique.rs
new file mode 100644
index 00000000000..0844dd21a59
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/unique.rs
@@ -0,0 +1,27 @@
+//@revisions: default uniq
+//@compile-flags: -Zmiri-tree-borrows
+//@[uniq]compile-flags: -Zmiri-unique-is-unique
+
+// A pattern that detects if `Unique` is treated as exclusive or not:
+// activate the pointer behind a `Unique` then do a read that is parent
+// iff `Unique` was specially reborrowed.
+
+#![feature(ptr_internals)]
+use core::ptr::Unique;
+
+fn main() {
+    let mut data = 0u8;
+    let refmut = &mut data;
+    let rawptr = refmut as *mut u8;
+
+    unsafe {
+        let uniq = Unique::new_unchecked(rawptr);
+        *uniq.as_ptr() = 1; // activation
+        let _maybe_parent = *rawptr; // maybe becomes Frozen
+        *uniq.as_ptr() = 2;
+        //~[uniq]^ ERROR: /write access through .* is forbidden/
+        let _definitely_parent = data; // definitely Frozen by now
+        *uniq.as_ptr() = 3;
+        //~[default]^ ERROR: /write access through .* is forbidden/
+    }
+}
diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr
new file mode 100644
index 00000000000..2d8936f273f
--- /dev/null
+++ b/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr
@@ -0,0 +1,38 @@
+error: Undefined Behavior: write access through <TAG> is forbidden
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         *uniq.as_ptr() = 2;
+   |         ^^^^^^^^^^^^^^^^^^ write access through <TAG> is forbidden
+   |
+   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
+   = help: the accessed tag <TAG> is a child of the conflicting tag <TAG>
+   = help: the conflicting tag <TAG> has state Frozen which forbids this child write access
+help: the accessed tag <TAG> was created here
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         *uniq.as_ptr() = 2;
+   |          ^^^^^^^^^^^^^
+help: the conflicting tag <TAG> was created here, in the initial state Reserved
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         let uniq = Unique::new_unchecked(rawptr);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: the conflicting tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         *uniq.as_ptr() = 1; // activation
+   |         ^^^^^^^^^^^^^^^^^^
+   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
+help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
+  --> $DIR/unique.rs:LL:CC
+   |
+LL |         let _maybe_parent = *rawptr; // maybe becomes Frozen
+   |                             ^^^^^^^
+   = help: this transition corresponds to a loss of write permissions
+   = note: BACKTRACE (of the first span):
+   = note: inside `main` at $DIR/unique.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.rs b/src/tools/miri/tests/pass/tree_borrows/formatting.rs
index 9021c417638..64697cac261 100644
--- a/src/tools/miri/tests/pass/tree_borrows/formatting.rs
+++ b/src/tools/miri/tests/pass/tree_borrows/formatting.rs
@@ -17,6 +17,7 @@ fn main() {
 unsafe fn alignment_check() {
     let data: &mut [u8] = &mut [0; 1024];
     name!(data.as_ptr()=>2, "data");
+    name!(data.as_ptr()=>2, "data");
     let alloc_id = alloc_id!(data.as_ptr());
     let x = &mut data[1];
     name!(x as *mut _, "data[1]");
diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
index 8c3779fe1f7..effd0d9f961 100644
--- a/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
+++ b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr
@@ -2,7 +2,7 @@
 Warning: this tree is indicative only. Some tags may have been hidden.
 0..  1..  2.. 10.. 11..100..101..1000..1001..1024
 | Act| Act| Act| Act| Act| Act|  Act|  Act|  Act|    └─┬──<TAG=root of the allocation>
-| Res| Act| Res| Act| Res| Act|  Res|  Act|  Res|      └─┬──<TAG=data>
+| Res| Act| Res| Act| Res| Act|  Res|  Act|  Res|      └─┬──<TAG=data, data>
 |----| Act|----|?Dis|----|?Dis| ----| ?Dis| ----|        ├────<TAG=data[1]>
 |----|----|----| Act|----|?Dis| ----| ?Dis| ----|        ├────<TAG=data[10]>
 |----|----|----|----|----| Frz| ----| ?Dis| ----|        ├────<TAG=data[100]>
diff --git a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
index aa6f7078890..6bdad695965 100644
--- a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
+++ b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs
@@ -1,4 +1,6 @@
+//@revisions: default uniq
 //@compile-flags: -Zmiri-tree-borrows
+//@[uniq]compile-flags: -Zmiri-unique-is-unique
 #![feature(allocator_api)]
 
 use std::mem;
diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr
new file mode 100644
index 00000000000..11e05d50f2c
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr
@@ -0,0 +1,21 @@
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Res|      └─┬──<TAG=base>
+| Res|        └────<TAG=raw, uniq, uniq>
+──────────────────────────────────────────────────────────────────────
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Act|      └─┬──<TAG=base>
+| Act|        └────<TAG=raw, uniq, uniq>
+──────────────────────────────────────────────────────────────────────
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Act|      └─┬──<TAG=base>
+| Act|        └────<TAG=raw, uniq, uniq>
+──────────────────────────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.rs b/src/tools/miri/tests/pass/tree_borrows/unique.rs
new file mode 100644
index 00000000000..d0c3d133da5
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/unique.rs
@@ -0,0 +1,66 @@
+//@revisions: default uniq
+//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
+//@[uniq]compile-flags: -Zmiri-unique-is-unique
+
+#![feature(ptr_internals)]
+
+#[path = "../../utils/mod.rs"]
+mod utils;
+use utils::macros::*;
+
+use core::ptr::Unique;
+
+// Check general handling of Unique
+
+fn main() {
+    unsafe {
+        let base = &mut 5u8;
+        let alloc_id = alloc_id!(base);
+        name!(base);
+
+        let raw = &mut *base as *mut u8;
+        name!(raw);
+
+        // We create a `Unique` and expect it to have a fresh tag
+        // and uninitialized permissions.
+        let uniq = Unique::new_unchecked(raw);
+
+        // With `-Zmiri-unique-is-unique`, `Unique::as_ptr` (which is called by
+        // `Vec::as_ptr`) generates pointers with a fresh tag, so to name the actual
+        // `base` pointer we care about we have to walk up the tree a bit.
+        //
+        // We care about naming this specific parent tag because it is the one
+        // that stays `Active` during the entire execution, unlike the leaves
+        // that will be invalidated the next time `as_ptr` is called.
+        //
+        // (We name it twice so that we have an indicator in the output of
+        // whether we got the distance correct:
+        // If the output shows
+        //
+        //    |- <XYZ: uniq>
+        //    '- <XYZ: uniq>
+        //
+        // then `nth_parent` is not big enough.
+        // The correct value for `nth_parent` should be the minimum
+        // integer for which the output shows
+        //
+        //    '- <XYZ: uniq, uniq>
+        // )
+        //
+        // Ultimately we want pointers obtained through independent
+        // calls of `as_ptr` to be able to alias, which will probably involve
+        // a new permission that allows aliasing when there is no protector.
+        let nth_parent = if cfg!(uniq) { 2 } else { 0 };
+        name!(uniq.as_ptr()=>nth_parent, "uniq");
+        name!(uniq.as_ptr()=>nth_parent, "uniq");
+        print_state!(alloc_id);
+
+        // We can activate the Unique and use it mutably.
+        *uniq.as_ptr() = 42;
+        print_state!(alloc_id);
+
+        // Write through the raw parent disables the Unique
+        *raw = 42;
+        print_state!(alloc_id);
+    }
+}
diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr
new file mode 100644
index 00000000000..5008b66741a
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr
@@ -0,0 +1,24 @@
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Res|      └─┬──<TAG=base>
+| Res|        └─┬──<TAG=raw>
+|----|          └────<TAG=uniq, uniq>
+──────────────────────────────────────────────────────────────────────
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Act|      └─┬──<TAG=base>
+| Act|        └─┬──<TAG=raw>
+| Act|          └────<TAG=uniq, uniq>
+──────────────────────────────────────────────────────────────────────
+──────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  1
+| Act|    └─┬──<TAG=root of the allocation>
+| Act|      └─┬──<TAG=base>
+| Act|        └─┬──<TAG=raw>
+| Dis|          └────<TAG=uniq, uniq>
+──────────────────────────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr
new file mode 100644
index 00000000000..f1af1ea3d8b
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr
@@ -0,0 +1,6 @@
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  2
+| Act|    └─┬──<TAG=root of the allocation>
+| Res|      └────<TAG=base.as_ptr(), base.as_ptr(), raw_parts.0, reconstructed.as_ptr(), reconstructed.as_ptr()>
+─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs
new file mode 100644
index 00000000000..3516f8d2ebf
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs
@@ -0,0 +1,68 @@
+//@revisions: default uniq
+//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
+//@[uniq]compile-flags: -Zmiri-unique-is-unique
+
+#![feature(vec_into_raw_parts)]
+
+#[path = "../../utils/mod.rs"]
+mod utils;
+use utils::macros::*;
+
+// Check general handling of `Unique`:
+// there is no *explicit* `Unique` being used here, but there is one
+// hidden a few layers inside `Vec` that should be reflected in the tree structure.
+
+fn main() {
+    unsafe {
+        let base = vec![0u8, 1];
+        let alloc_id = alloc_id!(base.as_ptr());
+
+        // With `-Zmiri-unique-is-unique`, `Unique::as_ptr` (which is called by
+        // `Vec::as_ptr`) generates pointers with a fresh tag, so to name the actual
+        // `base` pointer we care about we have to walk up the tree a bit.
+        //
+        // We care about naming this specific parent tag because it is the one
+        // that stays `Active` during the entire execution, unlike the leaves
+        // that will be invalidated the next time `as_ptr` is called.
+        //
+        // (We name it twice so that we have an indicator in the output of
+        // whether we got the distance correct:
+        // If the output shows
+        //
+        //    |- <XYZ: uniq>
+        //    '- <XYZ: uniq>
+        //
+        // then `nth_parent` is not big enough.
+        // The correct value for `nth_parent` should be the minimum
+        // integer for which the output shows
+        //
+        //    '- <XYZ: uniq, uniq>
+        // )
+        //
+        // Ultimately we want pointers obtained through independent
+        // calls of `as_ptr` to be able to alias, which will probably involve
+        // a new permission that allows aliasing when there is no protector.
+        let nth_parent = if cfg!(uniq) { 2 } else { 0 };
+        name!(base.as_ptr()=>nth_parent);
+        name!(base.as_ptr()=>nth_parent);
+
+        // Destruct the `Vec`
+        let (ptr, len, cap) = base.into_raw_parts();
+
+        // Expect this to be again the same pointer as the one obtained from `as_ptr`.
+        // Under `-Zmiri-unique-is-unique`, this will be a strict child.
+        name!(ptr, "raw_parts.0");
+
+        // This is where the presence of `Unique` has implications,
+        // because there will be a reborrow here iff the exclusivity of `Unique`
+        // is enforced.
+        let reconstructed = Vec::from_raw_parts(ptr, len, cap);
+
+        // The `as_ptr` here (twice for the same reason as above) return either
+        // the same pointer once more (default) or a strict child (uniq).
+        name!(reconstructed.as_ptr()=>nth_parent);
+        name!(reconstructed.as_ptr()=>nth_parent);
+
+        print_state!(alloc_id, false);
+    }
+}
diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr
new file mode 100644
index 00000000000..00ff1ee00eb
--- /dev/null
+++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr
@@ -0,0 +1,8 @@
+──────────────────────────────────────────────────────────────────────────
+Warning: this tree is indicative only. Some tags may have been hidden.
+0..  2
+| Act|    └─┬──<TAG=root of the allocation>
+|----|      └─┬──<TAG=base.as_ptr(), base.as_ptr()>
+|----|        └─┬──<TAG=raw_parts.0>
+|----|          └────<TAG=reconstructed.as_ptr(), reconstructed.as_ptr()>
+──────────────────────────────────────────────────────────────────────────
diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs
index 06ec2f99172..048f7d1c351 100644
--- a/src/tools/miri/tests/pass/vec.rs
+++ b/src/tools/miri/tests/pass/vec.rs
@@ -1,6 +1,7 @@
-//@revisions: stack tree
-//@[tree]compile-flags: -Zmiri-tree-borrows
+//@revisions: stack tree tree_uniq
 //@compile-flags: -Zmiri-strict-provenance
+//@[tree]compile-flags: -Zmiri-tree-borrows
+//@[tree_uniq]compile-flags: -Zmiri-tree-borrows -Zmiri-unique-is-unique
 #![feature(iter_advance_by, iter_next_chunk)]
 
 // Gather all references from a mutable iterator and make sure Miri notices if
diff --git a/src/tools/miri/tests/pass/vecdeque.rs b/src/tools/miri/tests/pass/vecdeque.rs
index dfbf9bb83c1..ccecf3d30a4 100644
--- a/src/tools/miri/tests/pass/vecdeque.rs
+++ b/src/tools/miri/tests/pass/vecdeque.rs
@@ -1,6 +1,8 @@
-//@revisions: stack tree
-//@[tree]compile-flags: -Zmiri-tree-borrows
+//@revisions: stack tree tree_uniq
 //@compile-flags: -Zmiri-strict-provenance
+//@[tree]compile-flags: -Zmiri-tree-borrows
+//@[tree_uniq]compile-flags: -Zmiri-tree-borrows -Zmiri-unique-is-unique
+
 use std::collections::VecDeque;
 
 fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
diff --git a/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout b/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout
new file mode 100644
index 00000000000..63de960ee2b
--- /dev/null
+++ b/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout
@@ -0,0 +1,2 @@
+[2, 2] Iter([2, 2], [])
+Iter([], [])
diff --git a/src/tools/miri/tests/utils/macros.rs b/src/tools/miri/tests/utils/macros.rs
index de223410fba..28b40954306 100644
--- a/src/tools/miri/tests/utils/macros.rs
+++ b/src/tools/miri/tests/utils/macros.rs
@@ -47,12 +47,12 @@ macro_rules! name {
     ($ptr:expr) => {
         crate::utils::macros::name!($ptr => 0, stringify!($ptr));
     };
-    ($ptr:expr => $nb:expr) => {
-        crate::utils::macros::name!($ptr => $nb, stringify!($ptr));
+    ($ptr:expr => $nth_parent:expr) => {
+        crate::utils::macros::name!($ptr => $nth_parent, stringify!($ptr));
     };
-    ($ptr:expr => $nb:expr, $name:expr) => {
+    ($ptr:expr => $nth_parent:expr, $name:expr) => {
         let name = $name.as_bytes();
-        crate::utils::miri_extern::miri_pointer_name($ptr as *const u8 as *const (), $nb, name);
+        crate::utils::miri_extern::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name);
     };
 }
 
diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs
index 6c4298c613b..55f3c1cc33e 100644
--- a/src/tools/miri/tests/utils/miri_extern.rs
+++ b/src/tools/miri/tests/utils/miri_extern.rs
@@ -65,7 +65,7 @@ extern "Rust" {
     ///
     /// This is only useful as an input to `miri_print_borrow_stacks`, and it is a separate call because
     /// getting a pointer to an allocation at runtime can change the borrow stacks in the allocation.
-    /// This function should be considered unstable. It exists only to support `miri_print_borrow_stacks` and so
+    /// This function should be considered unstable. It exists only to support `miri_print_borrow_state` and so
     /// inherits all of its instability.
     pub fn miri_get_alloc_id(ptr: *const ()) -> u64;