warn about uninit multi-variant enums

This commit is contained in:
Ralf Jung 2020-07-17 15:01:37 +02:00
parent c2dbebd3d4
commit 87b49764bc
3 changed files with 96 additions and 41 deletions

View file

@ -1922,6 +1922,14 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
None
}
/// Test if this enum has several actually "existing" variants.
/// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist".
fn is_multi_variant(adt: &ty::AdtDef) -> bool {
// As an approximation, we only count dataless variants. Those are definitely inhabited.
let existing_variants = adt.variants.iter().filter(|v| v.fields.is_empty()).count();
existing_variants > 1
}
/// Return `Some` only if we are sure this type does *not*
/// allow zero initialization.
fn ty_find_init_error<'tcx>(
@ -1950,7 +1958,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
}
// Recurse and checks for some compound types.
Adt(adt_def, substs) if !adt_def.is_union() => {
// First check f this ADT has a layout attribute (like `NonNull` and friends).
// First check if this ADT has a layout attribute (like `NonNull` and friends).
use std::ops::Bound;
match tcx.layout_scalar_valid_range(adt_def.did) {
// We exploit here that `layout_scalar_valid_range` will never
@ -2001,10 +2009,20 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
)
})
}
// Multi-variant enums are tricky: if all but one variant are
// uninhabited, we might actually do layout like for a single-variant
// enum, and then even leaving them uninitialized could be okay.
_ => None, // Conservative fallback for multi-variant enum.
// Multi-variant enum.
_ => {
if init == InitKind::Uninit && is_multi_variant(adt_def) {
let span = tcx.def_span(adt_def.did);
Some((
"enums have to be initialized to a variant".to_string(),
Some(span),
))
} else {
// In principle, for zero-initialization we could figure out which variant corresponds
// to tag 0, and check that... but for now we just accept all zero-initializations.
None
}
}
}
}
Tuple(..) => {

View file

@ -23,6 +23,18 @@ enum WrapEnum<T> { Wrapped(T) }
#[repr(transparent)]
pub(crate) struct NonBig(u64);
/// A two-variant enum, thus needs a tag and may not remain uninitialized.
enum Fruit {
Apple,
Banana,
}
/// Looks like two variants but really only has one.
enum OneFruit {
Apple(!),
Banana,
}
#[allow(unused)]
fn generic<T: 'static>() {
unsafe {
@ -80,6 +92,9 @@ fn main() {
let _val: NonBig = mem::zeroed();
let _val: NonBig = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
let _val: Fruit = mem::zeroed();
let _val: Fruit = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
// Transmute-from-0
let _val: &'static i32 = mem::transmute(0usize); //~ ERROR: does not permit zero-initialization
let _val: &'static [i32] = mem::transmute((0usize, 0usize)); //~ ERROR: does not permit zero-initialization
@ -96,5 +111,9 @@ fn main() {
let _val: MaybeUninit<&'static i32> = mem::zeroed();
let _val: i32 = mem::zeroed();
let _val: bool = MaybeUninit::zeroed().assume_init();
// Some things that happen to work due to rustc implementation details,
// but are not guaranteed to keep working.
let _val: i32 = mem::uninitialized();
let _val: OneFruit = mem::uninitialized();
}
}

View file

@ -1,5 +1,5 @@
error: the type `&T` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:29:32
--> $DIR/uninitialized-zeroed.rs:41:32
|
LL | let _val: &'static T = mem::zeroed();
| ^^^^^^^^^^^^^
@ -15,7 +15,7 @@ LL | #![deny(invalid_value)]
= note: references must be non-null
error: the type `&T` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:30:32
--> $DIR/uninitialized-zeroed.rs:42:32
|
LL | let _val: &'static T = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -26,7 +26,7 @@ LL | let _val: &'static T = mem::uninitialized();
= note: references must be non-null
error: the type `Wrap<&T>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:32:38
--> $DIR/uninitialized-zeroed.rs:44:38
|
LL | let _val: Wrap<&'static T> = mem::zeroed();
| ^^^^^^^^^^^^^
@ -41,7 +41,7 @@ LL | struct Wrap<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `Wrap<&T>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:33:38
--> $DIR/uninitialized-zeroed.rs:45:38
|
LL | let _val: Wrap<&'static T> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -56,7 +56,7 @@ LL | struct Wrap<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `!` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:40:23
--> $DIR/uninitialized-zeroed.rs:52:23
|
LL | let _val: ! = mem::zeroed();
| ^^^^^^^^^^^^^
@ -67,7 +67,7 @@ LL | let _val: ! = mem::zeroed();
= note: the `!` type has no valid value
error: the type `!` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:41:23
--> $DIR/uninitialized-zeroed.rs:53:23
|
LL | let _val: ! = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -78,7 +78,7 @@ LL | let _val: ! = mem::uninitialized();
= note: the `!` type has no valid value
error: the type `(i32, !)` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:43:30
--> $DIR/uninitialized-zeroed.rs:55:30
|
LL | let _val: (i32, !) = mem::zeroed();
| ^^^^^^^^^^^^^
@ -89,7 +89,7 @@ LL | let _val: (i32, !) = mem::zeroed();
= note: the `!` type has no valid value
error: the type `(i32, !)` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:44:30
--> $DIR/uninitialized-zeroed.rs:56:30
|
LL | let _val: (i32, !) = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -100,7 +100,7 @@ LL | let _val: (i32, !) = mem::uninitialized();
= note: the `!` type has no valid value
error: the type `Void` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:46:26
--> $DIR/uninitialized-zeroed.rs:58:26
|
LL | let _val: Void = mem::zeroed();
| ^^^^^^^^^^^^^
@ -111,7 +111,7 @@ LL | let _val: Void = mem::zeroed();
= note: enums with no variants have no valid value
error: the type `Void` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:47:26
--> $DIR/uninitialized-zeroed.rs:59:26
|
LL | let _val: Void = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -122,7 +122,7 @@ LL | let _val: Void = mem::uninitialized();
= note: enums with no variants have no valid value
error: the type `&i32` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:49:34
--> $DIR/uninitialized-zeroed.rs:61:34
|
LL | let _val: &'static i32 = mem::zeroed();
| ^^^^^^^^^^^^^
@ -133,7 +133,7 @@ LL | let _val: &'static i32 = mem::zeroed();
= note: references must be non-null
error: the type `&i32` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:50:34
--> $DIR/uninitialized-zeroed.rs:62:34
|
LL | let _val: &'static i32 = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -144,7 +144,7 @@ LL | let _val: &'static i32 = mem::uninitialized();
= note: references must be non-null
error: the type `Ref` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:52:25
--> $DIR/uninitialized-zeroed.rs:64:25
|
LL | let _val: Ref = mem::zeroed();
| ^^^^^^^^^^^^^
@ -159,7 +159,7 @@ LL | struct Ref(&'static i32);
| ^^^^^^^^^^^^
error: the type `Ref` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:53:25
--> $DIR/uninitialized-zeroed.rs:65:25
|
LL | let _val: Ref = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -174,7 +174,7 @@ LL | struct Ref(&'static i32);
| ^^^^^^^^^^^^
error: the type `fn()` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:55:26
--> $DIR/uninitialized-zeroed.rs:67:26
|
LL | let _val: fn() = mem::zeroed();
| ^^^^^^^^^^^^^
@ -185,7 +185,7 @@ LL | let _val: fn() = mem::zeroed();
= note: function pointers must be non-null
error: the type `fn()` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:56:26
--> $DIR/uninitialized-zeroed.rs:68:26
|
LL | let _val: fn() = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -196,7 +196,7 @@ LL | let _val: fn() = mem::uninitialized();
= note: function pointers must be non-null
error: the type `Wrap<fn()>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:58:32
--> $DIR/uninitialized-zeroed.rs:70:32
|
LL | let _val: Wrap<fn()> = mem::zeroed();
| ^^^^^^^^^^^^^
@ -211,7 +211,7 @@ LL | struct Wrap<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `Wrap<fn()>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:59:32
--> $DIR/uninitialized-zeroed.rs:71:32
|
LL | let _val: Wrap<fn()> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -226,7 +226,7 @@ LL | struct Wrap<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `WrapEnum<fn()>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:61:36
--> $DIR/uninitialized-zeroed.rs:73:36
|
LL | let _val: WrapEnum<fn()> = mem::zeroed();
| ^^^^^^^^^^^^^
@ -241,7 +241,7 @@ LL | enum WrapEnum<T> { Wrapped(T) }
| ^
error: the type `WrapEnum<fn()>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:62:36
--> $DIR/uninitialized-zeroed.rs:74:36
|
LL | let _val: WrapEnum<fn()> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -256,7 +256,7 @@ LL | enum WrapEnum<T> { Wrapped(T) }
| ^
error: the type `Wrap<(RefPair, i32)>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:64:42
--> $DIR/uninitialized-zeroed.rs:76:42
|
LL | let _val: Wrap<(RefPair, i32)> = mem::zeroed();
| ^^^^^^^^^^^^^
@ -271,7 +271,7 @@ LL | struct RefPair((&'static i32, i32));
| ^^^^^^^^^^^^^^^^^^^
error: the type `Wrap<(RefPair, i32)>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:65:42
--> $DIR/uninitialized-zeroed.rs:77:42
|
LL | let _val: Wrap<(RefPair, i32)> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -286,7 +286,7 @@ LL | struct RefPair((&'static i32, i32));
| ^^^^^^^^^^^^^^^^^^^
error: the type `std::ptr::NonNull<i32>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:67:34
--> $DIR/uninitialized-zeroed.rs:79:34
|
LL | let _val: NonNull<i32> = mem::zeroed();
| ^^^^^^^^^^^^^
@ -297,7 +297,7 @@ LL | let _val: NonNull<i32> = mem::zeroed();
= note: `std::ptr::NonNull<i32>` must be non-null
error: the type `std::ptr::NonNull<i32>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:68:34
--> $DIR/uninitialized-zeroed.rs:80:34
|
LL | let _val: NonNull<i32> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -308,7 +308,7 @@ LL | let _val: NonNull<i32> = mem::uninitialized();
= note: `std::ptr::NonNull<i32>` must be non-null
error: the type `*const dyn std::marker::Send` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:70:37
--> $DIR/uninitialized-zeroed.rs:82:37
|
LL | let _val: *const dyn Send = mem::zeroed();
| ^^^^^^^^^^^^^
@ -319,7 +319,7 @@ LL | let _val: *const dyn Send = mem::zeroed();
= note: the vtable of a wide raw pointer must be non-null
error: the type `*const dyn std::marker::Send` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:71:37
--> $DIR/uninitialized-zeroed.rs:83:37
|
LL | let _val: *const dyn Send = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -330,7 +330,7 @@ LL | let _val: *const dyn Send = mem::uninitialized();
= note: the vtable of a wide raw pointer must be non-null
error: the type `bool` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:75:26
--> $DIR/uninitialized-zeroed.rs:87:26
|
LL | let _val: bool = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -341,7 +341,7 @@ LL | let _val: bool = mem::uninitialized();
= note: booleans must be either `true` or `false`
error: the type `Wrap<char>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:78:32
--> $DIR/uninitialized-zeroed.rs:90:32
|
LL | let _val: Wrap<char> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -356,7 +356,7 @@ LL | struct Wrap<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `NonBig` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:81:28
--> $DIR/uninitialized-zeroed.rs:93:28
|
LL | let _val: NonBig = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -366,8 +366,26 @@ LL | let _val: NonBig = mem::uninitialized();
|
= note: `NonBig` must be initialized inside its custom valid range
error: the type `Fruit` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:96:27
|
LL | let _val: Fruit = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
|
note: enums have to be initialized to a variant
--> $DIR/uninitialized-zeroed.rs:27:1
|
LL | / enum Fruit {
LL | | Apple,
LL | | Banana,
LL | | }
| |_^
error: the type `&i32` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:84:34
--> $DIR/uninitialized-zeroed.rs:99:34
|
LL | let _val: &'static i32 = mem::transmute(0usize);
| ^^^^^^^^^^^^^^^^^^^^^^
@ -378,7 +396,7 @@ LL | let _val: &'static i32 = mem::transmute(0usize);
= note: references must be non-null
error: the type `&[i32]` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:85:36
--> $DIR/uninitialized-zeroed.rs:100:36
|
LL | let _val: &'static [i32] = mem::transmute((0usize, 0usize));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -389,7 +407,7 @@ LL | let _val: &'static [i32] = mem::transmute((0usize, 0usize));
= note: references must be non-null
error: the type `std::num::NonZeroU32` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:86:32
--> $DIR/uninitialized-zeroed.rs:101:32
|
LL | let _val: NonZeroU32 = mem::transmute(0);
| ^^^^^^^^^^^^^^^^^
@ -400,7 +418,7 @@ LL | let _val: NonZeroU32 = mem::transmute(0);
= note: `std::num::NonZeroU32` must be non-null
error: the type `std::ptr::NonNull<i32>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:89:34
--> $DIR/uninitialized-zeroed.rs:104:34
|
LL | let _val: NonNull<i32> = MaybeUninit::zeroed().assume_init();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -411,7 +429,7 @@ LL | let _val: NonNull<i32> = MaybeUninit::zeroed().assume_init();
= note: `std::ptr::NonNull<i32>` must be non-null
error: the type `std::ptr::NonNull<i32>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:90:34
--> $DIR/uninitialized-zeroed.rs:105:34
|
LL | let _val: NonNull<i32> = MaybeUninit::uninit().assume_init();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -422,7 +440,7 @@ LL | let _val: NonNull<i32> = MaybeUninit::uninit().assume_init();
= note: `std::ptr::NonNull<i32>` must be non-null
error: the type `bool` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:91:26
--> $DIR/uninitialized-zeroed.rs:106:26
|
LL | let _val: bool = MaybeUninit::uninit().assume_init();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -432,5 +450,5 @@ LL | let _val: bool = MaybeUninit::uninit().assume_init();
|
= note: booleans must be either `true` or `false`
error: aborting due to 35 previous errors
error: aborting due to 36 previous errors