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

Clarify the relationship between forget() and ManuallyDrop. #69618

Merged
merged 4 commits into from
Mar 20, 2020
Merged

Clarify the relationship between forget() and ManuallyDrop. #69618

merged 4 commits into from
Mar 20, 2020

Conversation

hniksic
Copy link
Contributor

@hniksic hniksic commented Mar 1, 2020

As discussed on reddit, this commit addresses two issues with the
documentation of mem::forget():

  • The documentation of mem::forget() can confuse the reader because of the
    discrepancy between usage examples that show correct usage and the
    accompanying text which speaks of the possibility of double-free. The
    text that says "if the panic occurs before mem::forget was called"
    refers to a variant of the second example that was never shown, modified
    to use mem::forget instead of ManuallyDrop. Ideally the documentation
    should show both variants, so it's clear what it's talking about.

    Also, the double free could be fixed just by placing mem::forget(v)
    before the construction of s. Since the lifetimes of s and v
    wouldn't overlap, there would be no point where panic could cause a double
    free. This could be mentioned, and contrasted against the more robust fix
    of using ManuallyDrop.

  • This sentence seems unjustified: "For some types, operations such as
    passing ownership (to a funcion like mem::forget) requires them to
    actually be fully owned right now [...]". Unlike C++, Rust has no move
    constructors, its moves are (possibly elided) bitwise copies. Even if you
    pass an invalid object to mem::forget, no harm should come to pass
    because mem::forget consumes the object and exists solely to prevent
    drop, so there no one left to observe the invalid state state.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @sfackler (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 1, 2020
@Centril
Copy link
Contributor

Centril commented Mar 1, 2020

r? @RalfJung

@rust-highfive rust-highfive assigned RalfJung and unassigned sfackler Mar 1, 2020
@rust-highfive

This comment has been minimized.

@RalfJung
Copy link
Member

RalfJung commented Mar 2, 2020

This sentence seems unjustified: "For some types, operations such as
passing ownership (to a funcion like mem::forget) requires them to
actually be fully owned right now [...]".

I added that sentence because the following is UB:

fn main() { unsafe {
    let x = Box::new(4);
    let x_copy = std::ptr::read(&x);
    drop(x);
    std::mem::forget(x_copy);
} }

Click "Tools - Miri" on playground to see the UB.

In other words, passing invalid object to any function can cause UB (it is "producing an invalid value"). The fact that you seem convinced of the opposite despite the docs stating this makes it even more important to keep this remark, I think. ;) But the remark is probably not very clear, and could be improved. Any suggestions?

@hniksic
Copy link
Contributor Author

hniksic commented Mar 2, 2020

@RalfJung Thanks for the clarification and the example. I can't suggest a better wording for that part because I don't yet understand the underlying problem.

Looking at the example, I cannot help but wonder if Miri is being too strict here. For example, changing let x = Box::new(4) to let x = 4 or even let x = Rc::new(4) removes the error from the Miri run. I'm curious in particular what is the difference between Box<i32> and Rc<i32> here - is some box-specific compiler magic going on?

@hniksic
Copy link
Contributor Author

hniksic commented Mar 2, 2020

After thinking about this some more, I'm beginning to see your point: passing a dangling Box to any function, including mem::forget, is undefined behavior by definition. Even if it doesn't currently appear to produce a bad outcome, it could do so at any time, and the documentation should warn against it. The wording of the definition makes it apparent why let x = 4 is accepted by Miri, but the box is not - destroying 4 doesn't produce an invalid integer (no integer is invalid, although a bool or an enum can be), but deallocating the box does produce a very invalid box.

It is curious that the definition explicitly includes a dangling Box, but doesn't (explicitly) cover other types of smart pointers, such as Rc and Arc. Is that an oversight in the definition or the result of more subtle reasons why one is ok while the others are not?

@RalfJung
Copy link
Member

RalfJung commented Mar 4, 2020

I'm curious in particular what is the difference between Box and Rc here - is some box-specific compiler magic going on?

Box is in some sense a primitive type that is recognized and treated specially by the compiler. It knows that it is a valid pointer that can be dereferenced etc. Rc, at least currently, does not get such a treatment. But I wouldn't rely on that... that's why I phrased the warning more generally.

