Skip to content

Commit

Permalink
Auto merge of #66059 - RalfJung:panic-on-non-zero, r=<try>
Browse files Browse the repository at this point in the history
mem::zeroed/uninit: panic on types that do not permit zero-initialization

r? @eddyb @oli-obk

Cc #62825
  • Loading branch information
bors committed Nov 5, 2019
2 parents 2e4da3c + 321f9e2 commit 8045dc6
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 138 deletions.
5 changes: 5 additions & 0 deletions src/libcore/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,11 @@ extern "rust-intrinsic" {
/// 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_non_zero<T>();

/// Gets a reference to a static `Location` indicating where it was called.
#[cfg(not(bootstrap))]
pub fn caller_location() -> &'static crate::panic::Location<'static>;
Expand Down
6 changes: 6 additions & 0 deletions src/libcore/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ pub const fn needs_drop<T>() -> bool {
#[allow(deprecated_in_future)]
#[allow(deprecated)]
pub unsafe fn zeroed<T>() -> T {
#[cfg(not(bootstrap))]
intrinsics::panic_if_non_zero::<T>();
#[cfg(bootstrap)]
intrinsics::panic_if_uninhabited::<T>();
intrinsics::init()
}
Expand Down Expand Up @@ -486,6 +489,9 @@ pub unsafe fn zeroed<T>() -> T {
#[allow(deprecated_in_future)]
#[allow(deprecated)]
pub unsafe fn uninitialized<T>() -> T {
#[cfg(not(bootstrap))]
intrinsics::panic_if_non_zero::<T>();
#[cfg(bootstrap)]
intrinsics::panic_if_uninhabited::<T>();
intrinsics::uninit()
}
Expand Down
30 changes: 0 additions & 30 deletions src/librustc/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1907,36 +1907,6 @@ impl<'tcx, T: HasTyCtxt<'tcx>> HasTyCtxt<'tcx> for LayoutCx<'tcx, T> {
}
}

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>> {
Expand Down
25 changes: 22 additions & 3 deletions src/librustc_codegen_ssa/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
};

// 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, IfNonZero };
let panic_intrinsic = intrinsic.and_then(|i| match i {
"panic_if_uninhabited" => Some(PanicIntrinsic::IfUninhabited),
"panic_if_non_zero" => Some(PanicIntrinsic::IfNonZero),
_ => 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(),
IfNonZero => !layout.might_permit_zero_init(&bx).unwrap(), // error type is `!`
};
if do_panic {
let msg_str = if layout.abi.is_uninhabited() {
// Use this error even for IfNonZero as it is more precise.
format!("attempted to instantiate uninhabited type `{}`", ty)
} else {
format!("attempted to zero-initialize non-zero type `{}`", ty)
};
let msg = bx.const_str(Symbol::intern(&msg_str));
let location = self.get_caller_location(&mut bx, span).immediate();

Expand Down
6 changes: 3 additions & 3 deletions src/librustc_index/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ macro_rules! newtype_index {
impl Idx for $type {
#[inline]
fn new(value: usize) -> Self {
Self::from(value)
Self::from_usize(value)
}

#[inline]
fn index(self) -> usize {
usize::from(self)
self.as_usize()
}
}

Expand Down Expand Up @@ -400,7 +400,7 @@ macro_rules! newtype_index {
(@decodable $type:ident) => (
impl ::rustc_serialize::Decodable for $type {
fn decode<D: ::rustc_serialize::Decoder>(d: &mut D) -> Result<Self, D::Error> {
d.read_u32().map(Self::from)
d.read_u32().map(Self::from_u32)
}
}
);
Expand Down
91 changes: 91 additions & 0 deletions src/librustc_target/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,7 @@ impl<'a, Ty> Deref for TyLayout<'a, Ty> {
}
}

/// Trait for context types that can compute layouts of things.
pub trait LayoutOf {
type Ty;
type TyLayout;
Expand All @@ -1014,6 +1015,39 @@ pub trait LayoutOf {
}
}

/// 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.
Expand Down Expand Up @@ -1055,10 +1089,14 @@ impl<'a, Ty> TyLayout<'a, Ty> {
where Ty: TyLayoutMethods<'a, C>, C: LayoutOf<Ty = Ty> {
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>, C: LayoutOf<Ty = Ty> {
Ty::field(self, cx, i)
}

pub fn pointee_info_at<C>(self, cx: &C, offset: Size) -> Option<PointeeInfo>
where Ty: TyLayoutMethods<'a, C>, C: LayoutOf<Ty = Ty> {
Ty::pointee_info_at(self, cx, offset)
Expand All @@ -1081,4 +1119,57 @@ impl<'a, Ty> TyLayout<'a, Ty> {
Abi::Aggregate { sized } => sized && self.size.bytes() == 0
}
}

/// Determines if zero-initializing this type might be okay.
/// This is conservative: in doubt, it will answer `true`.
pub fn might_permit_zero_init<C, E>(
&self,
cx: &C,
) -> Result<bool, E>
where
Self: Copy,
Ty: TyLayoutMethods<'a, C>,
C: LayoutOf<Ty = Ty, TyLayout: MaybeResult<Self, Error = E>>
{
fn scalar_allows_zero(s: &Scalar) -> bool {
(*s.valid_range.start() <= 0) || // `&& *s.valid_range.end() >= 0` would be redundant
(*s.valid_range.start() > *s.valid_range.end()) // wrap-around allows 0
}

// Abi is the most informative here.
let res = match &self.abi {
Abi::Uninhabited => false, // definitely UB
Abi::Scalar(s) => scalar_allows_zero(s),
Abi::ScalarPair(s1, s2) =>
scalar_allows_zero(s1) && scalar_allows_zero(s2),
Abi::Vector { element: s, count } =>
*count == 0 || scalar_allows_zero(s),
Abi::Aggregate { .. } => {
// For aggregates, recurse.
let inner = match self.variants {
Variants::Multiple { .. } => // FIXME: get variant with "0" discriminant.
return Ok(true),
Variants::Single { index } => self.for_variant(&cx, index),
};

match inner.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 || inner.field(cx, 0).to_result()?.might_permit_zero_init(cx)?
true,
FieldPlacement::Arbitrary { ref offsets, .. } =>
// Check that all fields accept zero-init.
(0..offsets.len()).try_fold(true, |accu, idx|
Ok(accu &&
inner.field(cx, idx).to_result()?.might_permit_zero_init(cx)?
)
)?
}
}
};
trace!("might_permit_zero_init({:?}) = {}", self.details, res);
Ok(res)
}
}
3 changes: 3 additions & 0 deletions src/librustc_target/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#![feature(box_syntax)]
#![feature(nll)]
#![feature(slice_patterns)]
#![feature(never_type)]
#![feature(associated_type_bounds)]
#![feature(exhaustive_patterns)]

#[macro_use] extern crate log;

Expand Down
1 change: 1 addition & 0 deletions src/librustc_typeck/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem) {
),
),
"panic_if_uninhabited" => (1, Vec::new(), tcx.mk_unit()),
"panic_if_non_zero" => (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()),
Expand Down
106 changes: 106 additions & 0 deletions src/test/ui/intrinsics/panic-uninitialized-zeroed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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 zero-initialize non-zero type `fn()`"
);
test_panic_msg(
|| mem::zeroed::<fn()>(),
"attempted to zero-initialize non-zero type `fn()`"
);

test_panic_msg(
|| mem::uninitialized::<*const dyn Send>(),
"attempted to zero-initialize non-zero type `*const dyn std::marker::Send`"
);
test_panic_msg(
|| mem::zeroed::<*const dyn Send>(),
"attempted to zero-initialize non-zero type `*const dyn std::marker::Send`"
);

test_panic_msg(
|| mem::uninitialized::<(NonNull<u32>, u32, u32)>(),
"attempted to zero-initialize non-zero type `(std::ptr::NonNull<u32>, u32, u32)`"
);
test_panic_msg(
|| mem::zeroed::<(NonNull<u32>, u32, u32)>(),
"attempted to zero-initialize non-zero type `(std::ptr::NonNull<u32>, u32, u32)`"
);

// Some things that should work.
let _val = mem::zeroed::<bool>();
let _val = mem::zeroed::<OneVariant>();
let _val = mem::zeroed::<Option<&'static i32>>();
}
}
Loading

0 comments on commit 8045dc6

Please sign in to comment.