[rust] Add TryClone to standard library Augments Box and CString with a fallible clone method via a new extension trait, TryClone. Bug: 193278128 Change-Id: I8e888f345576a884b048e305d76b921d05d52b28
diff --git a/lib/trusty-std/src/clone.rs b/lib/trusty-std/src/clone.rs new file mode 100644 index 0000000..f09b7fa --- /dev/null +++ b/lib/trusty-std/src/clone.rs
@@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use alloc::alloc::{AllocError, Allocator}; +use alloc::boxed::Box; +use alloc::vec::Vec; + +/// Trait for fallible duplication of types that can be cloned. +/// +/// See the [`Clone`] trait for more details. This trait is identical except +/// that duplication may fail, e.g. if the allocator could not allocate more +/// space for the result. +pub trait TryClone: Sized { + /// Error type when the clone fails + type Error; + + /// Attempt to duplicate the value. + /// + /// See [`Clone::clone()`]. This method may fail with `Self::Error`, which + /// for a heap allocation, generally indicates that space for a duplicate + /// value could not be allocated from the heap. + /// + /// # Examples + /// + /// ``` + /// let value = Box::new("Hello"); + /// + /// let cloned_value = value.try_clone().expect("Box could not be cloned"); + /// ``` + fn try_clone(&self) -> Result<Self, Self::Error>; +} + +impl<T: Clone, A: Allocator + Clone> TryClone for Box<T, A> { + type Error = AllocError; + + #[inline] + fn try_clone(&self) -> Result<Self, Self::Error> { + let mut boxed = Self::try_new_uninit_in(Box::allocator(self).clone())?; + unsafe { + boxed.as_mut_ptr().write((**self).clone()); + Ok(boxed.assume_init()) + } + } +} + +#[inline] +fn try_to_vec<T: TryConvertVec, A: Allocator>(s: &[T], alloc: A) -> Result<Vec<T, A>, AllocError> { + T::try_to_vec(s, alloc) +} + +trait TryConvertVec { + fn try_to_vec<A: Allocator>(s: &[Self], alloc: A) -> Result<Vec<Self, A>, AllocError> + where + Self: Sized; +} + +impl<T: Clone> TryConvertVec for T { + #[inline] + default fn try_to_vec<A: Allocator>(s: &[Self], alloc: A) -> Result<Vec<Self, A>, AllocError> { + struct DropGuard<'a, T, A: Allocator> { + vec: &'a mut Vec<T, A>, + num_init: usize, + } + impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> { + #[inline] + fn drop(&mut self) { + // SAFETY: + // items were marked initialized in the loop below + unsafe { + self.vec.set_len(self.num_init); + } + } + } + let mut vec = Vec::new_in(alloc); + // TODO: replace with try_with_capacity_in when + // https://github.com/rust-lang/rust/pull/86938 lands + vec.try_reserve_exact(s.len()).or(Err(AllocError))?; + let mut guard = DropGuard { vec: &mut vec, num_init: 0 }; + let slots = guard.vec.spare_capacity_mut(); + // .take(slots.len()) is necessary for LLVM to remove bounds checks + // and has better codegen than zip. + for (i, b) in s.iter().enumerate().take(slots.len()) { + guard.num_init = i; + slots[i].write(b.clone()); + } + core::mem::forget(guard); + // SAFETY: + // the vec was allocated and initialized above to at least this length. + unsafe { + vec.set_len(s.len()); + } + Ok(vec) + } +} + +#[cfg(not(no_global_oom_handling))] +impl<T: Copy> TryConvertVec for T { + #[inline] + fn try_to_vec<A: Allocator>(s: &[Self], alloc: A) -> Result<Vec<Self, A>, AllocError> { + let mut v = Vec::new_in(alloc); + // TODO: replace with try_with_capacity_in when + // https://github.com/rust-lang/rust/pull/86938 lands + v.try_reserve_exact(s.len()).or(Err(AllocError))?; + // SAFETY: + // allocated above with the capacity of `s`, and initialize to `s.len()` in + // ptr::copy_to_non_overlapping below. + unsafe { + s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len()); + v.set_len(s.len()); + } + Ok(v) + } +} + +impl<T: Clone, A: Allocator + Clone> TryClone for Box<[T], A> { + type Error = AllocError; + + #[inline] + fn try_clone(&self) -> Result<Self, Self::Error> { + let alloc = Box::allocator(self).clone(); + try_to_vec(&*self, alloc).map(Vec::into_boxed_slice) + } +}
diff --git a/lib/trusty-std/src/ffi/c_str.rs b/lib/trusty-std/src/ffi/c_str.rs index 6912619..6aa73c3 100644 --- a/lib/trusty-std/src/ffi/c_str.rs +++ b/lib/trusty-std/src/ffi/c_str.rs
@@ -37,6 +37,7 @@ //! removed. use crate::alloc::{AllocError, TryAllocInto}; +use crate::TryClone; use alloc::boxed::Box; use alloc::vec::Vec; use core::ascii; @@ -516,6 +517,14 @@ } } +impl TryClone for CString { + type Error = AllocError; + + fn try_clone(&self) -> Result<Self, Self::Error> { + Ok(Self { inner: self.inner.try_clone()? }) + } +} + impl fmt::Debug for CString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f)
diff --git a/lib/trusty-std/src/lib.rs b/lib/trusty-std/src/lib.rs index ed08369..ce63372 100644 --- a/lib/trusty-std/src/lib.rs +++ b/lib/trusty-std/src/lib.rs
@@ -33,20 +33,25 @@ #![feature(alloc_layout_extra)] #![feature(core_intrinsics)] #![feature(lang_items)] +#![feature(maybe_uninit_extra)] // min_specialization is only used to optimize CString::try_new(), so we can // remove it if needed #![feature(min_specialization)] +#![feature(new_uninit)] #![feature(nonnull_slice_from_raw_parts)] #![feature(panic_info_message)] #![feature(rustc_attrs)] #![feature(slice_internals)] #![feature(slice_ptr_get)] +#![feature(vec_spare_capacity)] pub mod alloc; +mod clone; pub mod ffi; pub mod io; mod panicking; mod rt; mod util; +pub use clone::TryClone; pub use core::write;