At least right now, you are correct that Box is the only type hat has non-trivial drop that gets that treatment. The other types the compiler tracks like that are the reference types. However, references wrapped in a newtype might eventually also get that treatment, and those could have drop glue as well.

passing a dangling Box to any function, including mem::forget, is undefined behavior by definition. Even if it doesn't currently appear to produce a bad outcome, it could do so at any time, and the documentation should warn against it.

Exactly.

Generally, ManuallyDrop is preferred over mem::forget. As usual, type-based approaches just work better. :)

@hniksic
Copy link
Contributor Author

hniksic commented Mar 4, 2020

@RalfJung I've now pushed a change that restores the warning against producing invalid values, only reworded in a way that (hopefully) clarifies the issue.

src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

RalfJung commented Mar 5, 2020

Honestly I am wondering if it is time to soft-deprecate mem::forget in favor of ManuallyDrop -- given that we seem unable to come up with a good usecase for the former above the latter?

@hniksic
Copy link
Contributor Author

hniksic commented Mar 5, 2020

mem::forget is still convenient for safe leaks, e.g. to prevent a File from getting closed in some situations. It could of course be emulated with ManuallyDrop, but mem::forget is much easier to understand and document in such cases.

I'll try to give one more shot at documenting the issues correctly (and fixing the grammar).

@RalfJung
Copy link
Member

RalfJung commented Mar 5, 2020

mem::forget is still convenient for safe leaks, e.g. to prevent a File from getting closed in some situations. It could of course be emulated with ManuallyDrop, but mem::forget is much easier to understand and document in such cases.

That is a good point.

@bors
Copy link
Contributor

bors commented Mar 8, 2020

☔ The latest upstream changes (presumably #69804) made this pull request unmergeable. Please resolve the merge conflicts.

@RalfJung RalfJung added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 8, 2020
@hniksic
Copy link
Contributor Author

hniksic commented Mar 14, 2020

I believe I've now fixed the issues raised in the review:

  • the "example" of mem::forget usage is marked as erroneous, with the accompanying text explaining why;
  • the merge conflict is fixed.

Additionally, the whole relationship with ManuallyDrop is moved to a separate section for clarity.

I believe this improves the current text, for reasons explained in the first bullet point of the PR's description.

The second bullet point of that description is largely invalidated, as the UB is shown to exist. Still, the PR hopefully now provides a more thorough explanation of the issue, especially for readers who (like me previously) think that the fact that Rust's moves are just bitwise copies makes it ok to invoke mem::forget with an invalid value.

@hniksic
Copy link
Contributor Author

hniksic commented Mar 17, 2020

@RalfJung Does the S-waiting-on-author label mean that an action is required from my side? If so, what do I need to do?

@RalfJung RalfJung added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 17, 2020
@RalfJung
Copy link
Member

No it's just not automatically updated. I have this in my queue and will get around to reviewing it eventually. :)

src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
src/libcore/mem/mod.rs Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

Thanks! :)
@bors r+

@bors
Copy link
Contributor

bors commented Mar 19, 2020

📌 Commit 0335c037af4149b392d5782d3bc2e91e13b6d228 has been approved by RalfJung

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 19, 2020
@hniksic
Copy link
Contributor Author

hniksic commented Mar 19, 2020

The len was also hard-coded in the ManuallyDrop example; I've now changed that one as well for consistency.

@RalfJung
Copy link
Member

Good call.

Could you squash the commits a bit? 13 commits seems excessive for this change.^^

@RalfJung
Copy link
Member

@bors r-

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Mar 19, 2020
@hniksic
Copy link
Contributor Author

hniksic commented Mar 19, 2020

Could you squash the commits a bit? 13 commits seems excessive for this change.^^

Sure! I kind of assumed you'd squash all of them at merge time, so I didn't bother tidying them up.

Is it ok to also rebase them on current master in order to avoid the bidirectional merge?

@RalfJung
Copy link
Member

Sure, whatever makes squashing easier for you.

hniksic and others added 4 commits March 19, 2020 14:48
As discussed on reddit, this commit addresses two issues with the
documentation of `mem::forget()`:

