Files
wasmtime/crates/runtime/src/memory/ptr.rs
Jakub Konka 2ad77538f5 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.
2020-02-19 10:58:55 +01:00

548 lines
15 KiB
Rust

use super::{array::GuestArray, GuestMemory};
use crate::{
borrow::BorrowHandle, GuestError, GuestType, GuestTypeClone, GuestTypeCopy, GuestTypePtr,
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> 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>(&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: GuestTypeCopy,
{
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: GuestTypeClone,
{
pub fn clone_from_guest(&self) -> Result<T, GuestError> {
T::read_from_guest(self)
}
}
impl<'a, T> GuestPtr<'a, T>
where
T: GuestTypePtr<'a>,
{
pub fn read_ptr_from_guest(&self) -> Result<T, GuestError> {
T::read_from_guest(self)
}
}
impl<'a, T> GuestType for GuestPtr<'a, T>
where
T: GuestType,
{
fn size() -> u32 {
4
}
fn align() -> u32 {
4
}
fn name() -> String {
format!("GuestPtr<{}>", T::name())
}
fn validate<'b>(location: &GuestPtr<'b, GuestPtr<'b, 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:
impl<'a, T> GuestTypePtr<'a> for GuestPtr<'a, T>
where
T: GuestType,
{
fn read_from_guest(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_to_guest(&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,
{
pub fn as_immut(&self) -> GuestPtr<'a, T> {
GuestPtr {
mem: self.mem,
region: self.region,
type_: self.type_,
}
}
pub fn as_raw(&self) -> *const u8 {
self.as_immut().as_raw()
}
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>(
&self,
offset: u32,
) -> Result<GuestPtrMut<'a, CastTo>, GuestError> {
self.mem.ptr_mut(self.region.start + offset)
}
}
impl<'a, T> GuestPtrMut<'a, T>
where
T: GuestTypeCopy,
{
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: GuestTypePtr<'a>,
{
pub fn read_ptr_from_guest(&self) -> Result<T, GuestError> {
T::read_from_guest(&self.as_immut())
}
pub fn write_ptr_to_guest(&self, ptr: &T) {
T::write_to_guest(ptr, &self);
}
}
impl<'a, T> GuestPtrMut<'a, T>
where
T: GuestTypeClone,
{
pub fn clone_from_guest(&self) -> Result<T, GuestError> {
T::read_from_guest(&self.as_immut())
}
pub fn clone_to_guest(&self, val: &T) {
T::write_to_guest(val, &self)
}
}
impl<'a, T> GuestType for GuestPtrMut<'a, T>
where
T: GuestType,
{
fn size() -> u32 {
4
}
fn align() -> u32 {
4
}
fn name() -> String {
format!("GuestPtrMut<{}>", T::name())
}
fn validate<'b>(location: &GuestPtr<'b, GuestPtrMut<'b, 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:
impl<'a, T> GuestTypePtr<'a> for GuestPtrMut<'a, T>
where
T: GuestType,
{
fn read_from_guest(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_to_guest(&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: GuestTypeCopy,
{
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: GuestTypeCopy,
{
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: GuestTypeCopy,
{
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");
}
}