Skip to content

Commit 60960a2

Browse files
committed
Auto merge of rust-lang#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 AltF02/x11-rs#99 (comment) for how this helps in some real-world code hitting this lint.
2 parents c01be67 + 0499923 commit 60960a2

File tree

3 files changed

+224
-74
lines changed

3 files changed

+224
-74
lines changed

src/librustc_lint/builtin.rs

+41-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
//! If you define a new `LateLintPass`, you will also need to add it to the
2222
//! `late_lint_methods!` invocation in `lib.rs`.
2323
24+
use std::fmt::Write;
25+
2426
use rustc::hir::def::{Res, DefKind};
2527
use rustc::hir::def_id::{DefId, LOCAL_CRATE};
2628
use rustc::ty::{self, Ty, TyCtxt, layout::VariantIdx};
@@ -1877,41 +1879,57 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
18771879
const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed];
18781880
const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized];
18791881

1880-
/// Return `false` only if we are sure this type does *not*
1882+
/// Information about why a type cannot be initialized this way.
1883+
/// Contains an error message and optionally a span to point at.
1884+
type InitError = (String, Option<Span>);
1885+
1886+
/// Return `Some` only if we are sure this type does *not*
18811887
/// allow zero initialization.
1882-
fn ty_maybe_allows_zero_init<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
1888+
fn ty_find_init_error<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<InitError> {
18831889
use rustc::ty::TyKind::*;
18841890
match ty.sty {
18851891
// Primitive types that don't like 0 as a value.
1886-
Ref(..) | FnPtr(..) | Never => false,
1887-
Adt(..) if ty.is_box() => false,
1892+
Ref(..) => Some((format!("References must be non-null"), None)),
1893+
Adt(..) if ty.is_box() => Some((format!("`Box` must be non-null"), None)),
1894+
FnPtr(..) => Some((format!("Function pointers must be non-null"), None)),
1895+
Never => Some((format!("The never type (`!`) has no valid value"), None)),
18881896
// Recurse for some compound types.
18891897
Adt(adt_def, substs) if !adt_def.is_union() => {
18901898
match adt_def.variants.len() {
1891-
0 => false, // Uninhabited enum!
1899+
0 => Some((format!("0-variant enums have no valid value"), None)),
18921900
1 => {
18931901
// Struct, or enum with exactly one variant.
18941902
// Proceed recursively, check all fields.
18951903
let variant = &adt_def.variants[VariantIdx::from_u32(0)];
1896-
variant.fields.iter().all(|field| {
1897-
ty_maybe_allows_zero_init(
1904+
variant.fields.iter().find_map(|field| {
1905+
ty_find_init_error(
18981906
tcx,
18991907
field.ty(tcx, substs),
1900-
)
1908+
).map(|(mut msg, span)| if span.is_none() {
1909+
// Point to this field, should be helpful for figuring
1910+
// out where the source of the error is.
1911+
let span = tcx.def_span(field.did);
1912+
write!(&mut msg, " (in this {} field)", adt_def.descr())
1913+
.unwrap();
1914+
(msg, Some(span))
1915+
} else {
1916+
// Just forward.
1917+
(msg, span)
1918+
})
19011919
})
19021920
}
1903-
_ => true, // Conservative fallback for multi-variant enum.
1921+
_ => None, // Conservative fallback for multi-variant enum.
19041922
}
19051923
}
19061924
Tuple(..) => {
19071925
// Proceed recursively, check all fields.
1908-
ty.tuple_fields().all(|field| ty_maybe_allows_zero_init(tcx, field))
1926+
ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field))
19091927
}
19101928
// FIXME: Would be nice to also warn for `NonNull`/`NonZero*`.
19111929
// FIXME: *Only for `mem::uninitialized`*, we could also warn for `bool`,
19121930
// `char`, and any multivariant enum.
19131931
// Conservative fallback.
1914-
_ => true,
1932+
_ => None,
19151933
}
19161934
}
19171935

@@ -1925,9 +1943,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19251943
// using zeroed or uninitialized memory.
19261944
// We are extremely conservative with what we warn about.
19271945
let conjured_ty = cx.tables.expr_ty(expr);
1928-
1929-
if !ty_maybe_allows_zero_init(cx.tcx, conjured_ty) {
1930-
cx.struct_span_lint(
1946+
if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty) {
1947+
let mut err = cx.struct_span_lint(
19311948
INVALID_VALUE,
19321949
expr.span,
19331950
&format!(
@@ -1939,11 +1956,16 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19391956
"being left uninitialized"
19401957
}
19411958
),
1942-
)
1943-
.note("this means that this code causes undefined behavior \
1944-
when executed")
1945-
.help("use `MaybeUninit` instead")
1946-
.emit();
1959+
);
1960+
err.span_label(expr.span,
1961+
"this code causes undefined behavior when executed");
1962+
err.span_label(expr.span, "help: use `MaybeUninit<T>` instead");
1963+
if let Some(span) = span {
1964+
err.span_note(span, &msg);
1965+
} else {
1966+
err.note(&msg);
1967+
}
1968+
err.emit();
19471969
}
19481970
}
19491971
}

src/test/ui/lint/uninitialized-zeroed.rs

+8
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ use std::mem::{self, MaybeUninit};
1111
enum Void {}
1212

1313
struct Ref(&'static i32);
14+
struct RefPair((&'static i32, i32));
1415

1516
struct Wrap<T> { wrapped: T }
17+
enum WrapEnum<T> { Wrapped(T) }
1618

1719
#[allow(unused)]
1820
fn generic<T: 'static>() {
@@ -48,6 +50,12 @@ fn main() {
4850
let _val: Wrap<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
4951
let _val: Wrap<fn()> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
5052

53+
let _val: WrapEnum<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
54+
let _val: WrapEnum<fn()> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
55+
56+
let _val: Wrap<(RefPair, i32)> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
57+
let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
58+
5159
// Some types that should work just fine.
5260
let _val: Option<&'static i32> = mem::zeroed();
5361
let _val: Option<fn()> = mem::zeroed();

0 commit comments

Comments
 (0)