From af24588a06b9fbed47495c787c20e930398bbb86 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 27 Mar 2022 13:10:34 -0400 Subject: [PATCH] invalid_value lint: detect invalid initialization of arrays --- compiler/rustc_lint/src/builtin.rs | 23 +++++--- src/test/ui/lint/uninitialized-zeroed.rs | 8 +++ src/test/ui/lint/uninitialized-zeroed.stderr | 55 ++++++++++++++++---- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 50a3df21a3bc9..d43c661dda6fa 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -2548,7 +2548,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { /// Return `Some` only if we are sure this type does *not* /// allow zero initialization. fn ty_find_init_error<'tcx>( - tcx: TyCtxt<'tcx>, + cx: &LateContext<'tcx>, ty: Ty<'tcx>, init: InitKind, ) -> Option { @@ -2575,7 +2575,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Adt(adt_def, substs) if !adt_def.is_union() => { // 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()) { + match cx.tcx.layout_scalar_valid_range(adt_def.did()) { // We exploit here that `layout_scalar_valid_range` will never // return `Bound::Excluded`. (And we have tests checking that we // handle the attribute correctly.) @@ -2603,12 +2603,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // Proceed recursively, check all fields. let variant = &adt_def.variant(VariantIdx::from_u32(0)); variant.fields.iter().find_map(|field| { - ty_find_init_error(tcx, field.ty(tcx, substs), init).map( + ty_find_init_error(cx, field.ty(cx.tcx, substs), init).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); + let span = cx.tcx.def_span(field.did); write!( &mut msg, " (in this {} field)", @@ -2627,7 +2627,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // Multi-variant enum. _ => { if init == InitKind::Uninit && is_multi_variant(*adt_def) { - let span = tcx.def_span(adt_def.did()); + let span = cx.tcx.def_span(adt_def.did()); Some(( "enums have to be initialized to a variant".to_string(), Some(span), @@ -2642,7 +2642,16 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { } Tuple(..) => { // Proceed recursively, check all fields. - ty.tuple_fields().iter().find_map(|field| ty_find_init_error(tcx, field, init)) + ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init)) + } + Array(ty, len) => { + if matches!(len.try_eval_usize(cx.tcx, cx.param_env), Some(v) if v > 0) { + // Array length known at array non-empty -- recurse. + ty_find_init_error(cx, *ty, init) + } else { + // Empty array or size unknown. + None + } } // Conservative fallback. _ => None, @@ -2655,7 +2664,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // We are extremely conservative with what we warn about. let conjured_ty = cx.typeck_results().expr_ty(expr); if let Some((msg, span)) = - with_no_trimmed_paths!(ty_find_init_error(cx.tcx, conjured_ty, init)) + with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) { cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| { let mut err = lint.build(&format!( diff --git a/src/test/ui/lint/uninitialized-zeroed.rs b/src/test/ui/lint/uninitialized-zeroed.rs index 122933c3c4e46..5cd323c01db8c 100644 --- a/src/test/ui/lint/uninitialized-zeroed.rs +++ b/src/test/ui/lint/uninitialized-zeroed.rs @@ -81,6 +81,9 @@ fn main() { let _val: *const dyn Send = mem::zeroed(); //~ ERROR: does not permit zero-initialization let _val: *const dyn Send = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + let _val: [fn(); 2] = mem::zeroed(); //~ ERROR: does not permit zero-initialization + let _val: [fn(); 2] = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + // Things that can be zero, but not uninit. let _val: bool = mem::zeroed(); let _val: bool = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized @@ -94,6 +97,9 @@ fn main() { let _val: Fruit = mem::zeroed(); let _val: Fruit = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + let _val: [bool; 2] = mem::zeroed(); + let _val: [bool; 2] = 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 @@ -110,6 +116,8 @@ fn main() { let _val: MaybeUninit<&'static i32> = mem::zeroed(); let _val: i32 = mem::zeroed(); let _val: bool = MaybeUninit::zeroed().assume_init(); + let _val: [bool; 0] = MaybeUninit::uninit().assume_init(); + let _val: [!; 0] = 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(); diff --git a/src/test/ui/lint/uninitialized-zeroed.stderr b/src/test/ui/lint/uninitialized-zeroed.stderr index 0af185ef61b5e..b6a66f0a95ad1 100644 --- a/src/test/ui/lint/uninitialized-zeroed.stderr +++ b/src/test/ui/lint/uninitialized-zeroed.stderr @@ -329,8 +329,30 @@ LL | let _val: *const dyn Send = mem::uninitialized(); | = note: the vtable of a wide raw pointer must be non-null +error: the type `[fn(); 2]` does not permit zero-initialization + --> $DIR/uninitialized-zeroed.rs:84:31 + | +LL | let _val: [fn(); 2] = mem::zeroed(); + | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: function pointers must be non-null + +error: the type `[fn(); 2]` does not permit being left uninitialized + --> $DIR/uninitialized-zeroed.rs:85:31 + | +LL | let _val: [fn(); 2] = mem::uninitialized(); + | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: function pointers must be non-null + error: the type `bool` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:86:26 + --> $DIR/uninitialized-zeroed.rs:89:26 | LL | let _val: bool = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -341,7 +363,7 @@ LL | let _val: bool = mem::uninitialized(); = note: booleans must be either `true` or `false` error: the type `Wrap` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:89:32 + --> $DIR/uninitialized-zeroed.rs:92:32 | LL | let _val: Wrap = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -356,7 +378,7 @@ LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ error: the type `NonBig` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:92:28 + --> $DIR/uninitialized-zeroed.rs:95:28 | LL | let _val: NonBig = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -367,7 +389,7 @@ 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:95:27 + --> $DIR/uninitialized-zeroed.rs:98:27 | LL | let _val: Fruit = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -384,8 +406,19 @@ LL | | Banana, LL | | } | |_^ +error: the type `[bool; 2]` does not permit being left uninitialized + --> $DIR/uninitialized-zeroed.rs:101:31 + | +LL | let _val: [bool; 2] = mem::uninitialized(); + | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: booleans must be either `true` or `false` + error: the type `&i32` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:98:34 + --> $DIR/uninitialized-zeroed.rs:104:34 | LL | let _val: &'static i32 = mem::transmute(0usize); | ^^^^^^^^^^^^^^^^^^^^^^ @@ -396,7 +429,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:99:36 + --> $DIR/uninitialized-zeroed.rs:105:36 | LL | let _val: &'static [i32] = mem::transmute((0usize, 0usize)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -407,7 +440,7 @@ LL | let _val: &'static [i32] = mem::transmute((0usize, 0usize)); = note: references must be non-null error: the type `NonZeroU32` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:100:32 + --> $DIR/uninitialized-zeroed.rs:106:32 | LL | let _val: NonZeroU32 = mem::transmute(0); | ^^^^^^^^^^^^^^^^^ @@ -418,7 +451,7 @@ LL | let _val: NonZeroU32 = mem::transmute(0); = note: `std::num::NonZeroU32` must be non-null error: the type `NonNull` does not permit zero-initialization - --> $DIR/uninitialized-zeroed.rs:103:34 + --> $DIR/uninitialized-zeroed.rs:109:34 | LL | let _val: NonNull = MaybeUninit::zeroed().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -429,7 +462,7 @@ LL | let _val: NonNull = MaybeUninit::zeroed().assume_init(); = note: `std::ptr::NonNull` must be non-null error: the type `NonNull` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:104:34 + --> $DIR/uninitialized-zeroed.rs:110:34 | LL | let _val: NonNull = MaybeUninit::uninit().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -440,7 +473,7 @@ LL | let _val: NonNull = MaybeUninit::uninit().assume_init(); = note: `std::ptr::NonNull` must be non-null error: the type `bool` does not permit being left uninitialized - --> $DIR/uninitialized-zeroed.rs:105:26 + --> $DIR/uninitialized-zeroed.rs:111:26 | LL | let _val: bool = MaybeUninit::uninit().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -450,5 +483,5 @@ LL | let _val: bool = MaybeUninit::uninit().assume_init(); | = note: booleans must be either `true` or `false` -error: aborting due to 36 previous errors +error: aborting due to 39 previous errors