Redesign the API for accessing the contents of a Wasm memory

This commit is contained in:
Amanieu d'Antras
2021-10-28 12:09:00 +01:00
committed by Manos Pitsidianakis
parent 447c2e3a15
commit 714bac5650
47 changed files with 2357 additions and 1948 deletions

View File

@@ -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<T>,
}
unsafe impl<T: ?Sized> Send for WasmCell<'_, T> where T: Send {}
unsafe impl<T: ?Sized> 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<T: PartialEq + Copy> PartialEq for WasmCell<'_, T> {
#[inline]
fn eq(&self, other: &WasmCell<T>) -> bool {
self.get() == other.get()
}
}
impl<T: Eq + Copy> Eq for WasmCell<'_, T> {}
impl<T: PartialOrd + Copy> PartialOrd for WasmCell<'_, T> {
#[inline]
fn partial_cmp(&self, other: &WasmCell<T>) -> Option<Ordering> {
self.get().partial_cmp(&other.get())
}
#[inline]
fn lt(&self, other: &WasmCell<T>) -> bool {
self.get() < other.get()
}
#[inline]
fn le(&self, other: &WasmCell<T>) -> bool {
self.get() <= other.get()
}
#[inline]
fn gt(&self, other: &WasmCell<T>) -> bool {
self.get() > other.get()
}
#[inline]
fn ge(&self, other: &WasmCell<T>) -> bool {
self.get() >= other.get()
}
}
impl<T: Ord + Copy> Ord for WasmCell<'_, T> {
#[inline]
fn cmp(&self, other: &WasmCell<T>) -> 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<T: Debug + Copy> Debug for WasmCell<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WasmCell({:?})", self.get())
}
}
impl<T: Sized> 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::<T>();
let ptr = &val as *const T as *const u8;
let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
self.memory.copy_from(slice);
}
}

View File

@@ -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<u8> = 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<T: ValueType>(&self) -> MemoryView<T> {
unimplemented!("The view function is not yet implemented in Wasmer Javascript");
}
/// A theoretical alais to `Self::view::<u8>` 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<u8>],
) -> 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 {

View File

@@ -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<MemoryAccessError> for RuntimeError {
fn from(err: MemoryAccessError) -> Self {
RuntimeError::new(err.to_string())
}
}
impl From<FromUtf8Error> 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<T, Memory32> {
WasmPtr::new(self.offset as u32)
}
/// Get a 64-bit `WasmPtr` for this `WasmRef`.
#[inline]
pub fn as_ptr64(self) -> WasmPtr<T, Memory64> {
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<T, MemoryAccessError> {
let mut out = MaybeUninit::uninit();
let buf =
unsafe { slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, mem::size_of::<T>()) };
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<u8>,
mem::size_of::<T>(),
)
};
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<Self, MemoryAccessError> {
let total_len = len
.checked_mul(mem::size_of::<T>() 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<T, Memory32> {
WasmPtr::new(self.offset as u32)
}
/// Get a 64-bit `WasmPtr` for this `WasmRef`.
#[inline]
pub fn as_ptr64(self) -> WasmPtr<T, Memory64> {
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::<T>() 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<u64>) -> 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::<T>() 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<T, MemoryAccessError> {
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<u8>,
buf.len() * mem::size_of::<T>(),
)
};
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<T>],
) -> 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<u8>,
buf.len() * mem::size_of::<T>(),
)
};
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::<T>())
};
self.memory.write(self.offset, bytes)
}
/// Reads this `WasmSlice` into a `Vec`.
#[inline]
pub fn read_to_vec(self) -> Result<Vec<T>, 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<u8>,
len * mem::size_of::<T>(),
)
};
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<Self::Item> {
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<usize>) {
(0..self.slice.len()).size_hint()
}
}
impl<'a, T: ValueType> DoubleEndedIterator for WasmSliceIter<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
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> {}

View File

@@ -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")]

View File

