Rewrite for recursive safety
This commit rewrites the runtime crate to provide safety in the face of recursive calls to the guest. The basic principle is that `GuestMemory` is now a trait which dynamically returns the pointer/length pair. This also has an implicit contract (hence the `unsafe` trait) that the pointer/length pair point to a valid list of bytes in host memory "until something is reentrant". After this changes the various suite of `Guest*` types were rewritten. `GuestRef` and `GuestRefMut` were both removed since they cannot safely exist. The `GuestPtrMut` type was removed for simplicity, and the final `GuestPtr` type subsumes `GuestString` and `GuestArray`. This means that there's only one guest pointer type, `GuestPtr<'a, T>`, where `'a` is the borrow into host memory, basically borrowing the `GuestMemory` trait object itself. Some core utilities are exposed on `GuestPtr`, but they're all 100% safe. Unsafety is now entirely contained within a few small locations: * Implementations of the `GuestType` for primitive types (e.g. `i8`, `u8`, etc) use `unsafe` to read/write memory. The `unsafe` trait of `GuestMemory` though should prove that they're safe. * `GuestPtr<'_, str>` has a method which validates utf-8 contents, and this requires `unsafe` internally to read all the bytes. This is guaranteed to be safe however given the contract of `GuestMemory`. And that's it! Everything else is a bunch of safe combinators all built up on the various utilities provided by `GuestPtr`. The general idioms are roughly the same as before, with various tweaks here and there. A summary of expected idioms are: * For small values you'd `.read()` or `.write()` very quickly. You'd pass around the type itself. * For strings, you'd pass `GuestPtr<'_, str>` down to the point where it's actually consumed. At that moment you'd either decide to copy it out (a safe operation) or you'd get a raw view to the string (an unsafe operation) and assert that you won't call back into wasm while you're holding that pointer. * Arrays are similar to strings, passing around `GuestPtr<'_, [T]>`. Arrays also have a `iter()` method which yields an iterator of `GuestPtr<'_, T>` for convenience. Overall there's still a lot of missing documentation on the runtime crate specifically around the safety of the `GuestMemory` trait as well as how the utilities/methods are expected to be used. Additionally there's utilities which aren't currently implemented which would be easy to implement. For example there's no method to copy out a string or a slice, although that would be pretty easy to add. In any case I'm curious to get feedback on this approach and see what y'all think!
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::region::Region;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BorrowHandle(usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GuestBorrows {
|
||||
immutable: HashMap<BorrowHandle, Region>,
|
||||
mutable: HashMap<BorrowHandle, Region>,
|
||||
next_handle: BorrowHandle,
|
||||
}
|
||||
|
||||
impl GuestBorrows {
|
||||
pub fn new() -> Self {
|
||||
GuestBorrows {
|
||||
immutable: HashMap::new(),
|
||||
mutable: HashMap::new(),
|
||||
next_handle: BorrowHandle(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_borrowed_immut(&self, r: Region) -> bool {
|
||||
!self.immutable.values().all(|b| !b.overlaps(r))
|
||||
}
|
||||
|
||||
fn is_borrowed_mut(&self, r: Region) -> bool {
|
||||
!self.mutable.values().all(|b| !b.overlaps(r))
|
||||
}
|
||||
|
||||
fn new_handle(&mut self) -> BorrowHandle {
|
||||
let h = self.next_handle;
|
||||
self.next_handle = BorrowHandle(h.0 + 1);
|
||||
h
|
||||
}
|
||||
|
||||
pub fn borrow_immut(&mut self, r: Region) -> Option<BorrowHandle> {
|
||||
if self.is_borrowed_mut(r) {
|
||||
return None;
|
||||
}
|
||||
let h = self.new_handle();
|
||||
self.immutable.insert(h, r);
|
||||
Some(h)
|
||||
}
|
||||
|
||||
pub fn unborrow_immut(&mut self, h: BorrowHandle) {
|
||||
self.immutable
|
||||
.remove(&h)
|
||||
.expect("handle exists in immutable borrows");
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&mut self, r: Region) -> Option<BorrowHandle> {
|
||||
if self.is_borrowed_immut(r) || self.is_borrowed_mut(r) {
|
||||
return None;
|
||||
}
|
||||
let h = self.new_handle();
|
||||
self.mutable.insert(h, r);
|
||||
Some(h)
|
||||
}
|
||||
|
||||
pub fn unborrow_mut(&mut self, h: BorrowHandle) {
|
||||
self.mutable
|
||||
.remove(&h)
|
||||
.expect("handle exists in mutable borrows");
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,80 @@
|
||||
use crate::{GuestError, GuestPtr, GuestPtrMut};
|
||||
|
||||
pub trait GuestType<'a>: Sized + Clone {
|
||||
// These are morally the same as Rust ::std::mem::size_of / align_of, but they return
|
||||
// a u32 because the wasm memory space is 32 bits. They have a different names so they
|
||||
// don't collide with the std::mem methods.
|
||||
fn size() -> u32;
|
||||
fn align() -> u32;
|
||||
fn name() -> String;
|
||||
fn validate(location: &GuestPtr<'a, Self>) -> Result<(), GuestError>;
|
||||
fn read(location: &GuestPtr<'a, Self>) -> Result<Self, GuestError>;
|
||||
fn write(&self, location: &GuestPtrMut<'a, Self>);
|
||||
}
|
||||
|
||||
/// Represents any guest type which can transparently be represented
|
||||
/// as a host type.
|
||||
pub trait GuestTypeTransparent<'a>: GuestType<'a> + Copy {}
|
||||
|
||||
macro_rules! builtin_type {
|
||||
( $( $t:ident ), * ) => {
|
||||
$(
|
||||
impl<'a> GuestType<'a> for $t {
|
||||
fn size() -> u32 {
|
||||
::std::mem::size_of::<$t>() as u32
|
||||
}
|
||||
fn align() -> u32 {
|
||||
::std::mem::align_of::<$t>() as u32
|
||||
}
|
||||
fn name() -> String {
|
||||
::std::stringify!($t).to_owned()
|
||||
}
|
||||
fn validate(_ptr: &GuestPtr<$t>) -> Result<(), GuestError> {
|
||||
Ok(())
|
||||
}
|
||||
fn read(location: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
Ok(*location.as_ref()?)
|
||||
}
|
||||
fn write(&self, location: &GuestPtrMut<'a, Self>) {
|
||||
unsafe { (location.as_raw() as *mut $t).write(*self) };
|
||||
}
|
||||
}
|
||||
impl<'a> GuestTypeTransparent<'a> for $t {}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// These definitions correspond to all the witx BuiltinType variants that are Copy:
|
||||
builtin_type!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, usize);
|
||||
use crate::{GuestError, GuestPtr};
|
||||
use std::mem;
|
||||
|
||||
pub trait GuestErrorType {
|
||||
type Context;
|
||||
fn success() -> Self;
|
||||
fn from_error(e: GuestError, ctx: &Self::Context) -> Self;
|
||||
}
|
||||
|
||||
pub trait GuestType<'a>: Sized {
|
||||
fn guest_size() -> u32;
|
||||
fn guest_align() -> usize;
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError>;
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError>;
|
||||
}
|
||||
|
||||
macro_rules! primitives {
|
||||
($($i:ident)*) => ($(
|
||||
impl<'a> GuestType<'a> for $i {
|
||||
fn guest_size() -> u32 { mem::size_of::<Self>() as u32 }
|
||||
fn guest_align() -> usize { mem::align_of::<Self>() }
|
||||
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
|
||||
// Any bit pattern for any primitive implemented with this
|
||||
// macro is safe, so our `as_raw` method will guarantee that if
|
||||
// we are given a pointer it's valid for the size of our type
|
||||
// as well as properly aligned. Consequently we should be able
|
||||
// to safely ready the pointer just after we validated it,
|
||||
// returning it along here.
|
||||
let host_ptr = ptr.mem().validate_size_align(
|
||||
ptr.offset(),
|
||||
Self::guest_align(),
|
||||
Self::guest_size(),
|
||||
)?;
|
||||
Ok(unsafe { *host_ptr.cast::<Self>() })
|
||||
}
|
||||
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError> {
|
||||
let host_ptr = ptr.mem().validate_size_align(
|
||||
ptr.offset(),
|
||||
Self::guest_align(),
|
||||
Self::guest_size(),
|
||||
)?;
|
||||
// Similar to above `as_raw` will do a lot of validation, and
|
||||
// then afterwards we can safely write our value into the
|
||||
// memory location.
|
||||
unsafe {
|
||||
*host_ptr.cast::<Self>() = val;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
primitives! {
|
||||
i8 i16 i32 i64 i128 isize
|
||||
u8 u16 u32 u64 u128 usize
|
||||
f32 f64
|
||||
}
|
||||
|
||||
// Support pointers-to-pointers where pointers are always 32-bits in wasm land
|
||||
impl<'a, T> GuestType<'a> for GuestPtr<'a, T> {
|
||||
fn guest_size() -> u32 {
|
||||
u32::guest_size()
|
||||
}
|
||||
fn guest_align() -> usize {
|
||||
u32::guest_align()
|
||||
}
|
||||
|
||||
fn read(ptr: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
let offset = ptr.cast::<u32>().read()?;
|
||||
Ok(GuestPtr::new(ptr.mem(), offset))
|
||||
}
|
||||
|
||||
fn write(ptr: &GuestPtr<'_, Self>, val: Self) -> Result<(), GuestError> {
|
||||
ptr.cast::<u32>().write(val.offset())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,217 @@
|
||||
mod borrow;
|
||||
use std::cell::Cell;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
use std::marker;
|
||||
use std::fmt;
|
||||
|
||||
mod error;
|
||||
mod guest_type;
|
||||
mod memory;
|
||||
mod region;
|
||||
|
||||
pub use error::GuestError;
|
||||
pub use guest_type::{GuestErrorType, GuestType, GuestTypeTransparent};
|
||||
pub use memory::{
|
||||
GuestArray, GuestMemory, GuestPtr, GuestPtrMut, GuestRef, GuestRefMut, GuestString,
|
||||
GuestStringRef,
|
||||
};
|
||||
pub use guest_type::{GuestErrorType, GuestType};
|
||||
pub use region::Region;
|
||||
|
||||
pub unsafe trait GuestMemory {
|
||||
fn base(&self) -> (*mut u8, u32);
|
||||
|
||||
fn validate_size_align(
|
||||
&self,
|
||||
offset: u32,
|
||||
align: usize,
|
||||
len: u32,
|
||||
) -> Result<*mut u8, GuestError> {
|
||||
let (base_ptr, base_len) = self.base();
|
||||
let region = Region { start: offset, len };
|
||||
|
||||
// Figure out our pointer to the start of memory
|
||||
let start = match (base_ptr as usize).checked_add(offset as usize) {
|
||||
Some(ptr) => ptr,
|
||||
None => return Err(GuestError::PtrOutOfBounds(region)),
|
||||
};
|
||||
// and use that to figure out the end pointer
|
||||
let end = match start.checked_add(len as usize) {
|
||||
Some(ptr) => ptr,
|
||||
None => return Err(GuestError::PtrOutOfBounds(region)),
|
||||
};
|
||||
// and then verify that our end doesn't reach past the end of our memory
|
||||
if end > (base_ptr as usize) + (base_len as usize) {
|
||||
return Err(GuestError::PtrOutOfBounds(region));
|
||||
}
|
||||
// and finally verify that the alignment is correct
|
||||
if start % align != 0 {
|
||||
return Err(GuestError::PtrNotAligned(region, align as u32));
|
||||
}
|
||||
Ok(start as *mut u8)
|
||||
}
|
||||
|
||||
fn ptr<'a, T>(&'a self, offset: T::Pointer) -> GuestPtr<'a, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: ?Sized + Pointee,
|
||||
{
|
||||
GuestPtr::new(self, offset)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
|
||||
fn base(&self) -> (*mut u8, u32) {
|
||||
T::base(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestPtr<'a, T: ?Sized + Pointee> {
|
||||
mem: &'a (dyn GuestMemory + 'a),
|
||||
pointer: T::Pointer,
|
||||
_marker: marker::PhantomData<&'a Cell<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + Pointee> GuestPtr<'a, T> {
|
||||
pub fn new(mem: &'a (dyn GuestMemory + 'a), pointer: T::Pointer) -> GuestPtr<'_, T> {
|
||||
GuestPtr {
|
||||
mem,
|
||||
pointer,
|
||||
_marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> T::Pointer {
|
||||
self.pointer
|
||||
}
|
||||
|
||||
pub fn mem(&self) -> &'a (dyn GuestMemory + 'a) {
|
||||
self.mem
|
||||
}
|
||||
|
||||
pub fn cast<U>(&self) -> GuestPtr<'a, U>
|
||||
where
|
||||
T: Pointee<Pointer = u32>,
|
||||
{
|
||||
GuestPtr::new(self.mem, self.pointer)
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Result<T, GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
T::read(self)
|
||||
}
|
||||
|
||||
pub fn write(&self, val: T) -> Result<(), GuestError>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
T::write(self, val)
|
||||
}
|
||||
|
||||
pub fn add(&self, amt: u32) -> Result<GuestPtr<'a, T>, GuestError>
|
||||
where T: GuestType<'a> + Pointee<Pointer = u32>,
|
||||
{
|
||||
let offset = amt.checked_mul(T::guest_size())
|
||||
.and_then(|o| self.pointer.checked_add(o));
|
||||
let offset = match offset {
|
||||
Some(o) => o,
|
||||
None => return Err(GuestError::InvalidFlagValue("")),
|
||||
};
|
||||
Ok(GuestPtr::new(self.mem, offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtr<'a, [T]> {
|
||||
pub fn offset_base(&self) -> u32 {
|
||||
self.pointer.0
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u32 {
|
||||
self.pointer.1
|
||||
}
|
||||
|
||||
pub fn iter<'b>(&'b self) -> impl ExactSizeIterator<Item = Result<GuestPtr<'a, T>, GuestError>> + 'b
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
let base = GuestPtr::new(self.mem, self.offset_base());
|
||||
(0..self.len()).map(move |i| base.add(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestPtr<'a, str> {
|
||||
pub fn offset_base(&self) -> u32 {
|
||||
self.pointer.0
|
||||
}
|
||||
|
||||
pub fn len(&self) -> u32 {
|
||||
self.pointer.1
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> GuestPtr<'a, [u8]> {
|
||||
GuestPtr::new(self.mem, self.pointer)
|
||||
}
|
||||
|
||||
pub fn as_raw(&self) -> Result<*mut str, GuestError> {
|
||||
let ptr = self.mem.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
||||
|
||||
// TODO: doc unsafety here
|
||||
unsafe {
|
||||
let s = slice::from_raw_parts_mut(ptr, self.pointer.1 as usize);
|
||||
match str::from_utf8_mut(s) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(GuestError::InvalidUtf8(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Pointee> Clone for GuestPtr<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Pointee> Copy for GuestPtr<'_, T> {}
|
||||
|
||||
impl<T: ?Sized + Pointee> fmt::Debug for GuestPtr<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
T::debug(self.pointer, f)
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
impl<T> Sealed for T {}
|
||||
impl<T> Sealed for [T] {}
|
||||
impl Sealed for str {}
|
||||
}
|
||||
|
||||
pub trait Pointee: private::Sealed {
|
||||
#[doc(hidden)]
|
||||
type Pointer: Copy;
|
||||
#[doc(hidden)]
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result;
|
||||
}
|
||||
|
||||
impl<T> Pointee for T {
|
||||
type Pointer = u32;
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "*guest {:#x}", pointer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Pointee for [T] {
|
||||
type Pointer = (u32, u32);
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "*guest {:#x}/{}", pointer.0, pointer.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pointee for str {
|
||||
type Pointer = (u32, u32);
|
||||
fn debug(pointer: Self::Pointer, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
<[u8]>::debug(pointer, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
use super::ptr::{GuestPtr, GuestRef};
|
||||
use crate::{GuestError, GuestType, GuestTypeTransparent};
|
||||
use std::{fmt, ops::Deref};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestArray<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
pub(super) ptr: GuestPtr<'a, T>,
|
||||
pub(super) num_elems: u32,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestArray<'a, T>
|
||||
where
|
||||
T: GuestType<'a> + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestArray {{ ptr: {:?}, num_elems: {:?} }}",
|
||||
self.ptr, self.num_elems
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestArray<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
pub fn iter(&self) -> GuestArrayIter<'a, T> {
|
||||
let next = GuestPtr {
|
||||
mem: self.ptr.mem,
|
||||
region: self.ptr.region,
|
||||
type_: self.ptr.type_,
|
||||
};
|
||||
GuestArrayIter {
|
||||
next,
|
||||
num_elems: self.num_elems,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestArrayIter<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
next: GuestPtr<'a, T>,
|
||||
num_elems: u32,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for GuestArrayIter<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
type Item = Result<GuestPtr<'a, T>, GuestError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.count < self.num_elems {
|
||||
// ok...
|
||||
Some(T::validate(&self.next).and_then(|()| {
|
||||
let curr = GuestPtr {
|
||||
mem: self.next.mem,
|
||||
region: self.next.region,
|
||||
type_: self.next.type_,
|
||||
};
|
||||
self.next = self.next.elem(1)?;
|
||||
self.count += 1;
|
||||
Ok(curr)
|
||||
}))
|
||||
} else {
|
||||
// no more elements...
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestArray<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
pub fn as_ref(&self) -> Result<GuestArrayRef<'a, T>, GuestError> {
|
||||
let mut next = self.ptr.elem(0)?;
|
||||
for _ in 0..self.num_elems {
|
||||
T::validate(&next)?;
|
||||
next = next.elem(1)?;
|
||||
}
|
||||
let region = self.ptr.region.extend(self.num_elems);
|
||||
let handle = {
|
||||
let mut borrows = self.ptr.mem.borrows.borrow_mut();
|
||||
borrows
|
||||
.borrow_immut(region)
|
||||
.ok_or_else(|| GuestError::PtrBorrowed(region))?
|
||||
};
|
||||
let ref_ = GuestRef {
|
||||
mem: self.ptr.mem,
|
||||
region,
|
||||
handle,
|
||||
type_: self.ptr.type_,
|
||||
};
|
||||
Ok(GuestArrayRef {
|
||||
ref_,
|
||||
num_elems: self.num_elems,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestArrayRef<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
ref_: GuestRef<'a, T>,
|
||||
num_elems: u32,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestArrayRef<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a> + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestArrayRef {{ ref_: {:?}, num_elems: {:?} }}",
|
||||
self.ref_, self.num_elems
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for GuestArrayRef<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
self.ref_.as_ptr().as_raw() as *const _,
|
||||
self.num_elems as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
memory::ptr::{GuestPtr, GuestPtrMut},
|
||||
GuestError, GuestMemory, GuestType, Region,
|
||||
};
|
||||
|
||||
#[repr(align(4096))]
|
||||
struct HostMemory {
|
||||
buffer: [u8; 4096],
|
||||
}
|
||||
|
||||
impl HostMemory {
|
||||
pub fn new() -> Self {
|
||||
Self { buffer: [0; 4096] }
|
||||
}
|
||||
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||
self.buffer.as_mut_ptr()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// try extracting an array out of memory bounds
|
||||
let ptr: GuestPtr<i32> = guest_memory.ptr(4092).expect("ptr to last i32 el");
|
||||
let err = ptr.array(2).expect_err("out of bounds ptr error");
|
||||
assert_eq!(err, GuestError::PtrOutOfBounds(Region::new(4092, 8)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptr_to_array() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// write a simple array into memory
|
||||
{
|
||||
let ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(0).expect("ptr mut to first el");
|
||||
let mut el = ptr.as_ref_mut().expect("ref mut to first el");
|
||||
*el = 1;
|
||||
let ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(4).expect("ptr mut to second el");
|
||||
let mut el = ptr.as_ref_mut().expect("ref mu to second el");
|
||||
*el = 2;
|
||||
let ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(8).expect("ptr mut to third el");
|
||||
let mut el = ptr.as_ref_mut().expect("ref mut to third el");
|
||||
*el = 3;
|
||||
}
|
||||
// extract as array
|
||||
let ptr: GuestPtr<i32> = guest_memory.ptr(0).expect("ptr to first el");
|
||||
let arr = ptr.array(3).expect("convert ptr to array");
|
||||
let as_ref = &*arr.as_ref().expect("array borrowed immutably");
|
||||
assert_eq!(as_ref, &[1, 2, 3]);
|
||||
// borrowing again should be fine
|
||||
let as_ref2 = &*arr.as_ref().expect("array borrowed immutably again");
|
||||
assert_eq!(as_ref2, as_ref);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptr_to_ptr_array() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
{
|
||||
let val_ptr: GuestPtrMut<u8> =
|
||||
guest_memory.ptr_mut(0).expect("ptr mut to the first value");
|
||||
let mut val = val_ptr.as_ref_mut().expect("ref mut to the first value");
|
||||
*val = 255;
|
||||
let val_ptr: GuestPtrMut<u8> = guest_memory
|
||||
.ptr_mut(4)
|
||||
.expect("ptr mut to the second value");
|
||||
let mut val = val_ptr.as_ref_mut().expect("ref mut to the second value");
|
||||
*val = 254;
|
||||
let val_ptr: GuestPtrMut<u8> =
|
||||
guest_memory.ptr_mut(8).expect("ptr mut to the third value");
|
||||
let mut val = val_ptr.as_ref_mut().expect("ref mut to the third value");
|
||||
*val = 253;
|
||||
}
|
||||
{
|
||||
let ptr = guest_memory.ptr_mut(12).expect("ptr mut to first el");
|
||||
ptr.write(
|
||||
&guest_memory
|
||||
.ptr::<GuestPtr<u8>>(0)
|
||||
.expect("ptr to the first value"),
|
||||
);
|
||||
let ptr = guest_memory.ptr_mut(16).expect("ptr mut to first el");
|
||||
ptr.write(
|
||||
&guest_memory
|
||||
.ptr::<GuestPtr<u8>>(4)
|
||||
.expect("ptr to the second value"),
|
||||
);
|
||||
let ptr = guest_memory.ptr_mut(20).expect("ptr mut to first el");
|
||||
ptr.write(
|
||||
&guest_memory
|
||||
.ptr::<GuestPtr<u8>>(8)
|
||||
.expect("ptr to the third value"),
|
||||
);
|
||||
}
|
||||
// extract as array
|
||||
let ptr: GuestPtr<GuestPtr<u8>> = guest_memory.ptr(12).expect("ptr to first el");
|
||||
let arr = ptr.array(3).expect("convert ptr to array");
|
||||
let contents = arr
|
||||
.iter()
|
||||
.map(|ptr_ptr| {
|
||||
*GuestType::read(&ptr_ptr.expect("valid ptr to ptr"))
|
||||
.expect("valid ptr to some value")
|
||||
.as_ref()
|
||||
.expect("deref ptr to some value")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(&contents, &[255, 254, 253]);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
mod array;
|
||||
mod ptr;
|
||||
mod string;
|
||||
|
||||
pub use array::*;
|
||||
pub use ptr::*;
|
||||
pub use string::*;
|
||||
|
||||
use crate::{borrow::GuestBorrows, GuestError, GuestType, Region};
|
||||
use std::{cell::RefCell, fmt, marker::PhantomData, rc::Rc};
|
||||
|
||||
pub struct GuestMemory<'a> {
|
||||
ptr: *mut u8,
|
||||
len: u32,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
borrows: Rc<RefCell<GuestBorrows>>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for GuestMemory<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestMemory {{ ptr: {:?}, len: {:?}, borrows: {:?} }}",
|
||||
self.ptr, self.len, self.borrows
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestMemory<'a> {
|
||||
pub fn new(ptr: *mut u8, len: u32) -> Self {
|
||||
assert_eq!(ptr as usize % 4096, 0, "GuestMemory must be page-aligned");
|
||||
Self {
|
||||
ptr,
|
||||
len,
|
||||
lifetime: PhantomData,
|
||||
borrows: Rc::new(RefCell::new(GuestBorrows::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(&self, r: Region) -> bool {
|
||||
r.start < self.len
|
||||
&& r.len < self.len // make sure next clause doesnt underflow
|
||||
&& r.start <= (self.len - r.len)
|
||||
}
|
||||
|
||||
pub fn ptr<T: GuestType<'a>>(&'a self, at: u32) -> Result<GuestPtr<'a, T>, GuestError> {
|
||||
let region = Region {
|
||||
start: at,
|
||||
len: T::size(),
|
||||
};
|
||||
if !self.contains(region) {
|
||||
Err(GuestError::PtrOutOfBounds(region))?;
|
||||
}
|
||||
if at % T::align() != 0 {
|
||||
Err(GuestError::PtrNotAligned(region, T::align()))?;
|
||||
}
|
||||
Ok(GuestPtr {
|
||||
mem: &self,
|
||||
region,
|
||||
type_: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ptr_mut<T: GuestType<'a>>(&'a self, at: u32) -> Result<GuestPtrMut<'a, T>, GuestError> {
|
||||
let ptr = self.ptr(at)?;
|
||||
Ok(GuestPtrMut {
|
||||
mem: ptr.mem,
|
||||
region: ptr.region,
|
||||
type_: ptr.type_,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
use super::{array::GuestArray, GuestMemory};
|
||||
use crate::{borrow::BorrowHandle, GuestError, GuestType, GuestTypeTransparent, Region};
|
||||
use std::{
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestPtr<'a, T> {
|
||||
pub(super) mem: &'a GuestMemory<'a>,
|
||||
pub(super) region: Region,
|
||||
pub(super) type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestPtr<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestPtr {{ mem: {:?}, region: {:?} }}",
|
||||
self.mem, self.region
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: GuestType<'a>> GuestPtr<'a, T> {
|
||||
pub fn as_raw(&self) -> *const u8 {
|
||||
(self.mem.ptr as usize + self.region.start as usize) as *const u8
|
||||
}
|
||||
|
||||
pub fn elem(&self, elements: i32) -> Result<Self, GuestError> {
|
||||
self.mem
|
||||
.ptr(self.region.start + (elements * self.region.len as i32) as u32)
|
||||
}
|
||||
|
||||
pub fn cast<CastTo: GuestType<'a>>(
|
||||
&self,
|
||||
offset: u32,
|
||||
) -> Result<GuestPtr<'a, CastTo>, GuestError> {
|
||||
self.mem.ptr(self.region.start + offset)
|
||||
}
|
||||
|
||||
pub fn array(&self, num_elems: u32) -> Result<GuestArray<'a, T>, GuestError> {
|
||||
let region = self.region.extend(num_elems);
|
||||
if self.mem.contains(region) {
|
||||
let ptr = GuestPtr {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
type_: self.type_,
|
||||
};
|
||||
Ok(GuestArray { ptr, num_elems })
|
||||
} else {
|
||||
Err(GuestError::PtrOutOfBounds(region))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtr<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
pub fn as_ref(&self) -> Result<GuestRef<'a, T>, GuestError> {
|
||||
T::validate(&self)?;
|
||||
let handle = {
|
||||
let mut borrows = self.mem.borrows.borrow_mut();
|
||||
borrows
|
||||
.borrow_immut(self.region)
|
||||
.ok_or_else(|| GuestError::PtrBorrowed(self.region))?
|
||||
};
|
||||
Ok(GuestRef {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
handle,
|
||||
type_: self.type_,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtr<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
pub fn read(&self) -> Result<T, GuestError> {
|
||||
T::read(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestType<'a> for GuestPtr<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
fn size() -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn align() -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
format!("GuestPtr<{}>", T::name())
|
||||
}
|
||||
|
||||
fn validate(location: &GuestPtr<'a, GuestPtr<'a, T>>) -> Result<(), GuestError> {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
let raw_ptr: u32 = unsafe { *(location.as_raw() as *const u32) };
|
||||
// GuestMemory can validate that the raw pointer contents are legal for T:
|
||||
let _guest_ptr: GuestPtr<T> = location.mem.ptr(raw_ptr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Operations for reading and writing Ptrs to memory:
|
||||
fn read(location: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
let raw_ptr: u32 = unsafe { *(location.as_raw() as *const u32) };
|
||||
// GuestMemory can validate that the raw pointer contents are legal for T:
|
||||
let guest_ptr: GuestPtr<'a, T> = location.mem.ptr(raw_ptr)?;
|
||||
Ok(guest_ptr)
|
||||
}
|
||||
|
||||
fn write(&self, location: &GuestPtrMut<'a, Self>) {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
unsafe {
|
||||
let raw_ptr: *mut u32 = location.as_raw() as *mut u32;
|
||||
raw_ptr.write(self.region.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestPtrMut<'a, T> {
|
||||
pub(super) mem: &'a GuestMemory<'a>,
|
||||
pub(super) region: Region,
|
||||
pub(super) type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestPtrMut<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestPtrMut {{ mem: {:?}, region: {:?} }}",
|
||||
self.mem, self.region
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtrMut<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
pub fn as_immut(&self) -> GuestPtr<'a, T> {
|
||||
GuestPtr {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
type_: self.type_,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raw(&self) -> *mut u8 {
|
||||
(self.mem.ptr as usize + self.region.start as usize) as *mut u8
|
||||
}
|
||||
|
||||
pub fn elem(&self, elements: i32) -> Result<Self, GuestError> {
|
||||
self.mem
|
||||
.ptr_mut(self.region.start + (elements * self.region.len as i32) as u32)
|
||||
}
|
||||
|
||||
pub fn cast<CastTo: GuestType<'a>>(
|
||||
&self,
|
||||
offset: u32,
|
||||
) -> Result<GuestPtrMut<'a, CastTo>, GuestError> {
|
||||
self.mem.ptr_mut(self.region.start + offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtrMut<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
pub fn as_ref(&self) -> Result<GuestRef<'a, T>, GuestError> {
|
||||
self.as_immut().as_ref()
|
||||
}
|
||||
|
||||
pub fn as_ref_mut(&self) -> Result<GuestRefMut<'a, T>, GuestError> {
|
||||
T::validate(&self.as_immut())?;
|
||||
let handle = {
|
||||
let mut borrows = self.mem.borrows.borrow_mut();
|
||||
borrows
|
||||
.borrow_mut(self.region)
|
||||
.ok_or_else(|| GuestError::PtrBorrowed(self.region))?
|
||||
};
|
||||
Ok(GuestRefMut {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
handle,
|
||||
type_: self.type_,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestPtrMut<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
pub fn read(&self) -> Result<T, GuestError> {
|
||||
T::read(&self.as_immut())
|
||||
}
|
||||
|
||||
pub fn write(&self, ptr: &T) {
|
||||
T::write(ptr, &self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestType<'a> for GuestPtrMut<'a, T>
|
||||
where
|
||||
T: GuestType<'a>,
|
||||
{
|
||||
fn size() -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn align() -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
format!("GuestPtrMut<{}>", T::name())
|
||||
}
|
||||
|
||||
fn validate(location: &GuestPtr<'a, GuestPtrMut<'a, T>>) -> Result<(), GuestError> {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
let raw_ptr: u32 = unsafe { *(location.as_raw() as *const u32) };
|
||||
// GuestMemory can validate that the raw pointer contents are legal for T:
|
||||
let _guest_ptr: GuestPtr<T> = location.mem.ptr(raw_ptr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Reading and writing GuestPtrMuts to memory:
|
||||
fn read(location: &GuestPtr<'a, Self>) -> Result<Self, GuestError> {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
let raw_ptr: u32 = unsafe { *(location.as_raw() as *const u32) };
|
||||
// GuestMemory can validate that the raw pointer contents are legal for T:
|
||||
let guest_ptr_mut: GuestPtrMut<'a, T> = location.mem.ptr_mut(raw_ptr)?;
|
||||
Ok(guest_ptr_mut)
|
||||
}
|
||||
|
||||
fn write(&self, location: &GuestPtrMut<'a, Self>) {
|
||||
// location is guaranteed to be in GuestMemory and aligned to 4
|
||||
unsafe {
|
||||
let raw_ptr: *mut u32 = location.as_raw() as *mut u32;
|
||||
raw_ptr.write(self.region.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestRef<'a, T> {
|
||||
pub(super) mem: &'a GuestMemory<'a>,
|
||||
pub(super) region: Region,
|
||||
pub(super) handle: BorrowHandle,
|
||||
pub(super) type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestRef<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestRef {{ mem: {:?}, region: {:?}, handle: {:?} }}",
|
||||
self.mem, self.region, self.handle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestRef<'a, T> {
|
||||
pub fn as_ptr(&self) -> GuestPtr<'a, T> {
|
||||
GuestPtr {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
type_: self.type_,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for GuestRef<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
((self.mem.ptr as usize + self.region.start as usize) as *const T)
|
||||
.as_ref()
|
||||
.expect("GuestRef implies non-null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for GuestRef<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut borrows = self.mem.borrows.borrow_mut();
|
||||
borrows.unborrow_immut(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestRefMut<'a, T> {
|
||||
pub(super) mem: &'a GuestMemory<'a>,
|
||||
pub(super) region: Region,
|
||||
pub(super) handle: BorrowHandle,
|
||||
pub(super) type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> fmt::Debug for GuestRefMut<'a, T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GuestRefMut {{ mem: {:?}, region: {:?}, handle: {:?} }}",
|
||||
self.mem, self.region, self.handle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GuestRefMut<'a, T> {
|
||||
pub fn as_ptr(&self) -> GuestPtr<'a, T> {
|
||||
GuestPtr {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
type_: self.type_,
|
||||
}
|
||||
}
|
||||
pub fn as_ptr_mut(&self) -> GuestPtrMut<'a, T> {
|
||||
GuestPtrMut {
|
||||
mem: self.mem,
|
||||
region: self.region,
|
||||
type_: self.type_,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ::std::ops::Deref for GuestRefMut<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
((self.mem.ptr as usize + self.region.start as usize) as *const T)
|
||||
.as_ref()
|
||||
.expect("GuestRef implies non-null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for GuestRefMut<'a, T>
|
||||
where
|
||||
T: GuestTypeTransparent<'a>,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe {
|
||||
((self.mem.ptr as usize + self.region.start as usize) as *mut T)
|
||||
.as_mut()
|
||||
.expect("GuestRef implies non-null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for GuestRefMut<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut borrows = self.mem.borrows.borrow_mut();
|
||||
borrows.unborrow_mut(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
super::{GuestError, GuestMemory, Region},
|
||||
{GuestPtr, GuestPtrMut},
|
||||
};
|
||||
|
||||
#[repr(align(4096))]
|
||||
struct HostMemory {
|
||||
buffer: [u8; 4096],
|
||||
}
|
||||
|
||||
impl HostMemory {
|
||||
pub fn new() -> Self {
|
||||
Self { buffer: [0; 4096] }
|
||||
}
|
||||
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||
self.buffer.as_mut_ptr()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// try extracting an immutable ptr out of memory bounds
|
||||
let err = guest_memory
|
||||
.ptr::<GuestPtr<i32>>(4096)
|
||||
.expect_err("out of bounds ptr error");
|
||||
assert_eq!(err, GuestError::PtrOutOfBounds(Region::new(4096, 4)));
|
||||
// try extracting an mutable ptr out of memory bounds
|
||||
let err = guest_memory
|
||||
.ptr_mut::<GuestPtrMut<i32>>(4096)
|
||||
.expect_err("out of bounds ptr error");
|
||||
assert_eq!(err, GuestError::PtrOutOfBounds(Region::new(4096, 4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_aligned() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// try extracting a misaligned immutable ptr
|
||||
let err = guest_memory
|
||||
.ptr::<GuestPtr<i32>>(2)
|
||||
.expect_err("ptr misaligned");
|
||||
assert_eq!(err, GuestError::PtrNotAligned(Region::new(2, 4), 4));
|
||||
// try extracting a misaligned mutable ptr
|
||||
let err = guest_memory
|
||||
.ptr_mut::<GuestPtrMut<i32>>(2)
|
||||
.expect_err("ptr mut misaligned");
|
||||
assert_eq!(err, GuestError::PtrNotAligned(Region::new(2, 4), 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptr_from_memory() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// write something to memory
|
||||
{
|
||||
let ptr: GuestPtrMut<i64> = guest_memory.ptr_mut(8).expect("ptr mut to the el");
|
||||
let mut el = ptr.as_ref_mut().expect("ref mut to the el");
|
||||
*el = 100;
|
||||
}
|
||||
// extract as ref
|
||||
let ptr: GuestPtr<i64> = guest_memory.ptr(8).expect("ptr to the el");
|
||||
let as_ref = ptr.as_ref().expect("el borrowed immutably");
|
||||
assert_eq!(*as_ref, 100);
|
||||
// borrowing again should be fine
|
||||
let as_ref2 = ptr.as_ref().expect("el borrowed immutably again");
|
||||
assert_eq!(*as_ref2, *as_ref);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptr_mut_from_memory() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// set elems of array to zero
|
||||
{
|
||||
let ptr: GuestPtrMut<i64> = guest_memory.ptr_mut(8).expect("ptr mut to the el");
|
||||
let mut el = ptr.as_ref_mut().expect("ref mut to the el");
|
||||
*el = 100;
|
||||
}
|
||||
// extract as ref
|
||||
let ptr: GuestPtrMut<i64> = guest_memory.ptr_mut(8).expect("ptr mut to the el");
|
||||
assert_eq!(*ptr.as_ref().expect("el borrowed immutably"), 100);
|
||||
// overwrite the memory and re-verify
|
||||
*ptr.as_ref_mut().expect("el borrowed mutably") = 2000;
|
||||
// re-validate
|
||||
assert_eq!(*ptr.as_ref().expect("el borrowed immutably"), 2000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "el borrowed immutably while borrowed mutably: PtrBorrowed(Region { start: 0, len: 2 })"
|
||||
)]
|
||||
fn borrow_mut_then_immut() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
let ptr: GuestPtrMut<i16> = guest_memory.ptr_mut(0).expect("ptr mut to the el");
|
||||
// borrow mutably
|
||||
let _as_mut = ptr
|
||||
.as_ref_mut()
|
||||
.expect("el borrowed mutably for the first time");
|
||||
// borrow immutably should fail
|
||||
let _as_ref = ptr
|
||||
.as_ref()
|
||||
.expect("el borrowed immutably while borrowed mutably");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "el borrowed mutably while borrowed mutably: PtrBorrowed(Region { start: 0, len: 2 })"
|
||||
)]
|
||||
fn borrow_mut_twice() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
let ptr: GuestPtrMut<i16> = guest_memory.ptr_mut(0).expect("ptr mut to the el");
|
||||
// borrow mutably
|
||||
let _as_mut = ptr
|
||||
.as_ref_mut()
|
||||
.expect("el borrowed mutably for the first time");
|
||||
// try borrowing mutably again
|
||||
let _as_mut2 = ptr
|
||||
.as_ref_mut()
|
||||
.expect("el borrowed mutably while borrowed mutably");
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
use super::array::{GuestArray, GuestArrayRef};
|
||||
use crate::GuestError;
|
||||
use std::fmt;
|
||||
|
||||
pub struct GuestString<'a> {
|
||||
pub(super) array: GuestArray<'a, u8>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for GuestString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "GuestString {{ array: {:?} }}", self.array)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestString<'a> {
|
||||
pub fn as_ref(&self) -> Result<GuestStringRef<'a>, GuestError> {
|
||||
let ref_ = self.array.as_ref()?;
|
||||
Ok(GuestStringRef { ref_ })
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> Result<String, GuestError> {
|
||||
Ok(self.as_ref()?.as_str()?.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<GuestArray<'a, u8>> for GuestString<'a> {
|
||||
fn from(array: GuestArray<'a, u8>) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuestStringRef<'a> {
|
||||
pub(super) ref_: GuestArrayRef<'a, u8>,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for GuestStringRef<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "GuestStringRef {{ ref_: {:?} }}", self.ref_)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GuestStringRef<'a> {
|
||||
pub fn as_str(&self) -> Result<&str, GuestError> {
|
||||
std::str::from_utf8(&*self.ref_).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{
|
||||
super::{
|
||||
ptr::{GuestPtr, GuestPtrMut},
|
||||
GuestError, GuestMemory,
|
||||
},
|
||||
GuestString,
|
||||
};
|
||||
|
||||
#[repr(align(4096))]
|
||||
struct HostMemory {
|
||||
buffer: [u8; 4096],
|
||||
}
|
||||
|
||||
impl HostMemory {
|
||||
pub fn new() -> Self {
|
||||
Self { buffer: [0; 4096] }
|
||||
}
|
||||
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||
self.buffer.as_mut_ptr()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_utf8() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// write string into memory
|
||||
let mut ptr: GuestPtrMut<u8> = guest_memory.ptr_mut(0).expect("ptr mut to start of string");
|
||||
let input_str = "cześć WASI!";
|
||||
for byte in input_str.as_bytes() {
|
||||
let mut ref_mut = ptr.as_ref_mut().expect("valid deref");
|
||||
*ref_mut = *byte;
|
||||
ptr = ptr.elem(1).expect("next ptr");
|
||||
}
|
||||
// read the string as GuestString
|
||||
let ptr: GuestPtr<u8> = guest_memory.ptr(0).expect("ptr to start of string");
|
||||
let guest_string: GuestString<'_> = ptr
|
||||
.array(input_str.len() as u32)
|
||||
.expect("valid null-terminated string")
|
||||
.into();
|
||||
let as_ref = guest_string.as_ref().expect("deref");
|
||||
assert_eq!(as_ref.as_str().expect("valid UTF-8"), input_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8() {
|
||||
let mut host_memory = HostMemory::new();
|
||||
let guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
// write string into memory
|
||||
let mut ptr: GuestPtrMut<u8> = guest_memory.ptr_mut(0).expect("ptr mut to start of string");
|
||||
let input_str = "cześć WASI!";
|
||||
let mut bytes = input_str.as_bytes().to_vec();
|
||||
// insert 0xFE which is an invalid UTF-8 byte
|
||||
bytes[5] = 0xfe;
|
||||
for byte in &bytes {
|
||||
let mut ref_mut = ptr.as_ref_mut().expect("valid deref");
|
||||
*ref_mut = *byte;
|
||||
ptr = ptr.elem(1).expect("next ptr");
|
||||
}
|
||||
// read the string as GuestString
|
||||
let ptr: GuestPtr<u8> = guest_memory.ptr(0).expect("ptr to start of string");
|
||||
let guest_string: GuestString<'_> = ptr
|
||||
.array(bytes.len() as u32)
|
||||
.expect("valid null-terminated string")
|
||||
.into();
|
||||
let as_ref = guest_string.as_ref().expect("deref");
|
||||
match as_ref.as_str().expect_err("should fail") {
|
||||
GuestError::InvalidUtf8(_) => {}
|
||||
x => assert!(false, "expected GuestError::InvalidUtf8(_), got {:?}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user