Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 10 commits into from
Mar 11, 2020
10 changes: 10 additions & 0 deletions src/libcore/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,16 @@ 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_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>;
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 @@ -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()
}
Expand Down Expand Up @@ -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()
}
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
127 changes: 92 additions & 35 deletions src/librustc_codegen_ssa/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,89 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
helper.do_call(self, &mut bx, fn_abi, llfn, &args, None, cleanup);
}

/// Returns `true` if this is indeed a panic intrinsic and codegen is done.
fn codegen_panic_intrinsic(
&mut self,
helper: &TerminatorCodegenHelper<'tcx>,
bx: &mut Bx,
intrinsic: Option<&str>,
instance: Option<Instance<'tcx>>,
span: Span,
destination: &Option<(mir::Place<'tcx>, mir::BasicBlock)>,
cleanup: Option<mir::BasicBlock>,
) -> bool {
// Emit a panic or a no-op for `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 {
// FIXME: Move to symbols instead of strings.
"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);
let do_panic = match intrinsic {
IfUninhabited => layout.abi.is_uninhabited(),
// We unwrap as the error type is `!`.
IfZeroInvalid => !layout.might_permit_raw_init(bx, /*zero:*/ true).unwrap(),
// We unwrap as the error type is `!`.
IfAnyInvalid => !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(bx, span).immediate();

// Obtain the panic entry point.
// FIXME: dedup this with `codegen_assert_terminator` above.
let def_id =
common::langcall(bx.tcx(), Some(span), "", lang_items::PanicFnLangItem);
let instance = ty::Instance::mono(bx.tcx(), def_id);
let fn_abi = FnAbi::of_instance(bx, instance, &[]);
let llfn = bx.get_fn_addr(instance);

if let Some((_, target)) = destination.as_ref() {
helper.maybe_sideeffect(self.mir, bx, &[*target]);
}
// Codegen the actual panic invoke/call.
helper.do_call(
self,
bx,
fn_abi,
llfn,
&[msg.0, msg.1, location],
destination.as_ref().map(|(_, bb)| (ReturnDest::Nothing, *bb)),
cleanup,
);
} else {
// a NOP
let target = destination.as_ref().unwrap().1;
helper.maybe_sideeffect(self.mir, bx, &[target]);
helper.funclet_br(self, bx, target)
}
true
} else {
false
}
}

fn codegen_call_terminator(
&mut self,
helper: TerminatorCodegenHelper<'tcx>,
Expand Down Expand Up @@ -520,41 +603,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
bug!("`miri_start_panic` should never end up in compiled code");
}

// Emit a panic or a no-op for `panic_if_uninhabited`.
if intrinsic == Some("panic_if_uninhabited") {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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 msg = bx.const_str(Symbol::intern(&msg_str));
let location = self.get_caller_location(&mut bx, span).immediate();

// Obtain the panic entry point.
let def_id =
common::langcall(bx.tcx(), Some(span), "", lang_items::PanicFnLangItem);
let instance = ty::Instance::mono(bx.tcx(), def_id);
let fn_abi = FnAbi::of_instance(&bx, instance, &[]);
let llfn = bx.get_fn_addr(instance);

if let Some((_, target)) = destination.as_ref() {
helper.maybe_sideeffect(self.mir, &mut bx, &[*target]);
}
// Codegen the actual panic invoke/call.
helper.do_call(
self,
&mut bx,
fn_abi,
llfn,
&[msg.0, msg.1, location],
destination.as_ref().map(|(_, bb)| (ReturnDest::Nothing, *bb)),
cleanup,
);
} else {
// a NOP
let target = destination.as_ref().unwrap().1;
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
helper.funclet_br(self, &mut bx, target)
}
if self.codegen_panic_intrinsic(
&helper,
&mut bx,
intrinsic,
instance,
span,
destination,
cleanup,
) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_index/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ macro_rules! newtype_index {

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

Expand Down
85 changes: 85 additions & 0 deletions src/librustc_target/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,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 @@ -929,6 +930,38 @@ 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(Ok(x): Result<T, Self::Error>) -> Self {
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 @@ -969,13 +1002,17 @@ impl<'a, Ty> TyLayout<'a, 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>,
Expand All @@ -999,4 +1036,52 @@ impl<'a, Ty> TyLayout<'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`.
/// `zero` indicates if the memory is zero-initialized, or alternatively
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
/// left entirely uninitialized.
/// This is conservative: in doubt, it will answer `true`.
///
/// FIXME: Once we removed all the conservatism, we could alternatively
/// create an all-0/all-undef constant and run the const value validator to see if
/// this is a valid value for the given type.
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>> + HasDataLayout,
{
let scalar_allows_raw_init = move |s: &Scalar| -> bool {
if zero {
let range = &s.valid_range;
// The range must contain 0.
range.contains(&0) || (*range.start() > *range.end()) // wrap-around allows 0
} else {
// The range must include all values. `valid_range_exclusive` handles
// the wrap-around using target arithmetic; with wrap-around then the full
// range is one where `start == end`.
let range = s.valid_range_exclusive(cx);
range.start == range.end
}
};

// 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, // Cannot be excluded *right now*.
};
if !valid {
// This is definitely not okay.
trace!("might_permit_raw_init({:?}, zero={}): not valid", self.details, zero);
return Ok(false);
}

// If we have not found an error yet, we need to recursively descend.
// FIXME(#66151): For now, we are conservative and do not do this.
Ok(true)
}
}
3 changes: 3 additions & 0 deletions src/librustc_target/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/librustc_typeck/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ 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()),
Expand Down
Loading