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

Uninhabited types are ZSTs despite partial initialization being allowed in safe code. #49298

Closed
eddyb opened this Issue Mar 23, 2018 · 10 comments

Comments

Projects
None yet
8 participants
@eddyb
Copy link
Member

eddyb commented Mar 23, 2018

When we optimized product types with uninhabited fields to be ZST, partial initialization was overlooked, i.e. writes to the inhabited sibling fields are allowed despite missing in the layout.
Thankfully, it's impossible to read or borrow such fields, because the whole value must be initialized (although this may be relaxed in the future). However , the initialized fields are still dropped.

One way we could work around this is track uninhabitedness but only take advantage of it in enums.
(I am not aware of a solution that works with variant types, which I think we should keep in mind.)
EDIT: we can disallow creating the enum from an incomplete variant. This is also required by niche optimizations because the niche slot being uninitialized would result in UB.
EDIT2: incomplete variants might be possible today already (#49298 (comment)).

As an example, in debug mode, this snippet prints 0, because x.1 and y overlap:

enum Void {}

fn main() {
    let mut x: (Void, usize);
    let y = 1;
    x.1 = 0;
    println!("{}", y)
}

cc @nikomatsakis @nagisa

@xfix

This comment has been minimized.

Copy link
Contributor

xfix commented Mar 23, 2018

Seems I-unsound.

enum Void {}

fn main() {
    let mut x: (Void, [u8; 20]);
    x.1 = [0x12; 20];
}
@xfix

This comment has been minimized.

Copy link
Contributor

xfix commented Mar 23, 2018

Also, this regression was introduced in Rust 1.24.0 from what I see, this correctly works in 1.23.0.

@cuviper cuviper added the C-bug label Mar 24, 2018

@ExpHP

This comment has been minimized.

Copy link
Contributor

ExpHP commented Mar 24, 2018

To pile on the scary labels, that also makes this a regression from stable to stable!

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Mar 28, 2018

#48493 looks like an instance of this - cc @alexcrichton

@rkruppe

This comment has been minimized.

Copy link
Contributor

rkruppe commented Mar 28, 2018

@eddyb Weak creates a value of the uninhabited type via mem::uninitialized, it does not perform partial initalization. It's UB anyway. This issue (edit: resolving this issue) would at best make the code working-but-still-UB.

@bstrie

This comment has been minimized.

Copy link
Contributor

bstrie commented Mar 28, 2018

@rkruppe is there a separate bug for that?

@rkruppe

This comment has been minimized.

Copy link
Contributor

rkruppe commented Mar 28, 2018

@eddyb Uh, I guess #48493 is that bug? Although if we land a workaround before the proper fix (MaybeUninitialized with unsizing support) becomes available, we should make sure we open a new bug for tracking removal of the workaround and introduction of the proper fix.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Apr 11, 2018

There's potentially an even more problematic implication here: initializing an uninhabited (because of its fields) variant and panicking while doing so could also result in writing over data following the whole enum, assuming the data is larger than the enum itself, e.g.:

let x: Option<(String, !)>;
let y = vec!["foo".to_string()];
x = Some((String::new("bar"), panic!("{:?}", y)));

This might be hard to demonstrate today, because of some extra temporaries, but optimizations would likely prefer to write to x directly, which would spell disaster.
I'm not sure what a proper solution would be here, we certainly need to discuss it.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 12, 2018

We discussed in the meeting. It seems like the rules want to be:

  • Ignore uninhabitedness when laying out product types (! is treated like any ZST)
  • Ignore variants for enums if they are both uninhabited and ZST

This ensures there is space for non-ZST data... we need to do this if we want to be able to optimize temporaries away pre-monomorphization.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Apr 12, 2018

Ignore variants for enums if they are both uninhabited and ZST

Specifically, the data inside the variant (not including any tag or anything else enum-specific).

@nikomatsakis nikomatsakis added P-medium and removed I-nominated labels Apr 26, 2018

@eddyb eddyb self-assigned this Apr 26, 2018

bors added a commit that referenced this issue May 13, 2018

Auto merge of #50622 - eddyb:make-room-for-ghosts, r=nikomatsakis
rustc: leave space for fields of uninhabited types to allow partial initialization.

Fixes #49298 by only collapsing uninhabited enum variants, and only if they only have ZST fields.
Fixes #50442 incidentally (@nox's optimization didn't take into account uninhabited variants).

@bors bors closed this in #50622 May 13, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment