Skip to content

Commit

Permalink
Fixed unsoundness of using &mut MaybeUninit<T> as the out type for …
Browse files Browse the repository at this point in the history
…`&mut T`
  • Loading branch information
danielhenrymantilla committed Jan 29, 2020
1 parent a42410f commit 62a5a14
Show file tree
Hide file tree
Showing 16 changed files with 1,149 additions and 533 deletions.
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ license = "MIT"
require_unsafe_in_body = "0.2.0"

[features]
chain = []
downcast_as_ReadIntoUninit = []
nightly = []
polonius-check = ["nightly"]
specialization = ["nightly"]
chain = []

default = []

Expand Down
52 changes: 38 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,28 @@ Note that there are other ways to trigger this UB without explicitely using
```

- this is exactly equivalent to calling `mem::uninitialized::<T>()`,
which breaks the _validity_ invariant of `T` and thus causes
"instant UB".

There currently only two exceptions / _valid_ use cases:

- either [`type T = [MaybeUninit<U>; N]`][`uninit_array`],

- or `T` is an inhabited ZST (this may, however, break safety
invariants associated with the properties of the type, causing UB
once such broken invariant is witnessed).

- yes, using [`MaybeUninit`] is more subtle than just changing a function
call.

- ```rust
let mut vec: Vec<u8> = Vec::with_capacity(100); // Fine
unsafe {
vec.set_len(100); // UB: we have an uninitialized [u8; 100] in the heap
vec.set_len(100); // we have an uninitialized [u8; 100] in the heap
// This has broken the _safety_ invariant of `Vec`, but is not yet UB
// since no code has witnessed the broken state
}
let heap_bytes: &[u8] = &*vec; // Witness the broken safety invariant: UB!
```

## Instead, (you can) use [`MaybeUninit`]
Expand Down Expand Up @@ -98,6 +111,7 @@ It is all about the _**delayed** initialization pattern_:
let mut x = MaybeUninit::<i32>::uninit();

x = MaybeUninit::new(42);
assert_eq!(42, unsafe { x.assume_init() });
```

- or through a raw `*mut T` pointer (contrary to Rust references,
Expand All @@ -110,9 +124,24 @@ It is all about the _**delayed** initialization pattern_:

unsafe {
x.as_mut_ptr().write(42);
assert_eq!(42, x.assume_init());
}
```

- or, if you use the tools of this crate, by upgrading the
`&mut MaybeUninit<T>` into a "`&out T`" type called
[`Out<T>`][`crate::prelude::Out`]:

```rust
#![forbid(unsafe_code)] // no unsafe!
use ::core::mem::MaybeUninit;
use ::uninit::prelude::*;

let mut x = MaybeUninit::uninit();
let at_init_x: &i32 = x.as_out::<i32>().write(42);
assert_eq!(42, *at_init_x);
```

3. **Type-level upgrade**

Once we know, for sure, that the memory has been initialized, we can
Expand Down Expand Up @@ -153,7 +182,7 @@ pub trait Read {

that is, there is no way to `.read()` into an unitialized buffer (it would
require an api taking either a `(*mut u8, usize)` pair, or, equivalently and
by the way more ergonomically, a `&mut [MaybeUninit<u8>]`).
by the way more ergonomically, a [`&out [u8]`][`crate::prelude::OutSlice`]).

# Enter `::uninit`

Expand All @@ -163,7 +192,7 @@ So, the objective of this crate is double:

For instance:

- [`uninit_byte_array!`]
- [`uninit_array!`]

- [`Vec::reserve_uninit`]

Expand All @@ -176,17 +205,12 @@ So, the objective of this crate is double:

- [`.init_with_copy_from_slice()`]

## Status

This is currently at an realy stage, so it "only" includes
utilities to work with **uninitialized bytes** or integers.

[`Read`]: https://doc.rust-lang.org/1.36.0/std/io/trait.Read.html
[`mem::uninitialized`]: https://doc.rust-lang.org/core/mem/fn.uninitialized.html
[`MaybeUninit`]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html
[`.assume_init_by_ref()`]: https://docs.rs/uninit/0.1.0/uninit/trait.MaybeUninitExt.html#method.assume_init_by_ref
[`.assume_init_by_mut()`]: https://docs.rs/uninit/0.1.0/uninit/trait.MaybeUninitExt.html#method.assume_init_by_mut
[`uninit_byte_array!`]: https://docs.rs/uninit/0.1.0/uninit/macro.uninit_byte_array.html
[`Vec::reserve_uninit`]: https://docs.rs/uninit/0.1.0/uninit/trait.VecReserveUninit.html#tymethod.reserve_uninit
[`.init_with_copy_from_slice()`]: https://docs.rs/uninit/0.1.0/uninit/trait.InitWithCopyFromSlice.html#tymethod.init_with_copy_from_slice
[`ReadIntoUninit`]: https://docs.rs/uninit/0.1.0/uninit/trait.ReadIntoUninit.html
[`.assume_init_by_ref()`]: `crate::extension_traits::MaybeUninitExt::assume_init_by_ref`
[`.assume_init_by_mut()`]: `crate::extension_traits::MaybeUninitExt::assume_init_by_mut`
[`uninit_array!`]: `uninit_array`
[`Vec::reserve_uninit`]: `crate::extension_traits::VecReserveUninit::reserve_uninit`
[`.init_with_copy_from_slice()`]: `crate::out_references::OutSlice::copy_from_slice`
[`ReadIntoUninit`]: `crate::read::ReadIntoUninit`
155 changes: 155 additions & 0 deletions src/extension_traits/as_out.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use_prelude!();

use ::core::mem::ManuallyDrop;

#[cfg(doc)]
use crate::extension_traits::ManuallyDropMut;

use private::Is;
mod private {
pub trait Is { type Eq : ?Sized; }
impl<T : ?Sized> Is for T { type Eq = T; }
}

/// Helper / extension trait to convert a `&mut _` into a `&out T` by calling
/// `.as_out::<T>()` on it.
///
/// By autoref, this means that you can even just extract a `&out T` reference
/// out of a `mut` element simply by calling `.as_out::<T>()` on it.
///
/// There is, however, one restriction: to be able to call `.as_out::<_>()` on
/// something, it needs to be either `Copy`, or a value wrapped in a
/// [`MaybeUninit`] / a [`ManuallyDrop`].
///
/// - (or a slice of such, in which case it yields a
/// [fat `&out` reference][`OutSlice`])
///
/// This is by design. Indeed, [`Out`] references do not call the destructor
/// of the overwritten element (since it may not be initialized).
/// This could cause memory leaks when there is an initialized element with
/// [drop glue][`core::mem::needs_drop`].
///
/// To solve this limitation, one must explicitly call
/// [`.manually_drop_mut()`][`ManuallyDropMut::manually_drop_mut`]
/// to automagically transmute the `&mut _` reference into a
/// `&mut ManuallyDrop<_>`.
///
/// # Examples
///
/// ```rust
/// use ::uninit::prelude::*;
///
/// let mut x = 0;
/// x.as_out::<u32>().write(42);
///
/// let mut y = ::core::mem::MaybeUninit::uninit();
/// y.as_out::<u32>().write(42);
/// let y = unsafe { y.assume_init() };
///
/// assert_eq!(x, y);
/// ```
pub
trait AsOut<Pointee_ : ?Sized> {
type Out;

fn as_out<Pointee : ?Sized + Is<Eq=Pointee_>> (self: Self)
-> Self::Out
;
}

impl<'out, T : 'out> AsOut<T> for &'out mut MaybeUninit<T> {
type Out = Out<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=T>> (self: &'out mut MaybeUninit<T>)
-> Out<'out, T>
{
self.into()
}
}

impl<'out, T : 'out> AsOut<T> for &'out mut T
where
T : Copy,
{
type Out = Out<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=T>> (self: &'out mut T)
-> Out<'out, T>
{
self.into()
}
}

impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>] {
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: &'out mut [MaybeUninit<T>])
-> OutSlice<'out, T>
{
self.into()
}
}

impl<'out, T : 'out> AsOut<[T]> for &'out mut [T]
where
T : Copy,
{
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: &'out mut [T])
-> OutSlice<'out, T>
{
self.into()
}
}

impl<'out, T : 'out> AsOut<T> for &'out mut ManuallyDrop<T> {
type Out = Out<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=T>> (self: &'out mut ManuallyDrop<T>)
-> Out<'out, T>
{
self.into()
}
}

impl<'out, T : 'out> AsOut<[T]> for &'out mut [ManuallyDrop<T>] {
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: &'out mut [ManuallyDrop<T>])
-> OutSlice<'out, T>
{
self.into()
}
}

