-
Notifications
You must be signed in to change notification settings - Fork 455
Closed
Description
This is hopefully a small enough repro snippet:
#[test]
fn family() {
struct Parent {
value: u8,
child: Option<Box<Self>>,
}
impl Parent {
fn declare_child(&mut self, value: u8) {
self.child.replace(Box::new(Self { value, child: None }));
}
fn declare_child_too(&mut self, value: u8) {
self.child = Some(Box::new(Self { value, child: None }));
}
}
// create the first ancestor
let mut progenitor = Parent {
value: 0,
child: None,
};
// we record the lineage as a stack of raw pointers to each generation
// we cannot have &mut as there would be aliasing/memory overlap
// (e.g. both `x` and its child `y` could write into `x.child.value` <-> `y.value`)
let mut past_parents = vec![];
// to make this safe to use, we keep around one exclusive &mut reference
// through which we perform all read and writes
let mut parent_today = &mut progenitor;
for i in 1..4 {
// today's parent is becoming tomorrow's grandparent, so:
// record it in the lineage (as a pointer)
past_parents.push(parent_today as *mut Parent);
// generate the new child
parent_today.child = Some(Box::new(Parent { value: i, child: None }));
// and make that the new parent for tomorrow
parent_today = parent_today.child.as_mut().unwrap();
}
// now we can use the stack to navigate up the lineage
while let Some(ptr) = past_parents.pop() {
// SAFETY:
// - ptr is valid (obtained directly from a valid &mut)
// - aliasing is respected (there are no other references to the data, just this one &mut)
parent_today = unsafe { ptr.as_mut().unwrap() };
}
// we should have reached the ancestor
assert_eq!(parent_today.value, 0);
}
I am experimenting with unsafe, so apologies if the code/comments above are wrong or misleading.
Running cargo miri test, possible UB is detected because
help: <192863> was created by a SharedReadWrite retag at offsets [0x0..0x10]
past_parents.push(parent_today as *mut Parent);
^^^^^^^^^^^^
help: <192863> was later invalidated at offsets [0x0..0x8] by a write access
parent_today.child = Some(Box::new(Parent { val...
^^^^^^^^^^^^^^^^^^
- I am not really sure I understand the problem here. If I rewrite that assignment as
parent_today.child.replace(Box::new(Self { value, child: None }));then Miri is happy and reports no issue. Could I get some insight on this? - If I declare helper methods
impl Parent {
fn declare_child(&mut self, value: u8) {
self.child.replace(Box::new(Self { value, child: None }));
}
fn declare_child_too(&mut self, value: u8) {
self.child = Some(Box::new(Self { value, child: None }));
}
}
which just wrap the two different behaviours of the previous point, then Miri reports no issue when using either (in place of the incriminated assignment). In particular, I would expect declare_child_too to trigger the same error reported originally.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels