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

Fix fallout from #57667 #58021

Merged
merged 1 commit into from
Mar 11, 2019
Merged

Fix fallout from #57667 #58021

merged 1 commit into from
Mar 11, 2019

Conversation

ishitatsuyuki
Copy link
Contributor

@ishitatsuyuki ishitatsuyuki commented Jan 31, 2019

PR #57667 fixed a memory leak in P::filter_map (where P is the special pointer type used in the compiler's AST).

but @eddyb pointed out a bug in it: #57667 (comment)

@ishitatsuyuki ishitatsuyuki requested a review from eddyb January 31, 2019 09:30
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 31, 2019
drop(Box::from_raw(p));
// Free the allocated memory only,
// do not drop T as it was ptr::read above.
drop(Box::from_raw(p as *mut ManuallyDrop<T>));
Copy link
Member

Choose a reason for hiding this comment

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

cc @RalfJung this is a clever trick I didn't consider!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I think this should work. I don't like the raw ptr cast though, it's so easy to get those wrong -- but I also don't know another way here.

@eddyb
Copy link
Member

eddyb commented Jan 31, 2019

r=me if @RalfJung can confirm it's a correct approach

@ishitatsuyuki
Copy link
Contributor Author

Do you suggestions on alternatives to raw pointer cast? If not, I guess the code should be merged as-is.

@ollie27
Copy link
Member

ollie27 commented Feb 8, 2019

This might be a shot in the dark but would something like the following work (and deal with the FIXME):

pub fn filter_map<F>(self, f: F) -> Option<P<T>> where
    F: FnOnce(T) -> Option<T>,
{
    unsafe {
        // Transmute to `Box<MaybeUninit<T>>` so if `f` `panic`s or returns
        // `None` the `Box` will be freed but not the inner T.
        let mut maybe_box: Box<MaybeUninit<T>> = mem::transmute(self.ptr);
        let x = f(ptr::read(maybe_box.as_ptr()))?;
        maybe_box.set(x);
        Some(P { ptr: mem::transmute(maybe_box) })
    }
}

@ishitatsuyuki
Copy link
Contributor Author

I think @ollie27's suggestion is pretty good, any opinions/alternatives regarding the use of transmute?

@RalfJung
Copy link
Member

It seems to me that using ManuallyDrop instead of MaybeUninit is sufficient?

@ishitatsuyuki
Copy link
Contributor Author

@RalfJung the snippet above uses MaybeUninit::set.

@ollie27
Copy link
Member

ollie27 commented Feb 10, 2019

I guess this would work:

pub fn filter_map<F>(self, f: F) -> Option<P<T>> where
    F: FnOnce(T) -> Option<T>,
{
    unsafe {
        // Transmute to `Box<ManuallyDrop<T>>` so if `f` `panic`s or returns
        // `None` the `Box` will be freed but not the inner T.
        let mut manaully_drop_box: Box<ManuallyDrop<T>> = mem::transmute(self.ptr);
        let x = f(ManuallyDrop::take(&mut manaully_drop_box))?;
        *manaully_drop_box = ManuallyDrop::new(x);
        Some(P { ptr: mem::transmute(manaully_drop_box) })
    }
}

@RalfJung
Copy link
Member

Yeah, something like that.

Either way though I think it'd be good to avoid "open" transmute and use helper functions instead. Turning Box<MaybeUninit<T>> into Box<T> should probably even become a stable (unsafe) operation some day.

@Dylan-DPC-zz
Copy link

ping from triage @ishitatsuyuki @eddyb what's the update on this?

@petrochenkov
Copy link
Contributor

r? @RalfJung

@rust-highfive rust-highfive assigned RalfJung and unassigned eddyb Mar 4, 2019
@RalfJung
Copy link
Member

RalfJung commented Mar 4, 2019

@ishitatsuyuki could you update this to one of the proposed panic-safe schemes? If not I can r+ this, because it is an improvement, but the other version is even better.

@ishitatsuyuki
Copy link
Contributor Author

Actually I invented one that is drastically simpler, based on ManuallyDrop.

The pains are with the Box<[T]> methods. I know that the best way would be adding some methods to Box itself, but I'm not inclined to do that since that's not future-proof (a pre-RFC has been on internals, for example).

@RalfJung
Copy link
Member

RalfJung commented Mar 5, 2019

Not sure if I like changing P itself -- for once, didn't you forget to add a impl Drop?

But it seems like some local private conversion functions between Box<T> and Box<ManuallyDrop<T>> would help. Long-term we probably want something like this in libstd. (We also want that for MaybeUninit, this should probably be co-designed.)

@ishitatsuyuki
Copy link
Contributor Author

I spent some effort and wrote things again. Hopefully this is what you wanted.

@@ -270,6 +270,20 @@ impl<T: ?Sized> Box<T> {
// additional requirements.
unsafe { Pin::new_unchecked(boxed) }
}

/// Converts a `Box<T>` into a `Box<ManuallyDrop<T>>`
#[unstable(feature = "box_manually_drop", issue = "0")]
Copy link
Member

Choose a reason for hiding this comment

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

I meant local helper functions in the P module... this also works but now you have to create a tracking issue and add it here.


