Is it safe to drop_in_place a pointer to a dangling ref?

What?

I haven't read through all the meta discussion regarding cultural and philosophical aspects of UB, but last time I checked the question whether invalid references (and what kind of invalidity) causes immediate UB was still somewhat undecided (which does indicate one should usually be better off cauciously assuming stricter rules, but it's maybe a bit oversimplified to claim "this is 100% definite UB" for many things around invalid references).

And on the specific question of calling std::ptr::drop_in_place on a *mut &T where the *mut points to valid memory containing a &T whose lifetime has expired, given precedent of many a #[may_dangle] usage in the standard library, though off the top of my head I'm not sure what precise source to point to to validate this claim, my take would be to give a clear "yes, that's fine, you may do that".

1 Like

The Rustonomicon mentions it in a small tangent in the section on PhantomData:

It's a scary feature, though.

Vec's Drop is implemented as:

unsafe impl<#[may_dangle] T, A: Allocator> Drop for Vec<T, A> {
    fn drop(&mut self) {
        unsafe {
            ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.as_mut_ptr(), self.len))
        }
        // RawVec handles deallocation
    }
}

Notice how the T may dangle, thus the ptr::drop_in_place may drop dangling references.

I was wondering if this is actually true and looks like MIRI seems to disagree with this! It finds UB when ptr::read is used but not with ptr::drop_in_place. I opened MIRI disagree with the documentation of `ptr::drop_in_place` · Issue #112015 · rust-lang/rust · GitHub for this.

3 Likes

Also I think that most of this discussion can easily be mooted: I don't think a reference type has drop glue.

            dbg!(core::mem::needs_drop::<T>()); // output: false

fn main() {
...
    let _b = MyBox(MaybeUninit::new(&mut s));

So OP can try this instead:

unsafe impl<#[may_dangle] &str> Drop for MyBox<&str> {
    fn drop(&mut self) {
        if std::mem::needs_drop::<&str>() /* always false */ {
          // SAFETY: Never executed.
          unsafe { drop_in_place(self.ptr.as_ptr()); } unreachable!("contents must not have drop glue");
        }
        let layout = Layout::new::<&str>();
        // SAFETY: We allocated the memory with a matching layout.
        unsafe { dealloc(self.ptr.as_ptr() as *mut _, layout); }
    }
}

needs_drop is allowed to spuriously return true for all types, and you're not intended to do a needs_drop guard around a single drop_in_place call (drop_in_place internally does the equivalent checks).

The real answer here is that #[may_dangle] is underdocumented magic which is more specific than and overrides that of drop_in_place when used. #[may_dangle] states that the only thing valid to do for the type parameter it is applied to is to drop_in_place it, and has essentially no documentation on what (if anything) it means when applied to a lifetime parameter.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.