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 all commits
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
2 changes: 2 additions & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
rustdocflags = ["-C", "opt-level=2"]
12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[lib]
crate-type = ["rlib"]

[package]
name = "uninit"
version = "0.1.0"
Expand All @@ -18,12 +21,13 @@ license = "MIT"
require_unsafe_in_body = "0.2.0"

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

default = []
default = ["std"]

[package.metadata.docs.rs]
features = [ "nightly" ]
56 changes: 40 additions & 16 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!(x.assume_init(), 42);
}
```

- 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().write(42);
assert_eq!(at_init_x, &42);
```

3. **Type-level upgrade**

Once we know, for sure, that the memory has been initialized, we can
Expand Down Expand Up @@ -151,9 +180,9 @@ pub trait Read {
}
```

that is, there is no way to `.read()` into an unitialized buffer (it would
that is, there is no way to `.read()` into an uninitialized 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::Out`]).

# 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 @@ -174,19 +203,14 @@ So, the objective of this crate is double:

- [`ReadIntoUninit`]

- [`.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.
- [Initialize an uninitialized buffer with `.copy_from_slice()`]

[`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()`]: https://docs.rs/uninit/0.2.0/uninit/extension_traits/trait.MaybeUninitExt.html#tymethod.assume_init_by_ref
[`.assume_init_by_mut()`]: https://docs.rs/uninit/0.2.0/uninit/extension_traits/trait.MaybeUninitExt.html#tymethod.assume_init_by_mut
[`uninit_array!`]: https://docs.rs/uninit/0.2.0/uninit/macro.uninit_byte_array.html
[`Vec::reserve_uninit`]: https://docs.rs/uninit/0.2.0/uninit/extension_traits/trait.VecCapacity.html#tymethod.reserve_uninit
[Initialize an uninitialized buffer with `.copy_from_slice()`]: https://docs.rs/uninit/0.2.0/uninit/out_ref/struct.Out.html#method.copy_from_slice
[`ReadIntoUninit`]: https://docs.rs/uninit/0.2.0/uninit/read/trait.ReadIntoUninit.html
174 changes: 174 additions & 0 deletions src/extension_traits/as_out.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use_prelude!();

use ::core::mem::ManuallyDrop;

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

/// Extension trait to convert a `&mut _` into a `&out ` by calling
/// `.as_out()` 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()` 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`].
///
/// 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().write(42);
///
/// let mut y = ::core::mem::MaybeUninit::uninit();
/// y.as_out().write(42);
/// let y = unsafe { y.assume_init() };
///
/// assert_eq!(x, y);
/// ```
pub
trait AsOut<Pointee : ?Sized> {
#[allow(missing_docs)]
fn as_out<'out> (self: &'out mut Self)
-> Out<'out, Pointee>
;
}
danielhenrymantilla marked this conversation as resolved.
Show resolved Hide resolved

impl<T> AsOut<T> for MaybeUninit<T> {
#[inline]
fn as_out<'out> (self: &'out mut MaybeUninit<T>)
-> Out<'out, T>
{
self.into()
}
}

impl<T> AsOut<T> for T
where
T : Copy,
{
#[inline]
fn as_out<'out> (self: &'out mut T)
-> Out<'out, T>
{
self.into()
}
}

impl<T> AsOut<[T]> for [MaybeUninit<T>] {
#[inline]
fn as_out<'out> (self: &'out mut [MaybeUninit<T>])
-> Out<'out, [T]>
{
self.into()
}
}

impl<T> AsOut<[T]> for [T]
where
T : Copy,
{
#[inline]
fn as_out<'out> (self: &'out mut [T])
-> Out<'out, [T]>
{
self.into()
}
}

impl<T> AsOut<T> for ManuallyDrop<T> {
#[inline]
fn as_out<'out> (self: &'out mut ManuallyDrop<T>)
-> Out<'out, T>
{
self.into()
}
}

impl<T> AsOut<[T]> for [ManuallyDrop<T>] {
#[inline]
fn as_out<'out> (self: &'out mut [ManuallyDrop<T>])
-> Out<'out, [T]>
{
self.into()
}
}

#[cfg(not(feature = "const_generics"))]
const _: () = {
macro_rules! impl_arrays {( $($N:tt)* ) => ($(
impl<T> AsOut<[T]> for [MaybeUninit<T>; $N] {
#[inline]
fn as_out<'out> (self: &'out mut Self)
-> Out<'out, [T]>
{
From::from(&mut self[..])
}
}
impl<T> AsOut<[T]> for [T; $N]
where
T : Copy,
{
#[inline]
fn as_out<'out> (self: &'out mut Self)
-> Out<'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
}
};

#[cfg(feature = "const_generics")]
const _: () = {
#[doc(cfg(feature = "const_generics"))]
impl<T, const N: usize> AsOut<[T]> for [MaybeUninit<T>; N] {
#[inline]
fn as_out<'out> (self: &'out mut Self)
-> Out<'out, [T]>
{
From::from(&mut self[..])
}
}
#[doc(cfg(feature = "const_generics"))]
impl<T, const N: usize> AsOut<[T]> for [T; N]
where
T : Copy,
{
#[inline]
fn as_out<'out> (self: &'out mut Self)
-> Out<'out, [T]>
{
From::from(&mut self[..])
}
}
};
Loading