* The documentation of `mem::forget()` can confuse the reader because of the
  discrepancy between usage examples that show correct usage and the
  accompanying text which speaks of the possibility of double-free.  The
  text that says "if the panic occurs before `mem::forget` was called"
  refers to a variant of the second example that was never shown, modified
  to use `mem::forget` instead of `ManuallyDrop`.  Ideally the documentation
  should show both variants, so it's clear what it's talking about.

  Also, the double free could be fixed just by placing `mem::forget(v)`
  before the construction of `s`.  Since the lifetimes of `s` and `v`
  wouldn't overlap, there would be no point where panic could cause a double
  free.  This could be mentioned, and contrasted against the more robust fix
  of using `ManuallyDrop`.

* This sentence seems unjustified: "For some types, operations such as
  passing ownership (to a funcion like `mem::forget`) requires them to
  actually be fully owned right now [...]".  Unlike C++, Rust has no move
  constructors, its moves are (possibly elided) bitwise copies.  Even if you
  pass an invalid object to `mem::forget`, no harm should come to pass
  because `mem::forget` consumes the object and exists solely to prevent
  drop, so there no one left to observe the invalid state state.
…m::forget.

As pointed out by Ralf Jung, dangling references and boxes are
undefined behavior as per
https://doc.rust-lang.org/reference/behavior-considered-undefined.html
and the Miri checker.
@hniksic
Copy link
Contributor Author

hniksic commented Mar 19, 2020

I've now rebased the change and reduced the commit count to four, each of which makes logical sense. Feel free to let me know if you want me to cut it further.

@RalfJung
Copy link
Member

Thanks a lot, that's perfect. :)

@bors r+

@bors
Copy link
Contributor

bors commented Mar 19, 2020

📌 Commit 2bebe8d has been approved by RalfJung

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 19, 2020
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Mar 19, 2020
Clarify the relationship between `forget()` and `ManuallyDrop`.

As discussed on reddit, this commit addresses two issues with the
documentation of `mem::forget()`:

* The documentation of `mem::forget()` can confuse the reader because of the
  discrepancy between usage examples that show correct usage and the
  accompanying text which speaks of the possibility of double-free.  The
  text that says "if the panic occurs before `mem::forget` was called"
  refers to a variant of the second example that was never shown, modified
  to use `mem::forget` instead of `ManuallyDrop`.  Ideally the documentation
  should show both variants, so it's clear what it's talking about.

  Also, the double free could be fixed just by placing `mem::forget(v)`
  before the construction of `s`.  Since the lifetimes of `s` and `v`
  wouldn't overlap, there would be no point where panic could cause a double
  free.  This could be mentioned, and contrasted against the more robust fix
  of using `ManuallyDrop`.

* This sentence seems unjustified: "For some types, operations such as
  passing ownership (to a funcion like `mem::forget`) requires them to
  actually be fully owned right now [...]".  Unlike C++, Rust has no move
  constructors, its moves are (possibly elided) bitwise copies.  Even if you
  pass an invalid object to `mem::forget`, no harm should come to pass
  because `mem::forget` consumes the object and exists solely to prevent
  drop, so there no one left to observe the invalid state state.
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 20, 2020
Rollup of 9 pull requests

Successful merges:

 - rust-lang#69618 (Clarify the relationship between `forget()` and `ManuallyDrop`.)
 - rust-lang#69768 (Compute the correct layout for variants of uninhabited enums)
 - rust-lang#69935 (codegen/mir: support polymorphic `InstanceDef`s)
 - rust-lang#70103 (Clean up E0437 explanation)
 - rust-lang#70131 (Add regression test for TAIT lifetime inference (issue rust-lang#55099))
 - rust-lang#70133 (remove unused imports)
 - rust-lang#70145 (doc: Add quote to .init_array)
 - rust-lang#70146 (Clean up e0438 explanation)
 - rust-lang#70150 (triagebot.toml: accept cleanup-crew)

Failed merges:

r? @ghost
@bors bors merged commit 5d39517 into rust-lang:master Mar 20, 2020
@hniksic hniksic deleted the mem-forget-doc-fix branch March 20, 2020 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants