Auto merge of #63483 - RalfJung:invalid-value, r=Centril

Improve invalid_value lint message

The lint now explains which type is involved and why it cannot be initialized this way. It also points at the innermost struct/enum field that has an offending type, if any.

See https://github.com/erlepereira/x11-rs/issues/99#issuecomment-520311911 for how this helps in some real-world code hitting this lint.
This commit is contained in:
bors 2019-08-12 12:43:33 +00:00
commit 60960a260f
3 changed files with 224 additions and 74 deletions

View file

@ -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<Span>);
/// 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<InitError> {
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<T>` instead");
if let Some(span) = span {
err.span_note(span, &msg);
} else {
err.note(&msg);
}
err.emit();
}
}
}

View file

@ -11,8 +11,10 @@ use std::mem::{self, MaybeUninit};
enum Void {}
struct Ref(&'static i32);
struct RefPair((&'static i32, i32));
struct Wrap<T> { wrapped: T }
enum WrapEnum<T> { Wrapped(T) }
#[allow(unused)]
fn generic<T: 'static>() {
@ -48,6 +50,12 @@ fn main() {
let _val: Wrap<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
let _val: Wrap<fn()> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
let _val: WrapEnum<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
let _val: WrapEnum<fn()> = 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<fn()> = mem::zeroed();

View file

@ -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<T>` 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<T>` 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<T>` 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<T> { 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<T>` 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<T> { 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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 `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<T>` 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<T>` 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<T>` 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<fn()>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:48:32
--> $DIR/uninitialized-zeroed.rs:50:32
|
LL | let _val: Wrap<fn()> = mem::zeroed();
| ^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead
|
= note: this means that 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<T> { wrapped: T }
| ^^^^^^^^^^
error: the type `Wrap<fn()>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:49:32
--> $DIR/uninitialized-zeroed.rs:51:32
|
LL | let _val: Wrap<fn()> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead
|
= note: this means that 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<T> { wrapped: T }
| ^^^^^^^^^^
error: aborting due to 18 previous errors
error: the type `WrapEnum<fn()>` does not permit zero-initialization
--> $DIR/uninitialized-zeroed.rs:53:36
|
LL | let _val: WrapEnum<fn()> = mem::zeroed();
| ^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead
|
note: Function pointers must be non-null (in this enum field)
--> $DIR/uninitialized-zeroed.rs:17:28
|
LL | enum WrapEnum<T> { Wrapped(T) }
| ^
error: the type `WrapEnum<fn()>` does not permit being left uninitialized
--> $DIR/uninitialized-zeroed.rs:54:36
|
LL | let _val: WrapEnum<fn()> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead
|
note: Function pointers must be non-null (in this enum field)
--> $DIR/uninitialized-zeroed.rs:17:28
|
LL | enum WrapEnum<T> { 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<T>` 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<T>` instead
|
note: References must be non-null (in this struct field)
--> $DIR/uninitialized-zeroed.rs:14:16
|
LL | struct RefPair((&'static i32, i32));
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to 22 previous errors