wiggle: redo docs for auto borrow checking
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user