use crate::js::cell::WasmCell;
use crate::js::{externals::Memory, FromToNativeWasmType};
use std::{fmt, marker::PhantomData, mem};
use wasmer_types::ValueType;
pub struct Array;
pub struct Item;
#[repr(transparent)]
pub struct WasmPtr<T: Copy, Ty = Item> {
offset: u32,
_phantom: PhantomData<(T, Ty)>,
}
impl<T: Copy, Ty> WasmPtr<T, Ty> {
#[inline]
pub fn new(offset: u32) -> Self {
Self {
offset,
_phantom: PhantomData,
}
}
#[inline]
pub fn offset(self) -> u32 {
self.offset
}
}
impl<T: Copy + ValueType> WasmPtr<T, Item> {
#[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;
}
let subarray = memory.uint8view().subarray(self.offset, end as u32);
Some(WasmCell::new(subarray))
}
}
impl<T: Copy + ValueType> WasmPtr<T, Array> {
#[inline]
pub fn deref(self, memory: &Memory, index: u32, length: u32) -> Option<Vec<WasmCell<T>>> {
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 unsafe fn get_utf8_str<'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)
}
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;
}
let view = memory.uint8view();
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()
}
}
unsafe impl<T: Copy, Ty> FromToNativeWasmType for WasmPtr<T, Ty> {
type Native = i32;
fn to_native(self) -> Self::Native {
self.offset as i32
}
fn from_native(n: Self::Native) -> Self {
Self {
offset: n as u32,
_phantom: PhantomData,
}
}
}
unsafe impl<T: Copy, Ty> ValueType for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> Clone for WasmPtr<T, Ty> {
fn clone(&self) -> Self {
Self {
offset: self.offset,
_phantom: PhantomData,
}
}
}
impl<T: Copy, Ty> Copy for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> PartialEq for WasmPtr<T, Ty> {
fn eq(&self, other: &Self) -> bool {
self.offset == other.offset
}
}
impl<T: Copy, Ty> Eq for WasmPtr<T, Ty> {}
impl<T: Copy, Ty> fmt::Debug for WasmPtr<T, Ty> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"WasmPtr(offset: {}, pointer: {:#x}, align: {})",
self.offset,
self.offset,
mem::align_of::<T>()
)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::js::{Memory, MemoryType, Store};
use wasm_bindgen_test::*;
#[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]);
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);
}
#[wasm_bindgen_test]
fn wasm_ptr_memory_bounds_checks_hold() {
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<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());
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());
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());
}
}
}