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

std: Fix segfaulting Weak<!>::new #49248

Closed

Conversation

alexcrichton
Copy link
Member

This commit is a fix for #48493 where calling Weak::new where T is an
uninhabited type would segfault. The cause for this issue was pretty subtle and
the fix here is mostly a holdover until #47650 is implemented.

The Weak<!> struct internally contains a NonNull<RcBox<!>>. The RcBox<!>
type is uninhabited, however, as it directly embeds the ! type. Consequently
the size of RcBox<!> is zero, which means that NonNull<RcBox<!>> always
contains a pointer with a value of 1. Currently all boxes of zero-sized-types
are actually pointers to the address 1 (as they shouldn't be read anyway).

The problem comes about when later on we have a method called Weak::inner
which previously returned &RcBox<T>. This was actually invalid because the
instance of &RcBox<T> never existed (only the uninitialized part). This
means that when we try to actually modify &RcBox's weak count in the
destructor for Weak::new we're modifying the address 1! This ends up causing a
segfault.

This commit takes the strategy of modifying the Weak::inner method to return
an Option<&RcBox<T>> that is None whenever the size of RcBox<T> is 0 (so
it couldn't actually exist). This does unfortunately add more dispatch code to
operations like Weak<Any>::clone. Eventually the "correct" fix for this is to
have RcBox<T> store a union of T and a ZST like (), and that way the size
of RcBox<T> will never be 0 and we won't need this branch. Until #47650 is
implemented, though, we can't use unions and unsized types together.

Closes #48493

@alexcrichton
Copy link
Member Author

r? @sfackler

@rust-highfive
Copy link
Collaborator

r? @joshtriplett

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 21, 2018
@pietroalbini
Copy link
Member

Ping from triage @sfackler! This PR needs your review!

@@ -991,11 +991,11 @@ impl<T> Weak<T> {
pub fn new() -> Weak<T> {
unsafe {
Weak {
ptr: Box::into_raw_non_null(box ArcInner {
ptr: Box::into_raw_non_null(Box::new(ArcInner {
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a comment here saying why it's important that we don't use box?

// branch.
unsafe {
let ptr = self.ptr.as_ref();
if mem::size_of_val(ptr) == 0 {
Copy link
Member

Choose a reason for hiding this comment

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

This is still inherently bogus since it's creating a reference to an uninhabited type, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we're actually safe here in the sense that we're not telling LLVM this is illegal today and this compiles ok, but it's certainly standing on quite the precipice. If you'd prefer though I could probably add enough conditions here to skip this check if T isn't a dynamically sized type and only run this check if it's a DST.

@sfackler
Copy link
Member

This seems like a really hacky fix but if it works it works. Hopefully we can move to a "real" fix in the future.

This commit is a fix for rust-lang#48493 where calling `Weak::new` where `T` is an
uninhabited type would segfault. The cause for this issue was pretty subtle and
the fix here is mostly a holdover until rust-lang#47650 is implemented.

The `Weak<!>` struct internally contains a `NonNull<RcBox<!>>`. The `RcBox<!>`
type is uninhabited, however, as it directly embeds the `!` type. Consequently
the size of `RcBox<!>` is zero, which means that `NonNull<RcBox<!>>` always
contains a pointer with a value of 1. Currently all boxes of zero-sized-types
are actually pointers to the address 1 (as they shouldn't be read anyway).

The problem comes about when later on we have a method called `Weak::inner`
which previously returned `&RcBox<T>`. This was actually invalid because the
instance of `&RcBox<T> ` never existed (only the uninitialized part). This
means that when we try to actually modify `&RcBox`'s weak count in the
destructor for `Weak::new` we're modifying the address 1! This ends up causing a
segfault.

This commit takes the strategy of modifying the `Weak::inner` method to return
an `Option<&RcBox<T>>` that is `None` whenever the size of `RcBox<T>` is 0 (so
it couldn't actually exist). This does unfortunately add more dispatch code to
operations like `Weak<Any>::clone`. Eventually the "correct" fix for this is to
have `RcBox<T>` store a union of `T` and a ZST like `()`, and that way the size
of `RcBox<T>` will never be 0 and we won't need this branch. Until rust-lang#47650 is
implemented, though, we can't use `union`s and unsized types together.

Closes rust-lang#48493
@alexcrichton
Copy link
Member Author

Updated!

@sfackler
Copy link
Member

[00:52:30] ---- [run-pass] run-pass/void-collections.rs stdout ----
[00:52:30] 	
[00:52:30] error: test run failed!
[00:52:30] status: signal: 11
[00:52:30] command: "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-pass/void-collections.stage2-x86_64-unknown-linux-gnu"

@alexcrichton
Copy link
Member Author

Alas :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Weak::new() segfaults on uninhabited types
5 participants