Made the Decode of [T; N] properly drop all instances of T when an error has occured

This commit is contained in:
Victor Koenders 2021-10-19 11:26:29 +02:00
parent 151edf46d3
commit 07b3c8cd76
2 changed files with 36 additions and 9 deletions

View File

@ -400,31 +400,55 @@ where
// Safety: this is taken from the example of https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html
// and is therefor assumed to be safe
use core::mem::MaybeUninit;
use core::mem::{ManuallyDrop, MaybeUninit};
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.
let mut data: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
// Dropping a `MaybeUninit` does nothing. Thus using raw pointer
// assignment instead of `ptr::write` does not cause the old
// uninitialized value to be dropped. Also if there is a panic during
// this loop, we have a memory leak, but there is no memory safety
// issue.
for elem in &mut data[..] {
elem.write(T::decode(&mut decoder)?);
for (idx, elem) in data.iter_mut().enumerate() {
match T::decode(&mut decoder) {
Ok(t) => {
elem.write(t);
}
Err(e) => {
// We encountered an error, clean up all previous entries
unsafe {
// Safety: we know we've written up to `idx` items
for item in &mut data[..idx] {
// Safety: We know the `item` is written with a valid value of `T`.
// And we know that `&mut item` won't be used any more after this.
ManuallyDrop::drop(&mut *(item as *mut _ as *mut ManuallyDrop<T>));
}
// make sure `data` isn't dropped twice
core::mem::forget(data);
}
return Err(e);
}
}
}
// Everything is initialized. Transmute the array to the
// initialized type.
// BlockedTODO: https://github.com/rust-lang/rust/issues/61956
// This does not work at the moment:
// let result = unsafe { transmute::<_, [T; N]>(data) };
// Alternatively we can use this:
// BlockedTODO: https://github.com/rust-lang/rust/issues/80908
// Const generics don't work well with transmute at the moment
// The above issue recommends doing the following
// The first issue above recommends doing the following:
let ptr = &mut data as *mut _ as *mut [T; N];
// Safety: we know this is an array with every value written,
// and that the array is valid.
// As well as the original `data` that will be forgotten so we won't get
// multiple (mutable) references to the same memory
let res = unsafe { ptr.read() };
core::mem::forget(data);
Ok(res)

View File

@ -92,6 +92,9 @@ pub fn decode_with_config<'__de, D: de::BorrowDecode<'__de>, C: Config>(
D::borrow_decode(&mut decoder)
}
// TODO: Currently our doctests fail when trying to include the specs because the specs depend on `derive` and `alloc`.
// But we want to have the specs in the docs always
#[cfg(all(feature = "alloc", feature = "derive"))]
pub mod spec {
#![doc = include_str!("../docs/spec.md")]
}