mem::zeroed/uninit: panic on types that do not permit zero-initialization

This commit is contained in:
Ralf Jung 2019-11-03 17:21:37 +01:00
parent 55aee8d496
commit 503026b622
10 changed files with 280 additions and 137 deletions

View File

@ -721,6 +721,16 @@
/// This will statically either panic, or do nothing.
pub fn panic_if_uninhabited<T>();
/// A guard for unsafe functions that cannot ever be executed if `T` does not permit
/// zero-initialization: This will statically either panic, or do nothing.
#[cfg(not(bootstrap))]
pub fn panic_if_zero_invalid<T>();
/// A guard for unsafe functions that cannot ever be executed if `T` has invalid
/// bit patterns: This will statically either panic, or do nothing.
#[cfg(not(bootstrap))]
pub fn panic_if_any_invalid<T>();
/// Gets a reference to a static `Location` indicating where it was called.
#[rustc_const_unstable(feature = "const_caller_location", issue = "47809")]
pub fn caller_location() -> &'static crate::panic::Location<'static>;

View File

@ -495,6 +495,9 @@ pub const fn needs_drop<T>() -> bool {
#[allow(deprecated)]
#[rustc_diagnostic_item = "mem_zeroed"]
pub unsafe fn zeroed<T>() -> T {
#[cfg(not(bootstrap))]
intrinsics::panic_if_zero_invalid::<T>();
#[cfg(bootstrap)]
intrinsics::panic_if_uninhabited::<T>();
intrinsics::init()
}
@ -528,6 +531,9 @@ pub unsafe fn zeroed<T>() -> T {
#[allow(deprecated)]
#[rustc_diagnostic_item = "mem_uninitialized"]
pub unsafe fn uninitialized<T>() -> T {
#[cfg(not(bootstrap))]
intrinsics::panic_if_any_invalid::<T>();
#[cfg(bootstrap)]
intrinsics::panic_if_uninhabited::<T>();
intrinsics::uninit()
}

View File

@ -1907,36 +1907,6 @@ fn tcx(&self) -> TyCtxt<'tcx> {
}
}
pub trait MaybeResult<T> {
type Error;
fn from(x: Result<T, Self::Error>) -> Self;
fn to_result(self) -> Result<T, Self::Error>;
}
impl<T> MaybeResult<T> for T {
type Error = !;
fn from(x: Result<T, Self::Error>) -> Self {
let Ok(x) = x;
x
}
fn to_result(self) -> Result<T, Self::Error> {
Ok(self)
}
}
impl<T, E> MaybeResult<T> for Result<T, E> {
type Error = E;
fn from(x: Result<T, Self::Error>) -> Self {
x
}
fn to_result(self) -> Result<T, Self::Error> {
self
}
}
pub type TyLayout<'tcx> = ::rustc_target::abi::TyLayout<'tcx, Ty<'tcx>>;
impl<'tcx> LayoutOf for LayoutCx<'tcx, TyCtxt<'tcx>> {

View File