macro_rules! impl_arrays {( $($N:tt)* ) => ($(
impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>; $N] {
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
-> OutSlice<'out, T>
{
From::from(&mut self[..])
}
}
)*)}

impl_arrays! {
0 1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64
128 256 512 1024
}
67 changes: 67 additions & 0 deletions src/extension_traits/manually_drop_mut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use ::core::mem::ManuallyDrop;

#[cfg(doc)] use crate::Out;

/// An extension trait providing a cast to the [`ManuallyDrop`] type.
///
/// This is useful if you want to use an [`Out`] reference to something that
/// is not `Copy` (potentially because it has drop glue, in which case you
/// either don't mind leaking / skipping that drop glue, or you know you will
/// be manually handling it).
///
/// # Example
///
/// ```rust,compile_fail
/// use ::core::cell::Cell;
/// use ::uninit::prelude::{AsOut, ManuallyDropMut};
///
/// let mut cell = Cell::new(0);
/// cell.as_out::<Cell<_>>().write(Cell::new(42)); // Error, not `Copy`
/// assert_eq!(cell.get(), 42);
/// ```
///
/// ```rust
/// use ::core::cell::Cell;
/// use ::uninit::prelude::{AsOut, ManuallyDropMut};
///
/// let mut cell = Cell::new(0);
/// cell.manually_drop_mut().as_out::<Cell<_>>().write(Cell::new(42)); // OK
/// assert_eq!(cell.get(), 42);
/// ```
pub
trait ManuallyDropMut {
type Ret : ?Sized;

fn manually_drop_mut (self: &'_ mut Self)
-> &'_ mut Self::Ret
;
}

impl<T> ManuallyDropMut for [T] {
type Ret = [ManuallyDrop<T>];

fn manually_drop_mut<'__> (self: &'__ mut [T])
-> &'__ mut [ManuallyDrop<T>]
{
let len = self.len();
unsafe {
// Safety: `ManuallyDrop<T>` is `#[repr(transparent)]`
::core::slice::from_raw_parts_mut(
self.as_mut_ptr().cast(), len,
)
}
}
}

impl<T> ManuallyDropMut for T {
type Ret = ManuallyDrop<T>;

fn manually_drop_mut<'__> (self: &'__ mut T)
-> &'__ mut ManuallyDrop<T>
{
unsafe {
// Safety: `ManuallyDrop<T>` is `#[repr(transparent)]`
::core::mem::transmute(self)
}
}
}
Loading

0 comments on commit 62a5a14

Please sign in to comment.