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

Code like this:

struct MyBox<&str> {
    ptr: NonNull<&str>,
}

unsafe impl<#[may_dangle] &str> Drop for MyBox<&str> {
    fn drop(&mut self) {
        unsafe {
            drop_in_place(self.ptr.as_ptr());
            let layout = Layout::new::<&str>();
            dealloc(self.ptr.as_ptr() as *mut _, layout);
        }
    }
}

fn main() {
    let _foo;
    let s = String::from("123");
    _foo = MyBox::new(&*s);
}

The MyBox::ptr maybe be dangling, but self.ptr.as_ptr(); does not need turns ptr to &str to return a *mut &str, it just returns it directly.

Safety requirement of drop_in_place as following:

  • to_drop must be valid for both reads and writes.

  • to_drop must be properly aligned.

  • The value to_drop points to must be valid for dropping, which may mean it must uphold additional invariants - this is type-dependent.

I don't quite understand what The value to_drop points to must be valid for dropping means.

So, is it safe or not? I believe it is safe, while some article I found insists it isn't

Dangling pointers are not valid for both reads and writes. So it's not safe.

pointer is not dangling, *pointer is a dangling &str; while, drop(x: *mut &str) requires **x to be valid more than *x is valid?

There is no drop glue for &T, However.

If *mut &str is a dangling ptr you can't read &str from it. No drop glue doesn't matter.

*mut &str is not a dangling ptr, and there is no reading &str from it --- Will drop_in_place(*mut T) read T from *mut T?

So there's no dangling ptr but it may points to invalid &str, right? Note that references must always points valid values. In this case you still can't read the ptr since reading invalid &str is UB even if underlying str will never accessed.

So it is still unsafe. Well, what about a *mut &str &str, is it safe to drop_in_place(*mut &str &str) if the &str points invalid memory but itself is valid?

References are valid only if it points to valid value.

Are you saying &&&&&&&&&&&&&&&&&&&&&&&str is not valid when the final &str is invalid?

Yes

1 Like

Note that the "final" would be str not &str

Ok, it depends, returns to drop_in_place, why drop_in_place(&&&&&&&&&str) is unsafe?

Documents says:

This is semantically equivalent to calling ptr::read and discarding the result, but has the following advantages

eg: drop_in_place(&&&&&&&&&str) equals ptr::read(&&&&&&&&&str) -> &&&&&&&&str;
Both the input argument and the returning value are valid references.

And it is not possible to access &str because no drop glue for &T.
It is hard to believe drop_in_place(&&&&&&&&&str) is unsafe

First of all, let's define this for my fingers.

type T = &&&&&&&&str;

You've asked why drop_in_place(&T) is unsafe. The answer is it's safe. Since &T is a reference it always points to valid T. and the T here is Copy so dropping is no-op. Safe Rust normally doesn't allows to access invalid values.

It may be confusing since we're using the term "safe" in 2 different meanings - the absence of the keyword unsafe in code and the validity of the code. For this reason in the safety context it's common to call the latter "sound" or "soundness". Sound code may contains some unsafe code but it correctly follows all the rules.

1 Like

Even if the final str is invalid?

Is it correct I conclude as that:
drop_in_place(&&str) is unsafe because it equals read(&&str) -> &str, but &str is invalid because str is invalid; but drop_in_place(&&&str) is safe because the returning value us &&str, and there is no recursively drop_in_place(&&str)

Specially, I am not very sure about drop_in_place(&&str) is unsafe, because

This is semantically equivalent

The real code is a lang builtin

#[stable(feature = "drop_in_place", since = "1.8.0")]
#[lang = "drop_in_place"]
#[allow(unconditional_recursion)]
pub unsafe fn drop_in_place<T: ?Sized>(to_drop: *mut T) {
    // Code here does not matter - this is replaced by the
    // real drop glue by the compiler.

    // SAFETY: see comment above
    unsafe { drop_in_place(to_drop) }
}

&T is invalid if it points to invalid T. So &str is invalid if it points to invalid str, and &&str is invalid if it points to invalid &str and so on. Valid str consists of 0 or more initialized bytes which also is well-formed UTF-8 sequence.

1 Like

Note that having invalid value itself is UB. You don't even need to call any function with it for UB.

3 Likes

so &&&&&&&&&str is still invalid if str is invalid.
drop_in_place(&&&&&&&&&str) will still cause UB?

if the reference is invalid &&&&&&&&&str; is UB so drop_in_place(&&&&&&&&&str) is UB even before the call.

1 Like

Existance of &str (and consequently of &&&&&&&&&str) with invalid str is already UB.

5 Likes