@ -521,11 +521,36 @@ fn codegen_call_terminator(
}
// Emit a panic or a no-op for `panic_if_uninhabited`.
if intrinsic == Some("panic_if_uninhabited") {
// These are intrinsics that compile to panics so that we can get a message
// which mentions the offending type, even from a const context.
#[derive(Debug, PartialEq)]
enum PanicIntrinsic { IfUninhabited, IfZeroInvalid, IfAnyInvalid };
let panic_intrinsic = intrinsic.and_then(|i| match i {
"panic_if_uninhabited" => Some(PanicIntrinsic::IfUninhabited),
"panic_if_zero_invalid" => Some(PanicIntrinsic::IfZeroInvalid),
"panic_if_any_invalid" => Some(PanicIntrinsic::IfAnyInvalid),
_ => None
});
if let Some(intrinsic) = panic_intrinsic {
use PanicIntrinsic::*;
let ty = instance.unwrap().substs.type_at(0);
let layout = bx.layout_of(ty);
if layout.abi.is_uninhabited() {
let msg_str = format!("Attempted to instantiate uninhabited type {}", ty);
let do_panic = match intrinsic {
IfUninhabited => layout.abi.is_uninhabited(),
IfZeroInvalid => // We unwrap as the error type is `!`.
!layout.might_permit_raw_init(&bx, /*zero:*/ true).unwrap(),
IfAnyInvalid => // We unwrap as the error type is `!`.
!layout.might_permit_raw_init(&bx, /*zero:*/ false).unwrap(),
};
if do_panic {
let msg_str = if layout.abi.is_uninhabited() {
// Use this error even for the other intrinsics as it is more precise.
format!("attempted to instantiate uninhabited type `{}`", ty)
} else if intrinsic == IfZeroInvalid {
format!("attempted to zero-initialize type `{}`, which is invalid", ty)
} else {
format!("attempted to leave type `{}` uninitialized, which is invalid", ty)
};
let msg = bx.const_str(Symbol::intern(&msg_str));
let location = self.get_caller_location(&mut bx, span).immediate();

View File

@ -196,7 +196,7 @@ fn new(value: usize) -> Self {
#[inline]
fn index(self) -> usize {
usize::from(self)
self.as_usize()
}
}

View File

@ -919,6 +919,7 @@ fn deref(&self) -> &&'a LayoutDetails {
}
}
/// Trait for context types that can compute layouts of things.
pub trait LayoutOf {
type Ty;
type TyLayout;
@ -929,6 +930,39 @@ fn spanned_layout_of(&self, ty: Self::Ty, _span: Span) -> Self::TyLayout {
}
}
/// The `TyLayout` above will always be a `MaybeResult<TyLayout<'_, Self>>`.
/// We can't add the bound due to the lifetime, but this trait is still useful when
/// writing code that's generic over the `LayoutOf` impl.
pub trait MaybeResult<T> {
type Error;
fn from(x: Result<T, Self::Error>) -> Self;
fn to_result(self) -> Result<T, Self::Error>;
}
impl<T> MaybeResult<T> for T {
type Error = !;
fn from(x: Result<T, Self::Error>) -> Self {
let Ok(x) = x;
x
}
fn to_result(self) -> Result<T, Self::Error> {
Ok(self)
}
}
impl<T, E> MaybeResult<T> for Result<T, E> {
type Error = E;
fn from(x: Result<T, Self::Error>) -> Self {
x
}
fn to_result(self) -> Result<T, Self::Error> {
self
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum PointerKind {
/// Most general case, we know no restrictions to tell LLVM.
@ -969,6 +1003,9 @@ pub fn for_variant<C>(self, cx: &C, variant_index: VariantIdx) -> Self
{
Ty::for_variant(self, cx, variant_index)
}
/// Callers might want to use `C: LayoutOf<Ty=Ty, TyLayout: MaybeResult<Self>>`
/// to allow recursion (see `might_permit_zero_init` below for an example).
pub fn field<C>(self, cx: &C, i: usize) -> C::TyLayout
where
Ty: TyLayoutMethods<'a, C>,
@ -976,6 +1013,7 @@ pub fn field<C>(self, cx: &C, i: usize) -> C::TyLayout
{
Ty::field(self, cx, i)
}
pub fn pointee_info_at<C>(self, cx: &C, offset: Size) -> Option<PointeeInfo>
where
Ty: TyLayoutMethods<'a, C>,
@ -999,4 +1037,81 @@ pub fn is_zst(&self) -> bool {
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`.
/// `zero` indicates if the memory is zero-initialized, or alternatively
/// left entirely uninitialized.
/// This is conservative: in doubt, it will answer `true`.
pub fn might_permit_raw_init<C, E>(
self,
cx: &C,
zero: bool,
) -> Result<bool, E>
where
Self: Copy,
Ty: TyLayoutMethods<'a, C>,
C: LayoutOf<Ty = Ty, TyLayout: MaybeResult<Self, Error = E>>
{
let scalar_allows_raw_init = move |s: &Scalar| -> bool {
let range = &s.valid_range;
if zero {
// The range must contain 0.
range.contains(&0) ||
(*range.start() > *range.end()) // wrap-around allows 0
} else {
// The range must include all values.
*range.start() == range.end().wrapping_add(1)
}
};
// Abi is the most informative here.
let res = 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 { .. } => {
match self.variants {
Variants::Multiple { .. } =>
if zero {
// FIXME: could we identify the variant with discriminant 0, check that?
true
} else {
// FIXME: This needs to have some sort of discriminant,
// which cannot be undef. But for now we are conservative.
true
},
Variants::Single { .. } => {
// For aggregates, recurse.
match self.fields {
FieldPlacement::Union(..) => true, // An all-0 unit is fine.
FieldPlacement::Array { .. } =>
// FIXME: The widely use smallvec 0.6 creates uninit arrays
// with any element type, so let us not (yet) complain about that.
// count == 0 ||
// self.field(cx, 0).to_result()?.might_permit_raw_init(cx, zero)?
true,
FieldPlacement::Arbitrary { ref offsets, .. } => {
let mut res = true;
// Check that all fields accept zero-init.
for idx in 0..offsets.len() {
let field = self.field(cx, idx).to_result()?;
if !field.might_permit_raw_init(cx, zero)? {
res = false;
break;
}
}
res
}
}
}
}
}
};
trace!("might_permit_raw_init({:?}, zero={}) = {}", self.details, zero, res);
Ok(res)
}
}

View File

@ -10,6 +10,9 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
#![feature(bool_to_option)]
#![feature(nll)]
#![feature(never_type)]
#![feature(associated_type_bounds)]
#![feature(exhaustive_patterns)]
#[macro_use]
extern crate log;

View File

@ -147,7 +147,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
),
"rustc_peek" => (1, vec![param(0)], param(0)),
"caller_location" => (0, vec![], tcx.caller_location_ty()),
"panic_if_uninhabited" => (1, Vec::new(), tcx.mk_unit()),
"panic_if_uninhabited" |
"panic_if_zero_invalid" |
"panic_if_any_invalid" =>
(1, Vec::new(), tcx.mk_unit()),
"init" => (1, Vec::new(), param(0)),
"uninit" => (1, Vec::new(), param(0)),
"forget" => (1, vec![param(0)], tcx.mk_unit()),

View File

@ -0,0 +1,113 @@
// run-pass
// ignore-wasm32-bare compiled with panic=abort by default
// This test checks panic emitted from `mem::{uninitialized,zeroed}`.
#![feature(never_type)]
#![allow(deprecated, invalid_value)]
use std::{mem, panic};
use std::ptr::NonNull;
#[allow(dead_code)]
struct Foo {
x: u8,
y: !,
}
enum Bar {}
#[allow(dead_code)]
enum OneVariant { Variant(i32) }
fn test_panic_msg<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::<String>()).map(|s| &**s),
Some(msg)
);
}
fn main() {
unsafe {
// Uninitialized types
test_panic_msg(
|| mem::uninitialized::<!>(),
"attempted to instantiate uninhabited type `!`"
);
test_panic_msg(
|| mem::zeroed::<!>(),
"attempted to instantiate uninhabited type `!`"
);
test_panic_msg(
|| mem::MaybeUninit::<!>::uninit().assume_init(),
"attempted to instantiate uninhabited type `!`"
);
test_panic_msg(
|| mem::uninitialized::<Foo>(),
"attempted to instantiate uninhabited type `Foo`"
);
test_panic_msg(
|| mem::zeroed::<Foo>(),
"attempted to instantiate uninhabited type `Foo`"
);
test_panic_msg(
|| mem::MaybeUninit::<Foo>::uninit().assume_init(),
"attempted to instantiate uninhabited type `Foo`"
);
test_panic_msg(
|| mem::uninitialized::<Bar>(),
"attempted to instantiate uninhabited type `Bar`"
);
test_panic_msg(
|| mem::zeroed::<Bar>(),
"attempted to instantiate uninhabited type `Bar`"
);
test_panic_msg(
|| mem::MaybeUninit::<Bar>::uninit().assume_init(),
"attempted to instantiate uninhabited type `Bar`"
);
// Types that do not like zero-initialziation
test_panic_msg(
|| mem::uninitialized::<fn()>(),
"attempted to leave type `fn()` uninitialized, which is invalid"
);
test_panic_msg(
|| mem::zeroed::<fn()>(),
"attempted to zero-initialize type `fn()`, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<*const dyn Send>(),
"attempted to leave type `*const dyn std::marker::Send` uninitialized, which is invalid"
);
test_panic_msg(
|| mem::zeroed::<*const dyn Send>(),
"attempted to zero-initialize type `*const dyn std::marker::Send`, which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<(NonNull<u32>, u32, u32)>(),
"attempted to leave type `(std::ptr::NonNull<u32>, u32, u32)` uninitialized, \
which is invalid"
);
test_panic_msg(
|| mem::zeroed::<(NonNull<u32>, u32, u32)>(),
"attempted to zero-initialize type `(std::ptr::NonNull<u32>, u32, u32)`, \
which is invalid"
);
test_panic_msg(
|| mem::uninitialized::<bool>(),
"attempted to leave type `bool` uninitialized, which is invalid"
);
// Some things that should work.
let _val = mem::zeroed::<bool>();
let _val = mem::zeroed::<OneVariant>();
let _val = mem::zeroed::<Option<&'static i32>>();
}
}

View File

@ -1,102 +0,0 @@
// run-pass
// ignore-wasm32-bare compiled with panic=abort by default
// This test checks that instantiating an uninhabited type via `mem::{uninitialized,zeroed}` results
// in a runtime panic.
#![feature(never_type)]
#![allow(deprecated, invalid_value)]
use std::{mem, panic};
#[allow(dead_code)]
struct Foo {
x: u8,
y: !,
}
enum Bar {}
fn main() {
unsafe {
assert_eq!(
panic::catch_unwind(|| {
mem::uninitialized::<!>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type !"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::zeroed::<!>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type !"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::MaybeUninit::<!>::uninit().assume_init()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type !"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::uninitialized::<Foo>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Foo"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::zeroed::<Foo>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Foo"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::MaybeUninit::<Foo>::uninit().assume_init()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Foo"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::uninitialized::<Bar>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Bar"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::zeroed::<Bar>()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Bar"
})),
Some(true)
);
assert_eq!(
panic::catch_unwind(|| {
mem::MaybeUninit::<Bar>::uninit().assume_init()
}).err().and_then(|a| a.downcast_ref::<String>().map(|s| {
s == "Attempted to instantiate uninhabited type Bar"
})),
Some(true)
);
}
}