diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 13ec27aa1ab3f..bb2a5cab7d9bf 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -21,6 +21,8 @@ //! If you define a new `LateLintPass`, you will also need to add it to the //! `late_lint_methods!` invocation in `lib.rs`. +use std::fmt::Write; + use rustc::hir::def::{Res, DefKind}; use rustc::hir::def_id::{DefId, LOCAL_CRATE}; use rustc::ty::{self, Ty, TyCtxt, layout::VariantIdx}; @@ -1877,41 +1879,57 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue { const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed]; const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized]; - /// Return `false` only if we are sure this type does *not* + /// Information about why a type cannot be initialized this way. + /// Contains an error message and optionally a span to point at. + type InitError = (String, Option); + + /// Return `Some` only if we are sure this type does *not* /// allow zero initialization. - fn ty_maybe_allows_zero_init<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + fn ty_find_init_error<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { use rustc::ty::TyKind::*; match ty.sty { // Primitive types that don't like 0 as a value. - Ref(..) | FnPtr(..) | Never => false, - Adt(..) if ty.is_box() => false, + Ref(..) => Some((format!("References must be non-null"), None)), + Adt(..) if ty.is_box() => Some((format!("`Box` must be non-null"), None)), + FnPtr(..) => Some((format!("Function pointers must be non-null"), None)), + Never => Some((format!("The never type (`!`) has no valid value"), None)), // Recurse for some compound types. Adt(adt_def, substs) if !adt_def.is_union() => { match adt_def.variants.len() { - 0 => false, // Uninhabited enum! + 0 => Some((format!("0-variant enums have no valid value"), None)), 1 => { // Struct, or enum with exactly one variant. // Proceed recursively, check all fields. let variant = &adt_def.variants[VariantIdx::from_u32(0)]; - variant.fields.iter().all(|field| { - ty_maybe_allows_zero_init( + variant.fields.iter().find_map(|field| { + ty_find_init_error( tcx, field.ty(tcx, substs), - ) + ).map(|(mut msg, span)| if span.is_none() { + // Point to this field, should be helpful for figuring + // out where the source of the error is. + let span = tcx.def_span(field.did); + write!(&mut msg, " (in this {} field)", adt_def.descr()) + .unwrap(); + (msg, Some(span)) + } else { + // Just forward. + (msg, span) + }) }) } - _ => true, // Conservative fallback for multi-variant enum. + _ => None, // Conservative fallback for multi-variant enum. } } Tuple(..) => { // Proceed recursively, check all fields. - ty.tuple_fields().all(|field| ty_maybe_allows_zero_init(tcx, field)) + ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field)) } // FIXME: Would be nice to also warn for `NonNull`/`NonZero*`. // FIXME: *Only for `mem::uninitialized`*, we could also warn for `bool`, // `char`, and any multivariant enum. // Conservative fallback. - _ => true, + _ => None, } } @@ -1925,9 +1943,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue { // using zeroed or uninitialized memory. // We are extremely conservative with what we warn about. let conjured_ty = cx.tables.expr_ty(expr); - - if !ty_maybe_allows_zero_init(cx.tcx, conjured_ty) { - cx.struct_span_lint( + if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty) { + let mut err = cx.struct_span_lint( INVALID_VALUE, expr.span, &format!( @@ -1939,11 +1956,16 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue { "being left uninitialized" } ), - ) - .note("this means that this code causes undefined behavior \ - when executed") - .help("use `MaybeUninit` instead") - .emit(); + ); + err.span_label(expr.span, + "this code causes undefined behavior when executed"); + err.span_label(expr.span, "help: use `MaybeUninit` instead"); + if let Some(span) = span { + err.span_note(span, &msg); + } else { + err.note(&msg); + } + err.emit(); } } } diff --git a/src/test/ui/lint/uninitialized-zeroed.rs b/src/test/ui/lint/uninitialized-zeroed.rs index 8f9ca8717bda6..d816479bbbbe3 100644 --- a/src/test/ui/lint/uninitialized-zeroed.rs +++ b/src/test/ui/lint/uninitialized-zeroed.rs @@ -11,8 +11,10 @@ use std::mem::{self, MaybeUninit}; enum Void {} struct Ref(&'static i32); +struct RefPair((&'static i32, i32)); struct Wrap { wrapped: T } +enum WrapEnum { Wrapped(T) } #[allow(unused)] fn generic() { @@ -48,6 +50,12 @@ fn main() { let _val: Wrap = mem::zeroed(); //~ ERROR: does not permit zero-initialization let _val: Wrap = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + let _val: WrapEnum = mem::zeroed(); //~ ERROR: does not permit zero-initialization + let _val: WrapEnum = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + + let _val: Wrap<(RefPair, i32)> = mem::zeroed(); //~ ERROR: does not permit zero-initialization + let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + // Some types that should work just fine. let _val: Option<&'static i32> = mem::zeroed(); let _val: Option = mem::zeroed(); diff --git a/src/test/ui/lint/uninitialized-zeroed.stderr b/src/test/ui/lint/uninitialized-zeroed.stderr index af54b16bd0b24..1b15fc2152559 100644 --- a/src/test/ui/lint/uninitialized-zeroed.stderr +++ b/src/test/ui/lint/uninitialized-zeroed.stderr @@ -1,169 +1,289 @@ error: the type `&'static T` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:20:32 + --> $DIR/uninitialized-zeroed.rs:22:32 | LL | let _val: &'static T = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | note: lint level defined here --> $DIR/uninitialized-zeroed.rs:7:9 | LL | #![deny(invalid_value)] | ^^^^^^^^^^^^^ - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: References must be non-null error: the type `&'static T` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:21:32 + --> $DIR/uninitialized-zeroed.rs:23:32 | LL | let _val: &'static T = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: References must be non-null error: the type `Wrap<&'static T>` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:23:38 + --> $DIR/uninitialized-zeroed.rs:25:38 | LL | let _val: Wrap<&'static T> = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:16:18 + | +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ error: the type `Wrap<&'static T>` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:24:38 + --> $DIR/uninitialized-zeroed.rs:26:38 | LL | let _val: Wrap<&'static T> = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:16:18 | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ error: the type `!` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:30:23 + --> $DIR/uninitialized-zeroed.rs:32:23 | LL | let _val: ! = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: The never type (`!`) has no valid value error: the type `!` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:31:23 + --> $DIR/uninitialized-zeroed.rs:33:23 | LL | let _val: ! = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: The never type (`!`) has no valid value error: the type `(i32, !)` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:33:30 + --> $DIR/uninitialized-zeroed.rs:35:30 | LL | let _val: (i32, !) = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: The never type (`!`) has no valid value error: the type `(i32, !)` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:34:30 + --> $DIR/uninitialized-zeroed.rs:36:30 | LL | let _val: (i32, !) = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: The never type (`!`) has no valid value error: the type `Void` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:36:26 + --> $DIR/uninitialized-zeroed.rs:38:26 | LL | let _val: Void = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: 0-variant enums have no valid value error: the type `Void` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:37:26 + --> $DIR/uninitialized-zeroed.rs:39:26 | LL | let _val: Void = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: 0-variant enums have no valid value error: the type `&'static i32` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:39:34 + --> $DIR/uninitialized-zeroed.rs:41:34 | LL | let _val: &'static i32 = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: References must be non-null error: the type `&'static i32` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:40:34 + --> $DIR/uninitialized-zeroed.rs:42:34 | LL | let _val: &'static i32 = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: References must be non-null error: the type `Ref` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:42:25 + --> $DIR/uninitialized-zeroed.rs:44:25 | LL | let _val: Ref = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:13:12 | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +LL | struct Ref(&'static i32); + | ^^^^^^^^^^^^ error: the type `Ref` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:43:25 + --> $DIR/uninitialized-zeroed.rs:45:25 | LL | let _val: Ref = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:13:12 + | +LL | struct Ref(&'static i32); + | ^^^^^^^^^^^^ error: the type `fn()` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:45:26 + --> $DIR/uninitialized-zeroed.rs:47:26 | LL | let _val: fn() = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: Function pointers must be non-null error: the type `fn()` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:46:26 + --> $DIR/uninitialized-zeroed.rs:48:26 | LL | let _val: fn() = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead + = note: Function pointers must be non-null error: the type `Wrap` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:48:32 + --> $DIR/uninitialized-zeroed.rs:50:32 | LL | let _val: Wrap = mem::zeroed(); | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: Function pointers must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:16:18 | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ error: the type `Wrap` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:49:32 + --> $DIR/uninitialized-zeroed.rs:51:32 | LL | let _val: Wrap = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: Function pointers must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:16:18 + | +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ + +error: the type `WrapEnum` does not permit zero-initialization + --> $DIR/uninitialized-zeroed.rs:53:36 + | +LL | let _val: WrapEnum = mem::zeroed(); + | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: Function pointers must be non-null (in this enum field) + --> $DIR/uninitialized-zeroed.rs:17:28 + | +LL | enum WrapEnum { Wrapped(T) } + | ^ + +error: the type `WrapEnum` does not permit being left uninitialized + --> $DIR/uninitialized-zeroed.rs:54:36 + | +LL | let _val: WrapEnum = mem::uninitialized(); + | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: Function pointers must be non-null (in this enum field) + --> $DIR/uninitialized-zeroed.rs:17:28 + | +LL | enum WrapEnum { Wrapped(T) } + | ^ + +error: the type `Wrap<(RefPair, i32)>` does not permit zero-initialization + --> $DIR/uninitialized-zeroed.rs:56:42 + | +LL | let _val: Wrap<(RefPair, i32)> = mem::zeroed(); + | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:14:16 + | +LL | struct RefPair((&'static i32, i32)); + | ^^^^^^^^^^^^^^^^^^^ + +error: the type `Wrap<(RefPair, i32)>` does not permit being left uninitialized + --> $DIR/uninitialized-zeroed.rs:57:42 + | +LL | let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); + | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead + | +note: References must be non-null (in this struct field) + --> $DIR/uninitialized-zeroed.rs:14:16 | - = note: this means that this code causes undefined behavior when executed - = help: use `MaybeUninit` instead +LL | struct RefPair((&'static i32, i32)); + | ^^^^^^^^^^^^^^^^^^^ -error: aborting due to 18 previous errors +error: aborting due to 22 previous errors