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

Fixed unsoundness of using &mut MaybeUninit<T> as the out type for … #1

Merged
merged 5 commits into from
Feb 29, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
danielhenrymantilla marked this conversation as resolved.
Show resolved Hide resolved
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
;
}
danielhenrymantilla marked this conversation as resolved.
Show resolved Hide resolved

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)* ) => ($(
danielhenrymantilla marked this conversation as resolved.
Show resolved Hide resolved
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