mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-07 21:28:21 +00:00
Redesign the API for accessing the contents of a Wasm memory
This commit is contained in:
committed by
Manos Pitsidianakis
parent
447c2e3a15
commit
714bac5650
@@ -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);
|
||||
}
|
||||
}
|
||||
154
lib/api/src/js/externals/memory.rs
vendored
154
lib/api/src/js/externals/memory.rs
vendored
@@ -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 {
|
||||
|
||||
382
lib/api/src/js/mem_access.rs
Normal file
382
lib/api/src/js/mem_access.rs
Normal 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> {}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user