tinywasm 0.11.0-beta.0

A tiny WebAssembly interpreter
Documentation
use tinywasm::engine::Config;
use tinywasm::types::{MemoryArch, MemoryType, RefType, RefValue, TableType};
use tinywasm::{Engine, Memory, ModuleInstance, ResourceLimiter, Store, Table, Trap};

type TestResult<T = ()> = Result<T, Box<dyn core::error::Error>>;

fn store_with_limiter(limiter: impl ResourceLimiter + 'static) -> Store {
    let engine = Engine::new(Config::new().with_resource_limiter(limiter));
    Store::new(engine)
}

#[test]
fn memory_read_write_roundtrip() -> TestResult {
    let mut store = Store::default();
    let memory = Memory::try_new(&mut store, MemoryType::new(MemoryArch::I32, 1, None, None))?;

    memory.copy_from_slice(&mut store, 0, &[1, 2, 3, 4, 5])?;
    assert_eq!(memory.read_vec(&store, 0, 5)?, &[1, 2, 3, 4, 5]);
    memory.fill(&mut store, 2, 2, 0)?;
    assert_eq!(memory.read_vec(&store, 0, 5)?, &[1, 2, 0, 0, 5]);
    Ok(())
}

#[test]
fn read_returns_short_count_at_end_of_memory() -> TestResult {
    let mut store = Store::default();
    let memory = Memory::try_new(&mut store, MemoryType::new(MemoryArch::I32, 1, Some(1), Some(4)))?;
    memory.copy_from_slice(&mut store, 0, &[1, 2, 3, 4])?;

    let mut dst = [9; 8];
    assert_eq!(memory.read(&store, 2, &mut dst)?, 2);
    assert_eq!(&dst[..2], &[3, 4]);
    assert_eq!(&dst[2..], &[9; 6]);

    Ok(())
}

#[test]
fn memory64_default_limit_is_not_memory32_limit() {
    let ty = MemoryType::new(MemoryArch::I64, 65_537, None, None);
    assert!(ty.page_count_max() > 65_536);
}

struct DenyAll;

impl ResourceLimiter for DenyAll {
    fn memory_growing(&self, _current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        Ok(false)
    }

    fn table_growing(&self, _current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        Ok(false)
    }
}

struct DenyGrowth;

impl ResourceLimiter for DenyGrowth {
    fn memory_growing(&self, current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        Ok(current == 0)
    }

    fn table_growing(&self, current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        Ok(current == 0)
    }
}

struct TrapGrowth;

impl ResourceLimiter for TrapGrowth {
    fn memory_growing(&self, current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        if current == 0 { Ok(true) } else { Err(Trap::Unreachable) }
    }

    fn table_growing(&self, current: usize, _desired: usize, _maximum: Option<usize>) -> Result<bool, Trap> {
        if current == 0 { Ok(true) } else { Err(Trap::Unreachable) }
    }
}

#[test]
fn resource_limiter_can_reject_growth() -> TestResult {
    let mut store = store_with_limiter(DenyGrowth);
    let memory = Memory::try_new(&mut store, MemoryType::new(MemoryArch::I32, 1, None, None))?;

    assert_eq!(memory.grow(&mut store, 1)?, None);
    assert_eq!(memory.page_count(&store)?, 1);
    Ok(())
}

#[test]
fn resource_limiter_rejects_guest_memory_grow() -> TestResult {
    let wasm = wat::parse_str(
        r#"
        (module
          (memory 1)
          (func (export "grow") (result i32)
            i32.const 1
            memory.grow))
        "#,
    )?;
    let module = tinywasm::parse_bytes(&wasm)?;
    let mut store = store_with_limiter(DenyGrowth);
    let instance = ModuleInstance::instantiate(&mut store, &module, None)?;

    let grow = instance.func::<(), i32>(&store, "grow")?;
    assert_eq!(grow.call(&mut store, ())?, -1);
    Ok(())
}

#[test]
fn resource_limiter_can_trap_guest_memory_grow() -> TestResult {
    let wasm = wat::parse_str(
        r#"
        (module
          (memory 1)
          (func (export "grow") (result i32)
            i32.const 1
            memory.grow))
        "#,
    )?;
    let module = tinywasm::parse_bytes(&wasm)?;
    let mut store = store_with_limiter(TrapGrowth);
    let instance = ModuleInstance::instantiate(&mut store, &module, None)?;

    let grow = instance.func::<(), i32>(&store, "grow")?;
    assert!(matches!(grow.call(&mut store, ()), Err(tinywasm::Error::Trap(Trap::Unreachable))));
    Ok(())
}

#[test]
fn resource_limiter_rejects_host_memory_initial_size() {
    let mut store = store_with_limiter(DenyAll);
    let result = Memory::try_new(&mut store, MemoryType::new(MemoryArch::I32, 1, None, None));

    assert!(matches!(result, Err(tinywasm::Error::Trap(Trap::OutOfMemory))));
}

#[test]
fn resource_limiter_rejects_module_memory_initial_size() -> TestResult {
    let wasm = wat::parse_str("(module (memory (export \"memory\") 1))")?;
    let module = tinywasm::parse_bytes(&wasm)?;
    let mut store = store_with_limiter(DenyAll);
    let result = ModuleInstance::instantiate(&mut store, &module, None);

    assert!(matches!(result, Err(tinywasm::Error::Trap(Trap::OutOfMemory))));
    Ok(())
}

#[test]
fn resource_limiter_rejects_table_initial_size() {
    let mut store = store_with_limiter(DenyAll);
    let result = Table::try_new(&mut store, TableType::new(RefType::FUNCREF, 1, None), RefValue::Null.into());

    assert!(matches!(result, Err(tinywasm::Error::Trap(Trap::OutOfMemory))));
}

#[test]
fn table_rejects_initial_size_above_maximum() {
    let mut store = Store::default();
    let result = Table::try_new(&mut store, TableType::new(RefType::FUNCREF, 2, Some(1)), RefValue::Null.into());

    assert!(matches!(result, Err(tinywasm::Error::Trap(Trap::OutOfMemory))));
}

#[test]
fn resource_limiter_can_reject_table_growth() -> TestResult {
    let mut store = store_with_limiter(DenyGrowth);
    let table = Table::try_new(&mut store, TableType::new(RefType::FUNCREF, 1, None), RefValue::Null.into())?;

    assert_eq!(table.grow(&mut store, 1, RefValue::Null.into())?, None);
    assert_eq!(table.size(&store)?, 1);
    Ok(())
}

#[test]
fn resource_limiter_can_trap_table_growth() -> TestResult {
    let mut store = store_with_limiter(TrapGrowth);
    let table = Table::try_new(&mut store, TableType::new(RefType::FUNCREF, 1, None), RefValue::Null.into())?;

    assert!(matches!(table.grow(&mut store, 1, RefValue::Null.into()), Err(tinywasm::Error::Trap(Trap::Unreachable))));
    Ok(())
}

#[test]
fn resource_limiter_allows_guest_memory_grow_by_default() -> TestResult {
    let wasm = wat::parse_str(
        r#"
        (module
          (memory 1)
          (func (export "grow") (result i32)
            i32.const 1
            memory.grow))
        "#,
    )?;
    let module = tinywasm::parse_bytes(&wasm)?;
    let mut store = Store::default();
    let instance = ModuleInstance::instantiate(&mut store, &module, None)?;

    let grow = instance.func::<(), i32>(&store, "grow")?;
    assert_eq!(grow.call(&mut store, ())?, 1);
    Ok(())
}