Merge pull request #1938 from bytecodealliance/pch/factor_borrowchecker_out_of_wiggle
wiggle: factor BorrowChecker concrete implementation to live in engines
This commit is contained in:
@@ -85,7 +85,7 @@ macro_rules! primitives {
|
|||||||
start: offset,
|
start: offset,
|
||||||
len: size,
|
len: size,
|
||||||
};
|
};
|
||||||
if ptr.borrow_checker().is_borrowed(region) {
|
if ptr.mem().is_borrowed(region) {
|
||||||
return Err(GuestError::PtrBorrowed(region));
|
return Err(GuestError::PtrBorrowed(region));
|
||||||
}
|
}
|
||||||
Ok(unsafe { *host_ptr.cast::<Self>() })
|
Ok(unsafe { *host_ptr.cast::<Self>() })
|
||||||
@@ -104,7 +104,7 @@ macro_rules! primitives {
|
|||||||
start: offset,
|
start: offset,
|
||||||
len: size,
|
len: size,
|
||||||
};
|
};
|
||||||
if ptr.borrow_checker().is_borrowed(region) {
|
if ptr.mem().is_borrowed(region) {
|
||||||
return Err(GuestError::PtrBorrowed(region));
|
return Err(GuestError::PtrBorrowed(region));
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
@@ -11,15 +11,12 @@ pub use wiggle_macro::from_witx;
|
|||||||
#[cfg(feature = "wiggle_metadata")]
|
#[cfg(feature = "wiggle_metadata")]
|
||||||
pub use witx;
|
pub use witx;
|
||||||
|
|
||||||
mod borrow;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod guest_type;
|
mod guest_type;
|
||||||
mod region;
|
mod region;
|
||||||
|
|
||||||
pub extern crate tracing;
|
pub extern crate tracing;
|
||||||
|
|
||||||
pub use borrow::BorrowChecker;
|
|
||||||
use borrow::BorrowHandle;
|
|
||||||
pub use error::GuestError;
|
pub use error::GuestError;
|
||||||
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
|
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
|
||||||
pub use region::Region;
|
pub use region::Region;
|
||||||
@@ -55,24 +52,27 @@ 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.
|
||||||
///
|
///
|
||||||
|
/// This type also provides methods for run-time borrow checking of references
|
||||||
|
/// into the memory. The safety of this mechanism depends on there being
|
||||||
|
/// exactly one associated tracking of borrows for a given WebAssembly memory.
|
||||||
|
/// There must be no other reads or writes of WebAssembly the memory by either
|
||||||
|
/// Rust or WebAssembly code while there are any outstanding borrows, as given
|
||||||
|
/// by `GuestMemory::has_outstanding_borrows()`.
|
||||||
///
|
///
|
||||||
/// # Using References
|
/// # Using References
|
||||||
///
|
///
|
||||||
/// See the safety guarantees of [`BorrowChecker`], which asserts that exactly
|
|
||||||
/// one `BorrowChecker` may be constructed for each WebAssembly memory.
|
|
||||||
///
|
|
||||||
/// The [`GuestPtr::as_slice`] or [`GuestPtr::as_str`] will return smart
|
/// The [`GuestPtr::as_slice`] or [`GuestPtr::as_str`] will return smart
|
||||||
/// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement
|
/// pointers [`GuestSlice`] and [`GuestStr`]. These types, which implement
|
||||||
/// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references
|
/// [`std::ops::Deref`] and [`std::ops::DerefMut`], provide mutable references
|
||||||
/// into the memory region given by a `GuestMemory`.
|
/// into the memory region given by a `GuestMemory`.
|
||||||
///
|
///
|
||||||
/// These smart pointers are dynamically borrow-checked by the `BorrowChecker`
|
/// These smart pointers are dynamically borrow-checked by the borrow checker
|
||||||
/// given by [`GuestMemory::borrow_checker()`]. While a `GuestSlice`
|
/// methods on this trait. While a `GuestSlice` or a `GuestStr` are live, the
|
||||||
/// or a `GuestStr` are live, the [`BorrowChecker::has_outstanding_borrows()`]
|
/// [`GuestMemory::has_outstanding_borrows()`] method will always return
|
||||||
/// method will always return `true`. If you need to re-enter the guest or
|
/// `true`. If you need to re-enter the guest or otherwise read or write to
|
||||||
/// otherwise read or write to the contents of a WebAssembly memory, all
|
/// the contents of a WebAssembly memory, all `GuestSlice`s and `GuestStr`s
|
||||||
/// `GuestSlice`s and `GuestStr`s for the memory must be dropped, at which
|
/// for the memory must be dropped, at which point
|
||||||
/// point `BorrowChecker::has_outstanding_borrows()` will return `false`.
|
/// `GuestMemory::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.
|
||||||
@@ -86,11 +86,6 @@ pub unsafe trait GuestMemory {
|
|||||||
/// [`GuestMemory`] documentation.
|
/// [`GuestMemory`] documentation.
|
||||||
fn base(&self) -> (*mut u8, u32);
|
fn base(&self) -> (*mut u8, u32);
|
||||||
|
|
||||||
/// Gives a reference to the [`BorrowChecker`] used to keep track of each
|
|
||||||
/// outstanding borrow of the memory region. [`BorrowChecker::new`] safety
|
|
||||||
/// rules require that exactly one checker exist for each memory region.
|
|
||||||
fn borrow_checker(&self) -> &BorrowChecker;
|
|
||||||
|
|
||||||
/// Validates a guest-relative pointer given various attributes, and returns
|
/// Validates a guest-relative pointer given various attributes, and returns
|
||||||
/// the corresponding host pointer.
|
/// the corresponding host pointer.
|
||||||
///
|
///
|
||||||
@@ -152,16 +147,53 @@ pub unsafe trait GuestMemory {
|
|||||||
{
|
{
|
||||||
GuestPtr::new(self, offset)
|
GuestPtr::new(self, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates whether any outstanding borrows are known to the
|
||||||
|
/// `GuestMemory`. This function must be `false` in order for it to be
|
||||||
|
/// safe to recursively call into a WebAssembly module, or to manipulate
|
||||||
|
/// the WebAssembly memory by any other means.
|
||||||
|
fn has_outstanding_borrows(&self) -> bool;
|
||||||
|
/// Check if a region of linear memory is borrowed. This is called during
|
||||||
|
/// any `GuestPtr::read` or `GuestPtr::write` operation to ensure that
|
||||||
|
/// wiggle is not reading or writing a region of memory which Rust believes
|
||||||
|
/// it has exclusive access to.
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool;
|
||||||
|
/// Borrow a region of linear memory. This is used when constructing a
|
||||||
|
/// `GuestSlice` or `GuestStr`. Those types will give Rust `&mut` access
|
||||||
|
/// to the region of linear memory, therefore, the `GuestMemory` impl must
|
||||||
|
/// guarantee that at most one `BorrowHandle` is issued to a given region,
|
||||||
|
/// `GuestMemory::has_outstanding_borrows` is true for the duration of the
|
||||||
|
/// borrow, and that `GuestMemory::is_borrowed` of any overlapping region
|
||||||
|
/// is false for the duration of the borrow.
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
|
||||||
|
/// Unborrow a previously borrowed region. As long as `GuestSlice` and
|
||||||
|
/// `GuestStr` are implemented correctly, a `BorrowHandle` should only be
|
||||||
|
/// unborrowed once.
|
||||||
|
fn unborrow(&self, h: BorrowHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forwarding trait implementations to the original type
|
/// A handle to a borrow on linear memory. It is produced by `borrow` and
|
||||||
|
/// consumed by `unborrow`. Only the `GuestMemory` impl should ever construct
|
||||||
|
/// a `BorrowHandle` or inspect its contents.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct BorrowHandle(pub usize);
|
||||||
|
|
||||||
|
// Forwarding trait implementations to the original type
|
||||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
T::base(self)
|
T::base(self)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
T::borrow_checker(self)
|
T::has_outstanding_borrows(self)
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
T::is_borrowed(self, r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::borrow(self, r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
T::unborrow(self, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +201,17 @@ unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
|
|||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
T::base(self)
|
T::base(self)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
T::borrow_checker(self)
|
T::has_outstanding_borrows(self)
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
T::is_borrowed(self, r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::borrow(self, r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
T::unborrow(self, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +219,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
|
|||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
T::base(self)
|
T::base(self)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
T::borrow_checker(self)
|
T::has_outstanding_borrows(self)
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
T::is_borrowed(self, r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::borrow(self, r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
T::unborrow(self, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +237,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Rc<T> {
|
|||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
T::base(self)
|
T::base(self)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
T::borrow_checker(self)
|
T::has_outstanding_borrows(self)
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
T::is_borrowed(self, r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::borrow(self, r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
T::unborrow(self, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,8 +255,17 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
|
|||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
T::base(self)
|
T::base(self)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
T::borrow_checker(self)
|
T::has_outstanding_borrows(self)
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
T::is_borrowed(self, r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::borrow(self, r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
T::unborrow(self, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,11 +350,6 @@ impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
|
|||||||
self.mem
|
self.mem
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the borrow checker that this pointer uses
|
|
||||||
pub fn borrow_checker(&self) -> &'a BorrowChecker {
|
|
||||||
self.mem.borrow_checker()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Casts this `GuestPtr` type to a different type.
|
/// Casts this `GuestPtr` type to a different type.
|
||||||
///
|
///
|
||||||
/// This is a safe method which is useful for simply reinterpreting the type
|
/// This is a safe method which is useful for simply reinterpreting the type
|
||||||
@@ -409,7 +472,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
|||||||
/// bounds checks and type validation. The `GuestSlice` is a smart pointer
|
/// bounds checks and type validation. The `GuestSlice` is a smart pointer
|
||||||
/// that can be used as a `&[T]` or a `&mut [T]` via the `Deref` and `DerefMut`
|
/// that can be used as a `&[T]` or a `&mut [T]` via the `Deref` and `DerefMut`
|
||||||
/// traits. The region of memory backing the slice will be marked as borrowed
|
/// traits. The region of memory backing the slice will be marked as borrowed
|
||||||
/// by the [`BorrowChecker`] until the `GuestSlice` is dropped.
|
/// by the [`GuestMemory`] until the `GuestSlice` is dropped.
|
||||||
///
|
///
|
||||||
/// This function will return a `GuestSlice` into host memory if all checks
|
/// This function will return a `GuestSlice` into host memory if all checks
|
||||||
/// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If
|
/// succeed (valid utf-8, valid pointers, memory is not borrowed, etc). If
|
||||||
@@ -426,7 +489,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
|||||||
self.mem
|
self.mem
|
||||||
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
||||||
|
|
||||||
let borrow = self.mem.borrow_checker().borrow(Region {
|
let borrow = self.mem.borrow(Region {
|
||||||
start: self.pointer.0,
|
start: self.pointer.0,
|
||||||
len,
|
len,
|
||||||
})?;
|
})?;
|
||||||
@@ -442,7 +505,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
|||||||
|
|
||||||
Ok(GuestSlice {
|
Ok(GuestSlice {
|
||||||
ptr,
|
ptr,
|
||||||
bc: self.mem.borrow_checker(),
|
mem: self.mem,
|
||||||
borrow,
|
borrow,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -509,7 +572,7 @@ impl<'a> GuestPtr<'a, str> {
|
|||||||
/// bounds checks and utf-8 checks. The resulting `GuestStr` can be used
|
/// bounds checks and utf-8 checks. The resulting `GuestStr` can be used
|
||||||
/// as a `&str` or `&mut str` via the `Deref` and `DerefMut` traits. The
|
/// as a `&str` or `&mut str` via the `Deref` and `DerefMut` traits. The
|
||||||
/// region of memory backing the `str` will be marked as borrowed by the
|
/// region of memory backing the `str` will be marked as borrowed by the
|
||||||
/// [`BorrowChecker`] until the `GuestStr` is dropped.
|
/// [`GuestMemory`] until the `GuestStr` is dropped.
|
||||||
///
|
///
|
||||||
/// This function will return `GuestStr` 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
|
||||||
@@ -519,7 +582,7 @@ impl<'a> GuestPtr<'a, str> {
|
|||||||
.mem
|
.mem
|
||||||
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
||||||
|
|
||||||
let borrow = self.mem.borrow_checker().borrow(Region {
|
let borrow = self.mem.borrow(Region {
|
||||||
start: self.pointer.0,
|
start: self.pointer.0,
|
||||||
len: self.pointer.1,
|
len: self.pointer.1,
|
||||||
})?;
|
})?;
|
||||||
@@ -531,7 +594,7 @@ impl<'a> GuestPtr<'a, str> {
|
|||||||
match str::from_utf8_mut(ptr) {
|
match str::from_utf8_mut(ptr) {
|
||||||
Ok(ptr) => Ok(GuestStr {
|
Ok(ptr) => Ok(GuestStr {
|
||||||
ptr,
|
ptr,
|
||||||
bc: self.mem.borrow_checker(),
|
mem: self.mem,
|
||||||
borrow,
|
borrow,
|
||||||
}),
|
}),
|
||||||
Err(e) => Err(GuestError::InvalidUtf8(e)),
|
Err(e) => Err(GuestError::InvalidUtf8(e)),
|
||||||
@@ -566,7 +629,7 @@ impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
|
|||||||
/// [`std::ops::DerefMut`].
|
/// [`std::ops::DerefMut`].
|
||||||
pub struct GuestSlice<'a, T> {
|
pub struct GuestSlice<'a, T> {
|
||||||
ptr: &'a mut [T],
|
ptr: &'a mut [T],
|
||||||
bc: &'a BorrowChecker,
|
mem: &'a dyn GuestMemory,
|
||||||
borrow: BorrowHandle,
|
borrow: BorrowHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +648,7 @@ impl<'a, T> std::ops::DerefMut for GuestSlice<'a, T> {
|
|||||||
|
|
||||||
impl<'a, T> Drop for GuestSlice<'a, T> {
|
impl<'a, T> Drop for GuestSlice<'a, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.bc.unborrow(self.borrow)
|
self.mem.unborrow(self.borrow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,7 +657,7 @@ impl<'a, T> Drop for GuestSlice<'a, T> {
|
|||||||
/// [`std::ops::DerefMut`].
|
/// [`std::ops::DerefMut`].
|
||||||
pub struct GuestStr<'a> {
|
pub struct GuestStr<'a> {
|
||||||
ptr: &'a mut str,
|
ptr: &'a mut str,
|
||||||
bc: &'a BorrowChecker,
|
mem: &'a dyn GuestMemory,
|
||||||
borrow: BorrowHandle,
|
borrow: BorrowHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +676,7 @@ impl<'a> std::ops::DerefMut for GuestStr<'a> {
|
|||||||
|
|
||||||
impl<'a> Drop for GuestStr<'a> {
|
impl<'a> Drop for GuestStr<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.bc.unborrow(self.borrow)
|
self.mem.unborrow(self.borrow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use crate::error::GuestError;
|
|
||||||
use crate::region::Region;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use wiggle::{BorrowHandle, GuestError, Region};
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct BorrowHandle(usize);
|
|
||||||
|
|
||||||
pub struct BorrowChecker {
|
pub struct BorrowChecker {
|
||||||
bc: RefCell<InnerBorrowChecker>,
|
bc: RefCell<InnerBorrowChecker>,
|
||||||
@@ -16,11 +12,7 @@ impl BorrowChecker {
|
|||||||
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
||||||
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
||||||
/// access memory with an outstanding borrow.
|
/// access memory with an outstanding borrow.
|
||||||
/// The safety of this mechanism depends on creating exactly one `BorrowChecker` per
|
pub fn new() -> Self {
|
||||||
/// WebAssembly memory. There must be no other reads or writes of WebAssembly the memory by
|
|
||||||
/// either Rust or WebAssembly code while there are any outstanding borrows, as given by
|
|
||||||
/// `BorrowChecker::has_outstanding_borrows()`.
|
|
||||||
pub unsafe fn new() -> Self {
|
|
||||||
BorrowChecker {
|
BorrowChecker {
|
||||||
bc: RefCell::new(InnerBorrowChecker::new()),
|
bc: RefCell::new(InnerBorrowChecker::new()),
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::marker;
|
use std::marker;
|
||||||
use wiggle::{BorrowChecker, GuestMemory};
|
use wiggle::{BorrowHandle, GuestMemory, Region};
|
||||||
|
|
||||||
|
mod borrow;
|
||||||
|
use borrow::BorrowChecker;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MemAreas(Vec<MemArea>);
|
pub struct MemAreas(Vec<MemArea>);
|
||||||
@@ -57,7 +60,7 @@ impl HostMemory {
|
|||||||
buffer: HostBuffer {
|
buffer: HostBuffer {
|
||||||
cell: UnsafeCell::new([0; 4096]),
|
cell: UnsafeCell::new([0; 4096]),
|
||||||
},
|
},
|
||||||
bc: unsafe { BorrowChecker::new() },
|
bc: BorrowChecker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +122,17 @@ unsafe impl GuestMemory for HostMemory {
|
|||||||
((*ptr).as_mut_ptr(), (*ptr).len() as u32)
|
((*ptr).as_mut_ptr(), (*ptr).len() as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
&self.bc
|
self.bc.has_outstanding_borrows()
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
self.bc.is_borrowed(r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.borrow(r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
self.bc.unborrow(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,13 +212,7 @@ fn generate_func(
|
|||||||
#handle_early_error
|
#handle_early_error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Wiggle does not expose any methods for functions to re-enter the WebAssembly
|
let mem = #runtime::WasmtimeGuestMemory::new(mem);
|
||||||
// instance, or expose the memory via non-wiggle mechanisms. However, the
|
|
||||||
// user-defined code may end up re-entering the instance, in which case this
|
|
||||||
// is an incorrect implementation - we require exactly one BorrowChecker exist
|
|
||||||
// per instance.
|
|
||||||
let bc = #runtime::BorrowChecker::new();
|
|
||||||
let mem = #runtime::WasmtimeGuestMemory::new( mem, bc );
|
|
||||||
#target_module::#name_ident(
|
#target_module::#name_ident(
|
||||||
&mut my_cx.borrow_mut(),
|
&mut my_cx.borrow_mut(),
|
||||||
&mem,
|
&mem,
|
||||||
|
|||||||
188
crates/wiggle/wasmtime/src/borrow.rs
Normal file
188
crates/wiggle/wasmtime/src/borrow.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use wiggle::{BorrowHandle, GuestError, Region};
|
||||||
|
|
||||||
|
pub struct BorrowChecker {
|
||||||
|
bc: RefCell<InnerBorrowChecker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorrowChecker {
|
||||||
|
/// A `BorrowChecker` manages run-time validation of borrows from a `GuestMemory`. It keeps
|
||||||
|
/// track of regions of guest memory which are possible to alias with Rust references (via the
|
||||||
|
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
||||||
|
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
||||||
|
/// access memory with an outstanding borrow.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
BorrowChecker {
|
||||||
|
bc: RefCell::new(InnerBorrowChecker::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Indicates whether any outstanding borrows are known to the `BorrowChecker`. This function
|
||||||
|
/// must be `false` in order for it to be safe to recursively call into a WebAssembly module,
|
||||||
|
/// or to manipulate the WebAssembly memory by any other means.
|
||||||
|
pub fn has_outstanding_borrows(&self) -> bool {
|
||||||
|
self.bc.borrow().has_outstanding_borrows()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.borrow_mut().borrow(r)
|
||||||
|
}
|
||||||
|
pub(crate) fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
self.bc.borrow_mut().unborrow(h)
|
||||||
|
}
|
||||||
|
pub(crate) fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
self.bc.borrow().is_borrowed(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// This is a pretty naive way to account for borrows. This datastructure
|
||||||
|
/// could be made a lot more efficient with some effort.
|
||||||
|
struct InnerBorrowChecker {
|
||||||
|
/// Map from handle to region borrowed. A HashMap is probably not ideal
|
||||||
|
/// for this but it works. It would be more efficient if we could
|
||||||
|
/// check `is_borrowed` without an O(n) iteration, by organizing borrows
|
||||||
|
/// by an ordering of Region.
|
||||||
|
borrows: HashMap<BorrowHandle, Region>,
|
||||||
|
/// Handle to give out for the next borrow. This is the bare minimum of
|
||||||
|
/// bookkeeping of free handles, and in a pathological case we could run
|
||||||
|
/// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
|
||||||
|
next_handle: BorrowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerBorrowChecker {
|
||||||
|
fn new() -> Self {
|
||||||
|
InnerBorrowChecker {
|
||||||
|
borrows: HashMap::new(),
|
||||||
|
next_handle: BorrowHandle(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
|
!self.borrows.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
!self.borrows.values().all(|b| !b.overlaps(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
|
||||||
|
// Reset handles to 0 if all handles have been returned.
|
||||||
|
if self.borrows.is_empty() {
|
||||||
|
self.next_handle = BorrowHandle(0);
|
||||||
|
}
|
||||||
|
let h = self.next_handle;
|
||||||
|
// Get the next handle. Since we don't recycle handles until all of
|
||||||
|
// them have been returned, there is a pathological case where a user
|
||||||
|
// may make a Very Large (usize::MAX) number of valid borrows and
|
||||||
|
// unborrows while always keeping at least one borrow outstanding, and
|
||||||
|
// we will run out of borrow handles.
|
||||||
|
self.next_handle = BorrowHandle(
|
||||||
|
h.0.checked_add(1)
|
||||||
|
.ok_or_else(|| GuestError::BorrowCheckerOutOfHandles)?,
|
||||||
|
);
|
||||||
|
Ok(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
if self.is_borrowed(r) {
|
||||||
|
return Err(GuestError::PtrBorrowed(r));
|
||||||
|
}
|
||||||
|
let h = self.new_handle()?;
|
||||||
|
self.borrows.insert(h, r);
|
||||||
|
Ok(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unborrow(&mut self, h: BorrowHandle) {
|
||||||
|
let _ = self.borrows.remove(&h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn nonoverlapping() {
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(0, 10);
|
||||||
|
let r2 = Region::new(10, 10);
|
||||||
|
assert!(!r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
bs.borrow(r2).expect("can borrow r2");
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(10, 10);
|
||||||
|
let r2 = Region::new(0, 10);
|
||||||
|
assert!(!r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
bs.borrow(r2).expect("can borrow r2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlapping() {
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(0, 10);
|
||||||
|
let r2 = Region::new(9, 10);
|
||||||
|
assert!(r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(0, 10);
|
||||||
|
let r2 = Region::new(2, 5);
|
||||||
|
assert!(r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(9, 10);
|
||||||
|
let r2 = Region::new(0, 10);
|
||||||
|
assert!(r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(2, 5);
|
||||||
|
let r2 = Region::new(0, 10);
|
||||||
|
assert!(r1.overlaps(r2));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(2, 5);
|
||||||
|
let r2 = Region::new(10, 5);
|
||||||
|
let r3 = Region::new(15, 5);
|
||||||
|
let r4 = Region::new(0, 10);
|
||||||
|
assert!(r1.overlaps(r4));
|
||||||
|
bs.borrow(r1).expect("can borrow r1");
|
||||||
|
bs.borrow(r2).expect("can borrow r2");
|
||||||
|
bs.borrow(r3).expect("can borrow r3");
|
||||||
|
assert!(bs.borrow(r4).is_err(), "cant borrow r4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unborrowing() {
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(0, 10);
|
||||||
|
let r2 = Region::new(10, 10);
|
||||||
|
assert!(!r1.overlaps(r2));
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
||||||
|
let h1 = bs.borrow(r1).expect("can borrow r1");
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
||||||
|
let h2 = bs.borrow(r2).expect("can borrow r2");
|
||||||
|
|
||||||
|
assert!(bs.borrow(r2).is_err(), "can't borrow r2 twice");
|
||||||
|
bs.unborrow(h2);
|
||||||
|
assert_eq!(
|
||||||
|
bs.has_outstanding_borrows(),
|
||||||
|
true,
|
||||||
|
"h1 is still outstanding"
|
||||||
|
);
|
||||||
|
bs.unborrow(h1);
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
||||||
|
|
||||||
|
let _h3 = bs
|
||||||
|
.borrow(r2)
|
||||||
|
.expect("can borrow r2 again now that its been unborrowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
pub use wasmtime_wiggle_macro::*;
|
pub use wasmtime_wiggle_macro::*;
|
||||||
pub use wiggle::*;
|
pub use wiggle::*;
|
||||||
|
|
||||||
|
mod borrow;
|
||||||
|
|
||||||
|
use borrow::BorrowChecker;
|
||||||
|
|
||||||
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
|
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
|
||||||
/// `wiggle::GuestMemory` trait on it.
|
/// `wiggle::GuestMemory` trait on it.
|
||||||
pub struct WasmtimeGuestMemory {
|
pub struct WasmtimeGuestMemory {
|
||||||
@@ -9,8 +13,20 @@ pub struct WasmtimeGuestMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WasmtimeGuestMemory {
|
impl WasmtimeGuestMemory {
|
||||||
pub fn new(mem: wasmtime::Memory, bc: BorrowChecker) -> Self {
|
pub fn new(mem: wasmtime::Memory) -> Self {
|
||||||
Self { mem, bc }
|
Self {
|
||||||
|
mem,
|
||||||
|
// Wiggle does not expose any methods for functions to re-enter
|
||||||
|
// the WebAssembly instance, or expose the memory via non-wiggle
|
||||||
|
// mechanisms. However, the user-defined code may end up
|
||||||
|
// re-entering the instance, in which case this is an incorrect
|
||||||
|
// implementation - we require exactly one BorrowChecker exist per
|
||||||
|
// instance.
|
||||||
|
// This BorrowChecker construction is a holdover until it is
|
||||||
|
// integrated fully with wasmtime:
|
||||||
|
// https://github.com/bytecodealliance/wasmtime/issues/1917
|
||||||
|
bc: BorrowChecker::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +34,16 @@ unsafe impl GuestMemory for WasmtimeGuestMemory {
|
|||||||
fn base(&self) -> (*mut u8, u32) {
|
fn base(&self) -> (*mut u8, u32) {
|
||||||
(self.mem.data_ptr(), self.mem.data_size() as _)
|
(self.mem.data_ptr(), self.mem.data_size() as _)
|
||||||
}
|
}
|
||||||
fn borrow_checker(&self) -> &BorrowChecker {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
&self.bc
|
self.bc.has_outstanding_borrows()
|
||||||
|
}
|
||||||
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
|
self.bc.is_borrowed(r)
|
||||||
|
}
|
||||||
|
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.borrow(r)
|
||||||
|
}
|
||||||
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
|
self.bc.unborrow(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user