@@ -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<T, Array>`
/// 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<u64> + TryFrom<u64>;
/// 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<T, Memory64>.
pub type WasmPtr64<T> = WasmPtr<T, Memory64>;
/// 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<u32>) {
/// 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<V3>) {
/// 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<T: Copy, Ty = Item> {
offset: u32,
_phantom: PhantomData<(T, Ty)>,
pub struct WasmPtr<T, M: MemorySize = Memory32> {
offset: M::Offset,
_phantom: PhantomData<*mut T>,
}
/// Methods relevant to all types of `WasmPtr`.
impl<T: Copy, Ty> WasmPtr<T, Ty> {
impl<T, M: MemorySize> WasmPtr<T, M> {
/// 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<T: Copy, Ty> WasmPtr<T, Ty> {
/// 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<T: Copy + ValueType> WasmPtr<T, Item> {
/// Dereference the `WasmPtr` getting access to a `&Cell<T>` 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<WasmCell<T>> {
let end = (self.offset as usize).checked_add(mem::size_of::<T>())?;
if end > memory.size().bytes().0 || mem::size_of::<T>() == 0 {
return None;
pub fn cast<U>(self) -> WasmPtr<U, M> {
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<Self, MemoryAccessError> {
let base = self.offset.into();
let index = offset.into();
let offset = index
.checked_mul(mem::size_of::<T>() 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<Self, MemoryAccessError> {
let base = self.offset.into();
let index = offset.into();
let offset = index
.checked_mul(mem::size_of::<T>() 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<T: Copy + ValueType> WasmPtr<T, Array> {
/// Dereference the `WasmPtr` getting access to a `&[Cell<T>]` 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<T: ValueType, M: MemorySize> WasmPtr<T, M> {
/// 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<Vec<WasmCell<T>>> {
// 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::<T>() 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::<Vec<_>>(),
)
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<T, MemoryAccessError> {
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<std::borrow::Cow<'a, str>> {
self.get_utf8_string(memory, str_len)
.map(std::borrow::Cow::from)
len: M::Offset,
) -> Result<WasmSlice<'a, T>, 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<String> {
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<Vec<T>, 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<u8> = 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<T: Copy, Ty> FromToNativeWasmType for WasmPtr<T, Ty> {
type Native = i32;
impl<M: MemorySize> WasmPtr<u8, M> {
/// 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<String, MemoryAccessError> {
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<String, MemoryAccessError> {
let vec = self.read_until(memory, |&byte| byte == 0)?;
Ok(String::from_utf8(vec)?)
}
}
unsafe impl<T: ValueType, M: MemorySize> FromToNativeWasmType for WasmPtr<T, M> {
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<T: Copy, Ty> ValueType for WasmPtr<T, Ty> {}
unsafe impl<T: ValueType, M: MemorySize> ValueType for WasmPtr<T, M> {
fn zero_padding_bytes(&self, _bytes: &mut [mem::MaybeUninit<u8>]) {}
}
impl<T: Copy, Ty> Clone for WasmPtr<T, Ty> {
impl<T: ValueType, M: MemorySize> Clone for WasmPtr<T, M> {
fn clone(&self) -> Self {
Self {
offset: self.offset,
@@ -219,131 +289,23 @@ impl<T: Copy, Ty> Clone for WasmPtr<T, Ty> {
}
}
impl<T: Copy, Ty> Copy for WasmPtr<T, Ty> {}
impl<T: ValueType, M: MemorySize> Copy for WasmPtr<T, M> {}
impl<T: Copy, Ty> PartialEq for WasmPtr<T, Ty> {
impl<T: ValueType, M: MemorySize> PartialEq for WasmPtr<T, M> {
fn eq(&self, other: &Self) -> bool {
self.offset == other.offset
self.offset.into() == other.offset.into()
}
}
impl<T: Copy, Ty> Eq for WasmPtr<T, Ty> {}
impl<T: ValueType, M: MemorySize> Eq for WasmPtr<T, M> {}
impl<T: Copy, Ty> fmt::Debug for WasmPtr<T, Ty> {
impl<T: ValueType, M: MemorySize> fmt::Debug for WasmPtr<T, M> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"WasmPtr(offset: {}, pointer: {:#x}, align: {})",
self.offset,
self.offset,
mem::align_of::<T>()
"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<u64> = 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<u8> = WasmPtr::new(0);
let start_wasm_ptr_array: WasmPtr<u8, Array> = 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<u8> = WasmPtr::new(last_valid_address_for_u8);
assert!(end_wasm_ptr.deref(&memory).is_some());
let end_wasm_ptr_array: WasmPtr<u8, Array> = 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<u32> = 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<u32>; 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<u32, Array> = 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<u32, Array>; 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());
}
}
}