Miri Undefined Behavior Error in Piecemeal Struct Drop

Hi everybody,

I am trying to write a custom smart pointer, and I need to call the destructor of a type (T) before the rest of the struct (S) in which it is. However, that struct (S) is accessed by a NonNull pointer only.. The type T and the type O, the type which is calling the destructor of T, both have this NonNull.

Specifically, the destructor of T looks at the data in S. However, this should not raise an error in Miri because NonNull<S< is not dropped or deallocated yet - only the destructor of a member of S (T) has been called.

I have attached a minimum reproducable code - as well as the full Miri traceback.

Code:

use std::{ptr::NonNull, mem::{ManuallyDrop, MaybeUninit}};

struct Wrapper<T> {
    x: String,
    v: T,
}

struct Outer {
    i: NonNull<Wrapper<Inner>>, 
}

struct Inner {
    i: NonNull<Wrapper<Inner>>,
}

impl Drop for Inner {
    fn drop(&mut self) {
        println!("{}", unsafe { self.i.as_ref() }.x);
    }
}

fn main() {
    let wrapper_uninit = NonNull::from(Box::leak(Box::from(Wrapper {
        x: String::from("HELLO"),
        v: MaybeUninit::<Inner>::uninit()
    })));
    let wrapper_ptr = wrapper_uninit.cast::<Wrapper<Inner>>();

    let inner = Inner {
        i: wrapper_ptr
    };

    let ptr = wrapper_ptr.as_ptr();
    unsafe { std::ptr::write(std::ptr::addr_of_mut!((*ptr).v), inner) };

    let mut data = NonNull::from(Box::leak(Box::from(Outer {
        i: wrapper_ptr
    })));

    unsafe { std::ptr::drop_in_place(&mut data.as_mut().i.as_mut().v as *mut _) };
    unsafe { std::ptr::drop_in_place(&mut data.as_mut().i.as_mut().x as *mut _) };
    
    unsafe { Box::from_raw(data.as_ref().i.as_ptr().cast::<ManuallyDrop<Outer>>()) };
    unsafe { Box::from_raw(data.as_ptr().cast::<ManuallyDrop<Outer>>()) };
}

Miri traceback:

Preparing a sysroot for Miri (target: x86_64-unknown-linux-gnu)... done
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `/home/ericbuehler/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner target/miri/x86_64-unknown-linux-gnu/debug/miri_err`
error: Undefined Behavior: not granting access to tag <2840> because that would remove [Unique for <3026>] which is strongly protected because it is an argument of call 810
   --> /home/ericbuehler/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:376:18
    |
376 |         unsafe { &*self.as_ptr() }
    |                  ^^^^^^^^^^^^^^^ not granting access to tag <2840> because that would remove [Unique for <3026>] which is strongly protected because it is an argument of call 810
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <2840> was created by a SharedReadWrite retag at offsets [0x0..0x20]
   --> src/main.rs:23:26
    |
23  |       let wrapper_uninit = NonNull::from(Box::leak(Box::from(Wrapper {
    |  __________________________^
24  | |         x: String::from("HELLO"),
25  | |         v: MaybeUninit::<Inner>::uninit()
26  | |     })));
    | |________^
help: <3026> is this argument
   --> src/main.rs:40:14
    |
40  |     unsafe { std::ptr::drop_in_place(&mut data.as_mut().i.as_mut().v as *mut _) };
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: BACKTRACE (of the first span):
    = note: inside `std::ptr::NonNull::<Wrapper<Inner>>::as_ref::<'_>` at /home/ericbuehler/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:376:18: 376:33
note: inside `<Inner as std::ops::Drop>::drop`
   --> src/main.rs:18:33
    |
18  |         println!("{}", unsafe { self.i.as_ref() }.x);
    |                                 ^^^^^^^^^^^^^^^
    = note: inside `std::ptr::drop_in_place::<Inner> - shim(Some(Inner))` at /home/ericbuehler/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:497:1: 497:56
note: inside `main`
   --> src/main.rs:40:14
    |
40  |     unsafe { std::ptr::drop_in_place(&mut data.as_mut().i.as_mut().v as *mut _) };
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

To install and use Miri:

rustup +nightly component add miri
cargo +nightly miri run

In this code:

impl Drop for Inner {
    fn drop(&mut self) {
        println!("{}", unsafe { self.i.as_ref() }.x);
    }
}

The mutable reference &mut self must be exclusive, which means that for the duration of that reference (at least the duration of the call to drop), you're not allowed to access self via any pointer not derived from the &mut self reference.

Since self.i.as_ref() constructs an immutable reference to the entire wrapper, this overlaps with the mutable reference. Thus, you are violating the aliasing rules.

2 Likes

Right, that make sense .
Replacing it with

println!("{}", unsafe { &*self.i.as_ptr() }.x);

did not work either. Is there a workaround?

Try using addr_of!. That will avoid going through a reference.

As a general note, you should use addr_of! for these kinds of field accesses.

I tried:

let ptr = unsafe {&*(addr_of!(self.i) as *const Wrapper<Inner>) };
println!("{}", &ptr.x);

However, it gives the same error. How should I go through the raw pointer without references?

You're using it in the wrong spot.

let ptr = self.i.as_ptr();
let x_ptr = addr_of!((*ptr).x);

I tried that, and it caused a SIGSEV. I think I will use a different method. Thanks!

This usually indicates you are trying to access memory after it has been free'd... Is ptr still valid at that point?

Thank you, that is right. However, I have already fixed it.