Add array generation to wiggle-generate crate (#9)

* Add array generation to wiggle-generate crate

This commit:
* adds array generation to `wiggle-generate` crate which implies
  that we can now generate arrays from `witx` files
* introduces two test interface functions `foo::reduce_excuses` and
  `foo::populate_excuses`, and adds matching prop-tests
* adds an out-of-boundary check to `HostMemory::mem_area_strat` since
  now, given we're generating arrays for testing with an arbitrary
  but bounded number of elements, it is possible to violate the boundary
* refactors `Region::extend` to a new signature `extend(times: u32)`
  which multiplies the current pointer `len` by `times`
* fixes bug in `GuestArray::as_ref` and `GuestArrayMut::as_ref_mut` methods
  where we were not validating the first element (we always started the
  validation from the second element)

* Fix generation of arrays in witx

This commit fixes how `arrays` are auto-generated from `witx` files.
In particular, the changes include:
* Since by design every `array` in `witx` represents an immutable
  slab of memory, we will only ever operate on `GuestArray` in which
  case I've gone ahead and removed `GuestArrayMut` so as to unclutter
  the code somewhat. If we find ourselves in need for it in the future,
  I reckon it will be better to write it from scratch (since the codebase
  will inevitably evolve by then considerably) rather than maintaining an
  unused implementation.
* I've rewritten `GuestArrayRef<T>` to be a wrapper for `Vec<GuestRef<T>>`.
  Also, `GuestArray::as_ref` now borrows each underlying "element" of the
  array one-by-one rather than borrowing the entire chunk of memory at once.
  This change is motivated by the inability to coerce type parameter `T` in
  `GuestArray<T>` in more complicated cases such as arrays of guest pointers
  `GuestPtr<T>` to `*const T` for reuse in `std::slice::from_raw_parts` call.
  (In general, the size of Wasm32 pointer is 4 bytes, while

```
std::mem::size_of::<T>() == std::mem::size_of::<GuestPtr<S>>() == 16
```

  which is problematic; i.e., I can't see how I could properly extract guest
  pointers from slices of 4 bytes and at the same time not allocate.)
* I've augmented fuzz tests by (re-)defining two `array` types:

```
(typename $const_excuse_array (array (@witx const_pointer $excuse)))
(typename $excuse_array (array (@witx pointer $excuse)))
```

  This should hopefully emulate and test the `iovec` and `ciovec` arrays
  present in WASI spec.
This commit is contained in:
Jakub Konka
2020-02-19 10:58:55 +01:00
committed by GitHub
parent 48a218b5c5
commit 2ad77538f5
9 changed files with 395 additions and 251 deletions

View File

@@ -6,5 +6,5 @@ mod region;
pub use error::GuestError;
pub use guest_type::{GuestErrorType, GuestType, GuestTypeClone, GuestTypeCopy, GuestTypePtr};
pub use memory::{GuestMemory, GuestPtr, GuestPtrMut, GuestRef, GuestRefMut};
pub use memory::{GuestArray, GuestMemory, GuestPtr, GuestPtrMut, GuestRef, GuestRefMut};
pub use region::Region;

View File

@@ -1,9 +1,6 @@
use super::ptr::{GuestPtr, GuestPtrMut, GuestRef, GuestRefMut};
use crate::{GuestError, GuestType, GuestTypeCopy};
use std::{
fmt,
ops::{Deref, DerefMut},
};
use super::ptr::{GuestPtr, GuestRef};
use crate::{GuestError, GuestType};
use std::{fmt, ops::Deref};
pub struct GuestArray<'a, T>
where
@@ -28,31 +25,28 @@ where
impl<'a, T> GuestArray<'a, T>
where
T: GuestTypeCopy,
T: GuestType,
{
pub fn as_ref(&self) -> Result<GuestArrayRef<'a, T>, GuestError> {
let mut ptr = self.ptr.clone();
let mut refs = Vec::new();
let mut next = self.ptr.elem(0)?;
for _ in 0..self.num_elems {
ptr = ptr.elem(1)?;
T::validate(&ptr)?;
T::validate(&next)?;
let handle = {
let mut borrows = next.mem.borrows.borrow_mut();
borrows
.borrow_immut(next.region)
.ok_or_else(|| GuestError::PtrBorrowed(next.region))?
};
refs.push(GuestRef {
mem: next.mem,
region: next.region,
handle,
type_: next.type_,
});
next = next.elem(1)?;
}
let region = self.ptr.region.extend((self.num_elems - 1) * T::size());
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,
})
Ok(GuestArrayRef { refs })
}
}
@@ -60,8 +54,7 @@ pub struct GuestArrayRef<'a, T>
where
T: GuestType,
{
ref_: GuestRef<'a, T>,
num_elems: u32,
refs: Vec<GuestRef<'a, T>>,
}
impl<'a, T> fmt::Debug for GuestArrayRef<'a, T>
@@ -69,137 +62,18 @@ where
T: GuestType + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GuestArrayRef {{ ref_: {:?}, num_elems: {:?} }}",
self.ref_, self.num_elems
)
write!(f, "GuestArrayRef {{ refs: {:?} }}", self.refs)
}
}
impl<'a, T> Deref for GuestArrayRef<'a, T>
where
T: GuestTypeCopy,
{
type Target = [T];
fn deref(&self) -> &Self::Target {
unsafe {
std::slice::from_raw_parts(
self.ref_.as_ptr().as_raw() as *const T,
self.num_elems as usize,
)
}
}
}
pub struct GuestArrayMut<'a, T>
where
T: GuestType,
{
pub(super) ptr: GuestPtrMut<'a, T>,
pub(super) num_elems: u32,
}
impl<'a, T> fmt::Debug for GuestArrayMut<'a, T>
where
T: GuestType + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GuestArrayMut {{ ptr: {:?}, num_elems: {:?} }}",
self.ptr, self.num_elems
)
}
}
impl<'a, T> GuestArrayMut<'a, T>
where
T: GuestTypeCopy,
{
pub fn as_ref(&self) -> Result<GuestArrayRef<'a, T>, GuestError> {
let arr = GuestArray {
ptr: self.ptr.as_immut(),
num_elems: self.num_elems,
};
arr.as_ref()
}
pub fn as_ref_mut(&self) -> Result<GuestArrayRefMut<'a, T>, GuestError> {
let mut ptr = self.ptr.as_immut();
for _ in 0..self.num_elems {
ptr = ptr.elem(1)?;
T::validate(&ptr)?;
}
let region = self.ptr.region.extend((self.num_elems - 1) * T::size());
let handle = {
let mut borrows = self.ptr.mem.borrows.borrow_mut();
borrows
.borrow_mut(region)
.ok_or_else(|| GuestError::PtrBorrowed(region))?
};
let ref_mut = GuestRefMut {
mem: self.ptr.mem,
region,
handle,
type_: self.ptr.type_,
};
Ok(GuestArrayRefMut {
ref_mut,
num_elems: self.num_elems,
})
}
}
pub struct GuestArrayRefMut<'a, T>
where
T: GuestType,
{
ref_mut: GuestRefMut<'a, T>,
num_elems: u32,
}
impl<'a, T> fmt::Debug for GuestArrayRefMut<'a, T>
where
T: GuestType + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GuestArrayRefMut {{ ref_mut: {:?}, num_elems: {:?} }}",
self.ref_mut, self.num_elems
)
}
}
impl<'a, T> Deref for GuestArrayRefMut<'a, T>
where
T: GuestTypeCopy,
{
type Target = [T];
type Target = [GuestRef<'a, T>];
fn deref(&self) -> &Self::Target {
unsafe {
std::slice::from_raw_parts(
self.ref_mut.as_ptr().as_raw() as *const T,
self.num_elems as usize,
)
}
}
}
impl<'a, T> DerefMut for GuestArrayRefMut<'a, T>
where
T: GuestTypeCopy,
{
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
std::slice::from_raw_parts_mut(
self.ref_mut.as_ptr_mut().as_raw() as *mut T,
self.num_elems as usize,
)
}
self.refs.as_slice()
}
}
@@ -231,14 +105,10 @@ mod 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 array out of memory bounds
// 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)));
// try extracting an mutable array out of memory bounds
let ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(4092).expect("ptr mut to last i32 el");
let err = ptr.array_mut(2).expect_err("out of bounds ptr error");
assert_eq!(err, GuestError::PtrOutOfBounds(Region::new(4092, 8)));
}
#[test]
@@ -260,76 +130,77 @@ mod test {
// 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_mut_to_array_mut() {
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<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 = 0;
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 = 0;
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 = 0;
}
// extract as array and verify all is zero
let ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(0).expect("ptr mut to first el");
let arr = ptr.array_mut(3).expect("convert ptr mut to array mut");
assert_eq!(&*arr.as_ref().expect("array borrowed immutably"), &[0; 3]);
// populate the array and re-verify
for el in &mut *arr.as_ref_mut().expect("array borrowed mutably") {
*el = 10;
}
// re-validate
assert_eq!(&*arr.as_ref().expect("array borrowed immutably"), &[10; 3]);
}
#[test]
#[should_panic(
expected = "array borrowed immutably while borrowed mutably: PtrBorrowed(Region { start: 0, len: 12 })"
)]
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<i32> = guest_memory.ptr_mut(0).expect("ptr mut to first el");
let arr = ptr.array_mut(3).expect("convert ptr mut to array mut");
// borrow mutably
let _as_mut = arr
.as_ref_mut()
.expect("array borrowed mutably for the first time");
// borrow immutably should fail
let _as_ref = arr
let as_ref = arr
.as_ref()
.expect("array borrowed immutably while borrowed mutably");
.expect("array borrowed immutably")
.iter()
.map(|x| **x)
.collect::<Vec<_>>();
assert_eq!(&as_ref, &[1, 2, 3]);
// borrowing again should be fine
let as_ref2 = arr
.as_ref()
.expect("array borrowed immutably again")
.iter()
.map(|x| **x)
.collect::<Vec<_>>();
assert_eq!(&as_ref2, &as_ref);
}
#[test]
#[should_panic(
expected = "array borrowed mutably while borrowed mutably: PtrBorrowed(Region { start: 0, len: 12 })"
)]
fn borrow_mut_twice() {
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 ptr: GuestPtrMut<i32> = guest_memory.ptr_mut(0).expect("ptr mut to first el");
let arr = ptr.array_mut(3).expect("convert ptr mut to array mut");
// borrow mutably
let _as_mut = arr
.as_ref_mut()
.expect("array borrowed mutably for the first time");
// try borrowing mutably again
let _as_mut2 = arr
.as_ref_mut()
.expect("array borrowed mutably while borrowed mutably");
{
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_ptr_to_guest(
&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_ptr_to_guest(
&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_ptr_to_guest(
&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 as_ref = arr
.as_ref()
.expect("array borrowed immutably")
.iter()
.map(|x| {
*x.as_ptr()
.read_ptr_from_guest()
.expect("valid ptr to some value")
.as_ref()
.expect("deref ptr to some value")
})
.collect::<Vec<_>>();
assert_eq!(&as_ref, &[255, 254, 253]);
}
}

View File

@@ -1,7 +1,4 @@
use super::{
array::{GuestArray, GuestArrayMut},
GuestMemory,
};
use super::{array::GuestArray, GuestMemory};
use crate::{
borrow::BorrowHandle, GuestError, GuestType, GuestTypeClone, GuestTypeCopy, GuestTypePtr,
Region,
@@ -47,7 +44,7 @@ impl<'a, T: GuestType> GuestPtr<'a, T> {
}
pub fn array(&self, num_elems: u32) -> Result<GuestArray<'a, T>, GuestError> {
let region = self.region.extend((num_elems - 1) * T::size());
let region = self.region.extend(num_elems);
if self.mem.contains(region) {
let ptr = GuestPtr {
mem: self.mem,
@@ -194,20 +191,6 @@ where
) -> Result<GuestPtrMut<'a, CastTo>, GuestError> {
self.mem.ptr_mut(self.region.start + offset)
}
pub fn array_mut(&self, num_elems: u32) -> Result<GuestArrayMut<'a, T>, GuestError> {
let region = self.region.extend((num_elems - 1) * T::size());
if self.mem.contains(region) {
let ptr = GuestPtrMut {
mem: self.mem,
region: self.region,
type_: self.type_,
};
Ok(GuestArrayMut { ptr, num_elems })
} else {
Err(GuestError::PtrOutOfBounds(region))
}
}
}
impl<'a, T> GuestPtrMut<'a, T>

View File

@@ -26,10 +26,11 @@ impl Region {
}
}
pub fn extend(&self, new_len: u32) -> Self {
pub fn extend(&self, times: u32) -> Self {
let len = self.len * times;
Self {
start: self.start,
len: self.len + new_len,
len,
}
}
}