diff --git a/Cargo.lock b/Cargo.lock index 9702795a6..18cc0a614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3249,6 +3249,7 @@ dependencies = [ "byteorder", "serde", "time", + "wasmer-derive", "wasmer-types", ] diff --git a/examples/exports_memory.rs b/examples/exports_memory.rs index e551bb1cc..a3ef6c8f5 100644 --- a/examples/exports_memory.rs +++ b/examples/exports_memory.rs @@ -11,7 +11,7 @@ //! //! Ready? -use wasmer::{imports, wat2wasm, Array, Instance, Module, Store, WasmPtr}; +use wasmer::{imports, wat2wasm, Instance, Module, Store, WasmPtr}; use wasmer_compiler_cranelift::Cranelift; use wasmer_engine_universal::Universal; @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { let load = instance .exports - .get_native_function::<(), (WasmPtr, i32)>("load")?; + .get_native_function::<(), (WasmPtr, i32)>("load")?; // Here we go. // @@ -67,11 +67,6 @@ fn main() -> Result<(), Box> { println!("Memory size (pages) {:?}", memory.size()); println!("Memory size (bytes) {:?}", memory.data_size()); - // Next, we'll want to read the contents of the memory. - // - // To do so, we have to get a `View` of the memory. - //let view = memory.view::(); - // Oh! Wait, before reading the contents, we need to know // where to find what we are looking for. // @@ -85,18 +80,18 @@ fn main() -> Result<(), Box> { // // We will get bytes out of the memory so we need to // decode them into a string. - let str = ptr.get_utf8_string(memory, length as u32).unwrap(); + let str = ptr.read_utf8_string(memory, length as u32).unwrap(); println!("Memory contents: {:?}", str); // What about changing the contents of the memory with a more // appropriate string? // - // To do that, we'll dereference our pointer and change the content - // of each `Cell` + // To do that, we'll make a slice from our pointer and change the content + // of each element. let new_str = b"Hello, Wasmer!"; - let values = ptr.deref(memory, 0, new_str.len() as u32).unwrap(); + let values = ptr.slice(memory, new_str.len() as u32).unwrap(); for i in 0..new_str.len() { - values[i].set(new_str[i]); + values.index(i as u64).write(new_str[i]).unwrap(); } // And now, let's see the result. @@ -106,7 +101,7 @@ fn main() -> Result<(), Box> { // before. println!("New string length: {:?}", new_str.len()); - let str = ptr.get_utf8_string(memory, new_str.len() as u32).unwrap(); + let str = ptr.read_utf8_string(memory, new_str.len() as u32).unwrap(); println!("New memory contents: {:?}", str); // Much better, don't you think? diff --git a/lib/api/src/js/cell.rs b/lib/api/src/js/cell.rs deleted file mode 100644 index b8c415079..000000000 --- a/lib/api/src/js/cell.rs +++ /dev/null @@ -1,140 +0,0 @@ -use core::cmp::Ordering; -use core::fmt::{self, Debug}; -use std::marker::PhantomData; - -use js_sys::Uint8Array; - -/// A mutable Wasm-memory location. -pub struct WasmCell<'a, T: ?Sized> { - pub(crate) memory: Uint8Array, - #[allow(dead_code)] - phantom: &'a PhantomData, -} - -unsafe impl Send for WasmCell<'_, T> where T: Send {} - -unsafe impl Sync for WasmCell<'_, T> {} - -impl<'a, T: Copy> Clone for WasmCell<'a, T> { - #[inline] - fn clone(&self) -> WasmCell<'a, T> { - WasmCell { - memory: self.memory.clone(), - phantom: &PhantomData, - } - } -} - -impl PartialEq for WasmCell<'_, T> { - #[inline] - fn eq(&self, other: &WasmCell) -> bool { - self.get() == other.get() - } -} - -impl Eq for WasmCell<'_, T> {} - -impl PartialOrd for WasmCell<'_, T> { - #[inline] - fn partial_cmp(&self, other: &WasmCell) -> Option { - self.get().partial_cmp(&other.get()) - } - - #[inline] - fn lt(&self, other: &WasmCell) -> bool { - self.get() < other.get() - } - - #[inline] - fn le(&self, other: &WasmCell) -> bool { - self.get() <= other.get() - } - - #[inline] - fn gt(&self, other: &WasmCell) -> bool { - self.get() > other.get() - } - - #[inline] - fn ge(&self, other: &WasmCell) -> bool { - self.get() >= other.get() - } -} - -impl Ord for WasmCell<'_, T> { - #[inline] - fn cmp(&self, other: &WasmCell) -> Ordering { - self.get().cmp(&other.get()) - } -} - -impl<'a, T> WasmCell<'a, T> { - /// Creates a new `WasmCell` containing the given value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// ``` - #[inline] - pub const fn new(memory: Uint8Array) -> WasmCell<'a, T> { - WasmCell { - memory, - phantom: &PhantomData, - } - } -} - -impl<'a, T: Copy> WasmCell<'a, T> { - /// Returns a copy of the contained value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// let five = wasm_cell.get(); - /// ``` - #[inline] - pub fn get(&self) -> T { - let vec = self.memory.to_vec(); - unsafe { *(vec.as_ptr() as *const T) } - } -} - -impl Debug for WasmCell<'_, T> { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "WasmCell({:?})", self.get()) - } -} - -impl WasmCell<'_, T> { - /// Sets the contained value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// wasm_cell.set(10); - /// assert_eq!(cell.get(), 10); - /// ``` - #[inline] - pub fn set(&self, val: T) { - let size = std::mem::size_of::(); - let ptr = &val as *const T as *const u8; - let slice = unsafe { std::slice::from_raw_parts(ptr, size) }; - self.memory.copy_from(slice); - } -} diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index cf1e782fc..ae6357372 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -2,13 +2,15 @@ use crate::js::export::{Export, VMMemory}; use crate::js::exports::{ExportError, Exportable}; use crate::js::externals::Extern; use crate::js::store::Store; -use crate::js::{MemoryType, MemoryView}; +use crate::js::{MemoryAccessError, MemoryType}; use std::convert::TryInto; +use std::mem::MaybeUninit; +use std::slice; use thiserror::Error; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use wasmer_types::{Bytes, Pages, ValueType}; +use wasmer_types::{Bytes, Pages}; /// Error type describing things that can go wrong when operating on Wasm Memories. #[derive(Error, Debug, Clone, PartialEq, Hash)] @@ -146,32 +148,8 @@ impl Memory { &self.store } - /// Retrieve a slice of the memory contents. - /// - /// # Safety - /// - /// Until the returned slice is dropped, it is undefined behaviour to - /// modify the memory contents in any way including by calling a wasm - /// function that writes to the memory or by resizing the memory. - pub unsafe fn data_unchecked(&self) -> &[u8] { - unimplemented!("direct data pointer access is not possible in JavaScript"); - } - - /// Retrieve a mutable slice of the memory contents. - /// - /// # Safety - /// - /// This method provides interior mutability without an UnsafeCell. Until - /// the returned value is dropped, it is undefined behaviour to read or - /// write to the pointed-to memory in any way except through this slice, - /// including by calling a wasm function that reads the memory contents or - /// by resizing this Memory. - #[allow(clippy::mut_from_ref)] - pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] { - unimplemented!("direct data pointer access is not possible in JavaScript"); - } - /// Returns the pointer to the raw bytes of the `Memory`. + #[doc(hidden)] pub fn data_ptr(&self) -> *mut u8 { unimplemented!("direct data pointer access is not possible in JavaScript"); } @@ -253,46 +231,7 @@ impl Memory { Ok(Pages(new_pages)) } - /// Return a "view" of the currently accessible memory. By - /// default, the view is unsynchronized, using regular memory - /// accesses. You can force a memory view to use atomic accesses - /// by calling the [`MemoryView::atomically`] method. - /// - /// # Notes: - /// - /// This method is safe (as in, it won't cause the host to crash or have UB), - /// but it doesn't obey rust's rules involving data races, especially concurrent ones. - /// Therefore, if this memory is shared between multiple threads, a single memory - /// location can be mutated concurrently without synchronization. - /// - /// # Usage: - /// - /// ``` - /// # use wasmer::{Memory, MemoryView}; - /// # use std::{cell::Cell, sync::atomic::Ordering}; - /// # fn view_memory(memory: Memory) { - /// // Without synchronization. - /// let view: MemoryView = memory.view(); - /// for byte in view[0x1000 .. 0x1010].iter().map(Cell::get) { - /// println!("byte: {}", byte); - /// } - /// - /// // With synchronization. - /// let atomic_view = view.atomically(); - /// for byte in atomic_view[0x1000 .. 0x1010].iter().map(|atom| atom.load(Ordering::SeqCst)) { - /// println!("byte: {}", byte); - /// } - /// # } - /// ``` - pub fn view(&self) -> MemoryView { - unimplemented!("The view function is not yet implemented in Wasmer Javascript"); - } - - /// A theoretical alais to `Self::view::` but it returns a `js::Uint8Array` in this case. - /// - /// This code is going to be refactored. Use it as your own risks. - #[doc(hidden)] - pub fn uint8view(&self) -> js_sys::Uint8Array { + pub(crate) fn uint8view(&self) -> js_sys::Uint8Array { js_sys::Uint8Array::new(&self.vm_memory.memory.buffer()) } @@ -318,6 +257,87 @@ impl Memory { pub fn same(&self, other: &Self) -> bool { self.vm_memory == other.vm_memory } + + /// Safely reads bytes from the memory at the given offset. + /// + /// The full buffer will be filled, otherwise a `MemoryAccessError` is returned + /// to indicate an out-of-bounds access. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent writes. + pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), MemoryAccessError> { + let view = self.uint8view(); + let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?; + let len: u32 = buf + .len() + .try_into() + .map_err(|_| MemoryAccessError::Overflow)?; + let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?; + if end > view.length() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + view.subarray(offset, end).copy_to(buf); + Ok(()) + } + + /// Safely reads bytes from the memory at the given offset. + /// + /// This method is similar to `read` but allows reading into an + /// uninitialized buffer. An initialized view of the buffer is returned. + /// + /// The full buffer will be filled, otherwise a `MemoryAccessError` is returned + /// to indicate an out-of-bounds access. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent writes. + pub fn read_uninit<'a>( + &self, + offset: u64, + buf: &'a mut [MaybeUninit], + ) -> Result<&'a mut [u8], MemoryAccessError> { + let view = self.uint8view(); + let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?; + let len: u32 = buf + .len() + .try_into() + .map_err(|_| MemoryAccessError::Overflow)?; + let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?; + if end > view.length() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + + // Zero-initialize the buffer to avoid undefined behavior with + // uninitialized data. + for elem in buf.iter_mut() { + *elem = MaybeUninit::new(0); + } + let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) }; + + view.subarray(offset, end).copy_to(buf); + Ok(buf) + } + + /// Safely writes bytes to the memory at the given offset. + /// + /// If the write exceeds the bounds of the memory then a `MemoryAccessError` is + /// returned. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent reads/writes. + pub fn write(&self, offset: u64, data: &[u8]) -> Result<(), MemoryAccessError> { + let view = self.uint8view(); + let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?; + let len: u32 = data + .len() + .try_into() + .map_err(|_| MemoryAccessError::Overflow)?; + let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?; + if end > view.length() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + view.subarray(offset, end).copy_from(data); + Ok(()) + } } impl<'a> Exportable<'a> for Memory { diff --git a/lib/api/src/js/mem_access.rs b/lib/api/src/js/mem_access.rs new file mode 100644 index 000000000..8a89a4d9c --- /dev/null +++ b/lib/api/src/js/mem_access.rs @@ -0,0 +1,382 @@ +use crate::RuntimeError; +use wasmer_types::ValueType; + +use crate::{Memory, Memory32, Memory64, WasmPtr}; +use std::{ + convert::TryInto, + error::Error, + fmt, + marker::PhantomData, + mem::{self, MaybeUninit}, + ops::Range, + slice, + string::FromUtf8Error, +}; + +/// Error for invalid [`Memory`] access. +#[derive(Debug)] +#[non_exhaustive] +pub enum MemoryAccessError { + /// Memory access is outside heap bounds. + HeapOutOfBounds, + /// Address calculation overflow. + Overflow, + /// String is not valid UTF-8. + NonUtf8String, +} + +impl fmt::Display for MemoryAccessError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MemoryAccessError::HeapOutOfBounds => write!(f, "memory access out of bounds"), + MemoryAccessError::Overflow => write!(f, "address calculation overflow"), + MemoryAccessError::NonUtf8String => write!(f, "string is not valid utf-8"), + } + } +} + +impl Error for MemoryAccessError {} + +impl From for RuntimeError { + fn from(err: MemoryAccessError) -> Self { + RuntimeError::new(err.to_string()) + } +} +impl From for MemoryAccessError { + fn from(_err: FromUtf8Error) -> Self { + MemoryAccessError::NonUtf8String + } +} + +/// Reference to a value in Wasm memory. +/// +/// The type of the value must satisfy the requirements of the `ValueType` +/// trait which guarantees that reading and writing such a value to untrusted +/// memory is safe. +/// +/// The address is not required to be aligned: unaligned accesses are fully +/// supported. +/// +/// This wrapper safely handles concurrent modifications of the data by another +/// thread. +#[derive(Clone, Copy)] +pub struct WasmRef<'a, T: ValueType> { + memory: &'a Memory, + offset: u64, + marker: PhantomData<*mut T>, +} + +impl<'a, T: ValueType> WasmRef<'a, T> { + /// Creates a new `WasmRef` at the given offset in a memory. + #[inline] + pub fn new(memory: &'a Memory, offset: u64) -> Self { + Self { + memory, + offset, + marker: PhantomData, + } + } + + /// Get the offset into Wasm linear memory for this `WasmRef`. + #[inline] + pub fn offset(self) -> u64 { + self.offset + } + + /// Get a `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr32(self) -> WasmPtr { + WasmPtr::new(self.offset as u32) + } + + /// Get a 64-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr64(self) -> WasmPtr { + WasmPtr::new(self.offset) + } + + /// Get a reference to the Wasm memory backing this reference. + #[inline] + pub fn memory(self) -> &'a Memory { + self.memory + } + + /// Reads the location pointed to by this `WasmRef`. + #[inline] + pub fn read(self) -> Result { + let mut out = MaybeUninit::uninit(); + let buf = + unsafe { slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, mem::size_of::()) }; + self.memory.read(self.offset, buf)?; + Ok(unsafe { out.assume_init() }) + } + + /// Writes to the location pointed to by this `WasmRef`. + #[inline] + pub fn write(self, val: T) -> Result<(), MemoryAccessError> { + let mut data = MaybeUninit::new(val); + let data = unsafe { + slice::from_raw_parts_mut( + data.as_mut_ptr() as *mut MaybeUninit, + mem::size_of::(), + ) + }; + val.zero_padding_bytes(data); + let data = unsafe { slice::from_raw_parts(data.as_ptr() as *const _, data.len()) }; + self.memory.write(self.offset, data) + } +} + +impl<'a, T: ValueType> fmt::Debug for WasmRef<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "WasmRef(offset: {}, pointer: {:#x})", + self.offset, self.offset + ) + } +} + +/// Reference to an array of values in Wasm memory. +/// +/// The type of the value must satisfy the requirements of the `ValueType` +/// trait which guarantees that reading and writing such a value to untrusted +/// memory is safe. +/// +/// The address is not required to be aligned: unaligned accesses are fully +/// supported. +/// +/// This wrapper safely handles concurrent modifications of the data by another +/// thread. +#[derive(Clone, Copy)] +pub struct WasmSlice<'a, T: ValueType> { + memory: &'a Memory, + offset: u64, + len: u64, + marker: PhantomData<*mut T>, +} + +impl<'a, T: ValueType> WasmSlice<'a, T> { + /// Creates a new `WasmSlice` starting at the given offset in memory and + /// with the given number of elements. + /// + /// Returns a `MemoryAccessError` if the slice length overflows. + #[inline] + pub fn new(memory: &'a Memory, offset: u64, len: u64) -> Result { + let total_len = len + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + offset + .checked_add(total_len) + .ok_or(MemoryAccessError::Overflow)?; + Ok(Self { + memory, + offset, + len, + marker: PhantomData, + }) + } + + /// Get the offset into Wasm linear memory for this `WasmSlice`. + #[inline] + pub fn offset(self) -> u64 { + self.offset + } + + /// Get a 32-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr32(self) -> WasmPtr { + WasmPtr::new(self.offset as u32) + } + + /// Get a 64-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr64(self) -> WasmPtr { + WasmPtr::new(self.offset) + } + + /// Get the number of elements in this slice. + #[inline] + pub fn len(self) -> u64 { + self.len + } + + /// Get a reference to the Wasm memory backing this reference. + #[inline] + pub fn memory(self) -> &'a Memory { + self.memory + } + + /// Get a `WasmRef` to an element in the slice. + #[inline] + pub fn index(self, idx: u64) -> WasmRef<'a, T> { + if idx >= self.len { + panic!("WasmSlice out of bounds"); + } + let offset = self.offset + idx * mem::size_of::() as u64; + WasmRef { + memory: self.memory, + offset, + marker: PhantomData, + } + } + + /// Get a `WasmSlice` for a subslice of this slice. + #[inline] + pub fn subslice(self, range: Range) -> WasmSlice<'a, T> { + if range.start > range.end || range.end > self.len { + panic!("WasmSlice out of bounds"); + } + let offset = self.offset + range.start * mem::size_of::() as u64; + Self { + memory: self.memory, + offset, + len: range.end - range.start, + marker: PhantomData, + } + } + + /// Get an iterator over the elements in this slice. + #[inline] + pub fn iter(self) -> WasmSliceIter<'a, T> { + WasmSliceIter { slice: self } + } + + /// Reads an element of this slice. + #[inline] + pub fn read(self, idx: u64) -> Result { + self.index(idx).read() + } + + /// Writes to an element of this slice. + #[inline] + pub fn write(self, idx: u64, val: T) -> Result<(), MemoryAccessError> { + self.index(idx).write(val) + } + + /// Reads the entire slice into the given buffer. + /// + /// The length of the buffer must match the length of the slice. + #[inline] + pub fn read_slice(self, buf: &mut [T]) -> Result<(), MemoryAccessError> { + assert_eq!( + buf.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut MaybeUninit, + buf.len() * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + Ok(()) + } + + /// Reads the entire slice into the given uninitialized buffer. + /// + /// The length of the buffer must match the length of the slice. + /// + /// This method returns an initialized view of the buffer. + #[inline] + pub fn read_slice_uninit( + self, + buf: &mut [MaybeUninit], + ) -> Result<&mut [T], MemoryAccessError> { + assert_eq!( + buf.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut MaybeUninit, + buf.len() * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + Ok(unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut T, buf.len()) }) + } + + /// Write the given slice into this `WasmSlice`. + /// + /// The length of the slice must match the length of the `WasmSlice`. + #[inline] + pub fn write_slice(self, data: &[T]) -> Result<(), MemoryAccessError> { + assert_eq!( + data.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::()) + }; + self.memory.write(self.offset, bytes) + } + + /// Reads this `WasmSlice` into a `Vec`. + #[inline] + pub fn read_to_vec(self) -> Result, MemoryAccessError> { + let len = self.len.try_into().expect("WasmSlice length overflow"); + let mut vec = Vec::with_capacity(len); + let bytes = unsafe { + slice::from_raw_parts_mut( + vec.as_mut_ptr() as *mut MaybeUninit, + len * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + unsafe { + vec.set_len(len); + } + Ok(vec) + } +} + +impl<'a, T: ValueType> fmt::Debug for WasmSlice<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "WasmSlice(offset: {}, len: {}, pointer: {:#x})", + self.offset, self.len, self.offset + ) + } +} + +/// Iterator over the elements of a `WasmSlice`. +pub struct WasmSliceIter<'a, T: ValueType> { + slice: WasmSlice<'a, T>, +} + +impl<'a, T: ValueType> Iterator for WasmSliceIter<'a, T> { + type Item = WasmRef<'a, T>; + + fn next(&mut self) -> Option { + if self.slice.len() != 0 { + let elem = self.slice.index(0); + self.slice = self.slice.subslice(1..self.slice.len()); + Some(elem) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (0..self.slice.len()).size_hint() + } +} + +impl<'a, T: ValueType> DoubleEndedIterator for WasmSliceIter<'a, T> { + fn next_back(&mut self) -> Option { + if self.slice.len() != 0 { + let elem = self.slice.index(self.slice.len() - 1); + self.slice = self.slice.subslice(0..self.slice.len() - 1); + Some(elem) + } else { + None + } + } +} + +impl<'a, T: ValueType> ExactSizeIterator for WasmSliceIter<'a, T> {} diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index 66c107c8c..9efdcd0a9 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -23,7 +23,6 @@ mod lib { } } -mod cell; mod env; mod error; mod export; @@ -32,6 +31,7 @@ mod externals; mod import_object; mod instance; mod js_import_object; +mod mem_access; mod module; #[cfg(feature = "wasm-types-polyfill")] mod module_info_polyfill; @@ -48,7 +48,6 @@ mod wasm_bindgen_polyfill; /// See the [`WasmerEnv`] trait for more information. pub use wasmer_derive::WasmerEnv; -pub use crate::js::cell::WasmCell; pub use crate::js::env::{HostEnvInitError, LazyInit, WasmerEnv}; pub use crate::js::error::{DeserializeError, SerializeError}; pub use crate::js::export::Export; @@ -60,9 +59,10 @@ pub use crate::js::externals::{ pub use crate::js::import_object::{ImportObject, ImportObjectIterator, LikeNamespace}; pub use crate::js::instance::{Instance, InstantiationError}; pub use crate::js::js_import_object::JsImportObject; +pub use crate::js::mem_access::{MemoryAccessError, WasmRef, WasmSlice, WasmSliceIter}; pub use crate::js::module::{Module, ModuleTypeHints}; pub use crate::js::native::NativeFunc; -pub use crate::js::ptr::{Array, Item, WasmPtr}; +pub use crate::js::ptr::{Memory32, Memory64, MemorySize, WasmPtr, WasmPtr64}; pub use crate::js::resolver::{ ChainableNamedResolver, NamedResolver, NamedResolverChain, Resolver, }; @@ -77,8 +77,8 @@ pub use crate::js::types::{Val as Value, ValType as Type}; pub use wasmer_types::is_wasm; pub use wasmer_types::{ - Atomically, Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, MemoryView, Pages, ValueType, - WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE, + Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, Pages, ValueType, WASM_MAX_PAGES, + WASM_MIN_PAGES, WASM_PAGE_SIZE, }; #[cfg(feature = "wat")] diff --git a/lib/api/src/js/ptr.rs b/lib/api/src/js/ptr.rs index 571c3bd67..ea175a684 100644 --- a/lib/api/src/js/ptr.rs +++ b/lib/api/src/js/ptr.rs @@ -1,22 +1,56 @@ -//! Types for a reusable pointer abstraction for accessing Wasm linear memory. -//! -//! This abstraction is safe: it ensures the memory is in bounds and that the pointer -//! is aligned (avoiding undefined behavior). -//! -//! Therefore, you should use this abstraction whenever possible to avoid memory -//! related bugs when implementing an ABI. - -use crate::js::cell::WasmCell; use crate::js::{externals::Memory, FromToNativeWasmType}; +use crate::{MemoryAccessError, WasmRef, WasmSlice}; +use std::convert::TryFrom; use std::{fmt, marker::PhantomData, mem}; -use wasmer_types::ValueType; +use wasmer_types::{NativeWasmType, ValueType}; -/// The `Array` marker type. This type can be used like `WasmPtr` -/// to get access to methods -pub struct Array; -/// The `Item` marker type. This is the default and does not usually need to be -/// specified. -pub struct Item; +/// Trait for the `Memory32` and `Memory64` marker types. +/// +/// This allows code to be generic over 32-bit and 64-bit memories. +pub unsafe trait MemorySize { + /// Type used to represent an offset into a memory. This is `u32` or `u64`. + type Offset: Copy + Into + TryFrom; + + /// Type used to pass this value as an argument or return value for a Wasm function. + type Native: NativeWasmType; + + /// Zero value used for `WasmPtr::is_null`. + const ZERO: Self::Offset; + + fn offset_to_native(offset: Self::Offset) -> Self::Native; + fn native_to_offset(native: Self::Native) -> Self::Offset; +} + +/// Marker trait for 32-bit memories. +pub struct Memory32; +unsafe impl MemorySize for Memory32 { + type Offset = u32; + type Native = i32; + const ZERO: Self::Offset = 0; + fn offset_to_native(offset: Self::Offset) -> Self::Native { + offset as Self::Native + } + fn native_to_offset(native: Self::Native) -> Self::Offset { + native as Self::Offset + } +} + +/// Marker trait for 64-bit memories. +pub struct Memory64; +unsafe impl MemorySize for Memory64 { + type Offset = u64; + type Native = i64; + const ZERO: Self::Offset = 0; + fn offset_to_native(offset: Self::Offset) -> Self::Native { + offset as Self::Native + } + fn native_to_offset(native: Self::Native) -> Self::Offset { + native as Self::Offset + } +} + +/// Alias for `WasmPtr. +pub type WasmPtr64 = WasmPtr; /// A zero-cost type that represents a pointer to something in Wasm linear /// memory. @@ -26,11 +60,11 @@ pub struct Item; /// # use wasmer::Memory; /// # use wasmer::WasmPtr; /// pub fn host_import(memory: Memory, ptr: WasmPtr) { -/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds"); -/// let inner_val: u32 = derefed_ptr.get(); +/// let derefed_ptr = ptr.deref(&memory); +/// let inner_val: u32 = derefed_ptr.read().expect("pointer in bounds"); /// println!("Got {} from Wasm memory address 0x{:X}", inner_val, ptr.offset()); /// // update the value being pointed to -/// derefed_ptr.set(inner_val + 1); +/// derefed_ptr.write(inner_val + 1).expect("pointer in bounds"); /// } /// ``` /// @@ -41,38 +75,35 @@ pub struct Item; /// # use wasmer::WasmPtr; /// # use wasmer::ValueType; /// -/// #[derive(Copy, Clone, Debug)] +/// // This is safe as the 12 bytes represented by this struct +/// // are valid for all bit combinations. +/// #[derive(Copy, Clone, Debug, ValueType)] /// #[repr(C)] /// struct V3 { /// x: f32, /// y: f32, /// z: f32 /// } -/// // This is safe as the 12 bytes represented by this struct -/// // are valid for all bit combinations. -/// unsafe impl ValueType for V3 { -/// } /// /// fn update_vector_3(memory: Memory, ptr: WasmPtr) { -/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds"); -/// let mut inner_val: V3 = derefed_ptr.get(); +/// let derefed_ptr = ptr.deref(&memory); +/// let mut inner_val: V3 = derefed_ptr.read().expect("pointer in bounds"); /// println!("Got {:?} from Wasm memory address 0x{:X}", inner_val, ptr.offset()); /// // update the value being pointed to /// inner_val.x = 10.4; -/// derefed_ptr.set(inner_val); +/// derefed_ptr.write(inner_val).expect("pointer in bounds"); /// } /// ``` #[repr(transparent)] -pub struct WasmPtr { - offset: u32, - _phantom: PhantomData<(T, Ty)>, +pub struct WasmPtr { + offset: M::Offset, + _phantom: PhantomData<*mut T>, } -/// Methods relevant to all types of `WasmPtr`. -impl WasmPtr { +impl WasmPtr { /// Create a new `WasmPtr` at the given offset. #[inline] - pub fn new(offset: u32) -> Self { + pub fn new(offset: M::Offset) -> Self { Self { offset, _phantom: PhantomData, @@ -81,136 +112,175 @@ impl WasmPtr { /// Get the offset into Wasm linear memory for this `WasmPtr`. #[inline] - pub fn offset(self) -> u32 { + pub fn offset(self) -> M::Offset { self.offset } -} -/// Methods for `WasmPtr`s to data that can be dereferenced, namely to types -/// that implement [`ValueType`], meaning that they're valid for all possible -/// bit patterns. -impl WasmPtr { - /// Dereference the `WasmPtr` getting access to a `&Cell` allowing for - /// reading and mutating of the inner value. - /// - /// This method is unsound if used with unsynchronized shared memory. - /// If you're unsure what that means, it likely does not apply to you. - /// This invariant will be enforced in the future. + /// Casts this `WasmPtr` to a `WasmPtr` of a different type. #[inline] - pub fn deref<'a>(self, memory: &'a Memory) -> Option> { - let end = (self.offset as usize).checked_add(mem::size_of::())?; - if end > memory.size().bytes().0 || mem::size_of::() == 0 { - return None; + pub fn cast(self) -> WasmPtr { + WasmPtr { + offset: self.offset, + _phantom: PhantomData, } + } - let subarray = memory.uint8view().subarray(self.offset, end as u32); - Some(WasmCell::new(subarray)) + /// Returns a null `UserPtr`. + #[inline] + pub fn null() -> Self { + WasmPtr::new(M::ZERO) + } + + /// Checks whether the `WasmPtr` is null. + #[inline] + pub fn is_null(self) -> bool { + self.offset.into() == 0 + } + + /// Calculates an offset from the current pointer address. The argument is + /// in units of `T`. + /// + /// This method returns an error if an address overflow occurs. + #[inline] + #[must_use] + pub fn add(self, offset: M::Offset) -> Result { + let base = self.offset.into(); + let index = offset.into(); + let offset = index + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + let address = base + .checked_add(offset) + .ok_or(MemoryAccessError::Overflow)?; + let address = M::Offset::try_from(address).map_err(|_| MemoryAccessError::Overflow)?; + Ok(WasmPtr::new(address)) + } + + /// Calculates an offset from the current pointer address. The argument is + /// in units of `T`. + /// + /// This method returns an error if an address overflow occurs. + #[inline] + #[must_use] + pub fn sub(self, offset: M::Offset) -> Result { + let base = self.offset.into(); + let index = offset.into(); + let offset = index + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + let address = base + .checked_sub(offset) + .ok_or(MemoryAccessError::Overflow)?; + let address = M::Offset::try_from(address).map_err(|_| MemoryAccessError::Overflow)?; + Ok(WasmPtr::new(address)) } } -/// Methods for `WasmPtr`s to arrays of data that can be dereferenced, namely to -/// types that implement [`ValueType`], meaning that they're valid for all -/// possible bit patterns. -impl WasmPtr { - /// Dereference the `WasmPtr` getting access to a `&[Cell]` allowing for - /// reading and mutating of the inner values. - /// - /// This method is unsound if used with unsynchronized shared memory. - /// If you're unsure what that means, it likely does not apply to you. - /// This invariant will be enforced in the future. +impl WasmPtr { + /// Creates a `WasmRef` from this `WasmPtr` which allows reading and + /// mutating of the value being pointed to. #[inline] - pub fn deref(self, memory: &Memory, index: u32, length: u32) -> Option>> { - // gets the size of the item in the array with padding added such that - // for any index, we will always result an aligned memory access - let item_size = mem::size_of::() as u32; - let slice_full_len = index.checked_add(length)?; - let memory_size = memory.size().bytes().0 as u32; - let end = self - .offset - .checked_add(item_size.checked_mul(slice_full_len)?)?; - if end > memory_size || item_size == 0 { - return None; - } - - Some( - (0..length) - .map(|i| { - let subarray = memory.uint8view().subarray( - self.offset + i * item_size, - self.offset + (i + 1) * item_size, - ); - WasmCell::new(subarray) - }) - .collect::>(), - ) + pub fn deref<'a>(self, memory: &'a Memory) -> WasmRef<'a, T> { + WasmRef::new(memory, self.offset.into()) } - /// Get a UTF-8 string from the `WasmPtr` with the given length. + /// Reads the address pointed to by this `WasmPtr` in a memory. + #[inline] + pub fn read(self, memory: &Memory) -> Result { + self.deref(memory).read() + } + + /// Writes to the address pointed to by this `WasmPtr` in a memory. + #[inline] + pub fn write(self, memory: &Memory, val: T) -> Result<(), MemoryAccessError> { + self.deref(memory).write(val) + } + + /// Creates a `WasmSlice` starting at this `WasmPtr` which allows reading + /// and mutating of an array of value being pointed to. /// - /// Note that . The - /// underlying data can be mutated if the Wasm is allowed to execute or - /// an aliasing `WasmPtr` is used to mutate memory. - /// - /// # Safety - /// This method returns a reference to Wasm linear memory. The underlying - /// data can be mutated if the Wasm is allowed to execute or an aliasing - /// `WasmPtr` is used to mutate memory. - /// - /// `str` has invariants that must not be broken by mutating Wasm memory. - /// Thus the caller must ensure that the backing memory is not modified - /// while the reference is held. - /// - /// Additionally, if `memory` is dynamic, the caller must also ensure that `memory` - /// is not grown while the reference is held. - pub unsafe fn get_utf8_str<'a>( + /// Returns a `MemoryAccessError` if the slice length overflows a 64-bit + /// address. + #[inline] + pub fn slice<'a>( self, memory: &'a Memory, - str_len: u32, - ) -> Option> { - self.get_utf8_string(memory, str_len) - .map(std::borrow::Cow::from) + len: M::Offset, + ) -> Result, MemoryAccessError> { + WasmSlice::new(memory, self.offset.into(), len.into()) } - /// Get a UTF-8 `String` from the `WasmPtr` with the given length. + /// Reads a sequence of values from this `WasmPtr` until a value that + /// matches the given condition is found. /// - /// an aliasing `WasmPtr` is used to mutate memory. - pub fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option { - let end = self.offset.checked_add(str_len)?; - if end as usize > memory.size().bytes().0 { - return None; + /// This last value is not included in the returned vector. + #[inline] + pub fn read_until<'a>( + self, + memory: &'a Memory, + mut end: impl FnMut(&T) -> bool, + ) -> Result, MemoryAccessError> { + let mut vec = Vec::new(); + for i in 0u64.. { + let i = M::Offset::try_from(i).map_err(|_| MemoryAccessError::Overflow)?; + let val = self.add(i)?.deref(memory).read()?; + if end(&val) { + break; + } + vec.push(val); } - - let view = memory.uint8view(); - // let subarray_as_vec = view.subarray(self.offset, str_len + 1).to_vec(); - - let mut subarray_as_vec: Vec = Vec::with_capacity(str_len as usize); - let base = self.offset; - for i in 0..(str_len) { - let byte = view.get_index(base + i); - subarray_as_vec.push(byte); - } - - String::from_utf8(subarray_as_vec).ok() + Ok(vec) } } -unsafe impl FromToNativeWasmType for WasmPtr { - type Native = i32; +impl WasmPtr { + /// Reads a UTF-8 string from the `WasmPtr` with the given length. + /// + /// This method is safe to call even if the memory is being concurrently + /// modified. + #[inline] + pub fn read_utf8_string<'a>( + self, + memory: &'a Memory, + len: M::Offset, + ) -> Result { + let vec = self.slice(memory, len)?.read_to_vec()?; + Ok(String::from_utf8(vec)?) + } + + /// Reads a null-terminated UTF-8 string from the `WasmPtr`. + /// + /// This method is safe to call even if the memory is being concurrently + /// modified. + #[inline] + pub fn read_utf8_string_with_nul<'a>( + self, + memory: &'a Memory, + ) -> Result { + let vec = self.read_until(memory, |&byte| byte == 0)?; + Ok(String::from_utf8(vec)?) + } +} + +unsafe impl FromToNativeWasmType for WasmPtr { + type Native = M::Native; fn to_native(self) -> Self::Native { - self.offset as i32 + M::offset_to_native(self.offset) } fn from_native(n: Self::Native) -> Self { Self { - offset: n as u32, + offset: M::native_to_offset(n), _phantom: PhantomData, } } } -unsafe impl ValueType for WasmPtr {} +unsafe impl ValueType for WasmPtr { + fn zero_padding_bytes(&self, _bytes: &mut [mem::MaybeUninit]) {} +} -impl Clone for WasmPtr { +impl Clone for WasmPtr { fn clone(&self) -> Self { Self { offset: self.offset, @@ -219,131 +289,23 @@ impl Clone for WasmPtr { } } -impl Copy for WasmPtr {} +impl Copy for WasmPtr {} -impl PartialEq for WasmPtr { +impl PartialEq for WasmPtr { fn eq(&self, other: &Self) -> bool { - self.offset == other.offset + self.offset.into() == other.offset.into() } } -impl Eq for WasmPtr {} +impl Eq for WasmPtr {} -impl fmt::Debug for WasmPtr { +impl fmt::Debug for WasmPtr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "WasmPtr(offset: {}, pointer: {:#x}, align: {})", - self.offset, - self.offset, - mem::align_of::() + "WasmPtr(offset: {}, pointer: {:#x})", + self.offset.into(), + self.offset.into() ) } } - -#[cfg(test)] -mod test { - use super::*; - use crate::js::{Memory, MemoryType, Store}; - use wasm_bindgen_test::*; - - /// Ensure that memory accesses work on the edges of memory and that out of - /// bounds errors are caught with both `deref` and `deref_mut`. - #[wasm_bindgen_test] - fn wasm_ptr_is_functional() { - let store = Store::default(); - let memory_descriptor = MemoryType::new(1, Some(1), false); - let memory = Memory::new(&store, memory_descriptor).unwrap(); - - let start_wasm_ptr: WasmPtr = WasmPtr::new(2); - let val = start_wasm_ptr.deref(&memory).unwrap(); - assert_eq!(val.memory.to_vec(), vec![0; 8]); - - val.set(1200); - - assert_eq!(val.memory.to_vec(), vec![176, 4, 0, 0, 0, 0, 0, 0]); - // Let's make sure the main memory is changed - assert_eq!( - memory.uint8view().subarray(0, 10).to_vec(), - vec![0, 0, 176, 4, 0, 0, 0, 0, 0, 0] - ); - - val.memory.copy_from(&[10, 0, 0, 0, 0, 0, 0, 0]); - - let value = val.get(); - assert_eq!(value, 10); - } - - /// Ensure that memory accesses work on the edges of memory and that out of - /// bounds errors are caught with both `deref` and `deref_mut`. - #[wasm_bindgen_test] - fn wasm_ptr_memory_bounds_checks_hold() { - // create a memory - let store = Store::default(); - let memory_descriptor = MemoryType::new(1, Some(1), false); - let memory = Memory::new(&store, memory_descriptor).unwrap(); - - // test that basic access works and that len = 0 works, but oob does not - let start_wasm_ptr: WasmPtr = WasmPtr::new(0); - let start_wasm_ptr_array: WasmPtr = WasmPtr::new(0); - - assert!(start_wasm_ptr.deref(&memory).is_some()); - assert!(start_wasm_ptr_array.deref(&memory, 0, 0).is_some()); - assert!(unsafe { start_wasm_ptr_array.get_utf8_str(&memory, 0).is_some() }); - assert!(start_wasm_ptr_array.get_utf8_string(&memory, 0).is_some()); - assert!(start_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - - // test that accessing the last valid memory address works correctly and OOB is caught - let last_valid_address_for_u8 = (memory.size().bytes().0 - 1) as u32; - let end_wasm_ptr: WasmPtr = WasmPtr::new(last_valid_address_for_u8); - assert!(end_wasm_ptr.deref(&memory).is_some()); - - let end_wasm_ptr_array: WasmPtr = WasmPtr::new(last_valid_address_for_u8); - - assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - let invalid_idx_len_combos: [(u32, u32); 3] = - [(last_valid_address_for_u8 + 1, 0), (0, 2), (1, 1)]; - for &(idx, len) in invalid_idx_len_combos.iter() { - assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none()); - } - assert!(unsafe { end_wasm_ptr_array.get_utf8_str(&memory, 2).is_none() }); - assert!(end_wasm_ptr_array.get_utf8_string(&memory, 2).is_none()); - - // test that accesing the last valid memory address for a u32 is valid - // (same as above test but with more edge cases to assert on) - let last_valid_address_for_u32 = (memory.size().bytes().0 - 4) as u32; - let end_wasm_ptr: WasmPtr = WasmPtr::new(last_valid_address_for_u32); - assert!(end_wasm_ptr.deref(&memory).is_some()); - assert!(end_wasm_ptr.deref(&memory).is_some()); - - let end_wasm_ptr_oob_array: [WasmPtr; 4] = [ - WasmPtr::new(last_valid_address_for_u32 + 1), - WasmPtr::new(last_valid_address_for_u32 + 2), - WasmPtr::new(last_valid_address_for_u32 + 3), - WasmPtr::new(last_valid_address_for_u32 + 4), - ]; - for oob_end_ptr in end_wasm_ptr_oob_array.iter() { - assert!(oob_end_ptr.deref(&memory).is_none()); - } - let end_wasm_ptr_array: WasmPtr = WasmPtr::new(last_valid_address_for_u32); - assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - - let invalid_idx_len_combos: [(u32, u32); 3] = - [(last_valid_address_for_u32 + 1, 0), (0, 2), (1, 1)]; - for &(idx, len) in invalid_idx_len_combos.iter() { - assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none()); - } - - let end_wasm_ptr_array_oob_array: [WasmPtr; 4] = [ - WasmPtr::new(last_valid_address_for_u32 + 1), - WasmPtr::new(last_valid_address_for_u32 + 2), - WasmPtr::new(last_valid_address_for_u32 + 3), - WasmPtr::new(last_valid_address_for_u32 + 4), - ]; - - for oob_end_array_ptr in end_wasm_ptr_array_oob_array.iter() { - assert!(oob_end_array_ptr.deref(&memory, 0, 1).is_none()); - assert!(oob_end_array_ptr.deref(&memory, 1, 0).is_none()); - } - } -} diff --git a/lib/api/src/sys/cell.rs b/lib/api/src/sys/cell.rs deleted file mode 100644 index c7d6bc4df..000000000 --- a/lib/api/src/sys/cell.rs +++ /dev/null @@ -1,148 +0,0 @@ -pub use std::cell::Cell; - -use core::cmp::Ordering; -use core::fmt::{self, Debug}; - -/// A mutable Wasm-memory location. -#[repr(transparent)] -pub struct WasmCell<'a, T: ?Sized> { - inner: &'a Cell, -} - -unsafe impl Send for WasmCell<'_, T> where T: Send {} - -unsafe impl Sync for WasmCell<'_, T> {} - -impl<'a, T: Copy> Clone for WasmCell<'a, T> { - #[inline] - fn clone(&self) -> WasmCell<'a, T> { - WasmCell { inner: self.inner } - } -} - -impl PartialEq for WasmCell<'_, T> { - #[inline] - fn eq(&self, other: &WasmCell) -> bool { - self.inner.eq(&other.inner) - } -} - -impl Eq for WasmCell<'_, T> {} - -impl PartialOrd for WasmCell<'_, T> { - #[inline] - fn partial_cmp(&self, other: &WasmCell) -> Option { - self.inner.partial_cmp(&other.inner) - } - - #[inline] - fn lt(&self, other: &WasmCell) -> bool { - self.inner < other.inner - } - - #[inline] - fn le(&self, other: &WasmCell) -> bool { - self.inner <= other.inner - } - - #[inline] - fn gt(&self, other: &WasmCell) -> bool { - self.inner > other.inner - } - - #[inline] - fn ge(&self, other: &WasmCell) -> bool { - self.inner >= other.inner - } -} - -impl Ord for WasmCell<'_, T> { - #[inline] - fn cmp(&self, other: &WasmCell) -> Ordering { - self.inner.cmp(&other.inner) - } -} - -impl<'a, T> WasmCell<'a, T> { - /// Creates a new `WasmCell` containing the given value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// ``` - #[inline] - pub const fn new(cell: &'a Cell) -> WasmCell<'a, T> { - WasmCell { inner: cell } - } -} - -impl<'a, T: Copy> WasmCell<'a, T> { - /// Returns a copy of the contained value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// let five = wasm_cell.get(); - /// ``` - #[inline] - pub fn get(&self) -> T { - self.inner.get() - } - - /// Get an unsafe mutable pointer to the inner item - /// in the Cell. - /// - /// # Safety - /// - /// This method is highly discouraged to use. We have it for - /// compatibility reasons with Emscripten. - /// It is unsafe because changing an item inline will change - /// the underlying memory. - /// - /// It's highly encouraged to use the `set` method instead. - #[deprecated( - since = "2.0.0", - note = "Please use the memory-safe set method instead" - )] - #[doc(hidden)] - pub unsafe fn get_mut(&self) -> &'a mut T { - &mut *self.inner.as_ptr() - } -} - -impl Debug for WasmCell<'_, T> { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "WasmCell({:?})", self.inner.get()) - } -} - -impl WasmCell<'_, T> { - /// Sets the contained value. - /// - /// # Examples - /// - /// ``` - /// use std::cell::Cell; - /// use wasmer::WasmCell; - /// - /// let cell = Cell::new(5); - /// let wasm_cell = WasmCell::new(&cell); - /// wasm_cell.set(10); - /// assert_eq!(cell.get(), 10); - /// ``` - #[inline] - pub fn set(&self, val: T) { - self.inner.set(val); - } -} diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index 2403c212f..46bc4a4d4 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -1,13 +1,16 @@ use crate::sys::exports::{ExportError, Exportable}; use crate::sys::externals::Extern; use crate::sys::store::Store; -use crate::sys::{MemoryType, MemoryView}; +use crate::sys::MemoryType; +use crate::MemoryAccessError; use loupe::MemoryUsage; use std::convert::TryInto; +use std::mem; +use std::mem::MaybeUninit; use std::slice; use std::sync::Arc; use wasmer_engine::Export; -use wasmer_types::{Pages, ValueType}; +use wasmer_types::Pages; use wasmer_vm::{MemoryError, VMMemory}; /// A WebAssembly `memory` instance. @@ -93,34 +96,11 @@ impl Memory { &self.store } - /// Retrieve a slice of the memory contents. - /// - /// # Safety - /// - /// Until the returned slice is dropped, it is undefined behaviour to - /// modify the memory contents in any way including by calling a wasm - /// function that writes to the memory or by resizing the memory. - pub unsafe fn data_unchecked(&self) -> &[u8] { - self.data_unchecked_mut() - } - - /// Retrieve a mutable slice of the memory contents. - /// - /// # Safety - /// - /// This method provides interior mutability without an UnsafeCell. Until - /// the returned value is dropped, it is undefined behaviour to read or - /// write to the pointed-to memory in any way except through this slice, - /// including by calling a wasm function that reads the memory contents or - /// by resizing this Memory. - #[allow(clippy::mut_from_ref)] - pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] { - let definition = self.vm_memory.from.vmmemory(); - let def = definition.as_ref(); - slice::from_raw_parts_mut(def.base, def.current_length.try_into().unwrap()) - } - /// Returns the pointer to the raw bytes of the `Memory`. + // + // This used by wasmer-emscripten and wasmer-c-api, but should be treated + // as deprecated and not used in future code. + #[doc(hidden)] pub fn data_ptr(&self) -> *mut u8 { let definition = self.vm_memory.from.vmmemory(); let def = unsafe { definition.as_ref() }; @@ -187,53 +167,6 @@ impl Memory { self.vm_memory.from.grow(delta.into()) } - /// Return a "view" of the currently accessible memory. By - /// default, the view is unsynchronized, using regular memory - /// accesses. You can force a memory view to use atomic accesses - /// by calling the [`MemoryView::atomically`] method. - /// - /// # Notes: - /// - /// This method is safe (as in, it won't cause the host to crash or have UB), - /// but it doesn't obey rust's rules involving data races, especially concurrent ones. - /// Therefore, if this memory is shared between multiple threads, a single memory - /// location can be mutated concurrently without synchronization. - /// - /// # Usage: - /// - /// ``` - /// # use wasmer::{Memory, MemoryView}; - /// # use std::{cell::Cell, sync::atomic::Ordering}; - /// # fn view_memory(memory: Memory) { - /// // Without synchronization. - /// let view: MemoryView = memory.view(); - /// for byte in view[0x1000 .. 0x1010].iter().map(Cell::get) { - /// println!("byte: {}", byte); - /// } - /// - /// // With synchronization. - /// let atomic_view = view.atomically(); - /// for byte in atomic_view[0x1000 .. 0x1010].iter().map(|atom| atom.load(Ordering::SeqCst)) { - /// println!("byte: {}", byte); - /// } - /// # } - /// ``` - pub fn view(&self) -> MemoryView { - let base = self.data_ptr(); - - let length = self.size().bytes().0 / std::mem::size_of::(); - - unsafe { MemoryView::new(base as _, length as u32) } - } - - /// A shortcut to [`Self::view::`][self::view]. - /// - /// This code is going to be refactored. Use it as your own risks. - #[doc(hidden)] - pub fn uint8view(&self) -> MemoryView { - self.view() - } - pub(crate) fn from_vm_export(store: &Store, vm_memory: VMMemory) -> Self { Self { store: store.clone(), @@ -268,6 +201,82 @@ impl Memory { pub unsafe fn get_vm_memory(&self) -> &VMMemory { &self.vm_memory } + + /// Safely reads bytes from the memory at the given offset. + /// + /// The full buffer will be filled, otherwise a `MemoryAccessError` is returned + /// to indicate an out-of-bounds access. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent writes. + pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), MemoryAccessError> { + let definition = self.vm_memory.from.vmmemory(); + let def = unsafe { definition.as_ref() }; + let end = offset + .checked_add(buf.len() as u64) + .ok_or(MemoryAccessError::Overflow)?; + if end > def.current_length.try_into().unwrap() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + unsafe { + volatile_memcpy_read(def.base.add(offset as usize), buf.as_mut_ptr(), buf.len()); + } + + Ok(()) + } + + /// Safely reads bytes from the memory at the given offset. + /// + /// This method is similar to `read` but allows reading into an + /// uninitialized buffer. An initialized view of the buffer is returned. + /// + /// The full buffer will be filled, otherwise a `MemoryAccessError` is returned + /// to indicate an out-of-bounds access. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent writes. + pub fn read_uninit<'a>( + &self, + offset: u64, + buf: &'a mut [MaybeUninit], + ) -> Result<&'a mut [u8], MemoryAccessError> { + let definition = self.vm_memory.from.vmmemory(); + let def = unsafe { definition.as_ref() }; + let end = offset + .checked_add(buf.len() as u64) + .ok_or(MemoryAccessError::Overflow)?; + if end > def.current_length.try_into().unwrap() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + let buf_ptr = buf.as_mut_ptr() as *mut u8; + unsafe { + volatile_memcpy_read(def.base.add(offset as usize), buf_ptr, buf.len()); + } + + Ok(unsafe { slice::from_raw_parts_mut(buf_ptr, buf.len()) }) + } + + /// Safely writes bytes to the memory at the given offset. + /// + /// If the write exceeds the bounds of the memory then a `MemoryAccessError` is + /// returned. + /// + /// This method is guaranteed to be safe (from the host side) in the face of + /// concurrent reads/writes. + pub fn write(&self, offset: u64, data: &[u8]) -> Result<(), MemoryAccessError> { + let definition = self.vm_memory.from.vmmemory(); + let def = unsafe { definition.as_ref() }; + let end = offset + .checked_add(data.len() as u64) + .ok_or(MemoryAccessError::Overflow)?; + if end > def.current_length.try_into().unwrap() { + Err(MemoryAccessError::HeapOutOfBounds)?; + } + unsafe { + volatile_memcpy_write(data.as_ptr(), def.base.add(offset as usize), data.len()); + } + Ok(()) + } } impl Clone for Memory { @@ -301,3 +310,63 @@ impl<'a> Exportable<'a> for Memory { .map(|v| *v = v.downgrade()); } } + +// We can't use a normal memcpy here because it has undefined behavior if the +// memory is being concurrently modified. So we need to write our own memcpy +// implementation which uses volatile operations. +// +// The implementation of these functions can optimize very well when inlined +// with a fixed length: they should compile down to a single load/store +// instruction for small (8/16/32/64-bit) copies. +#[inline] +unsafe fn volatile_memcpy_read(mut src: *const u8, mut dst: *mut u8, mut len: usize) { + #[inline] + unsafe fn copy_one(src: &mut *const u8, dst: &mut *mut u8, len: &mut usize) { + #[repr(packed)] + struct Unaligned(T); + let val = (*src as *const Unaligned).read_volatile(); + (*dst as *mut Unaligned).write(val); + *src = src.add(mem::size_of::()); + *dst = dst.add(mem::size_of::()); + *len -= mem::size_of::(); + } + + while len >= 8 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 4 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 2 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 1 { + copy_one::(&mut src, &mut dst, &mut len); + } +} +#[inline] +unsafe fn volatile_memcpy_write(mut src: *const u8, mut dst: *mut u8, mut len: usize) { + #[inline] + unsafe fn copy_one(src: &mut *const u8, dst: &mut *mut u8, len: &mut usize) { + #[repr(packed)] + struct Unaligned(T); + let val = (*src as *const Unaligned).read(); + (*dst as *mut Unaligned).write_volatile(val); + *src = src.add(mem::size_of::()); + *dst = dst.add(mem::size_of::()); + *len -= mem::size_of::(); + } + + while len >= 8 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 4 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 2 { + copy_one::(&mut src, &mut dst, &mut len); + } + if len >= 1 { + copy_one::(&mut src, &mut dst, &mut len); + } +} diff --git a/lib/api/src/sys/mem_access.rs b/lib/api/src/sys/mem_access.rs new file mode 100644 index 000000000..8a89a4d9c --- /dev/null +++ b/lib/api/src/sys/mem_access.rs @@ -0,0 +1,382 @@ +use crate::RuntimeError; +use wasmer_types::ValueType; + +use crate::{Memory, Memory32, Memory64, WasmPtr}; +use std::{ + convert::TryInto, + error::Error, + fmt, + marker::PhantomData, + mem::{self, MaybeUninit}, + ops::Range, + slice, + string::FromUtf8Error, +}; + +/// Error for invalid [`Memory`] access. +#[derive(Debug)] +#[non_exhaustive] +pub enum MemoryAccessError { + /// Memory access is outside heap bounds. + HeapOutOfBounds, + /// Address calculation overflow. + Overflow, + /// String is not valid UTF-8. + NonUtf8String, +} + +impl fmt::Display for MemoryAccessError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MemoryAccessError::HeapOutOfBounds => write!(f, "memory access out of bounds"), + MemoryAccessError::Overflow => write!(f, "address calculation overflow"), + MemoryAccessError::NonUtf8String => write!(f, "string is not valid utf-8"), + } + } +} + +impl Error for MemoryAccessError {} + +impl From for RuntimeError { + fn from(err: MemoryAccessError) -> Self { + RuntimeError::new(err.to_string()) + } +} +impl From for MemoryAccessError { + fn from(_err: FromUtf8Error) -> Self { + MemoryAccessError::NonUtf8String + } +} + +/// Reference to a value in Wasm memory. +/// +/// The type of the value must satisfy the requirements of the `ValueType` +/// trait which guarantees that reading and writing such a value to untrusted +/// memory is safe. +/// +/// The address is not required to be aligned: unaligned accesses are fully +/// supported. +/// +/// This wrapper safely handles concurrent modifications of the data by another +/// thread. +#[derive(Clone, Copy)] +pub struct WasmRef<'a, T: ValueType> { + memory: &'a Memory, + offset: u64, + marker: PhantomData<*mut T>, +} + +impl<'a, T: ValueType> WasmRef<'a, T> { + /// Creates a new `WasmRef` at the given offset in a memory. + #[inline] + pub fn new(memory: &'a Memory, offset: u64) -> Self { + Self { + memory, + offset, + marker: PhantomData, + } + } + + /// Get the offset into Wasm linear memory for this `WasmRef`. + #[inline] + pub fn offset(self) -> u64 { + self.offset + } + + /// Get a `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr32(self) -> WasmPtr { + WasmPtr::new(self.offset as u32) + } + + /// Get a 64-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr64(self) -> WasmPtr { + WasmPtr::new(self.offset) + } + + /// Get a reference to the Wasm memory backing this reference. + #[inline] + pub fn memory(self) -> &'a Memory { + self.memory + } + + /// Reads the location pointed to by this `WasmRef`. + #[inline] + pub fn read(self) -> Result { + let mut out = MaybeUninit::uninit(); + let buf = + unsafe { slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, mem::size_of::()) }; + self.memory.read(self.offset, buf)?; + Ok(unsafe { out.assume_init() }) + } + + /// Writes to the location pointed to by this `WasmRef`. + #[inline] + pub fn write(self, val: T) -> Result<(), MemoryAccessError> { + let mut data = MaybeUninit::new(val); + let data = unsafe { + slice::from_raw_parts_mut( + data.as_mut_ptr() as *mut MaybeUninit, + mem::size_of::(), + ) + }; + val.zero_padding_bytes(data); + let data = unsafe { slice::from_raw_parts(data.as_ptr() as *const _, data.len()) }; + self.memory.write(self.offset, data) + } +} + +impl<'a, T: ValueType> fmt::Debug for WasmRef<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "WasmRef(offset: {}, pointer: {:#x})", + self.offset, self.offset + ) + } +} + +/// Reference to an array of values in Wasm memory. +/// +/// The type of the value must satisfy the requirements of the `ValueType` +/// trait which guarantees that reading and writing such a value to untrusted +/// memory is safe. +/// +/// The address is not required to be aligned: unaligned accesses are fully +/// supported. +/// +/// This wrapper safely handles concurrent modifications of the data by another +/// thread. +#[derive(Clone, Copy)] +pub struct WasmSlice<'a, T: ValueType> { + memory: &'a Memory, + offset: u64, + len: u64, + marker: PhantomData<*mut T>, +} + +impl<'a, T: ValueType> WasmSlice<'a, T> { + /// Creates a new `WasmSlice` starting at the given offset in memory and + /// with the given number of elements. + /// + /// Returns a `MemoryAccessError` if the slice length overflows. + #[inline] + pub fn new(memory: &'a Memory, offset: u64, len: u64) -> Result { + let total_len = len + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + offset + .checked_add(total_len) + .ok_or(MemoryAccessError::Overflow)?; + Ok(Self { + memory, + offset, + len, + marker: PhantomData, + }) + } + + /// Get the offset into Wasm linear memory for this `WasmSlice`. + #[inline] + pub fn offset(self) -> u64 { + self.offset + } + + /// Get a 32-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr32(self) -> WasmPtr { + WasmPtr::new(self.offset as u32) + } + + /// Get a 64-bit `WasmPtr` for this `WasmRef`. + #[inline] + pub fn as_ptr64(self) -> WasmPtr { + WasmPtr::new(self.offset) + } + + /// Get the number of elements in this slice. + #[inline] + pub fn len(self) -> u64 { + self.len + } + + /// Get a reference to the Wasm memory backing this reference. + #[inline] + pub fn memory(self) -> &'a Memory { + self.memory + } + + /// Get a `WasmRef` to an element in the slice. + #[inline] + pub fn index(self, idx: u64) -> WasmRef<'a, T> { + if idx >= self.len { + panic!("WasmSlice out of bounds"); + } + let offset = self.offset + idx * mem::size_of::() as u64; + WasmRef { + memory: self.memory, + offset, + marker: PhantomData, + } + } + + /// Get a `WasmSlice` for a subslice of this slice. + #[inline] + pub fn subslice(self, range: Range) -> WasmSlice<'a, T> { + if range.start > range.end || range.end > self.len { + panic!("WasmSlice out of bounds"); + } + let offset = self.offset + range.start * mem::size_of::() as u64; + Self { + memory: self.memory, + offset, + len: range.end - range.start, + marker: PhantomData, + } + } + + /// Get an iterator over the elements in this slice. + #[inline] + pub fn iter(self) -> WasmSliceIter<'a, T> { + WasmSliceIter { slice: self } + } + + /// Reads an element of this slice. + #[inline] + pub fn read(self, idx: u64) -> Result { + self.index(idx).read() + } + + /// Writes to an element of this slice. + #[inline] + pub fn write(self, idx: u64, val: T) -> Result<(), MemoryAccessError> { + self.index(idx).write(val) + } + + /// Reads the entire slice into the given buffer. + /// + /// The length of the buffer must match the length of the slice. + #[inline] + pub fn read_slice(self, buf: &mut [T]) -> Result<(), MemoryAccessError> { + assert_eq!( + buf.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut MaybeUninit, + buf.len() * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + Ok(()) + } + + /// Reads the entire slice into the given uninitialized buffer. + /// + /// The length of the buffer must match the length of the slice. + /// + /// This method returns an initialized view of the buffer. + #[inline] + pub fn read_slice_uninit( + self, + buf: &mut [MaybeUninit], + ) -> Result<&mut [T], MemoryAccessError> { + assert_eq!( + buf.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut MaybeUninit, + buf.len() * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + Ok(unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut T, buf.len()) }) + } + + /// Write the given slice into this `WasmSlice`. + /// + /// The length of the slice must match the length of the `WasmSlice`. + #[inline] + pub fn write_slice(self, data: &[T]) -> Result<(), MemoryAccessError> { + assert_eq!( + data.len() as u64, + self.len, + "slice length doesn't match WasmSlice length" + ); + let bytes = unsafe { + slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::()) + }; + self.memory.write(self.offset, bytes) + } + + /// Reads this `WasmSlice` into a `Vec`. + #[inline] + pub fn read_to_vec(self) -> Result, MemoryAccessError> { + let len = self.len.try_into().expect("WasmSlice length overflow"); + let mut vec = Vec::with_capacity(len); + let bytes = unsafe { + slice::from_raw_parts_mut( + vec.as_mut_ptr() as *mut MaybeUninit, + len * mem::size_of::(), + ) + }; + self.memory.read_uninit(self.offset, bytes)?; + unsafe { + vec.set_len(len); + } + Ok(vec) + } +} + +impl<'a, T: ValueType> fmt::Debug for WasmSlice<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "WasmSlice(offset: {}, len: {}, pointer: {:#x})", + self.offset, self.len, self.offset + ) + } +} + +/// Iterator over the elements of a `WasmSlice`. +pub struct WasmSliceIter<'a, T: ValueType> { + slice: WasmSlice<'a, T>, +} + +impl<'a, T: ValueType> Iterator for WasmSliceIter<'a, T> { + type Item = WasmRef<'a, T>; + + fn next(&mut self) -> Option { + if self.slice.len() != 0 { + let elem = self.slice.index(0); + self.slice = self.slice.subslice(1..self.slice.len()); + Some(elem) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (0..self.slice.len()).size_hint() + } +} + +impl<'a, T: ValueType> DoubleEndedIterator for WasmSliceIter<'a, T> { + fn next_back(&mut self) -> Option { + if self.slice.len() != 0 { + let elem = self.slice.index(self.slice.len() - 1); + self.slice = self.slice.subslice(0..self.slice.len() - 1); + Some(elem) + } else { + None + } + } +} + +impl<'a, T: ValueType> ExactSizeIterator for WasmSliceIter<'a, T> {} diff --git a/lib/api/src/sys/mod.rs b/lib/api/src/sys/mod.rs index ab7d82e3f..ef7dfaec4 100644 --- a/lib/api/src/sys/mod.rs +++ b/lib/api/src/sys/mod.rs @@ -1,9 +1,9 @@ -mod cell; mod env; mod exports; mod externals; mod import_object; mod instance; +mod mem_access; mod module; mod native; mod ptr; @@ -26,7 +26,6 @@ pub mod internals { pub use crate::sys::externals::{WithEnv, WithoutEnv}; } -pub use crate::sys::cell::WasmCell; pub use crate::sys::env::{HostEnvInitError, LazyInit, WasmerEnv}; pub use crate::sys::exports::{ExportError, Exportable, Exports, ExportsIterator}; pub use crate::sys::externals::{ @@ -34,9 +33,10 @@ pub use crate::sys::externals::{ }; pub use crate::sys::import_object::{ImportObject, ImportObjectIterator, LikeNamespace}; pub use crate::sys::instance::{Instance, InstantiationError}; +pub use crate::sys::mem_access::{MemoryAccessError, WasmRef, WasmSlice, WasmSliceIter}; pub use crate::sys::module::Module; pub use crate::sys::native::NativeFunc; -pub use crate::sys::ptr::{Array, Item, WasmPtr}; +pub use crate::sys::ptr::{Memory32, Memory64, MemorySize, WasmPtr, WasmPtr64}; pub use crate::sys::store::{Store, StoreObject}; pub use crate::sys::tunables::BaseTunables; pub use crate::sys::types::{ @@ -53,6 +53,7 @@ pub use wasmer_compiler::{ pub use wasmer_compiler::{ CompileError, CpuFeature, Features, ParseCpuFeatureError, Target, WasmError, WasmResult, }; +pub use wasmer_derive::ValueType; pub use wasmer_engine::{ ChainableNamedResolver, DeserializeError, Engine, Export, FrameInfo, LinkError, NamedResolver, NamedResolverChain, Resolver, RuntimeError, SerializeError, Tunables, @@ -61,8 +62,8 @@ pub use wasmer_types::is_wasm; #[cfg(feature = "experimental-reference-types-extern-ref")] pub use wasmer_types::ExternRef; pub use wasmer_types::{ - Atomically, Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, MemoryView, Pages, ValueType, - WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE, + Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, Pages, ValueType, WASM_MAX_PAGES, + WASM_MIN_PAGES, WASM_PAGE_SIZE, }; // TODO: should those be moved into wasmer::vm as well? diff --git a/lib/api/src/sys/ptr.rs b/lib/api/src/sys/ptr.rs index 64b958784..3d50a2484 100644 --- a/lib/api/src/sys/ptr.rs +++ b/lib/api/src/sys/ptr.rs @@ -1,22 +1,59 @@ -//! Types for a reusable pointer abstraction for accessing Wasm linear memory. -//! -//! This abstraction is safe: it ensures the memory is in bounds and that the pointer -//! is aligned (avoiding undefined behavior). -//! -//! Therefore, you should use this abstraction whenever possible to avoid memory -//! related bugs when implementing an ABI. - -use crate::sys::cell::WasmCell; use crate::sys::{externals::Memory, FromToNativeWasmType}; -use std::{cell::Cell, fmt, marker::PhantomData, mem}; -use wasmer_types::ValueType; +use crate::{MemoryAccessError, WasmRef, WasmSlice}; +use std::convert::TryFrom; +use std::{fmt, marker::PhantomData, mem}; +use wasmer_types::{NativeWasmType, ValueType}; -/// The `Array` marker type. This type can be used like `WasmPtr` -/// to get access to methods -pub struct Array; -/// The `Item` marker type. This is the default and does not usually need to be -/// specified. -pub struct Item; +/// Trait for the `Memory32` and `Memory64` marker types. +/// +/// This allows code to be generic over 32-bit and 64-bit memories. +pub unsafe trait MemorySize { + /// Type used to represent an offset into a memory. This is `u32` or `u64`. + type Offset: Copy + Into + TryFrom; + + /// Type used to pass this value as an argument or return value for a Wasm function. + type Native: NativeWasmType; + + /// Zero value used for `WasmPtr::is_null`. + const ZERO: Self::Offset; + + /// Convert an `Offset` to a `Native`. + fn offset_to_native(offset: Self::Offset) -> Self::Native; + + /// Convert a `Native` to an `Offset`. + fn native_to_offset(native: Self::Native) -> Self::Offset; +} + +/// Marker trait for 32-bit memories. +pub struct Memory32; +unsafe impl MemorySize for Memory32 { + type Offset = u32; + type Native = i32; + const ZERO: Self::Offset = 0; + fn offset_to_native(offset: Self::Offset) -> Self::Native { + offset as Self::Native + } + fn native_to_offset(native: Self::Native) -> Self::Offset { + native as Self::Offset + } +} + +/// Marker trait for 64-bit memories. +pub struct Memory64; +unsafe impl MemorySize for Memory64 { + type Offset = u64; + type Native = i64; + const ZERO: Self::Offset = 0; + fn offset_to_native(offset: Self::Offset) -> Self::Native { + offset as Self::Native + } + fn native_to_offset(native: Self::Native) -> Self::Offset { + native as Self::Offset + } +} + +/// Alias for `WasmPtr. +pub type WasmPtr64 = WasmPtr; /// A zero-cost type that represents a pointer to something in Wasm linear /// memory. @@ -26,11 +63,11 @@ pub struct Item; /// # use wasmer::Memory; /// # use wasmer::WasmPtr; /// pub fn host_import(memory: Memory, ptr: WasmPtr) { -/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds"); -/// let inner_val: u32 = derefed_ptr.get(); +/// let derefed_ptr = ptr.deref(&memory); +/// let inner_val: u32 = derefed_ptr.read().expect("pointer in bounds"); /// println!("Got {} from Wasm memory address 0x{:X}", inner_val, ptr.offset()); /// // update the value being pointed to -/// derefed_ptr.set(inner_val + 1); +/// derefed_ptr.write(inner_val + 1).expect("pointer in bounds"); /// } /// ``` /// @@ -41,38 +78,35 @@ pub struct Item; /// # use wasmer::WasmPtr; /// # use wasmer::ValueType; /// -/// #[derive(Copy, Clone, Debug)] +/// // This is safe as the 12 bytes represented by this struct +/// // are valid for all bit combinations. +/// #[derive(Copy, Clone, Debug, ValueType)] /// #[repr(C)] /// struct V3 { /// x: f32, /// y: f32, /// z: f32 /// } -/// // This is safe as the 12 bytes represented by this struct -/// // are valid for all bit combinations. -/// unsafe impl ValueType for V3 { -/// } /// /// fn update_vector_3(memory: Memory, ptr: WasmPtr) { -/// let derefed_ptr = ptr.deref(&memory).expect("pointer in bounds"); -/// let mut inner_val: V3 = derefed_ptr.get(); +/// let derefed_ptr = ptr.deref(&memory); +/// let mut inner_val: V3 = derefed_ptr.read().expect("pointer in bounds"); /// println!("Got {:?} from Wasm memory address 0x{:X}", inner_val, ptr.offset()); /// // update the value being pointed to /// inner_val.x = 10.4; -/// derefed_ptr.set(inner_val); +/// derefed_ptr.write(inner_val).expect("pointer in bounds"); /// } /// ``` #[repr(transparent)] -pub struct WasmPtr { - offset: u32, - _phantom: PhantomData<(T, Ty)>, +pub struct WasmPtr { + offset: M::Offset, + _phantom: PhantomData<*mut T>, } -/// Methods relevant to all types of `WasmPtr`. -impl WasmPtr { +impl WasmPtr { /// Create a new `WasmPtr` at the given offset. #[inline] - pub fn new(offset: u32) -> Self { + pub fn new(offset: M::Offset) -> Self { Self { offset, _phantom: PhantomData, @@ -81,180 +115,175 @@ impl WasmPtr { /// Get the offset into Wasm linear memory for this `WasmPtr`. #[inline] - pub fn offset(self) -> u32 { + pub fn offset(self) -> M::Offset { self.offset } -} -#[inline(always)] -fn align_pointer(ptr: usize, align: usize) -> usize { - // clears bits below aligment amount (assumes power of 2) to align pointer - debug_assert!(align.count_ones() == 1); - ptr & !(align - 1) -} - -/// Methods for `WasmPtr`s to data that can be dereferenced, namely to types -/// that implement [`ValueType`], meaning that they're valid for all possible -/// bit patterns. -impl WasmPtr { - /// Dereference the `WasmPtr` getting access to a `&Cell` allowing for - /// reading and mutating of the inner value. - /// - /// This method is unsound if used with unsynchronized shared memory. - /// If you're unsure what that means, it likely does not apply to you. - /// This invariant will be enforced in the future. + /// Casts this `WasmPtr` to a `WasmPtr` of a different type. #[inline] - pub fn deref<'a>(self, memory: &'a Memory) -> Option> { - let end = (self.offset as usize).checked_add(mem::size_of::())?; - if end > memory.size().bytes().0 || mem::size_of::() == 0 { - return None; + pub fn cast(self) -> WasmPtr { + WasmPtr { + offset: self.offset, + _phantom: PhantomData, } + } - unsafe { - let cell_ptr = align_pointer( - memory.view::().as_ptr().add(self.offset as usize) as usize, - mem::align_of::(), - ) as *const Cell; - Some(WasmCell::new(&*cell_ptr)) - } + /// Returns a null `UserPtr`. + #[inline] + pub fn null() -> Self { + WasmPtr::new(M::ZERO) + } + + /// Checks whether the `WasmPtr` is null. + #[inline] + pub fn is_null(self) -> bool { + self.offset.into() == 0 + } + + /// Calculates an offset from the current pointer address. The argument is + /// in units of `T`. + /// + /// This method returns an error if an address overflow occurs. + #[inline] + #[must_use] + pub fn add(self, offset: M::Offset) -> Result { + let base = self.offset.into(); + let index = offset.into(); + let offset = index + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + let address = base + .checked_add(offset) + .ok_or(MemoryAccessError::Overflow)?; + let address = M::Offset::try_from(address).map_err(|_| MemoryAccessError::Overflow)?; + Ok(WasmPtr::new(address)) + } + + /// Calculates an offset from the current pointer address. The argument is + /// in units of `T`. + /// + /// This method returns an error if an address overflow occurs. + #[inline] + #[must_use] + pub fn sub(self, offset: M::Offset) -> Result { + let base = self.offset.into(); + let index = offset.into(); + let offset = index + .checked_mul(mem::size_of::() as u64) + .ok_or(MemoryAccessError::Overflow)?; + let address = base + .checked_sub(offset) + .ok_or(MemoryAccessError::Overflow)?; + let address = M::Offset::try_from(address).map_err(|_| MemoryAccessError::Overflow)?; + Ok(WasmPtr::new(address)) } } -/// Methods for `WasmPtr`s to arrays of data that can be dereferenced, namely to -/// types that implement [`ValueType`], meaning that they're valid for all -/// possible bit patterns. -impl WasmPtr { - /// Dereference the `WasmPtr` getting access to a `&[Cell]` allowing for - /// reading and mutating of the inner values. - /// - /// This method is unsound if used with unsynchronized shared memory. - /// If you're unsure what that means, it likely does not apply to you. - /// This invariant will be enforced in the future. +impl WasmPtr { + /// Creates a `WasmRef` from this `WasmPtr` which allows reading and + /// mutating of the value being pointed to. #[inline] - pub fn deref<'a>( + pub fn deref<'a>(self, memory: &'a Memory) -> WasmRef<'a, T> { + WasmRef::new(memory, self.offset.into()) + } + + /// Reads the address pointed to by this `WasmPtr` in a memory. + #[inline] + pub fn read(self, memory: &Memory) -> Result { + self.deref(memory).read() + } + + /// Writes to the address pointed to by this `WasmPtr` in a memory. + #[inline] + pub fn write(self, memory: &Memory, val: T) -> Result<(), MemoryAccessError> { + self.deref(memory).write(val) + } + + /// Creates a `WasmSlice` starting at this `WasmPtr` which allows reading + /// and mutating of an array of value being pointed to. + /// + /// Returns a `MemoryAccessError` if the slice length overflows a 64-bit + /// address. + #[inline] + pub fn slice<'a>( self, memory: &'a Memory, - index: u32, - length: u32, - ) -> Option>> { - // gets the size of the item in the array with padding added such that - // for any index, we will always result an aligned memory access - let item_size = mem::size_of::(); - let slice_full_len = (index as usize).checked_add(length as usize)?; - let memory_size = memory.size().bytes().0; - let end = (self.offset as usize).checked_add(item_size.checked_mul(slice_full_len)?)?; - if end > memory_size || item_size == 0 { - return None; - } - - let cell_ptrs = unsafe { - let cell_ptr = align_pointer( - memory.view::().as_ptr().add(self.offset as usize) as usize, - mem::align_of::(), - ) as *const Cell; - &std::slice::from_raw_parts(cell_ptr, slice_full_len)[index as usize..slice_full_len] - }; - - let wasm_cells = cell_ptrs - .iter() - .map(|ptr| WasmCell::new(ptr)) - .collect::>(); - Some(wasm_cells) + len: M::Offset, + ) -> Result, MemoryAccessError> { + WasmSlice::new(memory, self.offset.into(), len.into()) } - /// Get a UTF-8 string from the `WasmPtr` with the given length. + /// Reads a sequence of values from this `WasmPtr` until a value that + /// matches the given condition is found. /// - /// Note that . The - /// underlying data can be mutated if the Wasm is allowed to execute or - /// an aliasing `WasmPtr` is used to mutate memory. - /// - /// # Safety - /// This method returns a reference to Wasm linear memory. The underlying - /// data can be mutated if the Wasm is allowed to execute or an aliasing - /// `WasmPtr` is used to mutate memory. - /// - /// `str` has invariants that must not be broken by mutating Wasm memory. - /// Thus the caller must ensure that the backing memory is not modified - /// while the reference is held. - /// - /// Additionally, if `memory` is dynamic, the caller must also ensure that `memory` - /// is not grown while the reference is held. - pub unsafe fn get_utf8_str<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> { - let end = self.offset.checked_add(str_len)?; - if end as usize > memory.size().bytes().0 { - return None; + /// This last value is not included in the returned vector. + #[inline] + pub fn read_until<'a>( + self, + memory: &'a Memory, + mut end: impl FnMut(&T) -> bool, + ) -> Result, MemoryAccessError> { + let mut vec = Vec::new(); + for i in 0u64.. { + let i = M::Offset::try_from(i).map_err(|_| MemoryAccessError::Overflow)?; + let val = self.add(i)?.deref(memory).read()?; + if end(&val) { + break; + } + vec.push(val); } - - let ptr = memory.view::().as_ptr().add(self.offset as usize) as *const u8; - let slice: &[u8] = std::slice::from_raw_parts(ptr, str_len as usize); - std::str::from_utf8(slice).ok() - } - - /// Get a UTF-8 `String` from the `WasmPtr` with the given length. - /// - /// an aliasing `WasmPtr` is used to mutate memory. - pub fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option { - let end = self.offset.checked_add(str_len)?; - if end as usize > memory.size().bytes().0 { - return None; - } - - // TODO: benchmark the internals of this function: there is likely room for - // micro-optimization here and this may be a fairly common function in user code. - let view = memory.view::(); - - let mut vec: Vec = Vec::with_capacity(str_len as usize); - let base = self.offset as usize; - for i in 0..(str_len as usize) { - let byte = view[base + i].get(); - vec.push(byte); - } - - String::from_utf8(vec).ok() - } - - /// Get a UTF-8 string from the `WasmPtr`, where the string is nul-terminated. - /// - /// Note that this does not account for UTF-8 strings that _contain_ nul themselves, - /// [`WasmPtr::get_utf8_str`] has to be used for those. - /// - /// # Safety - /// This method behaves similarly to [`WasmPtr::get_utf8_str`], all safety invariants on - /// that method must also be upheld here. - pub unsafe fn get_utf8_str_with_nul<'a>(self, memory: &'a Memory) -> Option<&'a str> { - memory.view::()[(self.offset as usize)..] - .iter() - .map(|cell| cell.get()) - .position(|byte| byte == 0) - .and_then(|length| self.get_utf8_str(memory, length as u32)) - } - - /// Get a UTF-8 `String` from the `WasmPtr`, where the string is nul-terminated. - /// - /// Note that this does not account for UTF-8 strings that _contain_ nul themselves, - /// [`WasmPtr::get_utf8_string`] has to be used for those. - pub fn get_utf8_string_with_nul(self, memory: &Memory) -> Option { - unsafe { self.get_utf8_str_with_nul(memory) }.map(|s| s.to_owned()) + Ok(vec) } } -unsafe impl FromToNativeWasmType for WasmPtr { - type Native = i32; +impl WasmPtr { + /// Reads a UTF-8 string from the `WasmPtr` with the given length. + /// + /// This method is safe to call even if the memory is being concurrently + /// modified. + #[inline] + pub fn read_utf8_string<'a>( + self, + memory: &'a Memory, + len: M::Offset, + ) -> Result { + let vec = self.slice(memory, len)?.read_to_vec()?; + Ok(String::from_utf8(vec)?) + } + + /// Reads a null-terminated UTF-8 string from the `WasmPtr`. + /// + /// This method is safe to call even if the memory is being concurrently + /// modified. + #[inline] + pub fn read_utf8_string_with_nul<'a>( + self, + memory: &'a Memory, + ) -> Result { + let vec = self.read_until(memory, |&byte| byte == 0)?; + Ok(String::from_utf8(vec)?) + } +} + +unsafe impl FromToNativeWasmType for WasmPtr { + type Native = M::Native; fn to_native(self) -> Self::Native { - self.offset as i32 + M::offset_to_native(self.offset) } fn from_native(n: Self::Native) -> Self { Self { - offset: n as u32, + offset: M::native_to_offset(n), _phantom: PhantomData, } } } -unsafe impl ValueType for WasmPtr {} +unsafe impl ValueType for WasmPtr { + fn zero_padding_bytes(&self, _bytes: &mut [mem::MaybeUninit]) {} +} -impl Clone for WasmPtr { +impl Clone for WasmPtr { fn clone(&self) -> Self { Self { offset: self.offset, @@ -263,103 +292,23 @@ impl Clone for WasmPtr { } } -impl Copy for WasmPtr {} +impl Copy for WasmPtr {} -impl PartialEq for WasmPtr { +impl PartialEq for WasmPtr { fn eq(&self, other: &Self) -> bool { - self.offset == other.offset + self.offset.into() == other.offset.into() } } -impl Eq for WasmPtr {} +impl Eq for WasmPtr {} -impl fmt::Debug for WasmPtr { +impl fmt::Debug for WasmPtr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "WasmPtr(offset: {}, pointer: {:#x}, align: {})", - self.offset, - self.offset, - mem::align_of::() + "WasmPtr(offset: {}, pointer: {:#x})", + self.offset.into(), + self.offset.into() ) } } - -#[cfg(test)] -mod test { - use super::*; - use crate::sys::{Memory, MemoryType, Store}; - - /// Ensure that memory accesses work on the edges of memory and that out of - /// bounds errors are caught with `deref` - #[test] - fn wasm_ptr_memory_bounds_checks_hold() { - // create a memory - let store = Store::default(); - let memory_descriptor = MemoryType::new(1, Some(1), false); - let memory = Memory::new(&store, memory_descriptor).unwrap(); - - // test that basic access works and that len = 0 works, but oob does not - let start_wasm_ptr: WasmPtr = WasmPtr::new(0); - let start_wasm_ptr_array: WasmPtr = WasmPtr::new(0); - - assert!(start_wasm_ptr.deref(&memory).is_some()); - assert!(start_wasm_ptr_array.deref(&memory, 0, 0).is_some()); - assert!(unsafe { start_wasm_ptr_array.get_utf8_str(&memory, 0).is_some() }); - assert!(start_wasm_ptr_array.get_utf8_string(&memory, 0).is_some()); - assert!(start_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - - // test that accessing the last valid memory address works correctly and OOB is caught - let last_valid_address_for_u8 = (memory.size().bytes().0 - 1) as u32; - let end_wasm_ptr: WasmPtr = WasmPtr::new(last_valid_address_for_u8); - assert!(end_wasm_ptr.deref(&memory).is_some()); - - let end_wasm_ptr_array: WasmPtr = WasmPtr::new(last_valid_address_for_u8); - - assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - let invalid_idx_len_combos: [(u32, u32); 3] = - [(last_valid_address_for_u8 + 1, 0), (0, 2), (1, 1)]; - for &(idx, len) in invalid_idx_len_combos.iter() { - assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none()); - } - assert!(unsafe { end_wasm_ptr_array.get_utf8_str(&memory, 2).is_none() }); - assert!(end_wasm_ptr_array.get_utf8_string(&memory, 2).is_none()); - - // test that accesing the last valid memory address for a u32 is valid - // (same as above test but with more edge cases to assert on) - let last_valid_address_for_u32 = (memory.size().bytes().0 - 4) as u32; - let end_wasm_ptr: WasmPtr = WasmPtr::new(last_valid_address_for_u32); - assert!(end_wasm_ptr.deref(&memory).is_some()); - assert!(end_wasm_ptr.deref(&memory).is_some()); - - let end_wasm_ptr_oob_array: [WasmPtr; 4] = [ - WasmPtr::new(last_valid_address_for_u32 + 1), - WasmPtr::new(last_valid_address_for_u32 + 2), - WasmPtr::new(last_valid_address_for_u32 + 3), - WasmPtr::new(last_valid_address_for_u32 + 4), - ]; - for oob_end_ptr in end_wasm_ptr_oob_array.iter() { - assert!(oob_end_ptr.deref(&memory).is_none()); - } - let end_wasm_ptr_array: WasmPtr = WasmPtr::new(last_valid_address_for_u32); - assert!(end_wasm_ptr_array.deref(&memory, 0, 1).is_some()); - - let invalid_idx_len_combos: [(u32, u32); 3] = - [(last_valid_address_for_u32 + 1, 0), (0, 2), (1, 1)]; - for &(idx, len) in invalid_idx_len_combos.iter() { - assert!(end_wasm_ptr_array.deref(&memory, idx, len).is_none()); - } - - let end_wasm_ptr_array_oob_array: [WasmPtr; 4] = [ - WasmPtr::new(last_valid_address_for_u32 + 1), - WasmPtr::new(last_valid_address_for_u32 + 2), - WasmPtr::new(last_valid_address_for_u32 + 3), - WasmPtr::new(last_valid_address_for_u32 + 4), - ]; - - for oob_end_array_ptr in end_wasm_ptr_array_oob_array.iter() { - assert!(oob_end_array_ptr.deref(&memory, 0, 1).is_none()); - assert!(oob_end_array_ptr.deref(&memory, 1, 0).is_none()); - } - } -} diff --git a/lib/c-api/src/wasm_c_api/externals/memory.rs b/lib/c-api/src/wasm_c_api/externals/memory.rs index 97694adb2..65dc9fc4a 100644 --- a/lib/c-api/src/wasm_c_api/externals/memory.rs +++ b/lib/c-api/src/wasm_c_api/externals/memory.rs @@ -1,7 +1,6 @@ use super::super::store::wasm_store_t; use super::super::types::wasm_memorytype_t; use super::CApiExternTag; -use std::mem; use wasmer_api::{Memory, Pages}; #[allow(non_camel_case_types)] @@ -56,8 +55,7 @@ pub unsafe extern "C" fn wasm_memory_type( // get a raw pointer into bytes #[no_mangle] pub unsafe extern "C" fn wasm_memory_data(memory: &mut wasm_memory_t) -> *mut u8 { - mem::transmute::<&[std::cell::Cell], &[u8]>(&memory.inner.view()[..]) as *const [u8] - as *const u8 as *mut u8 + memory.inner.data_ptr() } // size in bytes diff --git a/lib/derive/src/env/mod.rs b/lib/derive/src/env/mod.rs new file mode 100644 index 000000000..8e034f670 --- /dev/null +++ b/lib/derive/src/env/mod.rs @@ -0,0 +1,255 @@ +use proc_macro2::TokenStream; +use proc_macro_error::{abort, set_dummy}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{spanned::Spanned, *}; + +mod parse; + +use self::parse::WasmerAttr; + +fn impl_wasmer_env_for_struct( + name: &Ident, + data: &DataStruct, + generics: &Generics, + _attrs: &[Attribute], +) -> TokenStream { + let (trait_methods, helper_methods) = derive_struct_fields(data); + let lifetimes_and_generics = generics.params.clone(); + let where_clause = generics.where_clause.clone(); + quote! { + impl < #lifetimes_and_generics > ::wasmer::WasmerEnv for #name < #lifetimes_and_generics > #where_clause{ + #trait_methods + } + + #[allow(dead_code)] + impl < #lifetimes_and_generics > #name < #lifetimes_and_generics > #where_clause { + #helper_methods + } + } +} + +pub fn impl_wasmer_env(input: &DeriveInput) -> TokenStream { + let struct_name = &input.ident; + + set_dummy(quote! { + impl ::wasmer::WasmerEnv for #struct_name { + fn init_with_instance(&mut self, instance: &::wasmer::Instance) -> ::core::result::Result<(), ::wasmer::HostEnvInitError> { + Ok(()) + } + } + }); + + match &input.data { + Data::Struct(ds) => { + impl_wasmer_env_for_struct(struct_name, ds, &input.generics, &input.attrs) + } + _ => todo!(), + } + /*match input.data { + Struct(ds /*DataStruct { + fields: syn::Fields::Named(ref fields), + .. + }*/) => , + Enum(ref e) => impl_wasmer_env_for_enum(struct_name, &e.variants, &input.attrs), + _ => abort_call_site!("Clap only supports non-tuple structs and enums"), + }*/ +} + +fn derive_struct_fields(data: &DataStruct) -> (TokenStream, TokenStream) { + let mut finish = vec![]; + let mut helpers = vec![]; + //let mut assign_tokens = vec![]; + let mut touched_fields = vec![]; + let fields: Vec = match &data.fields { + Fields::Named(ref fields) => fields.named.iter().cloned().collect(), + Fields::Unit => vec![], + Fields::Unnamed(fields) => fields.unnamed.iter().cloned().collect(), + }; + for (field_num, f) in fields.into_iter().enumerate() { + let field_idx = syn::Index::from(field_num); + let name = f.ident.clone(); + let top_level_ty: &Type = &f.ty; + touched_fields.push(name.clone()); + let mut wasmer_attr = None; + for attr in &f.attrs { + // if / filter + if attr.path.is_ident(&Ident::new("wasmer", attr.span())) { + let tokens = attr.tokens.clone(); + match syn::parse2(tokens) { + Ok(attr) => { + wasmer_attr = Some(attr); + break; + } + Err(e) => { + abort!(attr, "Failed to parse `wasmer` attribute: {}", e); + } + } + } + } + + if let Some(wasmer_attr) = wasmer_attr { + let inner_type = get_identifier(top_level_ty); + if let Some(name) = &name { + let name_ref_str = format!("{}_ref", name); + let name_ref = syn::Ident::new(&name_ref_str, name.span()); + let name_ref_unchecked_str = format!("{}_ref_unchecked", name); + let name_ref_unchecked = syn::Ident::new(&name_ref_unchecked_str, name.span()); + let helper_tokens = quote_spanned! {f.span()=> + /// Get access to the underlying data. + /// + /// If `WasmerEnv::finish` has been called, this function will never + /// return `None` unless the underlying data has been mutated manually. + pub fn #name_ref(&self) -> Option<&#inner_type> { + self.#name.get_ref() + } + /// Gets the item without checking if it's been initialized. + /// + /// # Safety + /// `WasmerEnv::finish` must have been called on this function or + /// this type manually initialized. + pub unsafe fn #name_ref_unchecked(&self) -> &#inner_type { + self.#name.get_unchecked() + } + }; + helpers.push(helper_tokens); + } + match wasmer_attr { + WasmerAttr::Export { + identifier, + optional, + aliases, + span, + } => { + let finish_tokens = if let Some(name) = name { + let name_str = name.to_string(); + let item_name = + identifier.unwrap_or_else(|| LitStr::new(&name_str, name.span())); + let mut access_expr = quote_spanned! { + f.span() => + instance.exports.get_with_generics_weak::<#inner_type, _, _>(#item_name) + }; + for alias in aliases { + access_expr = quote_spanned! { + f.span()=> + #access_expr .or_else(|_| instance.exports.get_with_generics_weak::<#inner_type, _, _>(#alias)) + }; + } + if optional { + quote_spanned! { + f.span()=> + match #access_expr { + Ok(#name) => { self.#name.initialize(#name); }, + Err(_) => (), + }; + } + } else { + quote_spanned! { + f.span()=> + let #name: #inner_type = #access_expr?; + self.#name.initialize(#name); + } + } + } else { + if let Some(identifier) = identifier { + let mut access_expr = quote_spanned! { + f.span() => + instance.exports.get_with_generics_weak::<#inner_type, _, _>(#identifier) + }; + for alias in aliases { + access_expr = quote_spanned! { + f.span()=> + #access_expr .or_else(|_| instance.exports.get_with_generics_weak::<#inner_type, _, _>(#alias)) + }; + } + let local_var = + Ident::new(&format!("field_{}", field_num), identifier.span()); + if optional { + quote_spanned! { + f.span()=> + match #access_expr { + Ok(#local_var) => { + self.#field_idx.initialize(#local_var); + }, + Err(_) => (), + } + } + } else { + quote_spanned! { + f.span()=> + let #local_var: #inner_type = #access_expr?; + self.#field_idx.initialize(#local_var); + } + } + } else { + abort!( + span, + "Expected `name` field on export attribute because field does not have a name. For example: `#[wasmer(export(name = \"wasm_ident\"))]`.", + ); + } + }; + + finish.push(finish_tokens); + } + } + } + } + + let trait_methods = quote! { + fn init_with_instance(&mut self, instance: &::wasmer::Instance) -> ::core::result::Result<(), ::wasmer::HostEnvInitError> { + #(#finish)* + Ok(()) + } + }; + + let helper_methods = quote! { + #(#helpers)* + }; + + (trait_methods, helper_methods) +} + +// TODO: name this something that makes sense +fn get_identifier(ty: &Type) -> TokenStream { + match ty { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + if let Some(PathSegment { ident, arguments }) = segments.last() { + if ident != "LazyInit" { + abort!( + ident, + "WasmerEnv derive expects all `export`s to be wrapped in `LazyInit`" + ); + } + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, .. + }) = arguments + { + // TODO: proper error handling + assert_eq!(args.len(), 1); + if let GenericArgument::Type(Type::Path(TypePath { + path: Path { segments, .. }, + .. + })) = &args[0] + { + segments + .last() + .expect("there must be at least one segment; TODO: error handling") + .to_token_stream() + } else { + abort!( + &args[0], + "unrecognized type in first generic position on `LazyInit`" + ); + } + } else { + abort!(arguments, "Expected a generic parameter on `LazyInit`"); + } + } else { + abort!(segments, "Unknown type found"); + } + } + _ => abort!(ty, "Unrecognized/unsupported type"), + } +} diff --git a/lib/derive/src/parse.rs b/lib/derive/src/env/parse.rs similarity index 100% rename from lib/derive/src/parse.rs rename to lib/derive/src/env/parse.rs diff --git a/lib/derive/src/lib.rs b/lib/derive/src/lib.rs index 63422408c..8a37a88e1 100644 --- a/lib/derive/src/lib.rs +++ b/lib/derive/src/lib.rs @@ -1,265 +1,23 @@ extern crate proc_macro; -use proc_macro2::TokenStream; -use proc_macro_error::{abort, proc_macro_error, set_dummy}; -use quote::{quote, quote_spanned, ToTokens}; -use syn::{spanned::Spanned, *}; +use proc_macro_error::proc_macro_error; +use syn::{parse_macro_input, DeriveInput}; -mod parse; - -use crate::parse::WasmerAttr; +mod env; +mod value_type; #[proc_macro_error] #[proc_macro_derive(WasmerEnv, attributes(wasmer))] pub fn derive_wasmer_env(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_wasmer_env(&input); + let input = parse_macro_input!(input as DeriveInput); + let gen = env::impl_wasmer_env(&input); gen.into() } -fn impl_wasmer_env_for_struct( - name: &Ident, - data: &DataStruct, - generics: &Generics, - _attrs: &[Attribute], -) -> TokenStream { - let (trait_methods, helper_methods) = derive_struct_fields(data); - let lifetimes_and_generics = generics.params.clone(); - let where_clause = generics.where_clause.clone(); - quote! { - impl < #lifetimes_and_generics > ::wasmer::WasmerEnv for #name < #lifetimes_and_generics > #where_clause{ - #trait_methods - } - - #[allow(dead_code)] - impl < #lifetimes_and_generics > #name < #lifetimes_and_generics > #where_clause { - #helper_methods - } - } -} - -fn impl_wasmer_env(input: &DeriveInput) -> TokenStream { - let struct_name = &input.ident; - - set_dummy(quote! { - impl ::wasmer::WasmerEnv for #struct_name { - fn init_with_instance(&mut self, instance: &::wasmer::Instance) -> ::core::result::Result<(), ::wasmer::HostEnvInitError> { - Ok(()) - } - } - }); - - match &input.data { - Data::Struct(ds) => { - impl_wasmer_env_for_struct(struct_name, ds, &input.generics, &input.attrs) - } - _ => todo!(), - } - /*match input.data { - Struct(ds /*DataStruct { - fields: syn::Fields::Named(ref fields), - .. - }*/) => , - Enum(ref e) => impl_wasmer_env_for_enum(struct_name, &e.variants, &input.attrs), - _ => abort_call_site!("Clap only supports non-tuple structs and enums"), - }*/ -} - -fn derive_struct_fields(data: &DataStruct) -> (TokenStream, TokenStream) { - let mut finish = vec![]; - let mut helpers = vec![]; - //let mut assign_tokens = vec![]; - let mut touched_fields = vec![]; - let fields: Vec = match &data.fields { - Fields::Named(ref fields) => fields.named.iter().cloned().collect(), - Fields::Unit => vec![], - Fields::Unnamed(fields) => fields.unnamed.iter().cloned().collect(), - }; - for (field_num, f) in fields.into_iter().enumerate() { - let field_idx = syn::Index::from(field_num); - let name = f.ident.clone(); - let top_level_ty: &Type = &f.ty; - touched_fields.push(name.clone()); - let mut wasmer_attr = None; - for attr in &f.attrs { - // if / filter - if attr.path.is_ident(&Ident::new("wasmer", attr.span())) { - let tokens = attr.tokens.clone(); - match syn::parse2(tokens) { - Ok(attr) => { - wasmer_attr = Some(attr); - break; - } - Err(e) => { - abort!(attr, "Failed to parse `wasmer` attribute: {}", e); - } - } - } - } - - if let Some(wasmer_attr) = wasmer_attr { - let inner_type = get_identifier(top_level_ty); - if let Some(name) = &name { - let name_ref_str = format!("{}_ref", name); - let name_ref = syn::Ident::new(&name_ref_str, name.span()); - let name_ref_unchecked_str = format!("{}_ref_unchecked", name); - let name_ref_unchecked = syn::Ident::new(&name_ref_unchecked_str, name.span()); - let helper_tokens = quote_spanned! {f.span()=> - /// Get access to the underlying data. - /// - /// If `WasmerEnv::finish` has been called, this function will never - /// return `None` unless the underlying data has been mutated manually. - pub fn #name_ref(&self) -> Option<&#inner_type> { - self.#name.get_ref() - } - /// Gets the item without checking if it's been initialized. - /// - /// # Safety - /// `WasmerEnv::finish` must have been called on this function or - /// this type manually initialized. - pub unsafe fn #name_ref_unchecked(&self) -> &#inner_type { - self.#name.get_unchecked() - } - }; - helpers.push(helper_tokens); - } - match wasmer_attr { - WasmerAttr::Export { - identifier, - optional, - aliases, - span, - } => { - let finish_tokens = if let Some(name) = name { - let name_str = name.to_string(); - let item_name = - identifier.unwrap_or_else(|| LitStr::new(&name_str, name.span())); - let mut access_expr = quote_spanned! { - f.span() => - instance.exports.get_with_generics_weak::<#inner_type, _, _>(#item_name) - }; - for alias in aliases { - access_expr = quote_spanned! { - f.span()=> - #access_expr .or_else(|_| instance.exports.get_with_generics_weak::<#inner_type, _, _>(#alias)) - }; - } - if optional { - quote_spanned! { - f.span()=> - match #access_expr { - Ok(#name) => { self.#name.initialize(#name); }, - Err(_) => (), - }; - } - } else { - quote_spanned! { - f.span()=> - let #name: #inner_type = #access_expr?; - self.#name.initialize(#name); - } - } - } else { - if let Some(identifier) = identifier { - let mut access_expr = quote_spanned! { - f.span() => - instance.exports.get_with_generics_weak::<#inner_type, _, _>(#identifier) - }; - for alias in aliases { - access_expr = quote_spanned! { - f.span()=> - #access_expr .or_else(|_| instance.exports.get_with_generics_weak::<#inner_type, _, _>(#alias)) - }; - } - let local_var = - Ident::new(&format!("field_{}", field_num), identifier.span()); - if optional { - quote_spanned! { - f.span()=> - match #access_expr { - Ok(#local_var) => { - self.#field_idx.initialize(#local_var); - }, - Err(_) => (), - } - } - } else { - quote_spanned! { - f.span()=> - let #local_var: #inner_type = #access_expr?; - self.#field_idx.initialize(#local_var); - } - } - } else { - abort!( - span, - "Expected `name` field on export attribute because field does not have a name. For example: `#[wasmer(export(name = \"wasm_ident\"))]`.", - ); - } - }; - - finish.push(finish_tokens); - } - } - } - } - - let trait_methods = quote! { - fn init_with_instance(&mut self, instance: &::wasmer::Instance) -> ::core::result::Result<(), ::wasmer::HostEnvInitError> { - #(#finish)* - Ok(()) - } - }; - - let helper_methods = quote! { - #(#helpers)* - }; - - (trait_methods, helper_methods) -} - -// TODO: name this something that makes sense -fn get_identifier(ty: &Type) -> TokenStream { - match ty { - Type::Path(TypePath { - path: Path { segments, .. }, - .. - }) => { - if let Some(PathSegment { ident, arguments }) = segments.last() { - if ident != "LazyInit" { - abort!( - ident, - "WasmerEnv derive expects all `export`s to be wrapped in `LazyInit`" - ); - } - if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args, .. - }) = arguments - { - // TODO: proper error handling - assert_eq!(args.len(), 1); - if let GenericArgument::Type(Type::Path(TypePath { - path: Path { segments, .. }, - .. - })) = &args[0] - { - segments - .last() - .expect("there must be at least one segment; TODO: error handling") - .to_token_stream() - } else { - abort!( - &args[0], - "unrecognized type in first generic position on `LazyInit`" - ); - } - } else { - abort!(arguments, "Expected a generic parameter on `LazyInit`"); - } - } else { - abort!(segments, "Unknown type found"); - } - } - _ => abort!(ty, "Unrecognized/unsupported type"), - } +#[proc_macro_error] +#[proc_macro_derive(ValueType)] +pub fn derive_value_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let gen = value_type::impl_value_type(&input); + gen.into() } diff --git a/lib/derive/src/value_type.rs b/lib/derive/src/value_type.rs new file mode 100644 index 000000000..48fbb4ff6 --- /dev/null +++ b/lib/derive/src/value_type.rs @@ -0,0 +1,111 @@ +use proc_macro2::TokenStream; +use proc_macro_error::abort; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Member, Meta, MetaList, NestedMeta}; + +/// We can only validate types that have a well defined layout. +fn check_repr(input: &DeriveInput) { + let reprs = input + .attrs + .iter() + .filter_map(|attr| { + if let Meta::List(MetaList { path, nested, .. }) = attr.parse_meta().unwrap() { + if path.is_ident("repr") { + return Some(nested.into_iter().collect::>()); + } + } + None + }) + .flatten(); + + // We require either repr(C) or repr(transparent) to ensure fields are in + // source code order. + for meta in reprs { + if let NestedMeta::Meta(Meta::Path(path)) = meta { + if path.is_ident("C") || path.is_ident("transparent") { + return; + } + } + } + + abort!( + input, + "ValueType can only be derived for #[repr(C)] or #[repr(transparent)] structs" + ) +} + +/// Zero out any padding bytes between fields. +fn zero_padding(fields: &Fields) -> TokenStream { + let names: Vec<_> = fields + .iter() + .enumerate() + .map(|(i, field)| match &field.ident { + Some(ident) => Member::Named(ident.clone()), + None => Member::Unnamed(i.into()), + }) + .collect(); + + let mut out = TokenStream::new(); + for i in 0..fields.len() { + let name = &names[i]; + let start = quote! { + &self.#name as *const _ as usize - self as *const _ as usize + }; + let len = quote! { + ::core::mem::size_of_val(&self.#name) + }; + let end = quote! { + #start + #len + }; + + // Zero out padding bytes within the current field. + // + // This also ensures that all fields implement ValueType. + out.extend(quote! { + ::wasmer::ValueType::zero_padding_bytes(&self.#name, &mut _bytes[#start..(#start + #len)]); + }); + + let padding_end = if i == fields.len() - 1 { + // Zero out padding bytes between the last field and the end of the struct. + let total_size = quote! { + ::core::mem::size_of_val(self) + }; + total_size + } else { + // Zero out padding bytes between the current field and the next one. + let next_name = &names[i + 1]; + let next_start = quote! { + &self.#next_name as *const _ as usize - self as *const _ as usize + }; + next_start + }; + out.extend(quote! { + for i in #end..#padding_end { + _bytes[i] = ::core::mem::MaybeUninit::new(0); + } + }); + } + out +} + +pub fn impl_value_type(input: &DeriveInput) -> TokenStream { + check_repr(input); + + let struct_name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let fields = match &input.data { + Data::Struct(ds) => &ds.fields, + _ => abort!(input, "ValueType can only be derived for structs"), + }; + + let zero_padding = zero_padding(&fields); + + quote! { + unsafe impl #impl_generics ::wasmer::ValueType for #struct_name #ty_generics #where_clause { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [::core::mem::MaybeUninit]) { + #zero_padding + } + } + } +} diff --git a/lib/emscripten/src/env/mod.rs b/lib/emscripten/src/env/mod.rs index 4cbc9479d..71e4011ca 100644 --- a/lib/emscripten/src/env/mod.rs +++ b/lib/emscripten/src/env/mod.rs @@ -12,17 +12,14 @@ pub use self::windows::*; use libc::c_char; -use crate::{ - allocate_on_stack, - ptr::{Array, WasmPtr}, - EmscriptenData, -}; +use crate::{allocate_on_stack, EmscriptenData}; use std::os::raw::c_int; use std::sync::MutexGuard; use crate::EmEnv; use wasmer::ValueType; +use wasmer::WasmPtr; pub fn call_malloc(ctx: &EmEnv, size: u32) -> u32 { get_emscripten_data(ctx) @@ -33,7 +30,7 @@ pub fn call_malloc(ctx: &EmEnv, size: u32) -> u32 { } #[warn(dead_code)] -pub fn call_malloc_with_cast(ctx: &EmEnv, size: u32) -> WasmPtr { +pub fn call_malloc_with_cast(ctx: &EmEnv, size: u32) -> WasmPtr { WasmPtr::new(call_malloc(ctx, size)) } @@ -166,7 +163,7 @@ pub fn _fpathconf(_ctx: &EmEnv, _fildes: c_int, name: c_int) -> c_int { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, ValueType)] #[repr(C)] pub struct EmAddrInfo { // int @@ -182,20 +179,16 @@ pub struct EmAddrInfo { // struct sockaddr* pub ai_addr: WasmPtr, // char* - pub ai_canonname: WasmPtr, + pub ai_canonname: WasmPtr, // struct addrinfo* pub ai_next: WasmPtr, } -unsafe impl ValueType for EmAddrInfo {} - // NOTE: from looking at emscripten JS, this should be a union // TODO: review this, highly likely to have bugs -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, ValueType)] #[repr(C)] pub struct EmSockAddr { pub sa_family: i16, pub sa_data: [c_char; 14], } - -unsafe impl ValueType for EmSockAddr {} diff --git a/lib/emscripten/src/env/unix/mod.rs b/lib/emscripten/src/env/unix/mod.rs index f0b3ed1c9..c41521f12 100644 --- a/lib/emscripten/src/env/unix/mod.rs +++ b/lib/emscripten/src/env/unix/mod.rs @@ -8,9 +8,9 @@ use std::mem; use std::os::raw::c_char; use crate::env::{call_malloc, call_malloc_with_cast, EmAddrInfo, EmSockAddr}; -use crate::ptr::{Array, WasmPtr}; use crate::utils::{copy_cstr_into_wasm, copy_terminated_array_of_cstrs}; use crate::EmEnv; +use wasmer::WasmPtr; // #[no_mangle] /// emscripten: _getenv // (name: *const char) -> *const c_char; @@ -148,12 +148,12 @@ pub fn _gai_strerror(ctx: &EmEnv, ecode: i32) -> i32 { let cstr = unsafe { std::ffi::CStr::from_ptr(libc::gai_strerror(ecode)) }; let bytes = cstr.to_bytes_with_nul(); - let string_on_guest: WasmPtr = call_malloc_with_cast(ctx, bytes.len() as _); + let string_on_guest: WasmPtr = call_malloc_with_cast(ctx, bytes.len() as _); let memory = ctx.memory(0); - let writer = string_on_guest.deref(&memory, 0, bytes.len() as _).unwrap(); + let writer = string_on_guest.slice(&memory, bytes.len() as _).unwrap(); for (i, byte) in bytes.iter().enumerate() { - writer[i].set(*byte as _); + writer.index(i as u64).write(*byte as _).unwrap(); } string_on_guest.offset() as _ @@ -170,29 +170,25 @@ pub fn _getaddrinfo( debug!("emscripten::_getaddrinfo"); let memory = ctx.memory(0); debug!(" => node = {}", { - node_ptr - .deref(&memory) - .map(|_np| { - unimplemented!(); - // std::ffi::CStr::from_ptr(np as *const Cell as *const c_char) - // .to_string_lossy() - }) - .unwrap_or(std::borrow::Cow::Borrowed("null")) + if node_ptr.is_null() { + std::borrow::Cow::Borrowed("null") + } else { + unimplemented!() + } }); debug!(" => server_str = {}", { - service_str_ptr - .deref(&memory) - .map(|_np| { - unimplemented!(); - // std::ffi::CStr::from_ptr(np as *const Cell as *const c_char) - // .to_string_lossy() - }) - .unwrap_or(std::borrow::Cow::Borrowed("null")) + if service_str_ptr.is_null() { + std::borrow::Cow::Borrowed("null") + } else { + unimplemented!() + } }); - let hints = hints_ptr.deref(&memory).map(|hints_memory| { - let hints_guest = hints_memory.get(); - addrinfo { + let hints = if hints_ptr.is_null() { + None + } else { + let hints_guest = hints_ptr.deref(&memory).read().unwrap(); + Some(addrinfo { ai_flags: hints_guest.ai_flags, ai_family: hints_guest.ai_family, ai_socktype: hints_guest.ai_socktype, @@ -201,26 +197,24 @@ pub fn _getaddrinfo( ai_addr: std::ptr::null_mut(), ai_canonname: std::ptr::null_mut(), ai_next: std::ptr::null_mut(), - } - }); + }) + }; let mut out_ptr: *mut addrinfo = std::ptr::null_mut(); // allocate equivalent memory for res_val_ptr let result = unsafe { libc::getaddrinfo( - (node_ptr.deref(&memory).map(|_m| { - unimplemented!(); - //m as *const Cell as *const c_char - })) - .unwrap_or(std::ptr::null()), - service_str_ptr - .deref(&memory) - .map(|_m| { - unimplemented!(); - // m as *const Cell as *const c_char - }) - .unwrap_or(std::ptr::null()), + if node_ptr.is_null() { + std::ptr::null() + } else { + unimplemented!() + }, + if service_str_ptr.is_null() { + std::ptr::null() + } else { + unimplemented!() + }, hints .as_ref() .map(|h| h as *const addrinfo) @@ -247,10 +241,10 @@ pub fn _getaddrinfo( // connect list if let Some(prev_guest) = previous_guest_node { - let derefed_prev_guest = prev_guest.deref(&memory).unwrap(); - let mut pg = derefed_prev_guest.get(); + let derefed_prev_guest = prev_guest.deref(&memory); + let mut pg = derefed_prev_guest.read().unwrap(); pg.ai_next = current_guest_node_ptr; - derefed_prev_guest.set(pg); + derefed_prev_guest.write(pg).unwrap(); } // update values @@ -262,11 +256,11 @@ pub fn _getaddrinfo( let guest_sockaddr_ptr: WasmPtr = call_malloc_with_cast(ctx, host_addrlen as _); - let derefed_guest_sockaddr = guest_sockaddr_ptr.deref(&memory).unwrap(); - let mut gs = derefed_guest_sockaddr.get(); + let derefed_guest_sockaddr = guest_sockaddr_ptr.deref(&memory); + let mut gs = derefed_guest_sockaddr.read().unwrap(); gs.sa_family = (*host_sockaddr_ptr).sa_family as i16; gs.sa_data = (*host_sockaddr_ptr).sa_data; - derefed_guest_sockaddr.set(gs); + derefed_guest_sockaddr.write(gs).unwrap(); guest_sockaddr_ptr }; @@ -278,13 +272,16 @@ pub fn _getaddrinfo( let canonname_cstr = std::ffi::CStr::from_ptr(str_ptr); let canonname_bytes = canonname_cstr.to_bytes_with_nul(); let str_size = canonname_bytes.len(); - let guest_canonname: WasmPtr = + let guest_canonname: WasmPtr = call_malloc_with_cast(ctx, str_size as _); let guest_canonname_writer = - guest_canonname.deref(&memory, 0, str_size as _).unwrap(); + guest_canonname.slice(&memory, str_size as _).unwrap(); for (i, b) in canonname_bytes.iter().enumerate() { - guest_canonname_writer[i].set(*b as _) + guest_canonname_writer + .index(i as u64) + .write(*b as _) + .unwrap(); } guest_canonname @@ -293,8 +290,8 @@ pub fn _getaddrinfo( } }; - let derefed_current_guest_node = current_guest_node_ptr.deref(&memory).unwrap(); - let mut cgn = derefed_current_guest_node.get(); + let derefed_current_guest_node = current_guest_node_ptr.deref(&memory); + let mut cgn = derefed_current_guest_node.read().unwrap(); cgn.ai_flags = (*current_host_node).ai_flags; cgn.ai_family = (*current_host_node).ai_family; cgn.ai_socktype = (*current_host_node).ai_socktype; @@ -303,7 +300,7 @@ pub fn _getaddrinfo( cgn.ai_addr = guest_sockaddr_ptr; cgn.ai_canonname = guest_canonname_ptr; cgn.ai_next = WasmPtr::new(0); - derefed_current_guest_node.set(cgn); + derefed_current_guest_node.write(cgn).unwrap(); previous_guest_node = Some(current_guest_node_ptr); current_host_node = (*current_host_node).ai_next; @@ -313,7 +310,7 @@ pub fn _getaddrinfo( head_of_list.unwrap_or_else(|| WasmPtr::new(0)) }; - res_val_ptr.deref(&memory).unwrap().set(head_of_list); + res_val_ptr.deref(&memory).write(head_of_list).unwrap(); 0 } diff --git a/lib/emscripten/src/env/windows/mod.rs b/lib/emscripten/src/env/windows/mod.rs index 9a2d00f7c..6a587e1e0 100644 --- a/lib/emscripten/src/env/windows/mod.rs +++ b/lib/emscripten/src/env/windows/mod.rs @@ -6,9 +6,9 @@ use std::mem; use std::os::raw::c_char; use crate::env::{call_malloc, EmAddrInfo}; -use crate::ptr::WasmPtr; use crate::utils::{copy_cstr_into_wasm, read_string_from_wasm}; use crate::EmEnv; +use wasmer::WasmPtr; extern "C" { #[link_name = "_putenv"] diff --git a/lib/emscripten/src/exec.rs b/lib/emscripten/src/exec.rs index 529b9943f..12afe9659 100644 --- a/lib/emscripten/src/exec.rs +++ b/lib/emscripten/src/exec.rs @@ -1,35 +1,33 @@ use crate::varargs::VarArgs; use crate::EmEnv; use libc::execvp as libc_execvp; -use std::cell::Cell; use std::ffi::CString; +use wasmer::WasmPtr; pub fn execvp(ctx: &EmEnv, command_name_offset: u32, argv_offset: u32) -> i32 { // a single reference to re-use let emscripten_memory = ctx.memory(0); // read command name as string - let command_name_string_vec: Vec = emscripten_memory.view() - [(command_name_offset as usize)..] - .iter() - .map(|cell| cell.get()) - .take_while(|&byte| byte != 0) - .collect(); + let command_name_string_vec = WasmPtr::::new(command_name_offset) + .read_until(&emscripten_memory, |&byte| byte == 0) + .unwrap(); let command_name_string = CString::new(command_name_string_vec).unwrap(); // get the array of args - let mut argv: Vec<*const i8> = emscripten_memory.view()[((argv_offset / 4) as usize)..] - .iter() - .map(|cell: &Cell| cell.get()) - .take_while(|&byte| byte != 0) - .map(|offset| { - let p: *const i8 = (emscripten_memory.view::()[(offset as usize)..]) - .iter() - .map(|cell| cell.as_ptr() as *const i8) - .collect::>()[0]; - p + let argv = WasmPtr::>::new(argv_offset) + .read_until(&emscripten_memory, |&ptr| ptr.is_null()) + .unwrap(); + let arg_strings: Vec = argv + .into_iter() + .map(|ptr| { + let vec = ptr + .read_until(&emscripten_memory, |&byte| byte == 0) + .unwrap(); + CString::new(vec).unwrap() }) .collect(); + let mut argv: Vec<*const i8> = arg_strings.iter().map(|s| s.as_ptr()).collect(); // push a nullptr on to the end of the args array argv.push(std::ptr::null()); diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index 18ad4114d..7e0328b9f 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -21,7 +21,7 @@ use std::sync::{Arc, Mutex, RwLock}; use wasmer::{ imports, namespace, Exports, Function, FunctionType, Global, ImportObject, Instance, LazyInit, Memory, MemoryType, Module, NativeFunc, Pages, RuntimeError, Store, Table, TableType, Val, - ValType, WasmerEnv, + ValType, WasmPtr, WasmerEnv, }; #[cfg(unix)] @@ -52,7 +52,6 @@ mod math; mod memory; mod process; mod pthread; -mod ptr; mod signal; mod storage; mod syscalls; @@ -413,13 +412,13 @@ pub fn emscripten_set_up_memory( memory: &Memory, globals: &EmscriptenGlobalsData, ) -> Result<(), String> { - let dynamictop_ptr = globals.dynamictop_ptr; + let dynamictop_ptr = WasmPtr::::new(globals.dynamictop_ptr).deref(memory); let dynamic_base = globals.dynamic_base; - if (dynamictop_ptr / 4) as usize >= memory.view::().len() { + if dynamictop_ptr.offset() >= memory.data_size() { return Err("dynamictop_ptr beyond memory len".to_string()); } - memory.view::()[(dynamictop_ptr / 4) as usize].set(dynamic_base); + dynamictop_ptr.write(dynamic_base as i32).unwrap(); Ok(()) } diff --git a/lib/emscripten/src/macros.rs b/lib/emscripten/src/macros.rs index bc26cf596..4cd2550b1 100644 --- a/lib/emscripten/src/macros.rs +++ b/lib/emscripten/src/macros.rs @@ -1,6 +1,5 @@ macro_rules! emscripten_memory_pointer { - ($memory:expr, $pointer:expr) => {{ - use std::cell::Cell; - (&$memory.view::()[($pointer as usize)..]).as_ptr() as *mut Cell as *mut u8 - }}; + ($memory:expr, $pointer:expr) => { + $memory.data_ptr().wrapping_add($pointer as usize) + }; } diff --git a/lib/emscripten/src/memory.rs b/lib/emscripten/src/memory.rs index 1be26f85f..aec81dd15 100644 --- a/lib/emscripten/src/memory.rs +++ b/lib/emscripten/src/memory.rs @@ -3,7 +3,7 @@ use super::process::abort_with_message; use crate::EmEnv; use libc::{c_int, c_void, memcpy, size_t}; // TODO: investigate max pages etc. probably in Wasm Common, maybe reexport -use wasmer::{Pages, WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE}; +use wasmer::{Pages, WasmPtr, WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE}; /// emscripten: _emscripten_memcpy_big pub fn _emscripten_memcpy_big(ctx: &EmEnv, dest: u32, src: u32, len: u32) -> u32 { @@ -73,16 +73,21 @@ pub fn sbrk(ctx: &EmEnv, increment: i32) -> i32 { debug!("emscripten::sbrk"); // let old_dynamic_top = 0; // let new_dynamic_top = 0; + let memory = ctx.memory(0); let dynamictop_ptr = { let globals = &get_emscripten_data(ctx).globals; - (globals.dynamictop_ptr) as usize + WasmPtr::::new(globals.dynamictop_ptr).deref(&memory) }; - let old_dynamic_top = ctx.memory(0).view::()[dynamictop_ptr].get() as i32; + let old_dynamic_top = dynamictop_ptr.read().unwrap(); let new_dynamic_top: i32 = old_dynamic_top + increment; let total_memory = _emscripten_get_heap_size(ctx) as i32; debug!( " => PTR {}, old: {}, new: {}, increment: {}, total: {}", - dynamictop_ptr, old_dynamic_top, new_dynamic_top, increment, total_memory + dynamictop_ptr.offset(), + old_dynamic_top, + new_dynamic_top, + increment, + total_memory ); if increment > 0 && new_dynamic_top < old_dynamic_top || new_dynamic_top < 0 { abort_on_cannot_grow_memory_old(ctx); @@ -94,7 +99,7 @@ pub fn sbrk(ctx: &EmEnv, increment: i32) -> i32 { return -1; } } - ctx.memory(0).view::()[dynamictop_ptr].set(new_dynamic_top as u32); + dynamictop_ptr.write(new_dynamic_top).unwrap(); old_dynamic_top as _ } diff --git a/lib/emscripten/src/ptr.rs b/lib/emscripten/src/ptr.rs deleted file mode 100644 index 6102cbafc..000000000 --- a/lib/emscripten/src/ptr.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! This is a wrapper around the `WasmPtr` abstraction that does not allow deref of address 0 -//! This is a common assumption in Emscripten code - -// this is a wrapper with extra logic around the runtime-core `WasmPtr`, so we -// don't want to warn about unusued code here -#![allow(dead_code)] - -use std::fmt; -pub use wasmer::{Array, FromToNativeWasmType, Memory, ValueType, WasmCell}; - -#[repr(transparent)] -pub struct WasmPtr(wasmer::WasmPtr); - -unsafe impl ValueType for WasmPtr {} -impl Copy for WasmPtr {} - -impl Clone for WasmPtr { - fn clone(&self) -> Self { - Self(self.0) - } -} - -impl fmt::Debug for WasmPtr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -unsafe impl FromToNativeWasmType for WasmPtr { - type Native = as FromToNativeWasmType>::Native; - - fn to_native(self) -> Self::Native { - self.0.to_native() - } - fn from_native(n: Self::Native) -> Self { - Self(wasmer::WasmPtr::from_native(n)) - } -} - -impl PartialEq for WasmPtr { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for WasmPtr {} - -impl WasmPtr { - #[inline(always)] - pub fn new(offset: u32) -> Self { - Self(wasmer::WasmPtr::new(offset)) - } - - #[inline(always)] - pub fn offset(self) -> u32 { - self.0.offset() - } -} - -impl WasmPtr { - #[inline(always)] - pub fn deref(self, memory: &'_ Memory) -> Option> { - if self.0.offset() == 0 { - None - } else { - self.0.deref(memory) - } - } -} - -impl WasmPtr { - #[inline(always)] - pub fn deref( - self, - memory: &'_ Memory, - index: u32, - length: u32, - ) -> Option>> { - if self.0.offset() == 0 { - None - } else { - self.0.deref(memory, index, length) - } - } - - #[inline(always)] - pub unsafe fn get_utf8_str(self, memory: &'_ Memory, str_len: u32) -> Option<&'_ str> { - if self.0.offset() == 0 { - None - } else { - self.0.get_utf8_str(memory, str_len) - } - } - - #[inline(always)] - pub fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option { - if self.0.offset() == 0 { - None - } else { - self.0.get_utf8_string(memory, str_len) - } - } -} diff --git a/lib/emscripten/src/syscalls/mod.rs b/lib/emscripten/src/syscalls/mod.rs index 21cd31162..b050b1b1f 100644 --- a/lib/emscripten/src/syscalls/mod.rs +++ b/lib/emscripten/src/syscalls/mod.rs @@ -11,7 +11,6 @@ pub use self::unix::*; pub use self::windows::*; use crate::{ - ptr::{Array, WasmPtr}, utils::{copy_stat_into_wasm, get_cstr_path, get_current_directory}, EmEnv, }; @@ -46,10 +45,10 @@ use libc::{ }; use super::env; -use std::cell::Cell; #[allow(unused_imports)] use std::io::Error; use std::slice; +use wasmer::WasmPtr; /// exit pub fn ___syscall1(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) { @@ -212,11 +211,10 @@ pub fn ___syscall42(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int { let emscripten_memory = ctx.memory(0); // convert the file descriptor into a vec with two slots - let mut fd_vec: Vec = emscripten_memory.view()[((fd_offset / 4) as usize)..] - .iter() - .map(|pipe_end: &Cell| pipe_end.get()) - .take(2) - .collect(); + let mut fd_vec: [c_int; 2] = WasmPtr::<[c_int; 2]>::new(fd_offset) + .deref(&emscripten_memory) + .read() + .unwrap(); // get it as a mutable pointer let fd_ptr = fd_vec.as_mut_ptr(); @@ -351,18 +349,18 @@ pub fn ___syscall163(_ctx: &EmEnv, _one: i32, _two: i32) -> i32 { // getcwd pub fn ___syscall183(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> i32 { debug!("emscripten::___syscall183"); - let buf_offset: WasmPtr = varargs.get(ctx); + let buf_offset: WasmPtr = varargs.get(ctx); let _size: c_int = varargs.get(ctx); let path = get_current_directory(ctx); let path_string = path.unwrap().display().to_string(); let len = path_string.len(); let memory = ctx.memory(0); - let buf_writer = buf_offset.deref(&memory, 0, len as u32 + 1).unwrap(); + let buf_writer = buf_offset.slice(&memory, len as u32 + 1).unwrap(); for (i, byte) in path_string.bytes().enumerate() { - buf_writer[i].set(byte as _); + buf_writer.index(i as u64).write(byte as _).unwrap(); } - buf_writer[len].set(0); + buf_writer.index(len as u64).write(0).unwrap(); buf_offset.offset() as i32 } @@ -414,8 +412,8 @@ pub fn ___syscall140(ctx: &EmEnv, _which: i32, mut varargs: VarArgs) -> i32 { let ret = unsafe { lseek(fd, offset as _, whence) as i64 }; let memory = ctx.memory(0); - let result_ptr = result_ptr_value.deref(&memory).unwrap(); - result_ptr.set(ret); + let result_ptr = result_ptr_value.deref(&memory); + result_ptr.write(ret).unwrap(); debug!( "=> fd: {}, offset: {}, result: {}, whence: {} = {}\nlast os error: {}", diff --git a/lib/emscripten/src/syscalls/unix.rs b/lib/emscripten/src/syscalls/unix.rs index de8eec7a3..c0c1855e1 100644 --- a/lib/emscripten/src/syscalls/unix.rs +++ b/lib/emscripten/src/syscalls/unix.rs @@ -1,4 +1,4 @@ -use crate::{ptr::WasmPtr, varargs::VarArgs, LibcDirWrapper}; +use crate::{varargs::VarArgs, LibcDirWrapper}; #[cfg(target_vendor = "apple")] use libc::size_t; /// NOTE: TODO: These syscalls only support wasm_32 for now because they assume offsets are u32 @@ -81,6 +81,7 @@ use libc::{ // TCGETS, // TCSETSW, }; +use wasmer::{ValueType, WasmPtr}; // They are not exposed in in Rust libc in macOS const TCGETS: u64 = 0x5401; @@ -630,10 +631,10 @@ pub fn ___syscall102(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int debug!( "=> socket: {}, address: {:?}, address_len: {}", socket, - address.deref(&memory).unwrap().get(), - address_len.deref(&memory).unwrap().get() + address.deref(&memory).read().unwrap(), + address_len.deref(&memory).read().unwrap() ); - let mut address_len_addr = address_len.deref(&memory).unwrap().get(); + let mut address_len_addr = address_len.deref(&memory).read().unwrap(); // let mut address_len_addr: socklen_t = 0; let mut host_address: sockaddr = sockaddr { @@ -643,7 +644,7 @@ pub fn ___syscall102(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int sa_len: Default::default(), }; let fd = unsafe { accept(socket, &mut host_address, &mut address_len_addr) }; - let mut address_addr = address.deref(&memory).unwrap().get(); + let mut address_addr = address.deref(&memory).read().unwrap(); address_addr.sa_family = host_address.sa_family as _; address_addr.sa_data = host_address.sa_data; @@ -667,7 +668,7 @@ pub fn ___syscall102(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int let socket: i32 = socket_varargs.get(ctx); let address: WasmPtr = socket_varargs.get(ctx); let address_len: WasmPtr = socket_varargs.get(ctx); - let address_len_addr = address_len.deref(&memory).unwrap().get(); + let address_len_addr = address_len.deref(&memory).read().unwrap(); let mut sock_addr_host: sockaddr = sockaddr { sa_family: Default::default(), @@ -683,7 +684,7 @@ pub fn ___syscall102(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int ) }; // translate from host data into emscripten data - let mut address_mut = address.deref(&memory).unwrap().get(); + let mut address_mut = address.deref(&memory).read().unwrap(); address_mut.sa_family = sock_addr_host.sa_family as _; address_mut.sa_data = sock_addr_host.sa_data; @@ -839,7 +840,7 @@ pub fn ___syscall132(ctx: &EmEnv, _which: c_int, mut varargs: VarArgs) -> c_int ret } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, ValueType)] #[repr(C)] pub struct EmPollFd { pub fd: i32, @@ -847,8 +848,6 @@ pub struct EmPollFd { pub revents: i16, } -unsafe impl wasmer::ValueType for EmPollFd {} - /// poll pub fn ___syscall168(ctx: &EmEnv, _which: i32, mut varargs: VarArgs) -> i32 { debug!("emscripten::___syscall168(poll)"); @@ -857,7 +856,7 @@ pub fn ___syscall168(ctx: &EmEnv, _which: i32, mut varargs: VarArgs) -> i32 { let timeout: i32 = varargs.get(ctx); let memory = ctx.memory(0); - let mut fds_mut = fds.deref(&memory).unwrap().get(); + let mut fds_mut = fds.deref(&memory).read().unwrap(); unsafe { libc::poll( diff --git a/lib/emscripten/src/utils.rs b/lib/emscripten/src/utils.rs index 7ad250ddd..5ea2e99ad 100644 --- a/lib/emscripten/src/utils.rs +++ b/lib/emscripten/src/utils.rs @@ -8,7 +8,7 @@ use std::mem::size_of; use std::os::raw::c_char; use std::path::PathBuf; use std::slice; -use wasmer::{GlobalInit, Memory, Module, Pages}; +use wasmer::{GlobalInit, Memory, Module, Pages, WasmPtr}; /// We check if a provided module is an Emscripten generated one pub fn is_emscripten_module(module: &Module) -> bool { @@ -214,12 +214,9 @@ pub unsafe fn copy_stat_into_wasm(ctx: &EmEnv, buf: u32, stat: &stat) { #[allow(dead_code)] // it's used in `env/windows/mod.rs`. pub fn read_string_from_wasm(memory: &Memory, offset: u32) -> String { - let v: Vec = memory.view()[(offset as usize)..] - .iter() - .map(|cell| cell.get()) - .take_while(|&byte| byte != 0) - .collect(); - String::from_utf8_lossy(&v).to_owned().to_string() + WasmPtr::::new(offset) + .read_utf8_string_with_nul(memory) + .unwrap() } /// This function trys to find an entry in mapdir diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 439dafe10..da421966b 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -61,7 +61,6 @@ mod indexes; mod initializers; mod libcalls; mod memory; -mod memory_view; mod module; mod native; mod table; @@ -84,7 +83,6 @@ pub use crate::indexes::{ pub use crate::initializers::{ DataInitializer, DataInitializerLocation, OwnedDataInitializer, TableInitializer, }; -pub use crate::memory_view::{Atomically, MemoryView}; pub use crate::module::{ExportsIterator, ImportsIterator, ModuleInfo}; pub use crate::native::{NativeWasmType, ValueType}; pub use crate::units::{ diff --git a/lib/types/src/memory_view.rs b/lib/types/src/memory_view.rs deleted file mode 100644 index 732e68731..000000000 --- a/lib/types/src/memory_view.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::lib::std::cell::Cell; -use crate::lib::std::marker::PhantomData; -use crate::lib::std::ops::Deref; -// use crate::lib::std::ops::{Bound, RangeBounds}; -use crate::lib::std::slice; -use crate::lib::std::sync::atomic::{ - AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicU16, AtomicU32, AtomicU64, AtomicU8, -}; -use crate::native::ValueType; - -pub trait Atomic { - type Output; -} - -macro_rules! atomic { - ( $($for:ty => $output:ty),+ ) => { - $( - impl Atomic for $for { - type Output = $output; - } - )+ - } -} - -atomic!( - i8 => AtomicI8, - i16 => AtomicI16, - i32 => AtomicI32, - i64 => AtomicI64, - u8 => AtomicU8, - u16 => AtomicU16, - u32 => AtomicU32, - u64 => AtomicU64, - f32 => AtomicU32, - f64 => AtomicU64 -); - -/// A trait that represants an atomic type. -pub trait Atomicity {} - -/// Atomically. -pub struct Atomically; -impl Atomicity for Atomically {} - -/// Non-atomically. -pub struct NonAtomically; -impl Atomicity for NonAtomically {} - -/// A view into a memory. -pub struct MemoryView<'a, T: 'a, A = NonAtomically> { - ptr: *mut T, - // Note: the length is in the terms of `size::()`. - // The total length in memory is `size::() * length`. - length: usize, - _phantom: PhantomData<(&'a [Cell], A)>, -} - -impl<'a, T> MemoryView<'a, T, NonAtomically> -where - T: ValueType, -{ - /// Creates a new MemoryView given a `pointer` and `length`. - pub unsafe fn new(ptr: *mut T, length: u32) -> Self { - Self { - ptr, - length: length as usize, - _phantom: PhantomData, - } - } - - /// Creates a subarray view from this `MemoryView`. - pub fn subarray(&self, start: u32, end: u32) -> Self { - assert!( - (start as usize) < self.length, - "The range start is bigger than current length" - ); - assert!( - (end as usize) < self.length, - "The range end is bigger than current length" - ); - - Self { - ptr: unsafe { self.ptr.add(start as usize) }, - length: (end - start) as usize, - _phantom: PhantomData, - } - } - - /// Copy the contents of the source slice into this `MemoryView`. - /// - /// This function will efficiently copy the memory from within the wasm - /// module’s own linear memory to this typed array. - /// - /// # Safety - /// - /// This method is unsafe because the caller will need to make sure - /// there are no data races when copying memory into the view. - pub unsafe fn copy_from(&self, src: &[T]) { - // We cap at a max length - let sliced_src = &src[..self.length]; - for (i, byte) in sliced_src.iter().enumerate() { - *self.ptr.offset(i as isize) = *byte; - } - } -} - -impl<'a, T: Atomic> MemoryView<'a, T> { - /// Get atomic access to a memory view. - pub fn atomically(&self) -> MemoryView<'a, T::Output, Atomically> { - MemoryView { - ptr: self.ptr as *mut T::Output, - length: self.length, - _phantom: PhantomData, - } - } -} - -impl<'a, T> Deref for MemoryView<'a, T, NonAtomically> { - type Target = [Cell]; - fn deref(&self) -> &[Cell] { - let mut_slice: &mut [T] = unsafe { slice::from_raw_parts_mut(self.ptr, self.length) }; - let cell_slice: &Cell<[T]> = Cell::from_mut(mut_slice); - cell_slice.as_slice_of_cells() - } -} - -impl<'a, T> Deref for MemoryView<'a, T, Atomically> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.ptr as *const T, self.length) } - } -} diff --git a/lib/types/src/native.rs b/lib/types/src/native.rs index f5759b364..da7d719d0 100644 --- a/lib/types/src/native.rs +++ b/lib/types/src/native.rs @@ -5,6 +5,8 @@ use crate::extern_ref::VMExternRef; use crate::lib::std::fmt; use crate::types::Type; use crate::values::{Value, WasmValueType}; +use std::marker::PhantomData; +use std::mem::MaybeUninit; /// `NativeWasmType` represents a Wasm type that has a direct /// representation on the host (hence the “native” term). @@ -246,25 +248,51 @@ mod test_native_type { /// Trait for a Value type. A Value type is a type that is always valid and may /// be safely copied. /// -/// That is, for all possible bit patterns a valid Value type can be constructed -/// from those bits. +/// To maintain safety, types which implement this trait must be valid for all +/// bit patterns. This means that it cannot contain enums, `bool`, references, +/// etc. /// /// Concretely a `u32` is a Value type because every combination of 32 bits is /// a valid `u32`. However a `bool` is _not_ a Value type because any bit patterns /// other than `0` and `1` are invalid in Rust and may cause undefined behavior if /// a `bool` is constructed from those bytes. -pub unsafe trait ValueType: Copy -where - Self: Sized, -{ +/// +/// Additionally this trait has a method which zeros out any uninitializes bytes +/// prior to writing them to Wasm memory, which prevents information leaks into +/// the sandbox. +pub unsafe trait ValueType: Copy { + /// This method is passed a byte slice which contains the byte + /// representation of `self`. It must zero out any bytes which are + /// uninitialized (e.g. padding bytes). + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]); } -macro_rules! impl_value_type_for { - ( $($type:ty),* ) => { - $( - unsafe impl ValueType for $type {} - )* - }; +// Trivial implementations for primitive types and arrays of them. +macro_rules! primitives { + ($($t:ident)*) => ($( + unsafe impl ValueType for $t { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} + } + unsafe impl ValueType for [$t; N] { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} + } + )*) +} +primitives! { + i8 u8 + i16 u16 + i32 u32 + i64 u64 + i128 u128 + isize usize + f32 f64 } -impl_value_type_for!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64); +// This impl for PhantomData allows #[derive(ValueType)] to work with types +// that contain a PhantomData. +unsafe impl ValueType for PhantomData { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} diff --git a/lib/wasi-types/Cargo.toml b/lib/wasi-types/Cargo.toml index 5f296c450..d91ed71b7 100644 --- a/lib/wasi-types/Cargo.toml +++ b/lib/wasi-types/Cargo.toml @@ -12,11 +12,10 @@ edition = "2018" [dependencies] wasmer-types = { path = "../types", version = "=2.3.0" } +wasmer-derive = { path = "../derive", version = "=2.3.0" } serde = { version = "1.0", features = ["derive"], optional=true } byteorder = "1.3" time = "0.2" [features] -enable-serde = [ - "serde", -] +enable-serde = ["serde"] diff --git a/lib/wasi-types/src/directory.rs b/lib/wasi-types/src/directory.rs index 9275951a2..71601821e 100644 --- a/lib/wasi-types/src/directory.rs +++ b/lib/wasi-types/src/directory.rs @@ -1,11 +1,11 @@ use crate::*; use std::mem; -use wasmer_types::ValueType; +use wasmer_derive::ValueType; pub type __wasi_dircookie_t = u64; pub const __WASI_DIRCOOKIE_START: u64 = 0; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_dirent_t { pub d_next: __wasi_dircookie_t, @@ -14,8 +14,6 @@ pub struct __wasi_dirent_t { pub d_type: __wasi_filetype_t, } -unsafe impl ValueType for __wasi_dirent_t {} - pub fn dirent_to_le_bytes(ent: &__wasi_dirent_t) -> Vec { use mem::transmute; diff --git a/lib/wasi-types/src/event.rs b/lib/wasi-types/src/event.rs index 0679f9269..c368f5b9b 100644 --- a/lib/wasi-types/src/event.rs +++ b/lib/wasi-types/src/event.rs @@ -1,8 +1,12 @@ use crate::*; -use std::fmt; +use std::{ + fmt, + mem::{self, MaybeUninit}, +}; +use wasmer_derive::ValueType; use wasmer_types::ValueType; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_event_fd_readwrite_t { pub nbytes: __wasi_filesize_t, @@ -63,7 +67,46 @@ impl __wasi_event_t { } } -unsafe impl ValueType for __wasi_event_t {} +unsafe impl ValueType for __wasi_event_t { + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]) { + macro_rules! field { + ($($f:tt)*) => { + &self.$($f)* as *const _ as usize - self as *const _ as usize + }; + } + macro_rules! field_end { + ($($f:tt)*) => { + field!($($f)*) + mem::size_of_val(&self.$($f)*) + }; + } + macro_rules! zero { + ($start:expr, $end:expr) => { + for i in $start..$end { + bytes[i] = MaybeUninit::new(0); + } + }; + } + self.userdata + .zero_padding_bytes(&mut bytes[field!(userdata)..field_end!(userdata)]); + zero!(field_end!(userdata), field!(error)); + self.error + .zero_padding_bytes(&mut bytes[field!(error)..field_end!(error)]); + zero!(field_end!(error), field!(type_)); + self.type_ + .zero_padding_bytes(&mut bytes[field!(type_)..field_end!(type_)]); + zero!(field_end!(type_), field!(u)); + match self.type_ { + __WASI_EVENTTYPE_FD_READ | __WASI_EVENTTYPE_FD_WRITE => unsafe { + self.u.fd_readwrite.zero_padding_bytes( + &mut bytes[field!(u.fd_readwrite)..field_end!(u.fd_readwrite)], + ); + zero!(field_end!(u.fd_readwrite), field_end!(u)); + }, + _ => zero!(field!(u), field_end!(u)), + } + zero!(field_end!(u), mem::size_of_val(self)); + } +} pub type __wasi_eventrwflags_t = u16; pub const __WASI_EVENT_FD_READWRITE_HANGUP: u16 = 1 << 0; diff --git a/lib/wasi-types/src/file.rs b/lib/wasi-types/src/file.rs index d1a48de6b..356a13398 100644 --- a/lib/wasi-types/src/file.rs +++ b/lib/wasi-types/src/file.rs @@ -1,7 +1,11 @@ use crate::*; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{ + fmt, + mem::{self, MaybeUninit}, +}; +use wasmer_derive::ValueType; use wasmer_types::ValueType; pub type __wasi_device_t = u64; @@ -21,14 +25,12 @@ pub const __WASI_FDFLAG_SYNC: u16 = 1 << 4; pub type __wasi_preopentype_t = u8; pub const __WASI_PREOPENTYPE_DIR: u8 = 0; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_prestat_u_dir_t { pub pr_name_len: u32, } -unsafe impl ValueType for __wasi_prestat_u_dir_t {} - #[derive(Copy, Clone)] #[repr(C)] pub union __wasi_prestat_u { @@ -41,8 +43,6 @@ impl fmt::Debug for __wasi_prestat_u { } } -unsafe impl ValueType for __wasi_prestat_u {} - #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct __wasi_prestat_t { @@ -77,9 +77,42 @@ impl __wasi_prestat_t { } } -unsafe impl ValueType for __wasi_prestat_t {} +unsafe impl ValueType for __wasi_prestat_t { + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]) { + macro_rules! field { + ($($f:tt)*) => { + &self.$($f)* as *const _ as usize - self as *const _ as usize + }; + } + macro_rules! field_end { + ($($f:tt)*) => { + field!($($f)*) + mem::size_of_val(&self.$($f)*) + }; + } + macro_rules! zero { + ($start:expr, $end:expr) => { + for i in $start..$end { + bytes[i] = MaybeUninit::new(0); + } + }; + } + self.pr_type + .zero_padding_bytes(&mut bytes[field!(pr_type)..field_end!(pr_type)]); + zero!(field_end!(pr_type), field!(u)); + match self.pr_type { + __WASI_PREOPENTYPE_DIR => unsafe { + self.u + .dir + .zero_padding_bytes(&mut bytes[field!(u.dir)..field_end!(u.dir)]); + zero!(field_end!(u.dir), field_end!(u)); + }, + _ => zero!(field!(u), field_end!(u)), + } + zero!(field_end!(u), mem::size_of_val(self)); + } +} -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_fdstat_t { pub fs_filetype: __wasi_filetype_t, @@ -88,13 +121,11 @@ pub struct __wasi_fdstat_t { pub fs_rights_inheriting: __wasi_rights_t, } -unsafe impl ValueType for __wasi_fdstat_t {} - pub type __wasi_filedelta_t = i64; pub type __wasi_filesize_t = u64; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, ValueType)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct __wasi_filestat_t { @@ -158,8 +189,6 @@ impl fmt::Debug for __wasi_filestat_t { } } -unsafe impl ValueType for __wasi_filestat_t {} - pub fn wasi_filetype_to_name(ft: __wasi_filetype_t) -> &'static str { match ft { __WASI_FILETYPE_UNKNOWN => "Unknown", diff --git a/lib/wasi-types/src/io.rs b/lib/wasi-types/src/io.rs index e42ff0e1c..55d3fc5c1 100644 --- a/lib/wasi-types/src/io.rs +++ b/lib/wasi-types/src/io.rs @@ -1,19 +1,15 @@ -use wasmer_types::ValueType; +use wasmer_derive::ValueType; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_ciovec_t { pub buf: u32, pub buf_len: u32, } -unsafe impl ValueType for __wasi_ciovec_t {} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_iovec_t { pub buf: u32, pub buf_len: u32, } - -unsafe impl ValueType for __wasi_iovec_t {} diff --git a/lib/wasi-types/src/lib.rs b/lib/wasi-types/src/lib.rs index f55de60f9..5a0acab71 100644 --- a/lib/wasi-types/src/lib.rs +++ b/lib/wasi-types/src/lib.rs @@ -8,6 +8,9 @@ //! Those types aim at being used by [the `wasmer-wasi` //! crate](https://github.com/wasmerio/wasmer/blob/master/lib/wasi). +// Needed for #[derive(ValueType)] +extern crate wasmer_types as wasmer; + mod advice; mod directory; mod error; diff --git a/lib/wasi-types/src/subscription.rs b/lib/wasi-types/src/subscription.rs index bd7d3c2c8..48b2f0a86 100644 --- a/lib/wasi-types/src/subscription.rs +++ b/lib/wasi-types/src/subscription.rs @@ -1,12 +1,14 @@ use crate::*; use std::convert::TryFrom; use std::fmt; +use std::mem::{self, MaybeUninit}; +use wasmer_derive::ValueType; use wasmer_types::ValueType; pub type __wasi_subclockflags_t = u16; pub const __WASI_SUBSCRIPTION_CLOCK_ABSTIME: u16 = 1 << 0; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_subscription_clock_t { pub clock_id: __wasi_clockid_t, @@ -15,7 +17,7 @@ pub struct __wasi_subscription_clock_t { pub flags: __wasi_subclockflags_t, } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_subscription_fs_readwrite_t { pub fd: __wasi_fd_t, @@ -122,7 +124,49 @@ impl fmt::Debug for __wasi_subscription_t { } } -unsafe impl ValueType for __wasi_subscription_t {} +unsafe impl ValueType for __wasi_subscription_t { + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]) { + macro_rules! field { + ($($f:tt)*) => { + &self.$($f)* as *const _ as usize - self as *const _ as usize + }; + } + macro_rules! field_end { + ($($f:tt)*) => { + field!($($f)*) + mem::size_of_val(&self.$($f)*) + }; + } + macro_rules! zero { + ($start:expr, $end:expr) => { + for i in $start..$end { + bytes[i] = MaybeUninit::new(0); + } + }; + } + self.userdata + .zero_padding_bytes(&mut bytes[field!(userdata)..field_end!(userdata)]); + zero!(field_end!(userdata), field!(u)); + self.type_ + .zero_padding_bytes(&mut bytes[field!(type_)..field_end!(type_)]); + zero!(field_end!(type_), field!(u)); + match self.type_ { + __WASI_EVENTTYPE_FD_READ | __WASI_EVENTTYPE_FD_WRITE => unsafe { + self.u.fd_readwrite.zero_padding_bytes( + &mut bytes[field!(u.fd_readwrite)..field_end!(u.fd_readwrite)], + ); + zero!(field_end!(u.fd_readwrite), field_end!(u)); + }, + __WASI_EVENTTYPE_CLOCK => unsafe { + self.u + .clock + .zero_padding_bytes(&mut bytes[field!(u.clock)..field_end!(u.clock)]); + zero!(field_end!(u.clock), field_end!(u)); + }, + _ => zero!(field!(u), field_end!(u)), + } + zero!(field_end!(u), mem::size_of_val(self)); + } +} pub enum SubscriptionEnum { Clock(__wasi_subscription_clock_t), diff --git a/lib/wasi-types/src/versions/snapshot0.rs b/lib/wasi-types/src/versions/snapshot0.rs index 32092611d..22ee860ad 100644 --- a/lib/wasi-types/src/versions/snapshot0.rs +++ b/lib/wasi-types/src/versions/snapshot0.rs @@ -2,11 +2,13 @@ use crate::*; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use std::fmt; +use std::mem::{self, MaybeUninit}; +use wasmer_derive::ValueType; use wasmer_types::ValueType; pub type __wasi_linkcount_t = u32; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueType)] #[repr(C)] pub struct __wasi_subscription_clock_t { pub userdata: __wasi_userdata_t, @@ -31,14 +33,56 @@ pub struct __wasi_subscription_t { pub u: __wasi_subscription_u, } -unsafe impl ValueType for __wasi_subscription_t {} +unsafe impl ValueType for __wasi_subscription_t { + fn zero_padding_bytes(&self, bytes: &mut [MaybeUninit]) { + macro_rules! field { + ($($f:tt)*) => { + &self.$($f)* as *const _ as usize - self as *const _ as usize + }; + } + macro_rules! field_end { + ($($f:tt)*) => { + field!($($f)*) + mem::size_of_val(&self.$($f)*) + }; + } + macro_rules! zero { + ($start:expr, $end:expr) => { + for i in $start..$end { + bytes[i] = MaybeUninit::new(0); + } + }; + } + self.userdata + .zero_padding_bytes(&mut bytes[field!(userdata)..field_end!(userdata)]); + zero!(field_end!(userdata), field!(u)); + self.type_ + .zero_padding_bytes(&mut bytes[field!(type_)..field_end!(type_)]); + zero!(field_end!(type_), field!(u)); + match self.type_ { + __WASI_EVENTTYPE_FD_READ | __WASI_EVENTTYPE_FD_WRITE => unsafe { + self.u.fd_readwrite.zero_padding_bytes( + &mut bytes[field!(u.fd_readwrite)..field_end!(u.fd_readwrite)], + ); + zero!(field_end!(u.fd_readwrite), field_end!(u)); + }, + __WASI_EVENTTYPE_CLOCK => unsafe { + self.u + .clock + .zero_padding_bytes(&mut bytes[field!(u.clock)..field_end!(u.clock)]); + zero!(field_end!(u.clock), field_end!(u)); + }, + _ => zero!(field!(u), field_end!(u)), + } + zero!(field_end!(u), mem::size_of_val(self)); + } +} pub type __wasi_whence_t = u8; pub const __WASI_WHENCE_CUR: u8 = 0; pub const __WASI_WHENCE_END: u8 = 1; pub const __WASI_WHENCE_SET: u8 = 2; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, ValueType)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct __wasi_filestat_t { @@ -52,8 +96,6 @@ pub struct __wasi_filestat_t { pub st_ctim: __wasi_timestamp_t, } -unsafe impl ValueType for __wasi_filestat_t {} - impl fmt::Debug for __wasi_filestat_t { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let convert_ts_into_time_string = |ts| { diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 18044ca26..5bd29d012 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -35,7 +35,6 @@ compile_error!( #[macro_use] mod macros; -mod ptr; mod state; mod syscalls; mod utils; @@ -56,8 +55,8 @@ pub use wasmer_vfs::{FsError, VirtualFile}; use thiserror::Error; use wasmer::{ - imports, ChainableNamedResolver, Function, ImportObject, LazyInit, Memory, Module, - NamedResolver, Store, WasmerEnv, + imports, ChainableNamedResolver, Function, ImportObject, LazyInit, Memory, MemoryAccessError, + Module, NamedResolver, Store, WasmerEnv, }; use std::sync::{Arc, Mutex, MutexGuard}; @@ -269,3 +268,12 @@ fn generate_import_object_snapshot1(store: &Store, env: WasiEnv) -> ImportObject } } } + +fn mem_error_to_wasi(err: MemoryAccessError) -> types::__wasi_errno_t { + match err { + MemoryAccessError::HeapOutOfBounds => types::__WASI_EFAULT, + MemoryAccessError::Overflow => types::__WASI_EOVERFLOW, + MemoryAccessError::NonUtf8String => types::__WASI_EINVAL, + _ => types::__WASI_EINVAL, + } +} diff --git a/lib/wasi/src/macros.rs b/lib/wasi/src/macros.rs index 8cec82ae0..e2488668e 100644 --- a/lib/wasi/src/macros.rs +++ b/lib/wasi/src/macros.rs @@ -22,15 +22,16 @@ macro_rules! wasi_try { }}; } -/// Reads a string from Wasm memory and returns the invalid argument error -/// code if it fails. -/// -/// # Safety -/// See the safety docs for [`wasmer::WasmPtr::get_utf8_str`]: the returned value -/// points into Wasm memory and care must be taken that it does not get -/// corrupted. -macro_rules! get_input_str { - ($memory:expr, $data:expr, $len:expr) => {{ - wasi_try!($data.get_utf8_string($memory, $len), __WASI_EINVAL) +/// Like `wasi_try` but converts a `MemoryAccessError` to a __wasi_errno_t`. +macro_rules! wasi_try_mem { + ($expr:expr) => {{ + wasi_try!($expr.map_err($crate::mem_error_to_wasi)) + }}; +} + +/// Reads a string from Wasm memory. +macro_rules! get_input_str { + ($memory:expr, $data:expr, $len:expr) => {{ + wasi_try_mem!($data.read_utf8_string($memory, $len)) }}; } diff --git a/lib/wasi/src/ptr.rs b/lib/wasi/src/ptr.rs deleted file mode 100644 index 7a472dcd4..000000000 --- a/lib/wasi/src/ptr.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! This is a wrapper around the `WasmPtr` abstraction that returns __WASI_EFAULT -//! if memory access failed - -use crate::syscalls::types::{__wasi_errno_t, __WASI_EFAULT}; -use std::fmt; -pub use wasmer::{ - Array, FromToNativeWasmType, Item, Memory, ValueType, WasmCell, WasmPtr as BaseWasmPtr, -}; - -#[repr(transparent)] -pub struct WasmPtr(BaseWasmPtr); - -unsafe impl ValueType for WasmPtr {} -impl Copy for WasmPtr {} - -impl Clone for WasmPtr { - fn clone(&self) -> Self { - #[allow(clippy::clone_on_copy)] - Self(self.0.clone()) - } -} - -impl fmt::Debug for WasmPtr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl From for WasmPtr { - fn from(offset: i32) -> Self { - Self::new(offset as _) - } -} - -unsafe impl FromToNativeWasmType for WasmPtr { - type Native = as FromToNativeWasmType>::Native; - - fn to_native(self) -> Self::Native { - self.0.to_native() - } - fn from_native(n: Self::Native) -> Self { - Self(BaseWasmPtr::from_native(n)) - } -} - -impl PartialEq for WasmPtr { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for WasmPtr {} - -impl WasmPtr { - #[inline(always)] - pub fn new(offset: u32) -> Self { - Self(BaseWasmPtr::new(offset)) - } - - #[inline(always)] - pub fn offset(self) -> u32 { - self.0.offset() - } -} - -impl WasmPtr { - #[inline(always)] - pub fn deref<'a>(self, memory: &'a Memory) -> Result, __wasi_errno_t> { - self.0.deref(memory).ok_or(__WASI_EFAULT) - } -} - -impl WasmPtr { - #[inline(always)] - pub fn deref<'a>( - self, - memory: &'a Memory, - index: u32, - length: u32, - ) -> Result>, __wasi_errno_t> { - self.0.deref(memory, index, length).ok_or(__WASI_EFAULT) - } - - #[inline(always)] - pub unsafe fn get_utf8_str<'a>( - self, - memory: &'a Memory, - str_len: u32, - ) -> Option> { - self.0.get_utf8_str(memory, str_len).map(Into::into) - } - - #[inline(always)] - pub unsafe fn get_utf8_string(self, memory: &Memory, str_len: u32) -> Option { - self.0.get_utf8_string(memory, str_len) - } -} diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs index 119f75457..1ed33a298 100644 --- a/lib/wasi/src/syscalls/legacy/snapshot0.rs +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -1,7 +1,7 @@ -use crate::ptr::{Array, WasmPtr}; use crate::syscalls; use crate::syscalls::types::{self, snapshot0}; -use crate::WasiEnv; +use crate::{mem_error_to_wasi, WasiEnv}; +use wasmer::WasmPtr; /// Wrapper around `syscalls::fd_filestat_get` with extra logic to handle the size /// difference of `wasi_filestat_t` @@ -19,12 +19,10 @@ pub fn fd_filestat_get( // transmute the WasmPtr into a WasmPtr where T2 > T1, this will read extra memory. // The edge case of this cenv.mausing an OOB is not handled, if the new field is OOB, then the entire // memory access will fail. - let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; + let new_buf: WasmPtr = buf.cast(); // Copy the data including the extra data - #[allow(clippy::clone_on_copy)] - let new_filestat_setup: types::__wasi_filestat_t = - wasi_try!(new_buf.deref(memory)).get().clone(); + let new_filestat_setup: types::__wasi_filestat_t = wasi_try_mem!(new_buf.read(memory)); // Set up complete, make the call with the pointer that will write to the // struct and some unrelated memory after the struct. @@ -34,7 +32,7 @@ pub fn fd_filestat_get( let memory = env.memory(); // get the values written to memory - let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + let new_filestat = wasi_try_mem!(new_buf.deref(memory).read()); // translate the new struct into the old struct in host memory let old_stat = snapshot0::__wasi_filestat_t { st_dev: new_filestat.st_dev, @@ -49,11 +47,11 @@ pub fn fd_filestat_get( // write back the original values at the pointer's memory locations // (including the memory unrelated to the pointer) - wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); + wasi_try_mem!(new_buf.deref(memory).write(new_filestat_setup)); // Now that this memory is back as it was, write the translated filestat // into memory leaving it as it should be - wasi_try!(buf.deref(memory)).set(old_stat); + wasi_try_mem!(buf.deref(memory).write(old_stat)); result } @@ -64,22 +62,20 @@ pub fn path_filestat_get( env: &WasiEnv, fd: types::__wasi_fd_t, flags: types::__wasi_lookupflags_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, buf: WasmPtr, ) -> types::__wasi_errno_t { // see `fd_filestat_get` in this file for an explanation of this strange behavior let memory = env.memory(); - let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; - #[allow(clippy::clone_on_copy)] - let new_filestat_setup: types::__wasi_filestat_t = - wasi_try!(new_buf.deref(memory)).get().clone(); + let new_buf: WasmPtr = buf.cast(); + let new_filestat_setup: types::__wasi_filestat_t = wasi_try_mem!(new_buf.read(memory)); let result = syscalls::path_filestat_get(env, fd, flags, path, path_len, new_buf); let memory = env.memory(); - let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + let new_filestat = wasi_try_mem!(new_buf.deref(memory).read()); let old_stat = snapshot0::__wasi_filestat_t { st_dev: new_filestat.st_dev, st_ino: new_filestat.st_ino, @@ -91,8 +87,8 @@ pub fn path_filestat_get( st_ctim: new_filestat.st_ctim, }; - wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); - wasi_try!(buf.deref(memory)).set(old_stat); + wasi_try_mem!(new_buf.deref(memory).write(new_filestat_setup)); + wasi_try_mem!(buf.deref(memory).write(old_stat)); result } @@ -120,8 +116,8 @@ pub fn fd_seek( /// userdata field back pub fn poll_oneoff( env: &WasiEnv, - in_: WasmPtr, - out_: WasmPtr, + in_: WasmPtr, + out_: WasmPtr, nsubscriptions: u32, nevents: WasmPtr, ) -> types::__wasi_errno_t { @@ -130,20 +126,17 @@ pub fn poll_oneoff( // we start by adjusting `in_` into a format that the new code can understand let memory = env.memory(); - let mut in_origs: Vec = vec![]; - for in_sub in wasi_try!(in_.deref(memory, 0, nsubscriptions)) { - in_origs.push(in_sub.get()); - } + let in_origs = wasi_try_mem!(in_.slice(memory, nsubscriptions)); + let in_origs = wasi_try_mem!(in_origs.read_to_vec()); // get a pointer to the smaller new type - let in_new_type_ptr: WasmPtr = - unsafe { std::mem::transmute(in_) }; + let in_new_type_ptr: WasmPtr = in_.cast(); - for (in_sub_new, orig) in wasi_try!(in_new_type_ptr.deref(memory, 0, nsubscriptions)) + for (in_sub_new, orig) in wasi_try_mem!(in_new_type_ptr.slice(memory, nsubscriptions)) .iter() .zip(in_origs.iter()) { - in_sub_new.set(types::__wasi_subscription_t { + wasi_try_mem!(in_sub_new.write(types::__wasi_subscription_t { userdata: orig.userdata, type_: orig.type_, u: if orig.type_ == types::__WASI_EVENTTYPE_CLOCK { @@ -160,7 +153,7 @@ pub fn poll_oneoff( fd_readwrite: unsafe { orig.u.fd_readwrite }, } }, - }); + })); } // make the call @@ -169,11 +162,11 @@ pub fn poll_oneoff( // replace the old values of in, in case the calling code reuses the memory let memory = env.memory(); - for (in_sub, orig) in wasi_try!(in_.deref(memory, 0, nsubscriptions)) + for (in_sub, orig) in wasi_try_mem!(in_.slice(memory, nsubscriptions)) .iter() .zip(in_origs.into_iter()) { - in_sub.set(orig); + wasi_try_mem!(in_sub.write(orig)); } result diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 5034e15f6..94a69f8cd 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -20,7 +20,7 @@ pub mod legacy; use self::types::*; use crate::{ - ptr::{Array, WasmPtr}, + mem_error_to_wasi, state::{ self, fs_error_into_wasi_err, iterate_poll_events, poll, virtual_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, PollEvent, @@ -32,7 +32,7 @@ use std::borrow::Borrow; use std::convert::{Infallible, TryInto}; use std::io::{self, Read, Seek, Write}; use tracing::{debug, trace}; -use wasmer::{Memory, RuntimeError, Value, WasmCell}; +use wasmer::{Memory, RuntimeError, Value, WasmPtr, WasmSlice}; use wasmer_vfs::{FsError, VirtualFile}; #[cfg(any( @@ -52,15 +52,16 @@ pub use wasm32::*; fn write_bytes_inner( mut write_loc: T, memory: &Memory, - iovs_arr_cell: &[WasmCell<__wasi_ciovec_t>], + iovs_arr_cell: WasmSlice<__wasi_ciovec_t>, ) -> Result { let mut bytes_written = 0; - for iov in iovs_arr_cell { - let iov_inner = iov.get(); - let bytes = WasmPtr::::new(iov_inner.buf).deref(memory, 0, iov_inner.buf_len)?; - write_loc - .write_all(&bytes.iter().map(|b_cell| b_cell.get()).collect::>()) - .map_err(|_| __WASI_EIO)?; + for iov in iovs_arr_cell.iter() { + let iov_inner = iov.read().map_err(mem_error_to_wasi)?; + let bytes = WasmPtr::::new(iov_inner.buf) + .slice(memory, iov_inner.buf_len) + .map_err(mem_error_to_wasi)?; + let bytes = bytes.read_to_vec().map_err(mem_error_to_wasi)?; + write_loc.write_all(&bytes).map_err(|_| __WASI_EIO)?; // TODO: handle failure more accurately bytes_written += iov_inner.buf_len; @@ -71,9 +72,9 @@ fn write_bytes_inner( fn write_bytes( mut write_loc: T, memory: &Memory, - iovs_arr_cell: &[WasmCell<__wasi_ciovec_t>], + iovs_arr: WasmSlice<__wasi_ciovec_t>, ) -> Result { - let result = write_bytes_inner(&mut write_loc, memory, iovs_arr_cell); + let result = write_bytes_inner(&mut write_loc, memory, iovs_arr); write_loc.flush(); result } @@ -81,7 +82,7 @@ fn write_bytes( fn read_bytes( mut reader: T, memory: &Memory, - iovs_arr_cell: &[WasmCell<__wasi_iovec_t>], + iovs_arr: WasmSlice<__wasi_iovec_t>, ) -> Result { let mut bytes_read = 0; @@ -89,20 +90,16 @@ fn read_bytes( // N times in the loop. let mut raw_bytes: Vec = vec![0; 1024]; - for iov in iovs_arr_cell { - let iov_inner = iov.get(); + for iov in iovs_arr.iter() { + let iov_inner = iov.read().map_err(mem_error_to_wasi)?; raw_bytes.clear(); raw_bytes.resize(iov_inner.buf_len as usize, 0); bytes_read += reader.read(&mut raw_bytes).map_err(|_| __WASI_EIO)? as u32; - unsafe { - memory - .uint8view() - .subarray( - iov_inner.buf as u32, - iov_inner.buf as u32 + iov_inner.buf_len as u32, - ) - .copy_from(&raw_bytes); - } + + let buf = WasmPtr::::new(iov_inner.buf) + .slice(memory, iov_inner.buf_len) + .map_err(mem_error_to_wasi)?; + buf.write_slice(&raw_bytes).map_err(mem_error_to_wasi)?; } Ok(bytes_read) } @@ -116,22 +113,21 @@ fn has_rights(rights_set: __wasi_rights_t, rights_check_set: __wasi_rights_t) -> fn write_buffer_array( memory: &Memory, from: &[Vec], - ptr_buffer: WasmPtr, Array>, - buffer: WasmPtr, + ptr_buffer: WasmPtr>, + buffer: WasmPtr, ) -> __wasi_errno_t { - let ptrs = wasi_try!(ptr_buffer.deref(memory, 0, from.len() as u32)); + let ptrs = wasi_try_mem!(ptr_buffer.slice(memory, from.len() as u32)); let mut current_buffer_offset = 0; for ((i, sub_buffer), ptr) in from.iter().enumerate().zip(ptrs.iter()) { debug!("ptr: {:?}, subbuffer: {:?}", ptr, sub_buffer); let new_ptr = WasmPtr::new(buffer.offset() + current_buffer_offset); - ptr.set(new_ptr); + wasi_try_mem!(ptr.write(new_ptr)); - let cells = wasi_try!(new_ptr.deref(memory, 0, sub_buffer.len() as u32 + 1)); + let data = wasi_try_mem!(new_ptr.slice(memory, sub_buffer.len() as u32)); + wasi_try_mem!(data.write_slice(sub_buffer)); + wasi_try_mem!(wasi_try_mem!(new_ptr.add(sub_buffer.len() as u32)).write(memory, 0)); - for (cell, &byte) in cells.iter().zip(sub_buffer.iter().chain([0].iter())) { - cell.set(byte); - } current_buffer_offset += sub_buffer.len() as u32 + 1; } @@ -157,8 +153,8 @@ fn get_current_time_in_nanos() -> Result<__wasi_timestamp_t, __wasi_errno_t> { /// pub fn args_get( env: &WasiEnv, - argv: WasmPtr, Array>, - argv_buf: WasmPtr, + argv: WasmPtr>, + argv_buf: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::args_get"); let (memory, mut state) = env.get_memory_and_wasi_state(0); @@ -194,13 +190,13 @@ pub fn args_sizes_get( debug!("wasi::args_sizes_get"); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let argc = wasi_try!(argc.deref(memory)); - let argv_buf_size = wasi_try!(argv_buf_size.deref(memory)); + let argc = argc.deref(memory); + let argv_buf_size = argv_buf_size.deref(memory); let argc_val = state.args.len() as u32; let argv_buf_size_val = state.args.iter().map(|v| v.len() as u32 + 1).sum(); - argc.set(argc_val); - argv_buf_size.set(argv_buf_size_val); + wasi_try_mem!(argc.write(argc_val)); + wasi_try_mem!(argv_buf_size.write(argv_buf_size_val)); debug!("=> argc={}, argv_buf_size={}", argc_val, argv_buf_size_val); @@ -223,7 +219,7 @@ pub fn clock_res_get( debug!("wasi::clock_res_get"); let memory = env.memory(); - let out_addr = wasi_try!(resolution.deref(memory)); + let out_addr = resolution.deref(memory); platform_clock_res_get(clock_id, out_addr) } @@ -249,11 +245,11 @@ pub fn clock_time_get( ); let memory = env.memory(); - let out_addr = wasi_try!(time.deref(memory)); + let out_addr = time.deref(memory); let result = platform_clock_time_get(clock_id, precision, out_addr); debug!( "time: {} => {}", - wasi_try!(time.deref(memory)).get(), + wasi_try_mem!(time.deref(memory).read()), result ); result @@ -269,8 +265,8 @@ pub fn clock_time_get( /// A pointer to a buffer to write the environment variable string data. pub fn environ_get( env: &WasiEnv, - environ: WasmPtr, Array>, - environ_buf: WasmPtr, + environ: WasmPtr>, + environ_buf: WasmPtr, ) -> __wasi_errno_t { debug!( "wasi::environ_get. Environ: {:?}, environ_buf: {:?}", @@ -297,13 +293,13 @@ pub fn environ_sizes_get( debug!("wasi::environ_sizes_get"); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let environ_count = wasi_try!(environ_count.deref(memory)); - let environ_buf_size = wasi_try!(environ_buf_size.deref(memory)); + let environ_count = environ_count.deref(memory); + let environ_buf_size = environ_buf_size.deref(memory); let env_var_count = state.envs.len() as u32; let env_buf_size = state.envs.iter().map(|v| v.len() as u32 + 1).sum(); - environ_count.set(env_var_count); - environ_buf_size.set(env_buf_size); + wasi_try_mem!(environ_count.write(env_var_count)); + wasi_try_mem!(environ_buf_size.write(env_buf_size)); debug!( "env_var_count: {}, env_buf_size: {}", @@ -446,9 +442,9 @@ pub fn fd_fdstat_get( let fd_entry = wasi_try!(state.fs.get_fd(fd)); let stat = wasi_try!(state.fs.fdstat(fd)); - let buf = wasi_try!(buf_ptr.deref(memory)); + let buf = buf_ptr.deref(memory); - buf.set(stat); + wasi_try_mem!(buf.write(stat)); __WASI_ESUCCESS } @@ -531,8 +527,8 @@ pub fn fd_filestat_get( let stat = wasi_try!(state.fs.filestat_fd(fd)); - let buf = wasi_try!(buf.deref(memory)); - buf.set(stat); + let buf = buf.deref(memory); + wasi_try_mem!(buf.write(stat)); __WASI_ESUCCESS } @@ -650,7 +646,7 @@ pub fn fd_filestat_set_times( pub fn fd_pread( env: &WasiEnv, fd: __wasi_fd_t, - iovs: WasmPtr<__wasi_iovec_t, Array>, + iovs: WasmPtr<__wasi_iovec_t>, iovs_len: u32, offset: __wasi_filesize_t, nread: WasmPtr, @@ -658,15 +654,15 @@ pub fn fd_pread( debug!("wasi::fd_pread: fd={}, offset={}", fd, offset); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iov_cells = wasi_try!(iovs.deref(memory, 0, iovs_len)); - let nread_cell = wasi_try!(nread.deref(memory)); + let iovs = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let nread_ref = nread.deref(memory); let bytes_read = match fd { __WASI_STDIN_FILENO => { if let Some(ref mut stdin) = wasi_try!(state.fs.stdin_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(read_bytes(stdin, memory, &iov_cells)) + wasi_try!(read_bytes(stdin, memory, iovs)) } else { return __WASI_EBADF; } @@ -693,7 +689,7 @@ pub fn fd_pread( h.seek(std::io::SeekFrom::Start(offset as u64)).ok(), __WASI_EIO ); - wasi_try!(read_bytes(h, memory, &iov_cells)) + wasi_try!(read_bytes(h, memory, iovs)) } else { return __WASI_EINVAL; } @@ -701,13 +697,13 @@ pub fn fd_pread( Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_pread"), Kind::Buffer { buffer } => { - wasi_try!(read_bytes(&buffer[(offset as usize)..], memory, &iov_cells)) + wasi_try!(read_bytes(&buffer[(offset as usize)..], memory, iovs)) } } } }; - nread_cell.set(bytes_read); + wasi_try_mem!(nread_ref.write(bytes_read)); debug!("Success: {} bytes read", bytes_read); __WASI_ESUCCESS } @@ -728,9 +724,9 @@ pub fn fd_prestat_get( debug!("wasi::fd_prestat_get: fd={}", fd); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let prestat_ptr = wasi_try!(buf.deref(memory)); + let prestat_ptr = buf.deref(memory); - prestat_ptr.set(wasi_try!(state.fs.prestat_fd(fd))); + wasi_try_mem!(prestat_ptr.write(wasi_try!(state.fs.prestat_fd(fd)))); __WASI_ESUCCESS } @@ -738,7 +734,7 @@ pub fn fd_prestat_get( pub fn fd_prestat_dir_name( env: &WasiEnv, fd: __wasi_fd_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { debug!( @@ -746,7 +742,7 @@ pub fn fd_prestat_dir_name( fd, path_len ); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let path_chars = wasi_try!(path.deref(memory, 0, path_len)); + let path_chars = wasi_try_mem!(path.slice(memory, path_len)); let real_fd = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); let inode_val = &state.fs.inodes[real_fd.inode]; @@ -757,20 +753,13 @@ pub fn fd_prestat_dir_name( match inode_val.kind { Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify this: null termination, etc - if inode_val.name.len() <= path_len as usize { - let mut i = 0; - for c in inode_val.name.bytes() { - path_chars[i].set(c); - i += 1 - } - path_chars[i].set(0); + if inode_val.name.len() < path_len as usize { + wasi_try_mem!(path_chars + .subslice(0..inode_val.name.len() as u64) + .write_slice(inode_val.name.as_bytes())); + wasi_try_mem!(path_chars.index(inode_val.name.len() as u64).write(0)); - debug!( - "=> result: \"{}\" (written: {}, {})", - unsafe { path.get_utf8_str(memory, path_len).unwrap() }, - i, - path_chars[0].get(), - ); + debug!("=> result: \"{}\"", inode_val.name); __WASI_ESUCCESS } else { @@ -798,7 +787,7 @@ pub fn fd_prestat_dir_name( pub fn fd_pwrite( env: &WasiEnv, fd: __wasi_fd_t, - iovs: WasmPtr<__wasi_ciovec_t, Array>, + iovs: WasmPtr<__wasi_ciovec_t>, iovs_len: u32, offset: __wasi_filesize_t, nwritten: WasmPtr, @@ -806,8 +795,8 @@ pub fn fd_pwrite( debug!("wasi::fd_pwrite"); // TODO: refactor, this is just copied from `fd_write`... let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); - let nwritten_cell = wasi_try!(nwritten.deref(memory)); + let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let nwritten_ref = nwritten.deref(memory); let bytes_written = match fd { __WASI_STDIN_FILENO => return __WASI_EINVAL, @@ -815,7 +804,7 @@ pub fn fd_pwrite( if let Some(ref mut stdout) = wasi_try!(state.fs.stdout_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(write_bytes(stdout, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(stdout, memory, iovs_arr)) } else { return __WASI_EBADF; } @@ -824,7 +813,7 @@ pub fn fd_pwrite( if let Some(ref mut stderr) = wasi_try!(state.fs.stderr_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(write_bytes(stderr, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(stderr, memory, iovs_arr)) } else { return __WASI_EBADF; } @@ -845,7 +834,7 @@ pub fn fd_pwrite( Kind::File { handle, .. } => { if let Some(handle) = handle { handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(write_bytes(handle, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(handle, memory, iovs_arr)) } else { return __WASI_EINVAL; } @@ -859,14 +848,14 @@ pub fn fd_pwrite( wasi_try!(write_bytes( &mut buffer[(offset as usize)..], memory, - &iovs_arr_cell + iovs_arr )) } } } }; - nwritten_cell.set(bytes_written); + wasi_try_mem!(nwritten_ref.write(bytes_written)); __WASI_ESUCCESS } @@ -886,22 +875,22 @@ pub fn fd_pwrite( pub fn fd_read( env: &WasiEnv, fd: __wasi_fd_t, - iovs: WasmPtr<__wasi_iovec_t, Array>, + iovs: WasmPtr<__wasi_iovec_t>, iovs_len: u32, nread: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::fd_read: fd={}", fd); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); - let nread_cell = wasi_try!(nread.deref(memory)); + let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let nread_ref = nread.deref(memory); let bytes_read = match fd { __WASI_STDIN_FILENO => { if let Some(ref mut stdin) = wasi_try!(state.fs.stdin_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(read_bytes(stdin, memory, &iovs_arr_cell)) + wasi_try!(read_bytes(stdin, memory, iovs_arr)) } else { return __WASI_EBADF; } @@ -923,7 +912,7 @@ pub fn fd_read( Kind::File { handle, .. } => { if let Some(handle) = handle { handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(read_bytes(handle, memory, &iovs_arr_cell)) + wasi_try!(read_bytes(handle, memory, iovs_arr)) } else { return __WASI_EINVAL; } @@ -934,7 +923,7 @@ pub fn fd_read( } Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), Kind::Buffer { buffer } => { - wasi_try!(read_bytes(&buffer[offset..], memory, &iovs_arr_cell)) + wasi_try!(read_bytes(&buffer[offset..], memory, iovs_arr)) } }; @@ -946,7 +935,7 @@ pub fn fd_read( } }; - nread_cell.set(bytes_read); + wasi_try_mem!(nread_ref.write(bytes_read)); __WASI_ESUCCESS } @@ -969,7 +958,7 @@ pub fn fd_read( pub fn fd_readdir( env: &WasiEnv, fd: __wasi_fd_t, - buf: WasmPtr, + buf: WasmPtr, buf_len: u32, cookie: __wasi_dircookie_t, bufused: WasmPtr, @@ -979,8 +968,8 @@ pub fn fd_readdir( // TODO: figure out how this is supposed to work; // is it supposed to pack the buffer full every time until it can't? or do one at a time? - let buf_arr_cell = wasi_try!(buf.deref(memory, 0, buf_len)); - let bufused_cell = wasi_try!(bufused.deref(memory)); + let buf_arr = wasi_try_mem!(buf.slice(memory, buf_len)); + let bufused_ref = bufused.deref(memory); let working_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); let mut cur_cookie = cookie; let mut buf_idx = 0; @@ -1063,7 +1052,7 @@ pub fn fd_readdir( std::mem::size_of::<__wasi_dirent_t>(), ); for i in 0..upper_limit { - buf_arr_cell[i + buf_idx].set(dirent_bytes[i]); + wasi_try_mem!(buf_arr.index((i + buf_idx) as u64).write(dirent_bytes[i])); } buf_idx += upper_limit; if upper_limit != std::mem::size_of::<__wasi_dirent_t>() { @@ -1071,7 +1060,7 @@ pub fn fd_readdir( } let upper_limit = std::cmp::min(buf_len as usize - buf_idx, namlen); for (i, b) in entry_path_str.bytes().take(upper_limit).enumerate() { - buf_arr_cell[i + buf_idx].set(b); + wasi_try_mem!(buf_arr.index((i + buf_idx) as u64).write(b)); } buf_idx += upper_limit; if upper_limit != namlen { @@ -1079,7 +1068,7 @@ pub fn fd_readdir( } } - bufused_cell.set(buf_idx as u32); + wasi_try_mem!(bufused_ref.write(buf_idx as u32)); __WASI_ESUCCESS } @@ -1126,7 +1115,7 @@ pub fn fd_seek( ) -> __wasi_errno_t { debug!("wasi::fd_seek: fd={}, offset={}", fd, offset); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let new_offset_cell = wasi_try!(newoffset.deref(memory)); + let new_offset_ref = newoffset.deref(memory); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -1172,7 +1161,7 @@ pub fn fd_seek( } // reborrow let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - new_offset_cell.set(fd_entry.offset); + wasi_try_mem!(new_offset_ref.write(fd_entry.offset)); __WASI_ESUCCESS } @@ -1227,7 +1216,7 @@ pub fn fd_tell( ) -> __wasi_errno_t { debug!("wasi::fd_tell"); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let offset_cell = wasi_try!(offset.deref(memory)); + let offset_ref = offset.deref(memory); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -1235,7 +1224,7 @@ pub fn fd_tell( return __WASI_EACCES; } - offset_cell.set(fd_entry.offset); + wasi_try_mem!(offset_ref.write(fd_entry.offset)); __WASI_ESUCCESS } @@ -1257,7 +1246,7 @@ pub fn fd_tell( pub fn fd_write( env: &WasiEnv, fd: __wasi_fd_t, - iovs: WasmPtr<__wasi_ciovec_t, Array>, + iovs: WasmPtr<__wasi_ciovec_t>, iovs_len: u32, nwritten: WasmPtr, ) -> __wasi_errno_t { @@ -1270,8 +1259,8 @@ pub fn fd_write( trace!("wasi::fd_write: fd={}", fd); } let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); - let nwritten_cell = wasi_try!(nwritten.deref(memory)); + let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let nwritten_ref = nwritten.deref(memory); let bytes_written = match fd { __WASI_STDIN_FILENO => return __WASI_EINVAL, @@ -1279,7 +1268,7 @@ pub fn fd_write( if let Some(ref mut stdout) = wasi_try!(state.fs.stdout_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(write_bytes(stdout, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(stdout, memory, iovs_arr)) } else { return __WASI_EBADF; } @@ -1288,7 +1277,7 @@ pub fn fd_write( if let Some(ref mut stderr) = wasi_try!(state.fs.stderr_mut().map_err(fs_error_into_wasi_err)) { - wasi_try!(write_bytes(stderr, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(stderr, memory, iovs_arr)) } else { return __WASI_EBADF; } @@ -1308,7 +1297,7 @@ pub fn fd_write( Kind::File { handle, .. } => { if let Some(handle) = handle { handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(write_bytes(handle, memory, &iovs_arr_cell)) + wasi_try!(write_bytes(handle, memory, iovs_arr)) } else { return __WASI_EINVAL; } @@ -1319,7 +1308,7 @@ pub fn fd_write( } Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_write"), Kind::Buffer { buffer } => { - wasi_try!(write_bytes(&mut buffer[offset..], memory, &iovs_arr_cell)) + wasi_try!(write_bytes(&mut buffer[offset..], memory, iovs_arr)) } }; @@ -1332,7 +1321,7 @@ pub fn fd_write( } }; - nwritten_cell.set(bytes_written); + wasi_try_mem!(nwritten_ref.write(bytes_written)); __WASI_ESUCCESS } @@ -1353,7 +1342,7 @@ pub fn fd_write( pub fn path_create_directory( env: &WasiEnv, fd: __wasi_fd_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_create_directory"); @@ -1457,7 +1446,7 @@ pub fn path_filestat_get( env: &WasiEnv, fd: __wasi_fd_t, flags: __wasi_lookupflags_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, buf: WasmPtr<__wasi_filestat_t>, ) -> __wasi_errno_t { @@ -1487,8 +1476,7 @@ pub fn path_filestat_get( .ok_or(__WASI_EIO)) }; - let buf_cell = wasi_try!(buf.deref(memory)); - buf_cell.set(stat); + wasi_try_mem!(buf.deref(memory).write(stat)); __WASI_ESUCCESS } @@ -1514,7 +1502,7 @@ pub fn path_filestat_set_times( env: &WasiEnv, fd: __wasi_fd_t, flags: __wasi_lookupflags_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, st_atim: __wasi_timestamp_t, st_mtim: __wasi_timestamp_t, @@ -1590,10 +1578,10 @@ pub fn path_link( env: &WasiEnv, old_fd: __wasi_fd_t, old_flags: __wasi_lookupflags_t, - old_path: WasmPtr, + old_path: WasmPtr, old_path_len: u32, new_fd: __wasi_fd_t, - new_path: WasmPtr, + new_path: WasmPtr, new_path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_link"); @@ -1673,7 +1661,7 @@ pub fn path_open( env: &WasiEnv, dirfd: __wasi_fd_t, dirflags: __wasi_lookupflags_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, o_flags: __wasi_oflags_t, fs_rights_base: __wasi_rights_t, @@ -1691,7 +1679,7 @@ pub fn path_open( return __WASI_ENAMETOOLONG; } - let fd_cell = wasi_try!(fd.deref(memory)); + let fd_ref = fd.deref(memory); // o_flags: // - __WASI_O_CREAT (create if it does not exist) @@ -1734,7 +1722,7 @@ pub fn path_open( if let Some(special_fd) = fd { // short circuit if we're dealing with a special file assert!(handle.is_some()); - fd_cell.set(*special_fd); + wasi_try_mem!(fd_ref.write(*special_fd)); return __WASI_ESUCCESS; } if o_flags & __WASI_O_DIRECTORY != 0 { @@ -1875,7 +1863,7 @@ pub fn path_open( inode )); - fd_cell.set(out_fd); + wasi_try_mem!(fd_ref.write(out_fd)); debug!("wasi::path_open returning fd {}", out_fd); __WASI_ESUCCESS @@ -1900,9 +1888,9 @@ pub fn path_open( pub fn path_readlink( env: &WasiEnv, dir_fd: __wasi_fd_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, - buf: WasmPtr, + buf: WasmPtr, buf_len: u32, buf_used: WasmPtr, ) -> __wasi_errno_t { @@ -1919,21 +1907,16 @@ pub fn path_readlink( if let Kind::Symlink { relative_path, .. } = &state.fs.inodes[inode].kind { let rel_path_str = relative_path.to_string_lossy(); debug!("Result => {:?}", rel_path_str); - let bytes = rel_path_str.bytes(); + let bytes = rel_path_str.as_bytes(); if bytes.len() >= buf_len as usize { return __WASI_EOVERFLOW; } - let out = wasi_try!(buf.deref(memory, 0, buf_len)); - let mut bytes_written = 0; - for b in bytes { - out[bytes_written].set(b); - bytes_written += 1; - } + let out = wasi_try_mem!(buf.slice(memory, bytes.len() as u32)); + wasi_try_mem!(out.write_slice(bytes)); // should we null terminate this? - let bytes_out = wasi_try!(buf_used.deref(memory)); - bytes_out.set(bytes_written as u32); + wasi_try_mem!(buf_used.deref(memory).write(bytes.len() as u32)); } else { return __WASI_EINVAL; } @@ -1945,7 +1928,7 @@ pub fn path_readlink( pub fn path_remove_directory( env: &WasiEnv, fd: __wasi_fd_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { // TODO check if fd is a dir, ensure it's within sandbox, etc. @@ -2019,10 +2002,10 @@ pub fn path_remove_directory( pub fn path_rename( env: &WasiEnv, old_fd: __wasi_fd_t, - old_path: WasmPtr, + old_path: WasmPtr, old_path_len: u32, new_fd: __wasi_fd_t, - new_path: WasmPtr, + new_path: WasmPtr, new_path_len: u32, ) -> __wasi_errno_t { debug!( @@ -2146,10 +2129,10 @@ pub fn path_rename( /// The number of bytes to read from `new_path` pub fn path_symlink( env: &WasiEnv, - old_path: WasmPtr, + old_path: WasmPtr, old_path_len: u32, fd: __wasi_fd_t, - new_path: WasmPtr, + new_path: WasmPtr, new_path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_symlink"); @@ -2226,7 +2209,7 @@ pub fn path_symlink( pub fn path_unlink_file( env: &WasiEnv, fd: __wasi_fd_t, - path: WasmPtr, + path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_unlink_file"); @@ -2321,8 +2304,8 @@ pub fn path_unlink_file( #[cfg(not(feature = "js"))] pub fn poll_oneoff( env: &WasiEnv, - in_: WasmPtr<__wasi_subscription_t, Array>, - out_: WasmPtr<__wasi_event_t, Array>, + in_: WasmPtr<__wasi_subscription_t>, + out_: WasmPtr<__wasi_event_t>, nsubscriptions: u32, nevents: WasmPtr, ) -> __wasi_errno_t { @@ -2330,10 +2313,10 @@ pub fn poll_oneoff( debug!(" => nsubscriptions = {}", nsubscriptions); let (memory, mut state) = env.get_memory_and_wasi_state(0); - let subscription_array = wasi_try!(in_.deref(memory, 0, nsubscriptions)); - let event_array = wasi_try!(out_.deref(memory, 0, nsubscriptions)); + let subscription_array = wasi_try_mem!(in_.slice(memory, nsubscriptions)); + let event_array = wasi_try_mem!(out_.slice(memory, nsubscriptions)); let mut events_seen = 0; - let out_ptr = wasi_try!(nevents.deref(memory)); + let out_ptr = nevents.deref(memory); let mut fds = vec![]; let mut clock_subs = vec![]; @@ -2341,7 +2324,7 @@ pub fn poll_oneoff( let mut total_ns_slept = 0; for sub in subscription_array.iter() { - let s: WasiSubscription = wasi_try!(sub.get().try_into()); + let s: WasiSubscription = wasi_try!(wasi_try_mem!(sub.read()).try_into()); let mut peb = PollEventBuilder::new(); let mut ns_to_sleep = 0; @@ -2469,9 +2452,9 @@ pub fn poll_oneoff( } } let event = __wasi_event_t { - userdata: subscription_array[i].get().userdata, + userdata: wasi_try_mem!(subscription_array.index(i as u64).read()).userdata, error, - type_: subscription_array[i].get().type_, + type_: wasi_try_mem!(subscription_array.index(i as u64).read()).type_, u: unsafe { __wasi_event_u { fd_readwrite: __wasi_event_fd_readwrite_t { @@ -2481,7 +2464,7 @@ pub fn poll_oneoff( } }, }; - event_array[events_seen].set(event); + wasi_try_mem!(event_array.index(events_seen as u64).write(event)); events_seen += 1; } for clock_info in clock_subs { @@ -2499,18 +2482,18 @@ pub fn poll_oneoff( } }, }; - event_array[events_seen].set(event); + wasi_try_mem!(event_array.index(events_seen as u64).write(event)); events_seen += 1; } - out_ptr.set(events_seen as u32); + wasi_try_mem!(out_ptr.write(events_seen as u32)); __WASI_ESUCCESS } #[cfg(feature = "js")] pub fn poll_oneoff( env: &WasiEnv, - in_: WasmPtr<__wasi_subscription_t, Array>, - out_: WasmPtr<__wasi_event_t, Array>, + in_: WasmPtr<__wasi_subscription_t>, + out_: WasmPtr<__wasi_event_t>, nsubscriptions: u32, nevents: WasmPtr, ) -> __wasi_errno_t { @@ -2541,12 +2524,8 @@ pub fn random_get(env: &WasiEnv, buf: u32, buf_len: u32) -> __wasi_errno_t { let res = getrandom::getrandom(&mut u8_buffer); match res { Ok(()) => { - unsafe { - memory - .uint8view() - .subarray(buf as u32, (buf as u32 + buf_len as u32)) - .copy_from(&u8_buffer); - } + let buf = wasi_try_mem!(WasmPtr::::new(buf).slice(memory, buf_len)); + wasi_try_mem!(buf.write_slice(&u8_buffer)); __WASI_ESUCCESS } Err(_) => __WASI_EIO, @@ -2564,7 +2543,7 @@ pub fn sched_yield(env: &WasiEnv) -> __wasi_errno_t { pub fn sock_recv( env: &WasiEnv, sock: __wasi_fd_t, - ri_data: WasmPtr<__wasi_iovec_t, Array>, + ri_data: WasmPtr<__wasi_iovec_t>, ri_data_len: u32, ri_flags: __wasi_riflags_t, ro_datalen: WasmPtr, @@ -2576,7 +2555,7 @@ pub fn sock_recv( pub fn sock_send( env: &WasiEnv, sock: __wasi_fd_t, - si_data: WasmPtr<__wasi_ciovec_t, Array>, + si_data: WasmPtr<__wasi_ciovec_t>, si_data_len: u32, si_flags: __wasi_siflags_t, so_datalen: WasmPtr, diff --git a/lib/wasi/src/syscalls/unix/mod.rs b/lib/wasi/src/syscalls/unix/mod.rs index a38d0810f..2b494e589 100644 --- a/lib/wasi/src/syscalls/unix/mod.rs +++ b/lib/wasi/src/syscalls/unix/mod.rs @@ -4,11 +4,11 @@ use libc::{ CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID, }; use std::mem; -use wasmer::WasmCell; +use wasmer::WasmRef; pub fn platform_clock_res_get( clock_id: __wasi_clockid_t, - resolution: WasmCell<__wasi_timestamp_t>, + resolution: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let unix_clock_id = match clock_id { __WASI_CLOCK_MONOTONIC => CLOCK_MONOTONIC, @@ -27,7 +27,7 @@ pub fn platform_clock_res_get( }; let t_out = (timespec_out.tv_sec * 1_000_000_000).wrapping_add(timespec_out.tv_nsec); - resolution.set(t_out as __wasi_timestamp_t); + wasi_try_mem!(resolution.write(t_out as __wasi_timestamp_t)); // TODO: map output of clock_getres to __wasi_errno_t __WASI_ESUCCESS @@ -36,7 +36,7 @@ pub fn platform_clock_res_get( pub fn platform_clock_time_get( clock_id: __wasi_clockid_t, precision: __wasi_timestamp_t, - time: WasmCell<__wasi_timestamp_t>, + time: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let unix_clock_id = match clock_id { __WASI_CLOCK_MONOTONIC => CLOCK_MONOTONIC, @@ -58,7 +58,7 @@ pub fn platform_clock_time_get( }; let t_out = (timespec_out.tv_sec * 1_000_000_000).wrapping_add(timespec_out.tv_nsec); - time.set(t_out as __wasi_timestamp_t); + wasi_try_mem!(time.write(t_out as __wasi_timestamp_t)); // TODO: map output of clock_gettime to __wasi_errno_t __WASI_ESUCCESS diff --git a/lib/wasi/src/syscalls/wasm32.rs b/lib/wasi/src/syscalls/wasm32.rs index 4c512138a..73cadb1b0 100644 --- a/lib/wasi/src/syscalls/wasm32.rs +++ b/lib/wasi/src/syscalls/wasm32.rs @@ -1,13 +1,13 @@ use crate::syscalls::types::*; use std::mem; -use wasmer::WasmCell; +use wasmer::WasmRef; pub fn platform_clock_res_get( clock_id: __wasi_clockid_t, - resolution: WasmCell<__wasi_timestamp_t>, + resolution: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let t_out = 1 * 1_000_000_000; - resolution.set(t_out as __wasi_timestamp_t); + wasi_try_mem!(resolution.write(t_out as __wasi_timestamp_t)); // TODO: map output of clock_getres to __wasi_errno_t __WASI_ESUCCESS @@ -16,10 +16,10 @@ pub fn platform_clock_res_get( pub fn platform_clock_time_get( clock_id: __wasi_clockid_t, precision: __wasi_timestamp_t, - time: WasmCell<__wasi_timestamp_t>, + time: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let t_out = 1 * 1_000_000_000; - time.set(t_out as __wasi_timestamp_t); + wasi_try_mem!(time.write(t_out as __wasi_timestamp_t)); __WASI_ESUCCESS } diff --git a/lib/wasi/src/syscalls/windows.rs b/lib/wasi/src/syscalls/windows.rs index bd04f0bdb..31fd8f42a 100644 --- a/lib/wasi/src/syscalls/windows.rs +++ b/lib/wasi/src/syscalls/windows.rs @@ -1,10 +1,10 @@ use crate::syscalls::types::*; use tracing::debug; -use wasmer::WasmCell; +use wasmer::WasmRef; pub fn platform_clock_res_get( clock_id: __wasi_clockid_t, - resolution: WasmCell<__wasi_timestamp_t>, + resolution: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let resolution_val = match clock_id { // resolution of monotonic clock at 10ms, from: @@ -20,14 +20,14 @@ pub fn platform_clock_res_get( } _ => return __WASI_EINVAL, }; - resolution.set(resolution_val); + wasi_try_mem!(resolution.write(resolution_val)); __WASI_ESUCCESS } pub fn platform_clock_time_get( clock_id: __wasi_clockid_t, precision: __wasi_timestamp_t, - time: WasmCell<__wasi_timestamp_t>, + time: WasmRef<__wasi_timestamp_t>, ) -> __wasi_errno_t { let nanos = match clock_id { __WASI_CLOCK_MONOTONIC => { @@ -51,6 +51,6 @@ pub fn platform_clock_time_get( } _ => return __WASI_EINVAL, }; - time.set(nanos); + wasi_try_mem!(time.write(nanos)); __WASI_ESUCCESS }