From d6f0b301c9ab2a248a786c19e71eb5d979c7e89d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sun, 22 Apr 2018 01:02:01 +0200 Subject: [PATCH] turn `mem::uninitialized` into a constant function --- src/libcore/mem.rs | 96 ++++++++++++++++++++ src/librustc_mir/interpret/const_eval.rs | 22 +++++ src/librustc_mir/transform/qualify_consts.rs | 4 +- 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index e3f08926610f6..41fafbade2aa6 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -606,12 +606,108 @@ pub unsafe fn zeroed() -> T { /// [copy]: ../intrinsics/fn.copy.html /// [copy_no]: ../intrinsics/fn.copy_nonoverlapping.html /// [`Drop`]: ../ops/trait.Drop.html +#[cfg(stage0)] #[inline] #[stable(feature = "rust1", since = "1.0.0")] pub unsafe fn uninitialized() -> T { intrinsics::uninit() } +/// Bypasses Rust's normal memory-initialization checks by pretending to +/// produce a value of type `T`, while doing nothing at all. +/// +/// **This is incredibly dangerous and should not be done lightly. Deeply +/// consider initializing your memory with a default value instead.** +/// +/// This is useful for [FFI] functions and initializing arrays sometimes, +/// but should generally be avoided. +/// +/// [FFI]: ../../book/first-edition/ffi.html +/// +/// # Undefined behavior +/// +/// It is [undefined behavior][ub] to read uninitialized memory, even just an +/// uninitialized boolean. For instance, if you branch on the value of such +/// a boolean, your program may take one, both, or neither of the branches. +/// +/// Writing to the uninitialized value is similarly dangerous. Rust believes the +/// value is initialized, and will therefore try to [`Drop`] the uninitialized +/// value and its fields if you try to overwrite it in a normal manner. The only way +/// to safely initialize an uninitialized value is with [`ptr::write`][write], +/// [`ptr::copy`][copy], or [`ptr::copy_nonoverlapping`][copy_no]. +/// +/// If the value does implement [`Drop`], it must be initialized before +/// it goes out of scope (and therefore would be dropped). Note that this +/// includes a `panic` occurring and unwinding the stack suddenly. +/// +/// # Examples +/// +/// Here's how to safely initialize an array of [`Vec`]s. +/// +/// ``` +/// use std::mem; +/// use std::ptr; +/// +/// // Only declare the array. This safely leaves it +/// // uninitialized in a way that Rust will track for us. +/// // However we can't initialize it element-by-element +/// // safely, and we can't use the `[value; 1000]` +/// // constructor because it only works with `Copy` data. +/// let mut data: [Vec; 1000]; +/// +/// unsafe { +/// // So we need to do this to initialize it. +/// data = mem::uninitialized(); +/// +/// // DANGER ZONE: if anything panics or otherwise +/// // incorrectly reads the array here, we will have +/// // Undefined Behavior. +/// +/// // It's ok to mutably iterate the data, since this +/// // doesn't involve reading it at all. +/// // (ptr and len are statically known for arrays) +/// for elem in &mut data[..] { +/// // *elem = Vec::new() would try to drop the +/// // uninitialized memory at `elem` -- bad! +/// // +/// // Vec::new doesn't allocate or do really +/// // anything. It's only safe to call here +/// // because we know it won't panic. +/// ptr::write(elem, Vec::new()); +/// } +/// +/// // SAFE ZONE: everything is initialized. +/// } +/// +/// println!("{:?}", &data[0]); +/// ``` +/// +/// This example emphasizes exactly how delicate and dangerous using `mem::uninitialized` +/// can be. Note that the [`vec!`] macro *does* let you initialize every element with a +/// value that is only [`Clone`], so the following is semantically equivalent and +/// vastly less dangerous, as long as you can live with an extra heap +/// allocation: +/// +/// ``` +/// let data: Vec> = vec![Vec::new(); 1000]; +/// println!("{:?}", &data[0]); +/// ``` +/// +/// [`Vec`]: ../../std/vec/struct.Vec.html +/// [`vec!`]: ../../std/macro.vec.html +/// [`Clone`]: ../../std/clone/trait.Clone.html +/// [ub]: ../../reference/behavior-considered-undefined.html +/// [write]: ../ptr/fn.write.html +/// [copy]: ../intrinsics/fn.copy.html +/// [copy_no]: ../intrinsics/fn.copy_nonoverlapping.html +/// [`Drop`]: ../ops/trait.Drop.html +#[inline] +#[cfg(not(stage0))] +#[stable(feature = "rust1", since = "1.0.0")] +pub const unsafe fn uninitialized() -> T { + intrinsics::uninit() +} + /// Swaps the values at two mutable locations, without deinitializing either one. /// /// # Examples diff --git a/src/librustc_mir/interpret/const_eval.rs b/src/librustc_mir/interpret/const_eval.rs index 954a3dbe5b9ab..65cae4718896c 100644 --- a/src/librustc_mir/interpret/const_eval.rs +++ b/src/librustc_mir/interpret/const_eval.rs @@ -284,6 +284,28 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator { ecx.write_primval(dest, PrimVal::from_u128(type_id), dest_layout.ty)?; } + "uninit" => { + let size = dest_layout.size.bytes(); + let uninit = |this: &mut EvalContext<'a, 'mir, 'tcx, Self>, val: Value| match val { + Value::ByRef(ptr, ..) => { + this.memory.mark_definedness(ptr, size, false)?; + Ok(val) + } + _ => Ok(Value::ByVal(PrimVal::Undef)), + }; + match dest { + Place::Local { frame, local } => ecx.modify_local(frame, local, uninit)?, + Place::Ptr { + ptr, + extra: PlaceExtra::None, + .. + } => ecx.memory.mark_definedness(ptr, size, false)?, + Place::Ptr { .. } => { + bug!("uninit intrinsic tried to write to fat or unaligned ptr target") + } + } + } + name => return Err(ConstEvalError::NeedsRfc(format!("calling intrinsic `{}`", name)).into()), } diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 591732fbba911..bdd696bcf7465 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -869,7 +869,9 @@ This does not pose a problem by itself because they can't be accessed directly." Abi::PlatformIntrinsic => { assert!(!self.tcx.is_const_fn(def_id)); match &self.tcx.item_name(def_id)[..] { - "size_of" | "min_align_of" | "type_id" => is_const_fn = Some(def_id), + "size_of" | "min_align_of" | "type_id" | "uninit" => { + is_const_fn = Some(def_id) + }, name if name.starts_with("simd_shuffle") => { is_shuffle = true;