// Recreate self from the raw pointer.
P { ptr: Box::from_raw(p) }
let x = f(ManuallyDrop::take(&mut manually_drop_box));
Copy link
Member

Choose a reason for hiding this comment

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

Can you use into_inner instead of take?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so, that would reallocate (or at least consume) the Box which defeats the point of P.

Copy link
Member

Choose a reason for hiding this comment

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

It would move out of the box and then later move back into it, why would it reallocate? This code does not reallocate either.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh wow. Box turns out to be even more magical than I realized. In this playground example shows that not only we can move a value out of Box without a DerefMove trait, but the compiler tracks such moves like for local variables and plain struct fields. Attempting to use the box before its contents are reassigned (if *x = … is removed) results in a compile time error[E0382]: use of moved value: `x` . But the box is still there and can be used again after its contents are reassigned.

And this is panic-safe too. In the assembly for https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=d4907435865f18159268fc36a8546016 we see that foo calls box_free but not drop_t.

So I think @RalfJung’s suggestion is the way to go.

Copy link
Contributor

Choose a reason for hiding this comment

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

Bonus possibly-unexpected detail:

rust/src/liballoc/boxed.rs

Lines 290 to 295 in f22dca0

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {
fn drop(&mut self) {
// FIXME: Do nothing, drop is currently performed by compiler.
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Wow, I was aware of Box special-casing, but I do not recall being able to reinitialize the Box and reuse it!

@nikomatsakis @pnkfelix Do you happen to know when that might've happened?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this probably happened as part of the NLL transition. We decided to stop hobbling the treatment of Box during that discussion.

@ishitatsuyuki
Copy link
Contributor Author

I will do the tracking issue thing tomorrow.

@RalfJung
Copy link
Member

RalfJung commented Mar 6, 2019

@rust-lang/libs any advise on how to proceed here -- should we add two new unstable methods with a tracking issue, or instead add two local private helper functions in syntax::ptr?

@SimonSapin
Copy link
Contributor

Per #58021 (comment) it looks like the one use case for those methods can be solved with plain Box<T> without ManuallyDrop. Unless there’s another use case, let’s not add public APIs.

@RalfJung
Copy link
Member

RalfJung commented Mar 6, 2019

I hadn't even thought so far, but you are right... this proper move tracking for boxes entirely sidesteps the need for ManuallyDrop. We can even write these methods in safe code.

@ishitatsuyuki
Copy link
Contributor Author

(Short on time this week, will update this weekend)

@bors
Copy link
Contributor

bors commented Mar 11, 2019

⌛ Testing commit a03e20d with merge e68bf8a...

bors added a commit that referenced this pull request Mar 11, 2019
kennytm added a commit to kennytm/rust that referenced this pull request Mar 11, 2019
@bors
Copy link
Contributor

bors commented Mar 11, 2019

☀️ Test successful - checks-travis, status-appveyor
Approved by: RalfJung
Pushing e68bf8a to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Mar 11, 2019
@bors bors merged commit a03e20d into rust-lang:master Mar 11, 2019
@pietroalbini pietroalbini added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Mar 28, 2019
@pnkfelix
Copy link
Member

Discussed in this week's T-compiler meeting. Approved for beta-backport.

(In parallel, @pnkfelix wants to make a regression test. But than need not hold up the backport.)

@pnkfelix pnkfelix added the beta-accepted Accepted for backporting to the compiler in the beta channel. label Mar 28, 2019
@pnkfelix
Copy link
Member

(In parallel, @pnkfelix wants to make a regression test. But than need not hold up the backport.)

I was careless and misread the patch, thinking that this was some fn map and fn filter_map in libstd; but that wasn't true at all, its in libsyntax. I'm not going to worry about a regression test.

@pietroalbini pietroalbini removed the beta-nominated Nominated for backporting to the compiler in the beta channel. label Apr 2, 2019
bors added a commit that referenced this pull request Apr 7, 2019
[beta] Rollup backports

Cherry-picked:

* #58021: Fix fallout from #57667
* #59599: Updated RELEASES.md for 1.34.0
* #59587: Remove #[doc(hidden)] from Error::type_id
* #58994: Hide deprecation warnings inside derive expansions
* #58015: Expand docs for `TryFrom` and `TryInto`.
* #59770: ci: pin android emulator to 28.0.23
* #59704: ci: Update FreeBSD tarball downloads
* #59257: Update CI configuration for building Redox libraries
* #59724: Function arguments should never get promoted

r? @ghost
bors added a commit that referenced this pull request Apr 8, 2019
[beta] Rollup backports

Cherry-picked:

* #58021: Fix fallout from #57667
* #59599: Updated RELEASES.md for 1.34.0
* #59587: Remove #[doc(hidden)] from Error::type_id
* #58994: Hide deprecation warnings inside derive expansions
* #58015: Expand docs for `TryFrom` and `TryInto`.
* #59770: ci: pin android emulator to 28.0.23
* #59704: ci: Update FreeBSD tarball downloads
* #59257: Update CI configuration for building Redox libraries
* #59724: Function arguments should never get promoted
* #59499: Fix broken download link in the armhf-gnu image
* #58330: Add rustdoc JS non-std tests
* #58848: Prevent cache issues on version updates

r? @ghost
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
beta-accepted Accepted for backporting to the compiler in the beta channel. merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.