From a0131f0a369dcb44c855d0ce483744e6efaa3df8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Sep 2022 11:33:16 +0200 Subject: [PATCH] change might_permit_raw_init to fully detect LLVM UB, but not more than that --- compiler/rustc_const_eval/src/lib.rs | 6 +- .../src/might_permit_raw_init.rs | 44 --- .../src/util/might_permit_raw_init.rs | 151 +++++++++++ compiler/rustc_const_eval/src/util/mod.rs | 2 + compiler/rustc_target/src/abi/mod.rs | 70 +---- src/test/ui/consts/assert-type-intrinsics.rs | 9 +- .../ui/consts/assert-type-intrinsics.stderr | 24 +- .../intrinsics/panic-uninitialized-zeroed.rs | 250 ++++++++++++------ src/test/ui/lint/invalid_value.rs | 5 +- src/test/ui/lint/invalid_value.stderr | 72 +++-- 10 files changed, 393 insertions(+), 240 deletions(-) delete mode 100644 compiler/rustc_const_eval/src/might_permit_raw_init.rs create mode 100644 compiler/rustc_const_eval/src/util/might_permit_raw_init.rs diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index ebdaf61e43905..230f841cf4d57 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -32,7 +32,6 @@ extern crate rustc_middle; pub mod const_eval; mod errors; pub mod interpret; -mod might_permit_raw_init; pub mod transform; pub mod util; @@ -61,7 +60,6 @@ pub fn provide(providers: &mut Providers) { const_eval::deref_mir_constant(tcx, param_env, value) }; providers.permits_uninit_init = - |tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Uninit); - providers.permits_zero_init = - |tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Zero); + |tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::UninitMitigated0x01Fill); + providers.permits_zero_init = |tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::Zero); } diff --git a/compiler/rustc_const_eval/src/might_permit_raw_init.rs b/compiler/rustc_const_eval/src/might_permit_raw_init.rs deleted file mode 100644 index 37ffa19ccd6b9..0000000000000 --- a/compiler/rustc_const_eval/src/might_permit_raw_init.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::const_eval::CompileTimeInterpreter; -use crate::interpret::{InterpCx, MemoryKind, OpTy}; -use rustc_middle::ty::layout::LayoutCx; -use rustc_middle::ty::{layout::TyAndLayout, ParamEnv, TyCtxt}; -use rustc_session::Limit; -use rustc_target::abi::InitKind; - -pub fn might_permit_raw_init<'tcx>( - tcx: TyCtxt<'tcx>, - ty: TyAndLayout<'tcx>, - kind: InitKind, -) -> bool { - let strict = tcx.sess.opts.unstable_opts.strict_init_checks; - - if strict { - let machine = CompileTimeInterpreter::new( - Limit::new(0), - /*can_access_statics:*/ false, - /*check_alignment:*/ true, - ); - - let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); - - let allocated = cx - .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) - .expect("OOM: failed to allocate for uninit check"); - - if kind == InitKind::Zero { - cx.write_bytes_ptr( - allocated.ptr, - std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()), - ) - .expect("failed to write bytes for zero valid check"); - } - - let ot: OpTy<'_, _> = allocated.into(); - - // Assume that if it failed, it's a validation failure. - cx.validate_operand(&ot).is_ok() - } else { - let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() }; - ty.might_permit_raw_init(&layout_cx, kind) - } -} diff --git a/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs b/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs new file mode 100644 index 0000000000000..6ca71223391d2 --- /dev/null +++ b/compiler/rustc_const_eval/src/util/might_permit_raw_init.rs @@ -0,0 +1,151 @@ +use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout}; +use rustc_middle::ty::{ParamEnv, TyCtxt}; +use rustc_session::Limit; +use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants}; + +use crate::const_eval::CompileTimeInterpreter; +use crate::interpret::{InterpCx, MemoryKind, OpTy}; + +/// Determines if this type permits "raw" initialization by just transmuting some memory into an +/// instance of `T`. +/// +/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume +/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing +/// LLVM UB. +/// +/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we +/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting +/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to +/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and +/// to the full uninit check). +pub fn might_permit_raw_init<'tcx>( + tcx: TyCtxt<'tcx>, + ty: TyAndLayout<'tcx>, + kind: InitKind, +) -> bool { + if tcx.sess.opts.unstable_opts.strict_init_checks { + might_permit_raw_init_strict(ty, tcx, kind) + } else { + let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() }; + might_permit_raw_init_lax(ty, &layout_cx, kind) + } +} + +/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for +/// details. +fn might_permit_raw_init_strict<'tcx>( + ty: TyAndLayout<'tcx>, + tcx: TyCtxt<'tcx>, + kind: InitKind, +) -> bool { + let machine = CompileTimeInterpreter::new( + Limit::new(0), + /*can_access_statics:*/ false, + /*check_alignment:*/ true, + ); + + let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); + + let allocated = cx + .allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap)) + .expect("OOM: failed to allocate for uninit check"); + + if kind == InitKind::Zero { + cx.write_bytes_ptr( + allocated.ptr, + std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()), + ) + .expect("failed to write bytes for zero valid check"); + } + + let ot: OpTy<'_, _> = allocated.into(); + + // Assume that if it failed, it's a validation failure. + // This does *not* actually check that references are dereferenceable, but since all types that + // require dereferenceability also require non-null, we don't actually get any false negatives + // due to this. + cx.validate_operand(&ot).is_ok() +} + +/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for +/// details. +fn might_permit_raw_init_lax<'tcx>( + this: TyAndLayout<'tcx>, + cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, + init_kind: InitKind, +) -> bool { + let scalar_allows_raw_init = move |s: Scalar| -> bool { + match init_kind { + InitKind::Zero => { + // The range must contain 0. + s.valid_range(cx).contains(0) + } + InitKind::UninitMitigated0x01Fill => { + // The range must include an 0x01-filled buffer. + let mut val: u128 = 0x01; + for _ in 1..s.size(cx).bytes() { + // For sizes >1, repeat the 0x01. + val = (val << 8) | 0x01; + } + s.valid_range(cx).contains(val) + } + } + }; + + // Check the ABI. + let valid = match this.abi { + Abi::Uninhabited => false, // definitely UB + Abi::Scalar(s) => scalar_allows_raw_init(s), + Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2), + Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s), + Abi::Aggregate { .. } => true, // Fields are checked below. + }; + if !valid { + // This is definitely not okay. + return false; + } + + // Special magic check for references and boxes (i.e., special pointer types). + if let Some(pointee) = this.ty.builtin_deref(false) { + let pointee = cx.layout_of(pointee.ty).expect("need to be able to compute layouts"); + // We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied. + if pointee.align.abi.bytes() > 1 { + // 0x01-filling is not aligned. + return false; + } + if pointee.size.bytes() > 0 { + // A 'fake' integer pointer is not sufficiently dereferenceable. + return false; + } + } + + // If we have not found an error yet, we need to recursively descend into fields. + match &this.fields { + FieldsShape::Primitive | FieldsShape::Union { .. } => {} + FieldsShape::Array { .. } => { + // Arrays never have scalar layout in LLVM, so if the array is not actually + // accessed, there is no LLVM UB -- therefore we can skip this. + } + FieldsShape::Arbitrary { offsets, .. } => { + for idx in 0..offsets.len() { + if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind) { + // We found a field that is unhappy with this kind of initialization. + return false; + } + } + } + } + + match &this.variants { + Variants::Single { .. } => { + // All fields of this single variant have already been checked above, there is nothing + // else to do. + } + Variants::Multiple { .. } => { + // We cannot tell LLVM anything about the details of this multi-variant layout, so + // invalid values "hidden" inside the variant cannot cause LLVM trouble. + } + } + + true +} diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index a1876bed83eca..7a05cfd235fae 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -3,8 +3,10 @@ mod alignment; mod call_kind; pub mod collect_writes; mod find_self_call; +mod might_permit_raw_init; pub use self::aggregate::expand_aggregate; pub use self::alignment::is_disaligned; pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind}; pub use self::find_self_call::find_self_call; +pub use self::might_permit_raw_init::might_permit_raw_init; diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs index ec334e5887ab7..7171ca7bf8950 100644 --- a/compiler/rustc_target/src/abi/mod.rs +++ b/compiler/rustc_target/src/abi/mod.rs @@ -1392,7 +1392,7 @@ pub struct PointeeInfo { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum InitKind { Zero, - Uninit, + UninitMitigated0x01Fill, } /// Trait that needs to be implemented by the higher-level type representation @@ -1498,72 +1498,4 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { Abi::Aggregate { sized } => sized && self.size.bytes() == 0, } } - - /// Determines if this type permits "raw" initialization by just transmuting some - /// memory into an instance of `T`. - /// - /// `init_kind` indicates if the memory is zero-initialized or left uninitialized. - /// - /// This code is intentionally conservative, and will not detect - /// * zero init of an enum whose 0 variant does not allow zero initialization - /// * making uninitialized types who have a full valid range (ints, floats, raw pointers) - /// * Any form of invalid value being made inside an array (unless the value is uninhabited) - /// - /// A strict form of these checks that uses const evaluation exists in - /// `rustc_const_eval::might_permit_raw_init`, and a tracking issue for making these checks - /// stricter is . - /// - /// FIXME: Once all the conservatism is removed from here, and the checks are ran by default, - /// we can use the const evaluation checks always instead. - pub fn might_permit_raw_init(self, cx: &C, init_kind: InitKind) -> bool - where - Self: Copy, - Ty: TyAbiInterface<'a, C>, - C: HasDataLayout, - { - let scalar_allows_raw_init = move |s: Scalar| -> bool { - match init_kind { - InitKind::Zero => { - // The range must contain 0. - s.valid_range(cx).contains(0) - } - InitKind::Uninit => { - // The range must include all values. - s.is_always_valid(cx) - } - } - }; - - // Check the ABI. - let valid = match self.abi { - Abi::Uninhabited => false, // definitely UB - Abi::Scalar(s) => scalar_allows_raw_init(s), - Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2), - Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s), - Abi::Aggregate { .. } => true, // Fields are checked below. - }; - if !valid { - // This is definitely not okay. - return false; - } - - // If we have not found an error yet, we need to recursively descend into fields. - match &self.fields { - FieldsShape::Primitive | FieldsShape::Union { .. } => {} - FieldsShape::Array { .. } => { - // FIXME(#66151): For now, we are conservative and do not check arrays by default. - } - FieldsShape::Arbitrary { offsets, .. } => { - for idx in 0..offsets.len() { - if !self.field(cx, idx).might_permit_raw_init(cx, init_kind) { - // We found a field that is unhappy with this kind of initialization. - return false; - } - } - } - } - - // FIXME(#66151): For now, we are conservative and do not check `self.variants`. - true - } } diff --git a/src/test/ui/consts/assert-type-intrinsics.rs b/src/test/ui/consts/assert-type-intrinsics.rs index 3ce3e1bdbac0f..6b5612dda90a7 100644 --- a/src/test/ui/consts/assert-type-intrinsics.rs +++ b/src/test/ui/consts/assert-type-intrinsics.rs @@ -11,12 +11,15 @@ fn main() { use std::mem::MaybeUninit; const _BAD1: () = unsafe { - MaybeUninit::::uninit().assume_init(); + intrinsics::assert_inhabited::(); //~ERROR: any use of this value will cause an error + //~^WARN: previously accepted }; const _BAD2: () = { - intrinsics::assert_uninit_valid::(); + intrinsics::assert_uninit_valid::(); //~ERROR: any use of this value will cause an error + //~^WARN: previously accepted }; const _BAD3: () = { - intrinsics::assert_zero_valid::<&'static i32>(); + intrinsics::assert_zero_valid::<&'static i32>(); //~ERROR: any use of this value will cause an error + //~^WARN: previously accepted }; } diff --git a/src/test/ui/consts/assert-type-intrinsics.stderr b/src/test/ui/consts/assert-type-intrinsics.stderr index a183856dff807..9f97d8367056f 100644 --- a/src/test/ui/consts/assert-type-intrinsics.stderr +++ b/src/test/ui/consts/assert-type-intrinsics.stderr @@ -3,26 +3,26 @@ error: any use of this value will cause an error | LL | const _BAD1: () = unsafe { | --------------- -LL | MaybeUninit::::uninit().assume_init(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` +LL | intrinsics::assert_inhabited::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` | = 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 = note: `#[deny(const_err)]` on by default error: any use of this value will cause an error - --> $DIR/assert-type-intrinsics.rs:17:9 + --> $DIR/assert-type-intrinsics.rs:18:9 | LL | const _BAD2: () = { | --------------- -LL | intrinsics::assert_uninit_valid::(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid +LL | intrinsics::assert_uninit_valid::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` | = 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: any use of this value will cause an error - --> $DIR/assert-type-intrinsics.rs:20:9 + --> $DIR/assert-type-intrinsics.rs:22:9 | LL | const _BAD3: () = { | --------------- @@ -40,8 +40,8 @@ error: any use of this value will cause an error | LL | const _BAD1: () = unsafe { | --------------- -LL | MaybeUninit::::uninit().assume_init(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` +LL | intrinsics::assert_inhabited::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` | = 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 @@ -49,12 +49,12 @@ LL | MaybeUninit::::uninit().assume_init(); Future breakage diagnostic: error: any use of this value will cause an error - --> $DIR/assert-type-intrinsics.rs:17:9 + --> $DIR/assert-type-intrinsics.rs:18:9 | LL | const _BAD2: () = { | --------------- -LL | intrinsics::assert_uninit_valid::(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid +LL | intrinsics::assert_uninit_valid::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` | = 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 @@ -62,7 +62,7 @@ LL | intrinsics::assert_uninit_valid::(); Future breakage diagnostic: error: any use of this value will cause an error - --> $DIR/assert-type-intrinsics.rs:20:9 + --> $DIR/assert-type-intrinsics.rs:22:9 | LL | const _BAD3: () = { | --------------- diff --git a/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs b/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs index 4aa869cca35be..ae44ffd29bd56 100644 --- a/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs +++ b/src/test/ui/intrinsics/panic-uninitialized-zeroed.rs @@ -34,6 +34,12 @@ 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)] @@ -63,6 +69,7 @@ enum ZeroIsValid { One(NonNull<()>) = 1, } +#[track_caller] fn test_panic_msg(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) { let err = panic::catch_unwind(op).err(); assert_eq!( @@ -71,6 +78,15 @@ fn test_panic_msg(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) { ); } +#[track_caller] +fn test_panic_msg_only_if_strict(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>()), + if cfg!(strict) { Some(&msg) } else { None }, + ); +} + fn main() { unsafe { // Uninhabited types @@ -139,92 +155,216 @@ fn main() { "attempted to instantiate uninhabited type `[Bar; 2]`" ); - // Types that do not like zero-initialziation + // Types that don't allow either. test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `fn()` uninitialized, which is invalid" + || mem::zeroed::<&i32>(), + "attempted to zero-initialize type `&i32`, which is invalid" ); test_panic_msg( - || mem::zeroed::(), - "attempted to zero-initialize type `fn()`, which is invalid" + || mem::uninitialized::<&i32>(), + "attempted to leave type `&i32` uninitialized, which is invalid" ); test_panic_msg( - || mem::uninitialized::<*const dyn Send>(), - "attempted to leave type `*const dyn core::marker::Send` uninitialized, which is invalid" + || 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::<(NonNull, u32, u32)>(), - "attempted to leave type `(core::ptr::non_null::NonNull, u32, u32)` uninitialized, \ + || 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::<(NonNull, u32, u32)>(), - "attempted to zero-initialize type `(core::ptr::non_null::NonNull, u32, u32)`, \ + || 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::uninitialized::(), - "attempted to leave type `OneVariant_NonZero` uninitialized, \ + || 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" ); - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `NoNullVariant` 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( - || mem::zeroed::(), - "attempted to zero-initialize type `NoNullVariant`, \ - which is invalid" + test_panic_msg_only_if_strict( + || mem::uninitialized::<*const ()>(), + "attempted to leave type `*const ()` uninitialized, which is invalid" ); - // Types that can be zero, but not uninit. - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `bool` 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" ); - // Some things that should work. let _val = mem::zeroed::(); - let _val = mem::zeroed::(); - 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]>(); @@ -233,59 +373,5 @@ fn main() { let _val = mem::uninitialized::<[!; 0]>(); let _val = mem::uninitialized::<()>(); let _val = mem::uninitialized::(); - - if cfg!(strict) { - test_panic_msg( - || mem::uninitialized::(), - "attempted to leave type `i32` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::uninitialized::<*const ()>(), - "attempted to leave type `*const ()` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::uninitialized::<[i32; 1]>(), - "attempted to leave type `[i32; 1]` uninitialized, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::>(), - "attempted to zero-initialize type `core::ptr::non_null::NonNull<()>`, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::<[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( - || mem::zeroed::(), - "attempted to zero-initialize type `LR_NonZero`, which is invalid" - ); - - test_panic_msg( - || mem::zeroed::>(), - "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 = mem::uninitialized::(); - let _val = mem::uninitialized::<*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 = mem::zeroed::(); - let _val = mem::zeroed::<[LR_NonZero; 1]>(); - let _val = mem::zeroed::<[NonNull<()>; 1]>(); - let _val = mem::uninitialized::<[NonNull<()>; 1]>(); - } } } diff --git a/src/test/ui/lint/invalid_value.rs b/src/test/ui/lint/invalid_value.rs index 51edb2b7baf59..946a0e3886139 100644 --- a/src/test/ui/lint/invalid_value.rs +++ b/src/test/ui/lint/invalid_value.rs @@ -88,6 +88,9 @@ fn main() { let _val: NonNull = mem::zeroed(); //~ ERROR: does not permit zero-initialization let _val: NonNull = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + let _val: (NonZeroU32, i32) = mem::zeroed(); //~ ERROR: does not permit zero-initialization + let _val: (NonZeroU32, i32) = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized + 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 @@ -133,7 +136,7 @@ fn main() { let _val: Result = mem::zeroed(); let _val: Result = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized - // Some things that happen to work due to rustc implementation details, + // Some things that happen to be UB-free due to rustc implementation details, // but are not guaranteed to keep working. let _val: OneFruit = mem::zeroed(); let _val: OneFruit = mem::uninitialized(); diff --git a/src/test/ui/lint/invalid_value.stderr b/src/test/ui/lint/invalid_value.stderr index f0dcf53dfeee3..3901692001abd 100644 --- a/src/test/ui/lint/invalid_value.stderr +++ b/src/test/ui/lint/invalid_value.stderr @@ -315,8 +315,30 @@ LL | let _val: NonNull = mem::uninitialized(); | = note: `std::ptr::NonNull` must be non-null +error: the type `(NonZeroU32, i32)` does not permit zero-initialization + --> $DIR/invalid_value.rs:91:39 + | +LL | let _val: (NonZeroU32, i32) = mem::zeroed(); + | ^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: `std::num::NonZeroU32` must be non-null + +error: the type `(NonZeroU32, i32)` does not permit being left uninitialized + --> $DIR/invalid_value.rs:92:39 + | +LL | let _val: (NonZeroU32, i32) = mem::uninitialized(); + | ^^^^^^^^^^^^^^^^^^^^ + | | + | this code causes undefined behavior when executed + | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done + | + = note: `std::num::NonZeroU32` must be non-null + error: the type `*const dyn Send` does not permit zero-initialization - --> $DIR/invalid_value.rs:91:37 + --> $DIR/invalid_value.rs:94:37 | LL | let _val: *const dyn Send = mem::zeroed(); | ^^^^^^^^^^^^^ @@ -327,7 +349,7 @@ LL | let _val: *const dyn Send = mem::zeroed(); = note: the vtable of a wide raw pointer must be non-null error: the type `*const dyn Send` does not permit being left uninitialized - --> $DIR/invalid_value.rs:92:37 + --> $DIR/invalid_value.rs:95:37 | LL | let _val: *const dyn Send = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -338,7 +360,7 @@ 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/invalid_value.rs:94:31 + --> $DIR/invalid_value.rs:97:31 | LL | let _val: [fn(); 2] = mem::zeroed(); | ^^^^^^^^^^^^^ @@ -349,7 +371,7 @@ LL | let _val: [fn(); 2] = mem::zeroed(); = note: function pointers must be non-null error: the type `[fn(); 2]` does not permit being left uninitialized - --> $DIR/invalid_value.rs:95:31 + --> $DIR/invalid_value.rs:98:31 | LL | let _val: [fn(); 2] = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -360,7 +382,7 @@ LL | let _val: [fn(); 2] = mem::uninitialized(); = note: function pointers must be non-null error: the type `TwoUninhabited` does not permit zero-initialization - --> $DIR/invalid_value.rs:97:36 + --> $DIR/invalid_value.rs:100:36 | LL | let _val: TwoUninhabited = mem::zeroed(); | ^^^^^^^^^^^^^ @@ -375,7 +397,7 @@ LL | enum TwoUninhabited { | ^^^^^^^^^^^^^^^^^^^ error: the type `TwoUninhabited` does not permit being left uninitialized - --> $DIR/invalid_value.rs:98:36 + --> $DIR/invalid_value.rs:101:36 | LL | let _val: TwoUninhabited = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -390,7 +412,7 @@ LL | enum TwoUninhabited { | ^^^^^^^^^^^^^^^^^^^ error: the type `OneFruitNonZero` does not permit zero-initialization - --> $DIR/invalid_value.rs:100:37 + --> $DIR/invalid_value.rs:103:37 | LL | let _val: OneFruitNonZero = mem::zeroed(); | ^^^^^^^^^^^^^ @@ -405,7 +427,7 @@ LL | Banana(NonZeroU32), | ^^^^^^^^^^ error: the type `OneFruitNonZero` does not permit being left uninitialized - --> $DIR/invalid_value.rs:101:37 + --> $DIR/invalid_value.rs:104:37 | LL | let _val: OneFruitNonZero = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -420,7 +442,7 @@ LL | Banana(NonZeroU32), | ^^^^^^^^^^ error: the type `bool` does not permit being left uninitialized - --> $DIR/invalid_value.rs:105:26 + --> $DIR/invalid_value.rs:108:26 | LL | let _val: bool = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -431,7 +453,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/invalid_value.rs:108:32 + --> $DIR/invalid_value.rs:111:32 | LL | let _val: Wrap = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -446,7 +468,7 @@ LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ error: the type `NonBig` does not permit being left uninitialized - --> $DIR/invalid_value.rs:111:28 + --> $DIR/invalid_value.rs:114:28 | LL | let _val: NonBig = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -457,7 +479,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/invalid_value.rs:114:27 + --> $DIR/invalid_value.rs:117:27 | LL | let _val: Fruit = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -472,7 +494,7 @@ LL | enum Fruit { | ^^^^^^^^^^ error: the type `[bool; 2]` does not permit being left uninitialized - --> $DIR/invalid_value.rs:117:31 + --> $DIR/invalid_value.rs:120:31 | LL | let _val: [bool; 2] = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -483,7 +505,7 @@ LL | let _val: [bool; 2] = mem::uninitialized(); = note: booleans must be either `true` or `false` error: the type `i32` does not permit being left uninitialized - --> $DIR/invalid_value.rs:120:25 + --> $DIR/invalid_value.rs:123:25 | LL | let _val: i32 = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -494,7 +516,7 @@ LL | let _val: i32 = mem::uninitialized(); = note: integers must not be uninitialized error: the type `f32` does not permit being left uninitialized - --> $DIR/invalid_value.rs:123:25 + --> $DIR/invalid_value.rs:126:25 | LL | let _val: f32 = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -505,7 +527,7 @@ LL | let _val: f32 = mem::uninitialized(); = note: floats must not be uninitialized error: the type `*const ()` does not permit being left uninitialized - --> $DIR/invalid_value.rs:126:31 + --> $DIR/invalid_value.rs:129:31 | LL | let _val: *const () = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -516,7 +538,7 @@ LL | let _val: *const () = mem::uninitialized(); = note: raw pointers must not be uninitialized error: the type `*const [()]` does not permit being left uninitialized - --> $DIR/invalid_value.rs:129:33 + --> $DIR/invalid_value.rs:132:33 | LL | let _val: *const [()] = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -527,7 +549,7 @@ LL | let _val: *const [()] = mem::uninitialized(); = note: raw pointers must not be uninitialized error: the type `Result` does not permit being left uninitialized - --> $DIR/invalid_value.rs:134:38 + --> $DIR/invalid_value.rs:137:38 | LL | let _val: Result = mem::uninitialized(); | ^^^^^^^^^^^^^^^^^^^^ @@ -542,7 +564,7 @@ LL | pub enum Result { | ^^^^^^^^^^^^^^^^^^^^^ error: the type `&i32` does not permit zero-initialization - --> $DIR/invalid_value.rs:142:34 + --> $DIR/invalid_value.rs:145:34 | LL | let _val: &'static i32 = mem::transmute(0usize); | ^^^^^^^^^^^^^^^^^^^^^^ @@ -553,7 +575,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/invalid_value.rs:143:36 + --> $DIR/invalid_value.rs:146:36 | LL | let _val: &'static [i32] = mem::transmute((0usize, 0usize)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -564,7 +586,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/invalid_value.rs:144:32 + --> $DIR/invalid_value.rs:147:32 | LL | let _val: NonZeroU32 = mem::transmute(0); | ^^^^^^^^^^^^^^^^^ @@ -575,7 +597,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/invalid_value.rs:147:34 + --> $DIR/invalid_value.rs:150:34 | LL | let _val: NonNull = MaybeUninit::zeroed().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -586,7 +608,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/invalid_value.rs:148:34 + --> $DIR/invalid_value.rs:151:34 | LL | let _val: NonNull = MaybeUninit::uninit().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -597,7 +619,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/invalid_value.rs:149:26 + --> $DIR/invalid_value.rs:152:26 | LL | let _val: bool = MaybeUninit::uninit().assume_init(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -607,5 +629,5 @@ LL | let _val: bool = MaybeUninit::uninit().assume_init(); | = note: booleans must be either `true` or `false` -error: aborting due to 48 previous errors +error: aborting due to 50 previous errors