From 203cb1d2e6d67afc289e7f856c3f1d61b050398f Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Tue, 2 Dec 2025 09:57:12 +0100 Subject: [PATCH 1/7] Revert "remove skip child logic" This reverts commit d8089dd3405ea83d44a1952a25016a380a76e21c. --- src/borrow_tracker/tree_borrows/tree.rs | 41 +++++++++- .../wildcard/protector_release.rs | 77 +++++++++++++++++++ .../wildcard/protector_release.stderr | 25 ++++++ .../wildcard/protector_release2.rs | 77 +++++++++++++++++++ .../wildcard/protector_release2.stderr | 25 ++++++ 5 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 tests/fail/tree_borrows/wildcard/protector_release.rs create mode 100644 tests/fail/tree_borrows/wildcard/protector_release.stderr create mode 100644 tests/fail/tree_borrows/wildcard/protector_release2.rs create mode 100644 tests/fail/tree_borrows/wildcard/protector_release2.stderr diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 900e9c3729..dbddc1bfa7 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -876,6 +876,24 @@ impl Tree { } impl<'tcx> LocationTree { + /// Returns the smallest exposed tag, if any, that is a transitive child of `root`. + fn get_min_exposed_child(root: UniIndex, nodes: &UniValMap) -> Option { + let mut stack = vec![root]; + let mut min_tag = None; + while let Some(idx) = stack.pop() { + let node = nodes.get(idx).unwrap(); + stack.extend_from_slice(node.children.as_slice()); + + if node.is_exposed { + min_tag = match min_tag { + Some(prev) if prev < node.tag => Some(prev), + _ => Some(node.tag), + }; + } + } + min_tag + } + /// Performs an access on this location. /// * `access_source`: The index, if any, where the access came from. /// * `visit_children`: Whether to skip updating the children of `access_source`. @@ -906,11 +924,26 @@ impl<'tcx> LocationTree { }; let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); - if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) { - // FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees. - return interp_ok(()); - } + // On a protector release access we skip the children of the accessed tag so that + // we can correctly return references from functions. However, if the tag has + // exposed children then some of the wildcard subtrees could also be children of + // the accessed node and would also need to be skipped. We can narrow down which + // child trees are children by comparing their root tag to the minimum exposed + // child of the accessed node. As the parent tag is always smaller than the child + // tag this means we only need to skip subtrees with a root tag larger than + // `min_exposed_child`. + let min_exposed_child = match visit_children { + ChildrenVisitMode::SkipChildrenOfAccessed => + Self::get_min_exposed_child(access_source.unwrap(), nodes), + _ => None, + }; for root in roots { + let tag = nodes.get(root).unwrap().tag; + if let Some(min_exposed_child) = min_exposed_child { + if tag > min_exposed_child { + break; + } + } // We don't perform a wildcard access on the tree we already performed a // normal access on. if Some(root) == accessed_root { diff --git a/tests/fail/tree_borrows/wildcard/protector_release.rs b/tests/fail/tree_borrows/wildcard/protector_release.rs new file mode 100644 index 0000000000..ff10a0cf01 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_release.rs @@ -0,0 +1,77 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +use std::cell::UnsafeCell; + +/// This is a variant of the test in `../protector-write-lazy.rs`, but with +/// wildcard references. +/// Checks that a protector release transitions a foreign reference on a reference, +/// if we can determine that it's not a child of the released tag. +/// For this version we know the tag is not a child, because its wildcard root has +/// a smaller tag then the released reference. +pub fn main() { + // We need two locations so that we can create a new reference + // that is foreign to an already active tag. + let mut x: UnsafeCell<[u32; 2]> = UnsafeCell::new([32, 33]); + let ref1 = &mut x; + let cell_ptr = ref1.get() as *mut u32; + + let int = ref1 as *mut UnsafeCell<[u32; 2]> as usize; + let wild = int as *mut UnsafeCell; + + let ref2 = unsafe { &mut *cell_ptr }; + + // `ref3` gets created before the protected ref `arg4`. + let ref3 = unsafe { &mut *wild.wrapping_add(1) }; + + let protect = |arg4: &mut u32| { + // Activates arg4. This would disable ref3 at [0] if it wasn't a cell. + *arg4 = 41; + + // Creates an exposed child of arg4. + let ref5 = &mut *arg4; + let _int = ref5 as *mut u32 as usize; + + // This creates ref6 from ref3 at [1], so that it doesn't disable arg4 at [0]. + let ref6 = unsafe { &mut *ref3.get() }; + + // ┌───────────┐ + // │ ref1* │ + // │ Cel │ Cel │ * + // └─────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ ref2 │ │ ref3 │ + // │ Act │ Res │ │ Cel │ Cel │ + // └─────┬─────┘ └─────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ arg4 │ │ ref6│ + // │ Act │ Res │ │ Res │ Res │ + // └─────┬─────┘ └───────────┘ + // │ + // │ + // ▼ + // ┌───────────┐ + // │ ref5* │ + // │ Res │ Res │ + // └───────────┘ + + // Creates a pointer to [0] with the provenance of ref6. + return (ref6 as *mut u32).wrapping_sub(1); + + // Protector release on arg4 happens here. + // This should cause a foreign write on all foreign nodes, + // unless they could be a child of arg4. + // arg4 has an exposed child, so some tags with a wildcard + // ancestor could be its children. + // However, the root of ref6 was created before arg4, so it + // cannot be a child of it. So it should get disabled + // (at location [0]). + }; + + let ptr = protect(ref2); + let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/protector_release.stderr b/tests/fail/tree_borrows/wildcard/protector_release.stderr new file mode 100644 index 0000000000..cd25ef40aa --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_release.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | let _fail = unsafe { *ptr }; + | ^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | let ref6 = unsafe { &mut *ref3.get() }; + | ^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a protector release (acting as a foreign write access) on every location previously accessed by this tag + --> tests/fail/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | }; + | ^ + = help: this transition corresponds to a loss of read and write permissions + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/fail/tree_borrows/wildcard/protector_release2.rs b/tests/fail/tree_borrows/wildcard/protector_release2.rs new file mode 100644 index 0000000000..b1ec779ae8 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_release2.rs @@ -0,0 +1,77 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +use std::cell::UnsafeCell; + +/// This is a variant of the test in `../protector-write-lazy.rs`, but with +/// wildcard references. +/// Checks that a protector release transitions a foreign reference on a reference, +/// if we can determine that it's not a child of the released tag. +/// For this version we know the tag is not a child, because its wildcard root has +/// a smaller tag then the exposed child of the protected tag. +pub fn main() { + // We need two locations so that we can create a new reference + // that is foreign to an already active tag. + let mut x: UnsafeCell<[u32; 2]> = UnsafeCell::new([32, 33]); + let ref1 = &mut x; + let cell_ptr = ref1.get() as *mut u32; + + let int = ref1 as *mut UnsafeCell<[u32; 2]> as usize; + let wild = int as *mut UnsafeCell; + + let ref2 = unsafe { &mut *cell_ptr }; + + let protect = |arg3: &mut u32| { + // `ref4` gets created after the protected ref `arg3` but before the exposed `ref5`. + let ref4 = unsafe { &mut *wild.wrapping_add(1) }; + + // Activates arg4. This would disable ref3 at [0] if it wasn't a cell. + *arg3 = 41; + + // Creates an exposed child of arg3. + let ref5 = &mut *arg3; + let _int = ref5 as *mut u32 as usize; + + // This creates ref6 from ref4 at [1], so that it doesn't disable arg3 at [0]. + let ref6 = unsafe { &mut *ref4.get() }; + + // ┌───────────┐ + // │ ref1* │ + // │ Cel │ Cel │ * + // └─────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ ref2 │ │ ref4│ + // │ Act │ Res │ │ Cel │ Cel │ + // └─────┬─────┘ └─────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ arg3 │ │ ref6│ + // │ Act │ Res │ │ Res │ Res │ + // └─────┬─────┘ └───────────┘ + // │ + // │ + // ▼ + // ┌───────────┐ + // │ ref5* │ + // │ Res │ Res │ + // └───────────┘ + + // Creates a pointer to [0] with the provenance of ref6. + return (ref6 as *mut u32).wrapping_sub(1); + + // Protector release on arg3 happens here. + // This should cause a foreign write on all foreign nodes, + // unless they could be a child of arg3. + // arg3 has an exposed child, so some tags with a wildcard + // ancestor could be its children. + // However, the root of ref6 was created before the exposed + // child ref5, so it cannot be a child of it. So it should + // get disabled (at location [0]). + }; + + let ptr = protect(ref2); + let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/tests/fail/tree_borrows/wildcard/protector_release2.stderr b/tests/fail/tree_borrows/wildcard/protector_release2.stderr new file mode 100644 index 0000000000..cd5f42aa04 --- /dev/null +++ b/tests/fail/tree_borrows/wildcard/protector_release2.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | let _fail = unsafe { *ptr }; + | ^^^^ Undefined Behavior occurred here + | + = 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: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | let ref6 = unsafe { &mut *ref4.get() }; + | ^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a protector release (acting as a foreign write access) on every location previously accessed by this tag + --> tests/fail/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | }; + | ^ + = help: this transition corresponds to a loss of read and write permissions + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + From 817d46bcf6a0be699b7078f4022044068208ba9b Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Tue, 2 Dec 2025 10:25:18 +0100 Subject: [PATCH 2/7] improve get_min_exposed --- src/borrow_tracker/tree_borrows/tree.rs | 28 +++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index dbddc1bfa7..e9d8dcb388 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -882,8 +882,10 @@ impl<'tcx> LocationTree { let mut min_tag = None; while let Some(idx) = stack.pop() { let node = nodes.get(idx).unwrap(); + if min_tag.is_some_and(|min| min < node.tag) { + continue; + } stack.extend_from_slice(node.children.as_slice()); - if node.is_exposed { min_tag = match min_tag { Some(prev) if prev < node.tag => Some(prev), @@ -924,14 +926,6 @@ impl<'tcx> LocationTree { }; let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); - // On a protector release access we skip the children of the accessed tag so that - // we can correctly return references from functions. However, if the tag has - // exposed children then some of the wildcard subtrees could also be children of - // the accessed node and would also need to be skipped. We can narrow down which - // child trees are children by comparing their root tag to the minimum exposed - // child of the accessed node. As the parent tag is always smaller than the child - // tag this means we only need to skip subtrees with a root tag larger than - // `min_exposed_child`. let min_exposed_child = match visit_children { ChildrenVisitMode::SkipChildrenOfAccessed => Self::get_min_exposed_child(access_source.unwrap(), nodes), @@ -939,10 +933,18 @@ impl<'tcx> LocationTree { }; for root in roots { let tag = nodes.get(root).unwrap().tag; - if let Some(min_exposed_child) = min_exposed_child { - if tag > min_exposed_child { - break; - } + // On a protector release access we skip the children of the accessed tag so that + // we can correctly return references from functions. However, if the tag has + // exposed children then some of the wildcard subtrees could also be children of + // the accessed node and would also need to be skipped. We can narrow down which + // child trees are children by comparing their root tag to the minimum exposed + // child of the accessed node. As the parent tag is always smaller than the child + // tag this means we only need to skip subtrees with a root tag larger than + // `min_exposed_child`. + if let Some(min_exposed_child) = min_exposed_child + && tag > min_exposed_child + { + break; } // We don't perform a wildcard access on the tree we already performed a // normal access on. From 41c6ce91bc30aaec69ab78db16df375ddab73616 Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Tue, 2 Dec 2025 11:45:07 +0100 Subject: [PATCH 3/7] move min_exposed_child outside of location loop --- src/borrow_tracker/tree_borrows/tree.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index e9d8dcb388..81b2a25d61 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -661,6 +661,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::VisitChildrenOfAccessed, &diagnostics, + None, )?; } interp_ok(()) @@ -686,6 +687,12 @@ impl<'tcx> Tree { let source_idx = self.tag_mapping.get(&tag).unwrap(); + let min_exposed_child = if self.roots.len() > 1 { + LocationTree::get_min_exposed_child(source_idx, &self.nodes) + } else { + None + }; + // This is a special access through the entire allocation. // It actually only affects `accessed` locations, so we need // to filter on those before initiating the traversal. @@ -716,6 +723,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::SkipChildrenOfAccessed, &diagnostics, + min_exposed_child, )?; } } @@ -899,6 +907,9 @@ impl<'tcx> LocationTree { /// Performs an access on this location. /// * `access_source`: The index, if any, where the access came from. /// * `visit_children`: Whether to skip updating the children of `access_source`. + /// * `min_exposed_child`: any subtree that could be a child of `access_source` must + /// have a root with a larger tag than this. If None then no other subtree can be + /// a child: fn perform_access( &mut self, roots: impl Iterator, @@ -908,6 +919,7 @@ impl<'tcx> LocationTree { global: &GlobalState, visit_children: ChildrenVisitMode, diagnostics: &DiagnosticInfo, + min_exposed_child: Option, ) -> InterpResult<'tcx> { let accessed_root = if let Some(idx) = access_source { Some(self.perform_normal_access( @@ -926,11 +938,6 @@ impl<'tcx> LocationTree { }; let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); - let min_exposed_child = match visit_children { - ChildrenVisitMode::SkipChildrenOfAccessed => - Self::get_min_exposed_child(access_source.unwrap(), nodes), - _ => None, - }; for root in roots { let tag = nodes.get(root).unwrap().tag; // On a protector release access we skip the children of the accessed tag so that From 69336b4d03945fc9b698128aea7c2338f3cd7f20 Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Tue, 16 Dec 2025 15:08:02 +0100 Subject: [PATCH 4/7] add more comments --- src/borrow_tracker/tree_borrows/tree.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 81b2a25d61..20e41f9e0f 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -661,7 +661,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::VisitChildrenOfAccessed, &diagnostics, - None, + None, // min_exposed_child only matters for protector end access, )?; } interp_ok(()) @@ -886,6 +886,8 @@ impl Tree { impl<'tcx> LocationTree { /// Returns the smallest exposed tag, if any, that is a transitive child of `root`. fn get_min_exposed_child(root: UniIndex, nodes: &UniValMap) -> Option { + // We cannot use the wildcard datastructure to improve this lookup. This is because + // the datastructure only tracks enabled nodes and we need to also consider disabled ones. let mut stack = vec![root]; let mut min_tag = None; while let Some(idx) = stack.pop() { @@ -909,7 +911,8 @@ impl<'tcx> LocationTree { /// * `visit_children`: Whether to skip updating the children of `access_source`. /// * `min_exposed_child`: any subtree that could be a child of `access_source` must /// have a root with a larger tag than this. If None then no other subtree can be - /// a child: + /// a child of `access_source`. + /// This only gets read if `visit_children==SkipChildrenOfAccessed`. fn perform_access( &mut self, roots: impl Iterator, From 3e17aa6d365855ffd150adc69106f6bfca0a965f Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Sun, 21 Dec 2025 16:10:03 +0100 Subject: [PATCH 5/7] comments & skip min_exposed_child skip on normal accesses --- src/borrow_tracker/tree_borrows/tree.rs | 31 ++++++++++--------- .../wildcard/protector_release.rs | 12 ++++--- .../wildcard/protector_release2.rs | 8 +++-- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/borrow_tracker/tree_borrows/tree.rs b/src/borrow_tracker/tree_borrows/tree.rs index 20e41f9e0f..2e96c0c61c 100644 --- a/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/borrow_tracker/tree_borrows/tree.rs @@ -661,7 +661,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::VisitChildrenOfAccessed, &diagnostics, - None, // min_exposed_child only matters for protector end access, + /* min_exposed_child */ None, // only matters for protector end access, )?; } interp_ok(()) @@ -690,6 +690,7 @@ impl<'tcx> Tree { let min_exposed_child = if self.roots.len() > 1 { LocationTree::get_min_exposed_child(source_idx, &self.nodes) } else { + // There's no point in computing this when there is just one tree. None }; @@ -893,6 +894,8 @@ impl<'tcx> LocationTree { while let Some(idx) = stack.pop() { let node = nodes.get(idx).unwrap(); if min_tag.is_some_and(|min| min < node.tag) { + // The minimum we found before is bigger than this tag, and therefore + // also bigger than all its children, so we can skip this subtree. continue; } stack.extend_from_slice(node.children.as_slice()); @@ -909,10 +912,9 @@ impl<'tcx> LocationTree { /// Performs an access on this location. /// * `access_source`: The index, if any, where the access came from. /// * `visit_children`: Whether to skip updating the children of `access_source`. - /// * `min_exposed_child`: any subtree that could be a child of `access_source` must - /// have a root with a larger tag than this. If None then no other subtree can be - /// a child of `access_source`. - /// This only gets read if `visit_children==SkipChildrenOfAccessed`. + /// * `min_exposed_child`: The tag of the smallest exposed (transitive) child of the accessed node. + /// This is only used with `visit_children == SkipChildrenOfAccessed`, where we need to skip children + /// of the accessed node. fn perform_access( &mut self, roots: impl Iterator, @@ -943,15 +945,16 @@ impl<'tcx> LocationTree { let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); for root in roots { let tag = nodes.get(root).unwrap().tag; - // On a protector release access we skip the children of the accessed tag so that - // we can correctly return references from functions. However, if the tag has - // exposed children then some of the wildcard subtrees could also be children of - // the accessed node and would also need to be skipped. We can narrow down which - // child trees are children by comparing their root tag to the minimum exposed - // child of the accessed node. As the parent tag is always smaller than the child - // tag this means we only need to skip subtrees with a root tag larger than - // `min_exposed_child`. - if let Some(min_exposed_child) = min_exposed_child + // On a protector release access we have to skip the children of the accessed tag. + // However, if the tag has exposed children then some of the wildcard subtrees could + // also be children of the accessed node and would also need to be skipped. We can + // narrow down which wildcard trees might be children by comparing their root tag to the + // minimum exposed child of the accessed node. As the parent tag is always smaller + // than the child tag this means we only need to skip subtrees with a root tag larger + // than `min_exposed_child`. Once we find such a root, we can leave the loop because roots + // are sorted by tag. + if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) + && let Some(min_exposed_child) = min_exposed_child && tag > min_exposed_child { break; diff --git a/tests/fail/tree_borrows/wildcard/protector_release.rs b/tests/fail/tree_borrows/wildcard/protector_release.rs index ff10a0cf01..c9bc0a85d0 100644 --- a/tests/fail/tree_borrows/wildcard/protector_release.rs +++ b/tests/fail/tree_borrows/wildcard/protector_release.rs @@ -3,9 +3,10 @@ use std::cell::UnsafeCell; /// This is a variant of the test in `../protector-write-lazy.rs`, but with /// wildcard references. -/// Checks that a protector release transitions a foreign reference on a reference, -/// if we can determine that it's not a child of the released tag. -/// For this version we know the tag is not a child, because its wildcard root has +/// Checks that a protector release access correctly determines that certain tags +/// cannot be children of the protected tag and that it updates them accordingly. +/// +/// For this version we know the tag is not a child because its wildcard root has /// a smaller tag then the released reference. pub fn main() { // We need two locations so that we can create a new reference @@ -41,7 +42,7 @@ pub fn main() { // │ │ // ▼ ▼ // ┌───────────┐ ┌───────────┐ - // │ ref2 │ │ ref3 │ + // │ ref2 │ │ ref3│ // │ Act │ Res │ │ Cel │ Cel │ // └─────┬─────┘ └─────┬─────┘ // │ │ @@ -71,7 +72,8 @@ pub fn main() { // cannot be a child of it. So it should get disabled // (at location [0]). }; - + // ptr has provenance of ref6 let ptr = protect(ref2); + // ref6 is disabled at [0]. let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ } diff --git a/tests/fail/tree_borrows/wildcard/protector_release2.rs b/tests/fail/tree_borrows/wildcard/protector_release2.rs index b1ec779ae8..29adc54b00 100644 --- a/tests/fail/tree_borrows/wildcard/protector_release2.rs +++ b/tests/fail/tree_borrows/wildcard/protector_release2.rs @@ -3,8 +3,9 @@ use std::cell::UnsafeCell; /// This is a variant of the test in `../protector-write-lazy.rs`, but with /// wildcard references. -/// Checks that a protector release transitions a foreign reference on a reference, -/// if we can determine that it's not a child of the released tag. +/// Checks that a protector release access correctly determines that certain tags +/// cannot be children of the protected tag and that it updates them accordingly. +/// /// For this version we know the tag is not a child, because its wildcard root has /// a smaller tag then the exposed child of the protected tag. pub fn main() { @@ -71,7 +72,8 @@ pub fn main() { // child ref5, so it cannot be a child of it. So it should // get disabled (at location [0]). }; - + // ptr has provenance of ref6 let ptr = protect(ref2); + // ref6 is disabled at [0]. let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ } From 6a8dc5064b6a92148a041ea56622124ca763eea0 Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Mon, 22 Dec 2025 16:48:05 +0100 Subject: [PATCH 6/7] update protector_release test diagrams --- tests/fail/tree_borrows/wildcard/protector_release.rs | 6 +++--- tests/fail/tree_borrows/wildcard/protector_release2.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/fail/tree_borrows/wildcard/protector_release.rs b/tests/fail/tree_borrows/wildcard/protector_release.rs index c9bc0a85d0..d1bb9d7815 100644 --- a/tests/fail/tree_borrows/wildcard/protector_release.rs +++ b/tests/fail/tree_borrows/wildcard/protector_release.rs @@ -42,21 +42,21 @@ pub fn main() { // │ │ // ▼ ▼ // ┌───────────┐ ┌───────────┐ - // │ ref2 │ │ ref3│ + // │ ref2│ │ │ │ ref3│ // │ Act │ Res │ │ Cel │ Cel │ // └─────┬─────┘ └─────┬─────┘ // │ │ // │ │ // ▼ ▼ // ┌───────────┐ ┌───────────┐ - // │ arg4 │ │ ref6│ + // │ arg4│ │ │ │ ref6│ // │ Act │ Res │ │ Res │ Res │ // └─────┬─────┘ └───────────┘ // │ // │ // ▼ // ┌───────────┐ - // │ ref5* │ + // │ref5*| │ // │ Res │ Res │ // └───────────┘ diff --git a/tests/fail/tree_borrows/wildcard/protector_release2.rs b/tests/fail/tree_borrows/wildcard/protector_release2.rs index 29adc54b00..bcc9090704 100644 --- a/tests/fail/tree_borrows/wildcard/protector_release2.rs +++ b/tests/fail/tree_borrows/wildcard/protector_release2.rs @@ -42,21 +42,21 @@ pub fn main() { // │ │ // ▼ ▼ // ┌───────────┐ ┌───────────┐ - // │ ref2 │ │ ref4│ + // │ ref2| │ │ | ref4│ // │ Act │ Res │ │ Cel │ Cel │ // └─────┬─────┘ └─────┬─────┘ // │ │ // │ │ // ▼ ▼ // ┌───────────┐ ┌───────────┐ - // │ arg3 │ │ ref6│ + // │ arg3| │ │ | ref6│ // │ Act │ Res │ │ Res │ Res │ // └─────┬─────┘ └───────────┘ // │ // │ // ▼ // ┌───────────┐ - // │ ref5* │ + // │ref5*| │ // │ Res │ Res │ // └───────────┘ From d35fd25ab955dee7f69d52636d106d7915f4e4dd Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Mon, 22 Dec 2025 16:54:19 +0100 Subject: [PATCH 7/7] more comments --- tests/fail/tree_borrows/wildcard/protector_release2.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fail/tree_borrows/wildcard/protector_release2.rs b/tests/fail/tree_borrows/wildcard/protector_release2.rs index bcc9090704..7bef1cb266 100644 --- a/tests/fail/tree_borrows/wildcard/protector_release2.rs +++ b/tests/fail/tree_borrows/wildcard/protector_release2.rs @@ -6,8 +6,10 @@ use std::cell::UnsafeCell; /// Checks that a protector release access correctly determines that certain tags /// cannot be children of the protected tag and that it updates them accordingly. /// -/// For this version we know the tag is not a child, because its wildcard root has +/// For this version we know the tag is not a child because its wildcard root has /// a smaller tag then the exposed child of the protected tag. +/// So this test checks that we don't just compare with the accessed tag but instead +/// find the smallest exposed child. pub fn main() { // We need two locations so that we can create a new reference // that is foreign to an already active tag.