diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 0454633091568..bd00202bf8717 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -328,6 +328,10 @@ language_item_table! { RangeTo, sym::RangeTo, range_to_struct, Target::Struct, GenericRequirement::None; String, sym::String, string, Target::Struct, GenericRequirement::None; + + // Lang item because the compiler inserts calls to it when uninit memory is used + AssertUninitValid, sym::assert_uninit_valid, assert_uninit_valid, Target::Fn, GenericRequirement::Exact(1); + AssertZeroValid, sym::assert_zero_valid, assert_zero_valid, Target::Fn, GenericRequirement::Exact(1); } pub enum GenericRequirement { diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 46184cddd51f5..16b101e06852a 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -3022,6 +3022,11 @@ impl fmt::Debug for Location { impl Location { pub const START: Location = Location { block: START_BLOCK, statement_index: 0 }; + /// Create a new location at the start of a basic block. + pub fn start_of_block(block: BasicBlock) -> Self { + Self { block, statement_index: 0 } + } + /// Returns the location immediately after this one within the enclosing block. /// /// Note that if this location represents a terminator, then the diff --git a/compiler/rustc_mir_transform/src/check_maybe_uninit.rs b/compiler/rustc_mir_transform/src/check_maybe_uninit.rs new file mode 100644 index 0000000000000..f89b4b900ce34 --- /dev/null +++ b/compiler/rustc_mir_transform/src/check_maybe_uninit.rs @@ -0,0 +1,164 @@ +//! This pass inserts the same validity checks into `MaybeUninit::{uninit,zeroed}().assert_init()` +//! as in `mem::{uninitialized,zeroed}`. +//! +//! Note that this module uses `uninit` to mean `uninit` or `zeroed` unless `zeroed` is used explicitly. +//! +//! It does this by first finding a call to `MaybeUninit::uninit`, and then figuring out +//! whether the successor basic block is a call to `MaybeUninit::assume_init` on the same local. + +use rustc_const_eval::interpret; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::patch::MirPatch; +use rustc_middle::mir::{ + BasicBlock, BasicBlockData, Body, Constant, ConstantKind, Operand, Place, SourceInfo, + Terminator, TerminatorKind, +}; +use rustc_middle::ty::{self, List, SubstsRef, TyCtxt}; +use rustc_span::{sym, Span}; + +use crate::MirPass; + +pub struct CheckMaybeUninit; + +impl<'tcx> MirPass<'tcx> for CheckMaybeUninit { + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let mut patch = MirPatch::new(body); + + for (mu_uninit_bb, _) in body.basic_blocks.iter_enumerated() { + let terminator = body.basic_blocks[mu_uninit_bb].terminator(); + + let TerminatorKind::Call { + func: mu_uninit_func, + target: assume_init_bb, + destination: uninit_place, + .. + } = &terminator.kind else { + continue; + }; + + let Some((mu_method_def_id, substs)) = mu_uninit_func.const_fn_def() else { + continue; + }; + + let Some(assume_init_bb) = assume_init_bb else { + continue; + }; + + let Some((assume_init_operand, assume_init_call_span)) = is_block_just_assume_init(tcx, &body.basic_blocks[*assume_init_bb]) else { + continue; + }; + + let Some(assume_init_place) = assume_init_operand.place() else { + continue; + }; + + if assume_init_place != *uninit_place { + // The calls here are a little sketchy, but the place that is assumed to be init is not the place that was just crated + // as uninit, so we conservatively bail out. + continue; + } + + // Select the right assertion intrinsic to call depending on which MaybeUninit method we called + let Some(init_check_def_id) = get_init_check_def_id(tcx, mu_method_def_id) else { + continue; + }; + + let assert_valid_bb = make_assert_valid_bb( + &mut patch, + tcx, + assume_init_call_span, + init_check_def_id, + *assume_init_bb, + substs, + ); + + let mut new_uninit_terminator = terminator.kind.clone(); + match new_uninit_terminator { + TerminatorKind::Call { ref mut target, .. } => { + *target = Some(assert_valid_bb); + } + _ => unreachable!("terminator must be TerminatorKind::Call as checked above"), + } + + patch.patch_terminator(mu_uninit_bb, new_uninit_terminator); + } + + patch.apply(body); + } +} + +fn is_block_just_assume_init<'tcx, 'blk>( + tcx: TyCtxt<'tcx>, + block: &'blk BasicBlockData<'tcx>, +) -> Option<(&'blk Operand<'tcx>, Span)> { + if block.statements.is_empty() + && let TerminatorKind::Call { + func, + args, + fn_span, + .. + } = &block.terminator().kind + && let Some((def_id, _)) = func.const_fn_def() + && tcx.is_diagnostic_item(sym::assume_init, def_id) + { + args.get(0).map(|operand| (operand, *fn_span)) + } else { + None + } +} + +fn get_init_check_def_id(tcx: TyCtxt<'_>, mu_method_def_id: DefId) -> Option { + if tcx.is_diagnostic_item(sym::maybe_uninit_uninit, mu_method_def_id) { + tcx.lang_items().assert_uninit_valid() + } else if tcx.is_diagnostic_item(sym::maybe_uninit_zeroed, mu_method_def_id) { + tcx.lang_items().assert_zero_valid() + } else { + None + } +} + +fn make_assert_valid_bb<'tcx>( + patch: &mut MirPatch<'tcx>, + tcx: TyCtxt<'tcx>, + fn_span: Span, + init_check_def_id: DefId, + target_bb: BasicBlock, + substs: SubstsRef<'tcx>, +) -> BasicBlock { + let func = make_fn_operand_for_assert_valid(tcx, init_check_def_id, fn_span, substs); + + let local = patch.new_temp(tcx.types.unit, fn_span); + + let terminator = TerminatorKind::Call { + func, + args: vec![], + destination: Place { local, projection: List::empty() }, + target: Some(target_bb), + cleanup: Some(patch.resume_block()), + from_hir_call: true, + fn_span, + }; + + let terminator = Terminator { source_info: SourceInfo::outermost(fn_span), kind: terminator }; + + let bb_data = BasicBlockData::new(Some(terminator)); + + let block = patch.new_block(bb_data); + block +} + +fn make_fn_operand_for_assert_valid<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: DefId, + span: Span, + substs: SubstsRef<'tcx>, +) -> Operand<'tcx> { + let fn_ty = ty::FnDef(def_id, substs); + let fn_ty = tcx.mk_ty(fn_ty); + + Operand::Constant(Box::new(Constant { + span, + literal: ConstantKind::Val(interpret::ConstValue::ZeroSized, fn_ty), + user_ty: None, + })) +} diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 45cd4024c9f57..e2ae40d251cbd 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -48,6 +48,7 @@ mod add_call_guards; mod add_moves_for_packed_drops; mod add_retag; mod check_const_item_mutation; +mod check_maybe_uninit; mod check_packed_ref; pub mod check_unsafety; // This pass is public to allow external drivers to perform MIR cleanup @@ -300,6 +301,7 @@ fn mir_const(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) -> &Steal< &Lint(check_const_item_mutation::CheckConstItemMutation), &Lint(function_item_references::FunctionItemReferences), // What we need to do constant evaluation. + &check_maybe_uninit::CheckMaybeUninit, &simplify::SimplifyCfg::new("initial"), &rustc_peek::SanityCheck, // Just a lint ], @@ -497,6 +499,8 @@ fn run_analysis_to_runtime_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx> /// After this series of passes, no lifetime analysis based on borrowing can be done. fn run_analysis_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let passes: &[&dyn MirPass<'tcx>] = &[ + // FIXME: Preferably we'd run this before const eval once the stability of the wrapper function is figured out + &check_maybe_uninit::CheckMaybeUninit, &cleanup_post_borrowck::CleanupPostBorrowck, &remove_noop_landing_pads::RemoveNoopLandingPads, &simplify::SimplifyCfg::new("early-opt"), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 37d2aea42ad8f..97432644551e5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -383,6 +383,7 @@ symbols! { assert_mem_uninitialized_valid, assert_ne_macro, assert_receiver_is_total_eq, + assert_uninit_valid, assert_zero_valid, asserting, associated_const_equality, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index b1ed3b31e430c..945491929a5f9 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -964,6 +964,14 @@ extern "rust-intrinsic" { #[rustc_safe_intrinsic] pub fn assert_mem_uninitialized_valid(); + /// A guard for `std::mem::uninitialized`. This will statically either panic, or do nothing. + /// + /// This intrinsic does not have a stable counterpart. + #[rustc_const_unstable(feature = "const_assert_type2", issue = "none")] + #[rustc_safe_intrinsic] + #[cfg(bootstrap)] + pub fn assert_uninit_valid(); + /// Gets a reference to a static `Location` indicating where it was called. /// /// Note that, unlike most intrinsics, this is safe to call; @@ -2527,3 +2535,41 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { write_bytes(dst, val, count) } } + +// Wrappers around the init assertion intrinsics. Calls to these +// are inserted in the check_maybe_uninit mir pass + +#[unstable( + feature = "maybe_uninit_checks_internals", + issue = "none", + reason = "implementation detail of panics on invalid MaybeUninit usage" +)] +#[rustc_allow_const_fn_unstable(const_assert_type2)] +#[rustc_const_stable( + feature = "const_maybe_uninit_checks_internals", + since = "CURRENT_RUSTC_VERSION" +)] +#[cfg_attr(not(bootstrap), lang = "assert_zero_valid")] +#[track_caller] +pub const fn assert_zero_valid_wrapper() { + assert_zero_valid::(); +} + +#[unstable( + feature = "maybe_uninit_checks_internals", + issue = "none", + reason = "implementation detail of panics on invalid MaybeUninit usage" +)] +#[rustc_allow_const_fn_unstable(const_assert_type2)] +#[rustc_const_stable( + feature = "const_maybe_uninit_checks_internals", + since = "CURRENT_RUSTC_VERSION" +)] +#[cfg_attr(not(bootstrap), lang = "assert_uninit_valid")] +#[track_caller] +pub const fn assert_uninit_valid_wrapper() { + #[cfg(bootstrap)] + assert_uninit_valid::(); + #[cfg(not(bootstrap))] + assert_mem_uninitialized_valid::(); +} diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index dc0702c467a4e..519348d6c7160 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -103,6 +103,7 @@ #![feature(const_align_of_val_raw)] #![feature(const_alloc_layout)] #![feature(const_arguments_as_str)] +#![feature(const_assert_type2)] #![feature(const_array_into_iter_constructors)] #![feature(const_bigint_helper_methods)] #![feature(const_black_box)] diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs index 3f491836551dc..23f948850e46b 100644 --- a/library/core/src/mem/maybe_uninit.rs +++ b/library/core/src/mem/maybe_uninit.rs @@ -624,6 +624,10 @@ impl MaybeUninit { // This also means that `self` must be a `value` variant. unsafe { intrinsics::assert_inhabited::(); + + // When this function is called directly after >::uninit, we insert calls to + // `intrinsics::assert_zero_valid_wrapper` or `intrinsics::assert_uninit_valid_wrapper` respectively. + ManuallyDrop::into_inner(self.value) } } diff --git a/tests/mir-opt/check_maybe_uninit.main.CheckMaybeUninit.diff b/tests/mir-opt/check_maybe_uninit.main.CheckMaybeUninit.diff new file mode 100644 index 0000000000000..606ac88ff246c --- /dev/null +++ b/tests/mir-opt/check_maybe_uninit.main.CheckMaybeUninit.diff @@ -0,0 +1,90 @@ +- // MIR for `main` before CheckMaybeUninit ++ // MIR for `main` after CheckMaybeUninit + + | User Type Annotations + | 0: user_ty: Canonical { max_universe: U0, variables: [CanonicalVarInfo { kind: Ty(General(U0)) }], value: TypeOf(DefId(2:2022 ~ core[4f75]::mem::maybe_uninit::{impl#2}::uninit), UserSubsts { substs: [^0], user_self_ty: Some(UserSelfTy { impl_def_id: DefId(2:2019 ~ core[4f75]::mem::maybe_uninit::{impl#2}), self_ty: std::mem::MaybeUninit }) }) }, span: $DIR/check-maybe-uninit.rs:6:17: 6:42, inferred_ty: fn() -> std::mem::MaybeUninit {std::mem::MaybeUninit::::uninit} + | 1: user_ty: Canonical { max_universe: U0, variables: [CanonicalVarInfo { kind: Ty(General(U0)) }], value: TypeOf(DefId(2:2022 ~ core[4f75]::mem::maybe_uninit::{impl#2}::uninit), UserSubsts { substs: [^0], user_self_ty: Some(UserSelfTy { impl_def_id: DefId(2:2019 ~ core[4f75]::mem::maybe_uninit::{impl#2}), self_ty: std::mem::MaybeUninit }) }) }, span: $DIR/check-maybe-uninit.rs:7:17: 7:46, inferred_ty: fn() -> std::mem::MaybeUninit {std::mem::MaybeUninit::::uninit} + | + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/check-maybe-uninit.rs:+0:11: +0:11 + let mut _1: u8; // in scope 0 at $DIR/check-maybe-uninit.rs:+2:17: +2:58 + let mut _2: std::mem::MaybeUninit; // in scope 0 at $DIR/check-maybe-uninit.rs:+2:17: +2:44 + let mut _3: std::string::String; // in scope 0 at $DIR/check-maybe-uninit.rs:+3:17: +3:62 + let mut _4: std::mem::MaybeUninit; // in scope 0 at $DIR/check-maybe-uninit.rs:+3:17: +3:48 ++ let mut _5: (); // in scope 0 at $DIR/check-maybe-uninit.rs:+2:45: +2:58 ++ let mut _6: (); // in scope 0 at $DIR/check-maybe-uninit.rs:+3:49: +3:62 + scope 1 { + scope 2 { + scope 3 { + } + } + } + + bb0: { + StorageLive(_1); // scope 1 at $DIR/check-maybe-uninit.rs:+2:17: +2:58 + StorageLive(_2); // scope 1 at $DIR/check-maybe-uninit.rs:+2:17: +2:44 +- _2 = MaybeUninit::::uninit() -> [return: bb1, unwind: bb6]; // scope 1 at $DIR/check-maybe-uninit.rs:+2:17: +2:44 ++ _2 = MaybeUninit::::uninit() -> [return: bb7, unwind: bb6]; // scope 1 at $DIR/check-maybe-uninit.rs:+2:17: +2:44 + // mir::Constant + // + span: $DIR/check-maybe-uninit.rs:6:17: 6:42 + // + user_ty: UserType(0) + // + literal: Const { ty: fn() -> MaybeUninit {MaybeUninit::::uninit}, val: Value() } + } + + bb1: { + _1 = MaybeUninit::::assume_init(move _2) -> [return: bb2, unwind: bb6]; // scope 1 at $DIR/check-maybe-uninit.rs:+2:17: +2:58 + // mir::Constant + // + span: $DIR/check-maybe-uninit.rs:6:45: 6:56 + // + literal: Const { ty: unsafe fn(MaybeUninit) -> u8 {MaybeUninit::::assume_init}, val: Value() } + } + + bb2: { + StorageDead(_2); // scope 1 at $DIR/check-maybe-uninit.rs:+2:57: +2:58 + StorageDead(_1); // scope 1 at $DIR/check-maybe-uninit.rs:+2:58: +2:59 + StorageLive(_3); // scope 2 at $DIR/check-maybe-uninit.rs:+3:17: +3:62 + StorageLive(_4); // scope 2 at $DIR/check-maybe-uninit.rs:+3:17: +3:48 +- _4 = MaybeUninit::::uninit() -> [return: bb3, unwind: bb6]; // scope 2 at $DIR/check-maybe-uninit.rs:+3:17: +3:48 ++ _4 = MaybeUninit::::uninit() -> [return: bb8, unwind: bb6]; // scope 2 at $DIR/check-maybe-uninit.rs:+3:17: +3:48 + // mir::Constant + // + span: $DIR/check-maybe-uninit.rs:7:17: 7:46 + // + user_ty: UserType(1) + // + literal: Const { ty: fn() -> MaybeUninit {MaybeUninit::::uninit}, val: Value() } + } + + bb3: { + _3 = MaybeUninit::::assume_init(move _4) -> [return: bb4, unwind: bb6]; // scope 2 at $DIR/check-maybe-uninit.rs:+3:17: +3:62 + // mir::Constant + // + span: $DIR/check-maybe-uninit.rs:7:49: 7:60 + // + literal: Const { ty: unsafe fn(MaybeUninit) -> String {MaybeUninit::::assume_init}, val: Value() } + } + + bb4: { + StorageDead(_4); // scope 2 at $DIR/check-maybe-uninit.rs:+3:61: +3:62 + drop(_3) -> [return: bb5, unwind: bb6]; // scope 2 at $DIR/check-maybe-uninit.rs:+3:62: +3:63 + } + + bb5: { + StorageDead(_3); // scope 2 at $DIR/check-maybe-uninit.rs:+3:62: +3:63 + _0 = const (); // scope 1 at $DIR/check-maybe-uninit.rs:+1:5: +4:6 + return; // scope 0 at $DIR/check-maybe-uninit.rs:+5:2: +5:2 + } + + bb6 (cleanup): { + resume; // scope 0 at $DIR/check-maybe-uninit.rs:+0:1: +5:2 ++ } ++ ++ bb7: { ++ _5 = : fn() {assert_uninit_valid_wrapper::}() -> [return: bb1, unwind: bb6]; // scope 0 at $DIR/check-maybe-uninit.rs:+2:45: +2:58 ++ // mir::Constant ++ // + span: $DIR/check-maybe-uninit.rs:6:45: 6:58 ++ // + literal: Const { ty: fn() {assert_uninit_valid_wrapper::}, val: Value(ValTree::Branch(..)) } ++ } ++ ++ bb8: { ++ _6 = : fn() {assert_uninit_valid_wrapper::}() -> [return: bb3, unwind: bb6]; // scope 0 at $DIR/check-maybe-uninit.rs:+3:49: +3:62 ++ // mir::Constant ++ // + span: $DIR/check-maybe-uninit.rs:7:49: 7:62 ++ // + literal: Const { ty: fn() {assert_uninit_valid_wrapper::}, val: Value(ValTree::Branch(..)) } + } + } + diff --git a/tests/mir-opt/check_maybe_uninit.rs b/tests/mir-opt/check_maybe_uninit.rs new file mode 100644 index 0000000000000..4c11dcb49eddb --- /dev/null +++ b/tests/mir-opt/check_maybe_uninit.rs @@ -0,0 +1,9 @@ +use std::mem::MaybeUninit; + +// EMIT_MIR check_maybe_uninit.main.CheckMaybeUninit.diff +fn main() { + unsafe { + let _ = MaybeUninit::::uninit().assume_init(); + let _ = MaybeUninit::::uninit().assume_init(); + } +} diff --git a/tests/ui/consts/assert-type-intrinsics.stderr b/tests/ui/consts/assert-type-intrinsics.stderr index 70aec91e22622..242ae89306ada 100644 --- a/tests/ui/consts/assert-type-intrinsics.stderr +++ b/tests/ui/consts/assert-type-intrinsics.stderr @@ -1,21 +1,75 @@ -error[E0080]: evaluation of constant value failed - --> $DIR/assert-type-intrinsics.rs:12:9 +error: any use of this value will cause an error + --> $DIR/assert-type-intrinsics.rs:14:36 | +LL | const _BAD1: () = unsafe { + | --------------- LL | MaybeUninit::::uninit().assume_init(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` + | ^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 -error[E0080]: evaluation of constant value failed - --> $DIR/assert-type-intrinsics.rs:16:9 +error: any use of this value will cause an error + --> $DIR/assert-type-intrinsics.rs:17:9 + | +LL | const _BAD2: () = { + | --------------- +LL | intrinsics::assert_uninit_valid::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid | -LL | intrinsics::assert_mem_uninitialized_valid::<&'static i32>(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `&i32` uninitialized, which is invalid + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 -error[E0080]: evaluation of constant value failed +error: any use of this value will cause an error --> $DIR/assert-type-intrinsics.rs:20:9 | +LL | const _BAD3: () = { + | --------------- LL | intrinsics::assert_zero_valid::<&'static i32>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `&i32`, which is invalid + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0080`. +Future incompatibility report: Future breakage diagnostic: +error: any use of this value will cause an error + --> $DIR/assert-type-intrinsics.rs:14:36 + | +LL | const _BAD1: () = unsafe { + | --------------- +LL | MaybeUninit::::uninit().assume_init(); + | ^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + +Future breakage diagnostic: +error: any use of this value will cause an error + --> $DIR/assert-type-intrinsics.rs:17:9 + | +LL | const _BAD2: () = { + | --------------- +LL | intrinsics::assert_uninit_valid::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + +Future breakage diagnostic: +error: any use of this value will cause an error + --> $DIR/assert-type-intrinsics.rs:20:9 + | +LL | const _BAD3: () = { + | --------------- +LL | intrinsics::assert_zero_valid::<&'static i32>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `&i32`, which is invalid + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + diff --git a/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.rs b/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.rs new file mode 100644 index 0000000000000..74e50ae48913c --- /dev/null +++ b/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.rs @@ -0,0 +1,15 @@ +use std::mem::MaybeUninit; + +// Make sure it panics during const eval as well +const X: () = { + unsafe { MaybeUninit::<&'static ()>::uninit().assume_init(); } //~ WARN: the type `&()` does not permit being left uninitialized + unsafe { MaybeUninit::::uninit().assume_init(); } //~ WARN: the type `bool` does not permit being left uninitialized + unsafe { MaybeUninit::::uninit().assume_init(); } //~ WARN: the type `char` does not permit being left uninitialized + unsafe { MaybeUninit::::uninit().assume_init(); } //~ WARN: the type `u8` does not permit being left uninitialized +}; +//~^^^^^ ERROR: any use of this value will cause an error +//~| WARN: this was previously accepted by the compiler but is being phased out + +fn main() { + println!("{X:?}"); +} diff --git a/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.stderr b/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.stderr new file mode 100644 index 0000000000000..6ac6f5abcb6d3 --- /dev/null +++ b/tests/ui/intrinsics/maybe_uninit_checks_consts_invalid.stderr @@ -0,0 +1,72 @@ +error: any use of this value will cause an error + --> $DIR/maybe_uninit_checks_consts_invalid.rs:5:51 + | +LL | const X: () = { + | ----------- +LL | unsafe { MaybeUninit::<&'static ()>::uninit().assume_init(); } + | ^^^^^^^^^^^^^ aborted execution: attempted to leave type `&()` uninitialized, which is invalid + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + +warning: the type `&()` does not permit being left uninitialized + --> $DIR/maybe_uninit_checks_consts_invalid.rs:5:14 + | +LL | unsafe { MaybeUninit::<&'static ()>::uninit().assume_init(); } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: `#[warn(invalid_value)]` on by default + = note: references must be non-null + +warning: the type `bool` does not permit being left uninitialized + --> $DIR/maybe_uninit_checks_consts_invalid.rs:6:14 + | +LL | unsafe { MaybeUninit::::uninit().assume_init(); } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | 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` + +warning: the type `char` does not permit being left uninitialized + --> $DIR/maybe_uninit_checks_consts_invalid.rs:7:14 + | +LL | unsafe { MaybeUninit::::uninit().assume_init(); } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: characters must be a valid Unicode codepoint + +warning: the type `u8` does not permit being left uninitialized + --> $DIR/maybe_uninit_checks_consts_invalid.rs:8:14 + | +LL | unsafe { MaybeUninit::::uninit().assume_init(); } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: integers must not be uninitialized + +error: aborting due to previous error; 4 warnings emitted + +Future incompatibility report: Future breakage diagnostic: +error: any use of this value will cause an error + --> $DIR/maybe_uninit_checks_consts_invalid.rs:5:51 + | +LL | const X: () = { + | ----------- +LL | unsafe { MaybeUninit::<&'static ()>::uninit().assume_init(); } + | ^^^^^^^^^^^^^ aborted execution: attempted to leave type `&()` uninitialized, which is invalid + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + diff --git a/tests/ui/intrinsics/maybe_uninit_checks_consts_valid.rs b/tests/ui/intrinsics/maybe_uninit_checks_consts_valid.rs new file mode 100644 index 0000000000000..9680a59f2fd2f --- /dev/null +++ b/tests/ui/intrinsics/maybe_uninit_checks_consts_valid.rs @@ -0,0 +1,13 @@ +// run-pass + +use std::mem::MaybeUninit; + +// This test makes sure MU is still usable in consts +const X: () = { + unsafe { MaybeUninit::>::uninit().assume_init(); } + unsafe { MaybeUninit::<()>::uninit().assume_init(); } +}; + +fn main() { + println!("{X:?}"); +} diff --git a/tests/ui/intrinsics/panic-uninitialized-zeroed.rs b/tests/ui/intrinsics/panic-uninitialized-zeroed.rs index 1a0104b859e53..ddc39d33d9607 100644 --- a/tests/ui/intrinsics/panic-uninitialized-zeroed.rs +++ b/tests/ui/intrinsics/panic-uninitialized-zeroed.rs @@ -1,19 +1,20 @@ // run-pass -// revisions: default strict +// needs-unwind +// ignore-wasm32-bare compiled with panic=abort by default +// revisions: mir thir strict +// [thir]compile-flags: -Zthir-unsafeck // [strict]compile-flags: -Zstrict-init-checks // ignore-tidy-linelength -// ignore-emscripten spawning processes is not supported -// ignore-sgx no processes -// This test checks panic emitted from `mem::{uninitialized,zeroed}`. +// This test checks panic emitted from `mem::{uninitialized,zeroed}` and `MaybeUninit::{uninit,zeroed}::assume_init`. -#![feature(never_type)] +#![feature(never_type, arbitrary_enum_discriminant)] #![allow(deprecated, invalid_value)] use std::{ - mem::{self, MaybeUninit, ManuallyDrop}, + mem::{uninitialized as mem_uninit, zeroed as mem_zeroed, ManuallyDrop, MaybeUninit}, + num, panic, ptr::NonNull, - num, }; #[allow(dead_code)] @@ -25,7 +26,9 @@ struct Foo { enum Bar {} #[allow(dead_code)] -enum OneVariant { Variant(i32) } +enum OneVariant { + Variant(i32), +} #[allow(dead_code, non_camel_case_types)] enum OneVariant_NonZero { @@ -33,12 +36,6 @@ enum OneVariant_NonZero { DeadVariant(Bar), } -#[allow(dead_code, non_camel_case_types)] -enum OneVariant_Ref { - Variant(&'static i32), - DeadVariant(Bar), -} - // An `Aggregate` abi enum where 0 is not a valid discriminant. #[allow(dead_code)] #[repr(i32)] @@ -68,330 +65,221 @@ enum ZeroIsValid { One(NonNull<()>) = 1, } -#[track_caller] -fn test_panic_msg T) + 'static>(op: F, msg: &str) { - use std::{panic, env, process}; - - // The tricky part is that we can't just run `op`, as that would *abort* the process. - // So instead, we reinvoke this process with the caller location as argument. - // For the purpose of this test, the line number is unique enough. - // If we are running in such a re-invocation, we skip all the tests *except* for the one with that type name. - let our_loc = panic::Location::caller().line().to_string(); - let mut args = env::args(); - let this = args.next().unwrap(); - if let Some(loc) = args.next() { - if loc == our_loc { - op(); - panic!("we did not abort"); - } else { - // Nothing, we are running another test. +fn test_panic_msg(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) { + let err = panic::catch_unwind(op).err(); + assert_eq!(err.as_ref().and_then(|a| a.downcast_ref::<&str>()), Some(&msg)); +} + +macro_rules! test_zero_uninit { + ($zero_fn:ident, $uninit_fn:ident) => { + unsafe { + // Uninhabited types + test_panic_msg( + || $uninit_fn::(), + "attempted to instantiate uninhabited type `!`" + ); + test_panic_msg( + || $zero_fn::(), + "attempted to instantiate uninhabited type `!`" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to instantiate uninhabited type `Foo`" + ); + test_panic_msg( + || $zero_fn::(), + "attempted to instantiate uninhabited type `Foo`" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to instantiate uninhabited type `Bar`" + ); + test_panic_msg( + || $zero_fn::(), + "attempted to instantiate uninhabited type `Bar`" + ); + + test_panic_msg( + || $uninit_fn::<[Foo; 2]>(), + "attempted to instantiate uninhabited type `[Foo; 2]`" + ); + test_panic_msg( + || $zero_fn::<[Foo; 2]>(), + "attempted to instantiate uninhabited type `[Foo; 2]`" + ); + + test_panic_msg( + || $uninit_fn::<[Bar; 2]>(), + "attempted to instantiate uninhabited type `[Bar; 2]`" + ); + test_panic_msg( + || $zero_fn::<[Bar; 2]>(), + "attempted to instantiate uninhabited type `[Bar; 2]`" + ); + + // Types that do not like zero-initialziation + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `fn()` uninitialized, which is invalid" + ); + test_panic_msg( + || $zero_fn::(), + "attempted to zero-initialize type `fn()`, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::<*const dyn Send>(), + "attempted to leave type `*const dyn core::marker::Send` uninitialized, which is invalid" + ); + test_panic_msg( + || $zero_fn::<*const dyn Send>(), + "attempted to zero-initialize type `*const dyn core::marker::Send`, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::<(NonNull, u32, u32)>(), + "attempted to leave type `(core::ptr::non_null::NonNull, u32, u32)` uninitialized, \ + which is invalid" + ); + + test_panic_msg( + || $zero_fn::<(NonNull, u32, u32)>(), + "attempted to zero-initialize type `(core::ptr::non_null::NonNull, u32, u32)`, \ + which is invalid" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `OneVariant_NonZero` uninitialized, \ + which is invalid" + ); + test_panic_msg( + || $zero_fn::(), + "attempted to zero-initialize type `OneVariant_NonZero`, \ + which is invalid" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `LR_NonZero` uninitialized, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::>(), + "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, \ + which is invalid" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `NoNullVariant` uninitialized, \ + which is invalid" + ); + + test_panic_msg( + || $zero_fn::(), + "attempted to zero-initialize type `NoNullVariant`, \ + which is invalid" + ); + + // Types that can be zero, but not uninit. + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `bool` uninitialized, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `LR` uninitialized, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::>(), + "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, which is invalid" + ); + + // Some things that should work. + let _val = $zero_fn::(); + let _val = $zero_fn::(); + let _val = $zero_fn::>(); + let _val = $zero_fn::(); + let _val = $zero_fn::>(); + let _val = $zero_fn::>>(); + let _val = $zero_fn::<[!; 0]>(); + let _val = $zero_fn::(); + let _val = $uninit_fn::>(); + let _val = $uninit_fn::<[!; 0]>(); + let _val = $uninit_fn::<()>(); + let _val = $uninit_fn::(); + + if cfg!(strict) { + test_panic_msg( + || $uninit_fn::(), + "attempted to leave type `i32` uninitialized, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::<*const ()>(), + "attempted to leave type `*const ()` uninitialized, which is invalid" + ); + + test_panic_msg( + || $uninit_fn::<[i32; 1]>(), + "attempted to leave type `[i32; 1]` uninitialized, which is invalid" + ); + + test_panic_msg( + || $zero_fn::>(), + "attempted to zero-initialize type `core::ptr::non_null::NonNull<()>`, which is invalid" + ); + + test_panic_msg( + || $zero_fn::<[NonNull<()>; 1]>(), + "attempted to zero-initialize type `[core::ptr::non_null::NonNull<()>; 1]`, which is invalid" + ); + + // FIXME(#66151) we conservatively do not error here yet (by default). + test_panic_msg( + || $zero_fn::(), + "attempted to zero-initialize type `LR_NonZero`, which is invalid" + ); + + test_panic_msg( + || $zero_fn::>(), + "attempted to zero-initialize type `core::mem::manually_drop::ManuallyDrop`, \ + which is invalid" + ); + } else { + // These are UB because they have not been officially blessed, but we await the resolution + // of before doing + // anything about that. + let _val = $uninit_fn::(); + let _val = $uninit_fn::<*const ()>(); + + // These are UB, but best to test them to ensure we don't become unintentionally + // stricter. + + // It's currently unchecked to create invalid enums and values inside arrays. + let _val = $zero_fn::(); + let _val = $zero_fn::<[LR_NonZero; 1]>(); + let _val = $zero_fn::<[NonNull<()>; 1]>(); + let _val = $uninit_fn::<[NonNull<()>; 1]>(); + } } - } else { - // Invoke new process for actual test, and check result. - let mut cmd = process::Command::new(this); - cmd.arg(our_loc); - let res = cmd.output().unwrap(); - assert!(!res.status.success(), "test did not fail"); - let stderr = String::from_utf8_lossy(&res.stderr); - assert!(stderr.contains(msg), "test did not contain expected output: looking for {:?}, output:\n{}", msg, stderr); } } -#[track_caller] -fn test_panic_msg_only_if_strict(op: impl (FnOnce() -> T) + 'static, msg: &str) { - if !cfg!(strict) { - // Just run it. - op(); - } else { - test_panic_msg(op, msg); - } +unsafe fn maybe_uninit_uninit() -> T { + MaybeUninit::uninit().assume_init() +} + +unsafe fn maybe_uninit_zeroed() -> T { + MaybeUninit::zeroed().assume_init() } fn main() { - unsafe { - // Uninhabited types - test_panic_msg( - || mem::uninitialized::(), - "attempted to instantiate uninhabited type `!`" - ); - test_panic_msg( - || mem::zeroed::(), - "attempted to instantiate uninhabited type `!`" - ); - test_panic_msg( - || MaybeUninit::::uninit().assume_init(), - "attempted to instantiate uninhabited type `!`" - ); - - test_panic_msg( - || mem::uninitialized::(), - "attempted to instantiate uninhabited type `Foo`" - ); - test_panic_msg( - || mem::zeroed::(), - "attempted to instantiate uninhabited type `Foo`" - ); - test_panic_msg( - || MaybeUninit::::uninit().assume_init(), - "attempted to instantiate uninhabited type `Foo`" - ); - - test_panic_msg( - || mem::uninitialized::(), - "attempted to instantiate uninhabited type `Bar`" - ); - test_panic_msg( - || mem::zeroed::(), - "attempted to instantiate uninhabited type `Bar`" - ); - test_panic_msg( - || MaybeUninit::::uninit().assume_init(), - "attempted to instantiate uninhabited type `Bar`" - ); - - test_panic_msg( - || mem::uninitialized::<[Foo; 2]>(), - "attempted to instantiate uninhabited type `[Foo; 2]`" - ); - test_panic_msg( - || mem::zeroed::<[Foo; 2]>(), - "attempted to instantiate uninhabited type `[Foo; 2]`" - ); - test_panic_msg( - || MaybeUninit::<[Foo; 2]>::uninit().assume_init(), - "attempted to instantiate uninhabited type `[Foo; 2]`" - ); - - test_panic_msg( - || mem::uninitialized::<[Bar; 2]>(), - "attempted to instantiate uninhabited type `[Bar; 2]`" - ); - test_panic_msg( - || mem::zeroed::<[Bar; 2]>(), - "attempted to instantiate uninhabited type `[Bar; 2]`" - ); - test_panic_msg( - || MaybeUninit::<[Bar; 2]>::uninit().assume_init(), - "attempted to instantiate uninhabited type `[Bar; 2]`" - ); - - // Types that don't allow either. - test_panic_msg( - || mem::zeroed::<&i32>(), - "attempted to zero-initialize type `&i32`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::<&i32>(), - "attempted to leave type `&i32` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::>(), - "attempted to zero-initialize type `alloc::boxed::Box<[i32; 0]>`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::>(), - "attempted to leave type `alloc::boxed::Box<[i32; 0]>` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::>(), - "attempted to zero-initialize type `alloc::boxed::Box`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::>(), - "attempted to leave type `alloc::boxed::Box` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&[i32]>(), - "attempted to zero-initialize type `&[i32]`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::<&[i32]>(), - "attempted to leave type `&[i32]` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&(u8, [u8])>(), - "attempted to zero-initialize type `&(u8, [u8])`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::<&(u8, [u8])>(), - "attempted to leave type `&(u8, [u8])` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&dyn Send>(), - "attempted to zero-initialize type `&dyn core::marker::Send`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::<&dyn Send>(), - "attempted to leave type `&dyn core::marker::Send` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<*const dyn Send>(), - "attempted to zero-initialize type `*const dyn core::marker::Send`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::<*const dyn Send>(), - "attempted to leave type `*const dyn core::marker::Send` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `NoNullVariant` uninitialized, \ - which is invalid" - ); - test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `NoNullVariant`, \ - which is invalid" - ); - - test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `OneVariant_Ref`, \ - which is invalid" - ); - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `OneVariant_Ref` uninitialized, which is invalid" - ); - - // Types where both are invalid, but we allow uninit since the 0x01-filling is not LLVM UB. - test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `fn()`, which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::(), - "attempted to leave type `fn()` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&()>(), - "attempted to zero-initialize type `&()`, which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::<&()>(), - "attempted to leave type `&()` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&[u8]>(), - "attempted to zero-initialize type `&[u8]`, which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::<&[u8]>(), - "attempted to leave type `&[u8]` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<&str>(), - "attempted to zero-initialize type `&str`, which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::<&str>(), - "attempted to leave type `&str` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<(NonNull, u32, u32)>(), - "attempted to zero-initialize type `(core::ptr::non_null::NonNull, u32, u32)`, \ - which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::<(NonNull, u32, u32)>(), - "attempted to leave type `(core::ptr::non_null::NonNull, u32, u32)` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `OneVariant_NonZero`, \ - which is invalid" - ); - test_panic_msg_only_if_strict( - || mem::uninitialized::(), - "attempted to leave type `OneVariant_NonZero` uninitialized, which is invalid" - ); - - // Types where both are invalid but we allow the zeroed form since it is not LLVM UB. - test_panic_msg_only_if_strict( - || mem::zeroed::(), - "attempted to zero-initialize type `LR_NonZero`, which is invalid" - ); - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `LR_NonZero` uninitialized, which is invalid" - ); - - test_panic_msg_only_if_strict( - || mem::zeroed::>(), - "attempted to zero-initialize type `core::mem::manually_drop::ManuallyDrop`, \ - which is invalid" - ); - test_panic_msg( - || mem::uninitialized::>(), - "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, \ - which is invalid" - ); - - // Some strict-only things - test_panic_msg_only_if_strict( - || mem::uninitialized::(), - "attempted to leave type `i32` uninitialized, which is invalid" - ); - - test_panic_msg_only_if_strict( - || mem::uninitialized::<*const ()>(), - "attempted to leave type `*const ()` uninitialized, which is invalid" - ); - - test_panic_msg_only_if_strict( - || mem::uninitialized::<[i32; 1]>(), - "attempted to leave type `[i32; 1]` uninitialized, which is invalid" - ); - - test_panic_msg_only_if_strict( - || mem::zeroed::<[NonNull<()>; 1]>(), - "attempted to zero-initialize type `[core::ptr::non_null::NonNull<()>; 1]`, which is invalid" - ); - - // Types that can be zero, but not uninit (though some are mitigated). - let _val = mem::zeroed::(); - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `LR` uninitialized, which is invalid" - ); - - let _val = mem::zeroed::>(); - test_panic_msg( - || mem::uninitialized::>(), - "attempted to leave type `core::mem::manually_drop::ManuallyDrop` uninitialized, which is invalid" - ); - - let _val = mem::zeroed::(); - test_panic_msg_only_if_strict( - || mem::uninitialized::(), - "attempted to leave type `bool` uninitialized, which is invalid" - ); - - let _val = mem::zeroed::(); - test_panic_msg_only_if_strict( - || mem::uninitialized::(), - "attempted to leave type `OneVariant` uninitialized, which is invalid" - ); - - // Some things that are actually allowed. - let _val = mem::zeroed::>(); - let _val = mem::zeroed::>>(); - let _val = mem::zeroed::<[!; 0]>(); - let _val = mem::zeroed::(); - let _val = mem::uninitialized::>(); - let _val = mem::uninitialized::<[!; 0]>(); - let _val = mem::uninitialized::<()>(); - let _val = mem::uninitialized::(); - } + test_zero_uninit!(mem_zeroed, mem_uninit); + test_zero_uninit!(maybe_uninit_zeroed, maybe_uninit_uninit); }