Skip to content

Commit

Permalink
warn about uninit multi-variant enums
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Jul 17, 2020
1 parent c2dbebd commit 87b4976
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 41 deletions.
28 changes: 23 additions & 5 deletions src/librustc_lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(..) => {
Expand Down
19 changes: 19 additions & 0 deletions src/test/ui/lint/uninitialized-zeroed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
}
Loading

0 comments on commit 87b4976

Please sign in to comment.