1
Fork 0

Rollup merge of #101061 - RalfJung:panic-on-uninit, r=oli-obk

panic-on-uninit: adjust checks to 0x01-filling

Now that `mem::uninitiailized` actually fills memory with `0x01` (https://github.com/rust-lang/rust/pull/99182), we can make it panic in a few less cases without risking a lot more UB -- which hopefully slightly improves compatibility with some old code, and which might increase the chance that we can check inside arrays in the future.

We detect almost all of these with our lint, so authors of such code should still be warned -- but if this happens deep inside a dependency, the panic can be quite interruptive, so it might be better not to do it when there is no risk of LLVM UB.  Therefore, adjust the `might_permit_raw_init` logic to care primarily about LLVM UB. To my knowledge, it actually covers all cases of LLVM UB now.

Fixes https://github.com/rust-lang/rust/issues/66151

Cc ``@5225225``
This commit is contained in:
Dylan DPC 2022-10-05 17:27:32 +05:30 committed by GitHub
commit ab88c19f15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 411 additions and 258 deletions

View file

@ -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);
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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;

View file

@ -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 <https://github.com/rust-lang/rust/issues/66151>.
///
/// 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<C>(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
}
}

View file

@ -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::<bool>();
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
};
}

View file

@ -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 <https://github.com/rust-lang/rust/issues/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::<bool>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/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::<bool>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 <https://github.com/rust-lang/rust/issues/71800>
@ -62,7 +62,7 @@ LL | intrinsics::assert_uninit_valid::<bool>();
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: () = {
| ---------------

View file

@ -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<T>(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<T>(op: impl (FnOnce() -> T) + panic::UnwindSafe, msg: &str) {
);
}
#[track_caller]
fn test_panic_msg_only_if_strict<T>(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,57 +155,68 @@ 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::<fn()>(),
"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::<fn()>(),
"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::<Box<[i32; 0]>>(),
"attempted to zero-initialize type `alloc::boxed::Box<[i32; 0]>`, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<Box<[i32; 0]>>(),
"attempted to leave type `alloc::boxed::Box<[i32; 0]>` uninitialized, which is invalid"
);
test_panic_msg(
|| mem::zeroed::<Box<u8>>(),
"attempted to zero-initialize type `alloc::boxed::Box<u8>`, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<Box<u8>>(),
"attempted to leave type `alloc::boxed::Box<u8>` 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::<(NonNull<u32>, u32, u32)>(),
"attempted to leave type `(core::ptr::non_null::NonNull<u32>, u32, u32)` uninitialized, \
which is invalid"
);
test_panic_msg(
|| mem::zeroed::<(NonNull<u32>, u32, u32)>(),
"attempted to zero-initialize type `(core::ptr::non_null::NonNull<u32>, u32, u32)`, \
which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<OneVariant_NonZero>(),
"attempted to leave type `OneVariant_NonZero` uninitialized, \
which is invalid"
);
test_panic_msg(
|| mem::zeroed::<OneVariant_NonZero>(),
"attempted to zero-initialize type `OneVariant_NonZero`, \
which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<LR_NonZero>(),
"attempted to leave type `LR_NonZero` uninitialized, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<ManuallyDrop<LR_NonZero>>(),
"attempted to leave type `core::mem::manually_drop::ManuallyDrop<LR_NonZero>` uninitialized, \
which is invalid"
|| mem::uninitialized::<*const dyn Send>(),
"attempted to leave type `*const dyn core::marker::Send` uninitialized, which is invalid"
);
test_panic_msg(
@ -197,34 +224,147 @@ fn main() {
"attempted to leave type `NoNullVariant` uninitialized, \
which is invalid"
);
test_panic_msg(
|| mem::zeroed::<NoNullVariant>(),
"attempted to zero-initialize type `NoNullVariant`, \
which is invalid"
);
// Types that can be zero, but not uninit.
test_panic_msg(
|| mem::uninitialized::<bool>(),
"attempted to leave type `bool` uninitialized, which is invalid"
|| mem::zeroed::<OneVariant_Ref>(),
"attempted to zero-initialize type `OneVariant_Ref`, \
which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<OneVariant_Ref>(),
"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::<fn()>(),
"attempted to zero-initialize type `fn()`, which is invalid"
);
test_panic_msg_only_if_strict(
|| mem::uninitialized::<fn()>(),
"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, u32)>(),
"attempted to zero-initialize type `(core::ptr::non_null::NonNull<u32>, u32, u32)`, \
which is invalid"
);
test_panic_msg_only_if_strict(
|| mem::uninitialized::<(NonNull<u32>, u32, u32)>(),
"attempted to leave type `(core::ptr::non_null::NonNull<u32>, u32, u32)` uninitialized, which is invalid"
);
test_panic_msg(
|| mem::zeroed::<OneVariant_NonZero>(),
"attempted to zero-initialize type `OneVariant_NonZero`, \
which is invalid"
);
test_panic_msg_only_if_strict(
|| mem::uninitialized::<OneVariant_NonZero>(),
"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::<LR_NonZero>(),
"attempted to zero-initialize type `LR_NonZero`, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<LR_NonZero>(),
"attempted to leave type `LR_NonZero` uninitialized, which is invalid"
);
test_panic_msg_only_if_strict(
|| mem::zeroed::<ManuallyDrop<LR_NonZero>>(),
"attempted to zero-initialize type `core::mem::manually_drop::ManuallyDrop<LR_NonZero>`, \
which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<ManuallyDrop<LR_NonZero>>(),
"attempted to leave type `core::mem::manually_drop::ManuallyDrop<LR_NonZero>` uninitialized, \
which is invalid"
);
// Some strict-only things
test_panic_msg_only_if_strict(
|| mem::uninitialized::<i32>(),
"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::<LR>();
test_panic_msg(
|| mem::uninitialized::<LR>(),
"attempted to leave type `LR` uninitialized, which is invalid"
);
let _val = mem::zeroed::<ManuallyDrop<LR>>();
test_panic_msg(
|| mem::uninitialized::<ManuallyDrop<LR>>(),
"attempted to leave type `core::mem::manually_drop::ManuallyDrop<LR>` uninitialized, which is invalid"
);
// Some things that should work.
let _val = mem::zeroed::<bool>();
let _val = mem::zeroed::<LR>();
let _val = mem::zeroed::<ManuallyDrop<LR>>();
test_panic_msg_only_if_strict(
|| mem::uninitialized::<bool>(),
"attempted to leave type `bool` uninitialized, which is invalid"
);
let _val = mem::zeroed::<OneVariant>();
test_panic_msg_only_if_strict(
|| mem::uninitialized::<OneVariant>(),
"attempted to leave type `OneVariant` uninitialized, which is invalid"
);
// Some things that are actually allowed.
let _val = mem::zeroed::<Option<&'static i32>>();
let _val = mem::zeroed::<MaybeUninit<NonNull<u32>>>();
let _val = mem::zeroed::<[!; 0]>();
@ -233,59 +373,5 @@ fn main() {
let _val = mem::uninitialized::<[!; 0]>();
let _val = mem::uninitialized::<()>();
let _val = mem::uninitialized::<ZeroSized>();
if cfg!(strict) {
test_panic_msg(
|| mem::uninitialized::<i32>(),
"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::<NonNull<()>>(),
"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::<LR_NonZero>(),
"attempted to zero-initialize type `LR_NonZero`, which is invalid"
);
test_panic_msg(
|| mem::zeroed::<ManuallyDrop<LR_NonZero>>(),
"attempted to zero-initialize type `core::mem::manually_drop::ManuallyDrop<LR_NonZero>`, \
which is invalid"
);
} else {
// These are UB because they have not been officially blessed, but we await the resolution
// of <https://github.com/rust-lang/unsafe-code-guidelines/issues/71> before doing
// anything about that.
let _val = mem::uninitialized::<i32>();
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::<LR_NonZero>();
let _val = mem::zeroed::<[LR_NonZero; 1]>();
let _val = mem::zeroed::<[NonNull<()>; 1]>();
let _val = mem::uninitialized::<[NonNull<()>; 1]>();
}
}
}

View file

@ -88,6 +88,9 @@ fn main() {
let _val: NonNull<i32> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
let _val: NonNull<i32> = 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<i32, i32> = mem::zeroed();
let _val: Result<i32, i32> = 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();

View file

@ -315,8 +315,30 @@ LL | let _val: NonNull<i32> = mem::uninitialized();
|
= note: `std::ptr::NonNull<i32>` 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<T>` 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<T>` 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<char>` does not permit being left uninitialized
--> $DIR/invalid_value.rs:108:32
--> $DIR/invalid_value.rs:111:32
|
LL | let _val: Wrap<char> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -446,7 +468,7 @@ LL | struct Wrap<T> { 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<i32, i32>` does not permit being left uninitialized
--> $DIR/invalid_value.rs:134:38
--> $DIR/invalid_value.rs:137:38
|
LL | let _val: Result<i32, i32> = mem::uninitialized();
| ^^^^^^^^^^^^^^^^^^^^
@ -542,7 +564,7 @@ LL | pub enum Result<T, E> {
| ^^^^^^^^^^^^^^^^^^^^^
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<i32>` does not permit zero-initialization
--> $DIR/invalid_value.rs:147:34
--> $DIR/invalid_value.rs:150:34
|
LL | let _val: NonNull<i32> = MaybeUninit::zeroed().assume_init();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -586,7 +608,7 @@ LL | let _val: NonNull<i32> = MaybeUninit::zeroed().assume_init();
= note: `std::ptr::NonNull<i32>` must be non-null
error: the type `NonNull<i32>` does not permit being left uninitialized
--> $DIR/invalid_value.rs:148:34
--> $DIR/invalid_value.rs:151:34
|
LL | let _val: NonNull<i32> = MaybeUninit::uninit().assume_init();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -597,7 +619,7 @@ LL | let _val: NonNull<i32> = MaybeUninit::uninit().assume_init();
= note: `std::ptr::NonNull<i32>` 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