wiggle: redo docs for auto borrow checking

This commit is contained in:
Pat Hickey
2020-05-19 17:35:16 -07:00
parent c30194dfa1
commit 056a7d0729

View File

@@ -53,37 +53,24 @@ pub use region::Region;
/// must be "somehow nonzero in length" to allow users of `GuestMemory` and /// must be "somehow nonzero in length" to allow users of `GuestMemory` and
/// `GuestPtr` to safely read and write interior data. /// `GuestPtr` to safely read and write interior data.
/// ///
/// # Using Raw Pointers
/// ///
/// Methods like [`GuestMemory::base`] or [`GuestPtr::as_raw`] will return raw /// # Using References
/// pointers to use. Returning raw pointers is significant because it shows
/// there are hazards with using the returned pointers, and they can't blanket
/// be used in a safe fashion. It is possible to use these pointers safely, but
/// any usage needs to uphold a few guarantees.
/// ///
/// * Whenever a `*mut T` is accessed or modified, it must be guaranteed that /// See the safety guarantees of [`BorrowChecker`], which asserts that exactly
/// since the pointer was originally obtained the guest memory wasn't /// one `BorrowChecker` may be constructed for each WebAssembly memory.
/// relocated in any way. This means you can't call back into the guest, call
/// other arbitrary functions which might call into the guest, etc. The
/// problem here is that the guest could execute instructions like
/// `memory.grow` which would invalidate the raw pointer. If, however, after
/// you acquire `*mut T` you only execute your own code and it doesn't touch
/// the guest, then `*mut T` is still guaranteed to point to valid code.
/// ///
/// * Furthermore, Rust's aliasing rules must still be upheld. For example you /// The [`GuestMemory::as_slice`] or [`GuestPtr::as_str`] will return smart
/// can't have two `&mut T` types that point to the area or overlap in any /// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement
/// way. This in particular becomes an issue when you're dealing with multiple /// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references
/// `GuestPtr` types. If you want to simultaneously work with them then you /// into the memory region given by a `GuestMemory`.
/// need to dynamically validate that you're either working with them all in a
/// shared fashion (e.g. as if they were `&T`) or you must verify that they do
/// not overlap to work with them as `&mut T`.
/// ///
/// Note that safely using the raw pointers is relatively difficult. This crate /// These smart pointers are dynamically borrow-checked by the `BorrowChecker`
/// strives to provide utilities to safely work with guest pointers so long as /// passed to the wiggle-generated ABI-level functions. While a `GuestSlice`
/// the previous guarantees are all upheld. If advanced operations are done with /// or a `GuestStr` are live, the [`BorrowChecker::has_outstanding_borrows()`]
/// guest pointers it's recommended to be extremely cautious and thoroughly /// method will always return `true`. If you need to re-enter the guest or
/// consider possible ramifications with respect to this API before codifying /// otherwise read or write to the contents of a WebAssembly memory, all
/// implementation details. /// `GuestSlice`s and `GuestStr`s for the memory must be dropped, at which
/// point `BorrowChecker::has_outstanding_borrows()` will return `false`.
pub unsafe trait GuestMemory { pub unsafe trait GuestMemory {
/// Returns the base allocation of this guest memory, located in host /// Returns the base allocation of this guest memory, located in host
/// memory. /// memory.
@@ -208,8 +195,12 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
/// Note that the type parameter does not need to implement the `Sized` trait, /// Note that the type parameter does not need to implement the `Sized` trait,
/// so you can implement types such as this: /// so you can implement types such as this:
/// ///
/// * `GuestPtr<'_, str>` - a pointer to a guest string /// * `GuestPtr<'_, str>` - a pointer to a guest string. Has the method
/// * `GuestPtr<'_, [T]>` - a pointer to a guest array /// [`GuestPtr::as_str`], which gives a dynamically borrow-checked
/// `GuestStr<'_>`, which `DerefMut`s to a `&mut str`.
/// * `GuestPtr<'_, [T]>` - a pointer to a guest array. Has the method
/// [`GuestPtr::as_slice`], which gives a dynamically borrow-checked
/// `GuestSlice<'_, T>`, which `DerefMut`s to a `&mut [T]`.
/// ///
/// Unsized types such as this may have extra methods and won't have methods /// Unsized types such as this may have extra methods and won't have methods
/// like [`GuestPtr::read`] or [`GuestPtr::write`]. /// like [`GuestPtr::read`] or [`GuestPtr::write`].
@@ -398,23 +389,15 @@ impl<'a, T> GuestPtr<'a, [T]> {
(0..self.len()).map(move |i| base.add(i)) (0..self.len()).map(move |i| base.add(i))
} }
/// Attempts to read a raw `*mut [T]` pointer from this pointer, performing /// Attempts to create a [`GuestSlice<'_, T>`] from this pointer, performing
/// bounds checks and type validation. /// bounds checks and type validation. The `GuestSlice` is a smart pointer
/// The resulting `*mut [T]` can be used as a `&mut [t]` as long as the /// that can be used as a `&[T]` or a `&mut [T]` via the `Deref` and `DerefMut`
/// reference is dropped before any Wasm code is re-entered. /// traits. The region of memory backing the slice will be marked as borrowed
/// by the [`BorrowChecker`] until the `GuestSlice` is dropped.
/// ///
/// This function will return a raw pointer into host memory if all checks /// This function will return a `GuestSlice` into host memory if all checks
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then /// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If
/// `GuestError` will be returned. /// any checks fail then `GuestError` will be returned.
///
/// Note that the `*mut [T]` pointer is still unsafe to use in general, but
/// there are specific situations that it is safe to use. For more
/// information about using the raw pointer, consult the [`GuestMemory`]
/// trait documentation.
///
/// For safety against overlapping mutable borrows, the user must use the
/// same `GuestBorrows` to create all `*mut str` or `*mut [T]` that are alive
/// at the same time.
pub fn as_slice(&self) -> Result<GuestSlice<'a, T>, GuestError> pub fn as_slice(&self) -> Result<GuestSlice<'a, T>, GuestError>
where where
T: GuestTypeTransparent<'a>, T: GuestTypeTransparent<'a>,
@@ -438,12 +421,8 @@ impl<'a, T> GuestPtr<'a, [T]> {
T::validate(unsafe { ptr.add(offs as usize) })?; T::validate(unsafe { ptr.add(offs as usize) })?;
} }
// SAFETY: iff there are no overlapping borrows (all uses of as_raw use this same // SAFETY: iff there are no overlapping borrows it is valid to construct a &mut [T]
// GuestBorrows), its valid to construct a *mut [T] let ptr = unsafe { slice::from_raw_parts_mut(ptr, self.pointer.1 as usize) };
let ptr = unsafe {
let s = slice::from_raw_parts_mut(ptr, self.pointer.1 as usize);
s as *mut [T]
};
Ok(GuestSlice { Ok(GuestSlice {
ptr, ptr,
@@ -504,23 +483,15 @@ impl<'a> GuestPtr<'a, str> {
GuestPtr::new(self.mem, self.bc, self.pointer) GuestPtr::new(self.mem, self.bc, self.pointer)
} }
/// Attempts to read a raw `*mut str` pointer from this pointer, performing /// Attempts to create a [`GuestStr<'_>`] from this pointer, performing
/// bounds checks and utf-8 checks. /// bounds checks and utf-8 checks. The resulting `GuestStr` can be used
/// The resulting `*mut str` can be used as a `&mut str` as long as the /// as a `&str` or `&mut str` via the `Deref` and `DerefMut` traits. The
/// reference is dropped before any Wasm code is re-entered. /// region of memory backing the `str` will be marked as borrowed by the
/// [`BorrowChecker`] until the `GuestStr` is dropped.
/// ///
/// This function will return a raw pointer into host memory if all checks /// This function will return `GuestStr` into host memory if all checks
/// succeed (valid utf-8, valid pointers, etc). If any checks fail then /// succeed (valid utf-8, valid pointers, etc). If any checks fail then
/// `GuestError` will be returned. /// `GuestError` will be returned.
///
/// Note that the `*mut str` pointer is still unsafe to use in general, but
/// there are specific situations that it is safe to use. For more
/// information about using the raw pointer, consult the [`GuestMemory`]
/// trait documentation.
///
/// For safety against overlapping mutable borrows, the user must use the
/// same `GuestBorrows` to create all `*mut str` or `*mut [T]` that are
/// alive at the same time.
pub fn as_str(&self) -> Result<GuestStr<'a>, GuestError> { pub fn as_str(&self) -> Result<GuestStr<'a>, GuestError> {
let ptr = self let ptr = self
.mem .mem
@@ -531,9 +502,10 @@ impl<'a> GuestPtr<'a, str> {
len: self.pointer.1, len: self.pointer.1,
})?; })?;
// SAFETY: iff there are no overlapping borrows (all uses of as_raw use this same // SAFETY: iff there are no overlapping borrows it is ok to construct
// GuestBorrows), its valid to construct a *mut str // a &mut str.
let ptr = unsafe { slice::from_raw_parts_mut(ptr, self.pointer.1 as usize) }; let ptr = unsafe { slice::from_raw_parts_mut(ptr, self.pointer.1 as usize) };
// Validate that contents are utf-8:
match str::from_utf8_mut(ptr) { match str::from_utf8_mut(ptr) {
Ok(ptr) => Ok(GuestStr { Ok(ptr) => Ok(GuestStr {
ptr, ptr,
@@ -559,8 +531,11 @@ impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
} }
} }
/// A smart pointer to a mutable slice in guest memory.
/// Usable as a `&'a [T]` via [`std::ops::Deref`] and as a `&'a mut [T]` via
/// [`std::ops::DerefMut`].
pub struct GuestSlice<'a, T> { pub struct GuestSlice<'a, T> {
ptr: *mut [T], ptr: &'a mut [T],
bc: &'a BorrowChecker, bc: &'a BorrowChecker,
borrow: BorrowHandle, borrow: BorrowHandle,
} }
@@ -568,13 +543,13 @@ pub struct GuestSlice<'a, T> {
impl<'a, T> std::ops::Deref for GuestSlice<'a, T> { impl<'a, T> std::ops::Deref for GuestSlice<'a, T> {
type Target = [T]; type Target = [T];
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref().expect("ptr guaranteed to be non-null") } self.ptr
} }
} }
impl<'a, T> std::ops::DerefMut for GuestSlice<'a, T> { impl<'a, T> std::ops::DerefMut for GuestSlice<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut().expect("ptr guaranteed to be non-null") } self.ptr
} }
} }
@@ -584,8 +559,11 @@ impl<'a, T> Drop for GuestSlice<'a, T> {
} }
} }
/// A smart pointer to a mutable `str` in guest memory.
/// Usable as a `&'a str` via [`std::ops::Deref`] and as a `&'a mut str` via
/// [`std::ops::DerefMut`].
pub struct GuestStr<'a> { pub struct GuestStr<'a> {
ptr: *mut str, ptr: &'a mut str,
bc: &'a BorrowChecker, bc: &'a BorrowChecker,
borrow: BorrowHandle, borrow: BorrowHandle,
} }
@@ -593,13 +571,13 @@ pub struct GuestStr<'a> {
impl<'a> std::ops::Deref for GuestStr<'a> { impl<'a> std::ops::Deref for GuestStr<'a> {
type Target = str; type Target = str;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref().expect("ptr guaranteed to be non-null") } self.ptr
} }
} }
impl<'a> std::ops::DerefMut for GuestStr<'a> { impl<'a> std::ops::DerefMut for GuestStr<'a> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut().expect("ptr guaranteed to be non-null") } self.ptr
} }
} }