Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add note about PhantomData and dropck #432

Merged
merged 9 commits into from
Sep 7, 2020
Merged

Add note about PhantomData and dropck #432

merged 9 commits into from
Sep 7, 2020

Conversation

KodrAus
Copy link
Contributor

@KodrAus KodrAus commented Sep 7, 2020

Based on a soundness issue with SyncOnceCell introduced when we added a #[may_dangle] attribute without also informing dropck that we logically own the value: rust-lang/rust#76367

cc @matklad @m-ou-se

r? @RalfJung

@@ -189,6 +189,23 @@ Changes to collection internals may affect the order their items are dropped in.

Generic types that manually implement `Drop` should consider whether a `#[may_dangle]` attribute is appropriate. The [Nomicon][dropck] has some details on what `#[may_dangle]` is all about.

If the type pretends to store a value that may dangle, but doesn't do so directly (perhaps through `MaybeUninit<T>` or a raw pointer) then make sure there's an appropriate use of `PhantomData` to support dropck. As a [real-world example][rust/issues/76367], adding a `#[may_dangle]` attribute to an `OptionCell<T>` that internally stores its value as a `MaybeUninit<T>` requires both a `PhantomData` marker and a `#[may_dangle]` attribute:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When reading this paragraph, I wouldn't know what "pretends to store a value that may dangle" means -- specifically the "may dangle" part.

The key point here is that if dropping Type<T> means that a T could be dropped, then dropck needs to know this. If the drop occurs through the normal auto-generated drop glue, no action is required as dropck know what that one does. But if the drop occurs through a manual drop impl, then there needs to be a PhantomData<T>.

@KodrAus
Copy link
Contributor Author

KodrAus commented Sep 7, 2020

Thanks for the review @RalfJung! That first cut was clearly sketchy and deserved more consideration 🙂

How does this look now? I've linked to the appropriate RFC and I've tried to suggest that our original implementation of OptionCell<T> without a PhantomData<T> field was problematic even before we added #[may_dangle]. Would you agree?

Copy link
Member

@m-ou-se m-ou-se left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nice, an example makes it a lot clearer.

Few comments:

I've tried to suggest that our original implementation of OptionCell<T> without a PhantomData<T> field was problematic even before we added #[may_dangle]. Would you agree?

Hm, not sure. Without #[may_dangle], dropck assumes we're going to access some potential T regardless, so that's safe. Although it might be good to add it just in case a #[may_dangle] is added later, I think it's better to just say that only adding #[may_dangle] requires thinking about dropck and related PhantomData.

Generic types that manually implement `Drop` should consider whether a `#[may_dangle]` attribute is appropriate. The [Nomicon][dropck] has some details on what `#[may_dangle]` is all about.
A generic `Type<T>` that manually implements `Drop` should consider whether a `#[may_dangle]` attribute is appropriate on `T`. The [Nomicon][dropck] has some details on what `#[may_dangle]` is all about.

If a generic `Type<T>` has a manual drop implementation that may also involve dropping `T` then dropck needs to know about it. If `Type<T>`'s ownership of `T` is expressed indirectly through pointers, such as `*mut T` or `MaybeUninit<T>`, then `Type<T>` also [needs a `PhantomData<T>` field][RFC 0769 PhantomData] to tell dropck that `T` may be dropped. Types in the standard library that use the internal `Unique<T>` pointer type don't need a `PhantomData<T>` marker field. That's taken care of for them by `Unique<T>`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Type<T>'s ownership of T is expressed indirectly through pointers, such as *mut T or MaybeUninit<T>

This now suggests that MaybeUninit<T> owns a T through pointers, instead of directly.

Might be good to mention ManuallyDrop<T> as well. That one's more closely related to the problem, as it does exactly the one thing that makes this problem a problem.

src/libs/maintaining-std.md Outdated Show resolved Hide resolved
src/libs/maintaining-std.md Outdated Show resolved Hide resolved
KodrAus and others added 3 commits September 7, 2020 19:25
Co-authored-by: Mara Bos <m-ou.se@m-ou.se>
Co-authored-by: Mara Bos <m-ou.se@m-ou.se>
@RalfJung
Copy link
Member

RalfJung commented Sep 7, 2020

Ah nice, an example makes it a lot clearer.

Few comments:

I've tried to suggest that our original implementation of OptionCell<T> without a PhantomData<T> field was problematic even before we added #[may_dangle]. Would you agree?

Hm, not sure. Without #[may_dangle], dropck assumes we're going to access some potential T regardless, so that's safe. Although it might be good to add it just in case a #[may_dangle] is added later, I think it's better to just say that only adding #[may_dangle] requires thinking about dropck and related PhantomData.

Yes, I was about to raise a similar concern. dropck by default has to assume that a manual drop impl does everything safe code can do, which includes dropping some T. Only when #[may_dangle] is added does it assume that no operation on the may-dangle-parameter is called, except for dropping it but only if that is reflected via a struct field (either a T is really owned or it is PhantomData-owned).

impl<T> Drop for OptionCell<T> {
fn drop(&mut self) {
// Safety: The cell is being dropped, so it can't be accessed again.
unsafe { self.take_inner() };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to at least state the type signature for take_inner. I assume it returns Option<T>.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just write out the code:

if self.is_init {
    let _ = unsafe { self.value.read() };
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd find drop(unsafe { self.value.read() }) clearer, after all the point is that this drops the value.

Copy link
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most clear explanation of the problem that I have seen so far. :)

@RalfJung
Copy link
Member

RalfJung commented Sep 7, 2020

We should reference these docs from rust-lang/rust#34761 once the PR lands.

@KodrAus
Copy link
Contributor Author

KodrAus commented Sep 7, 2020

Or just write out the code

Good idea!

Thanks @RalfJung and @m-ou-se for helping shape this up! ❤️

I’ll drop a link in the tracking issue when it’s all deployed.

@KodrAus KodrAus merged commit 537aea0 into master Sep 7, 2020
@KodrAus KodrAus deleted the KodrAus-patch-1 branch September 7, 2020 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants