use super::{ErrorExt, skip_pooling_allocator_tests};
use wasmtime::*;
#[test]
fn successful_instantiation() -> Result<()> {
let pool = crate::small_pool_config();
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(0);
config.memory_reservation(1 << 16);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &module, &[])?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn memory_limit() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memory_size(3 << 16);
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(1 << 16);
config.memory_reservation(3 << 16);
config.wasm_multi_memory(true);
let engine = Engine::new(&config)?;
match Module::new(&engine, r#"(module (memory 1) (memory 1))"#) {
Ok(_) => panic!("module instantiation should fail"),
Err(e) => {
e.assert_contains("defined memories count of 2 exceeds the per-instance limit of 1")
}
}
match Module::new(&engine, r#"(module (memory 4))"#) {
Ok(_) => panic!("module instantiation should fail"),
Err(e) => {
e.assert_contains(
"memory index 0 is unsupported in this pooling allocator \
configuration",
);
e.assert_contains(
"memory has a minimum byte size of 262144 which exceeds \
the limit of 0x30000 bytes",
);
}
}
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (func (export "f") (result i32) (memory.grow (i32.const 1))))"#,
)?;
{
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0);
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1);
assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 2);
assert_eq!(
f.call(&mut store, ()).expect("function should not trap"),
-1
);
assert_eq!(
f.call(&mut store, ()).expect("function should not trap"),
-1
);
}
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let memory = instance.get_memory(&mut store, "m").unwrap();
assert_eq!(memory.size(&store), 0);
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 0);
assert_eq!(memory.size(&store), 1);
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 1);
assert_eq!(memory.size(&store), 2);
assert_eq!(memory.grow(&mut store, 1).expect("memory should grow"), 2);
assert_eq!(memory.size(&store), 3);
assert!(memory.grow(&mut store, 1).is_err());
Ok(())
}
#[test]
fn memory_init() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memory_size(2 << 16).table_elements(0);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(memory (export "m") 2)
(data (i32.const 65530) "this data spans multiple pages")
(data (i32.const 10) "hello world")
)
"#,
)?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let memory = instance.get_memory(&mut store, "m").unwrap();
assert_eq!(
&memory.data(&store)[65530..65560],
b"this data spans multiple pages"
);
assert_eq!(&memory.data(&store)[10..21], b"hello world");
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn memory_guard_page_trap() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memory_size(2 << 16).table_elements(0);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(memory (export "m") 0)
(func (export "f") (param i32) local.get 0 i32.load drop)
)
"#,
)?;
for _ in 0..10 {
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let m = instance.get_memory(&mut store, "m").unwrap();
let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?;
let trap = f
.call(&mut store, 0)
.expect_err("function should trap")
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
let trap = f
.call(&mut store, 1)
.expect_err("function should trap")
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
m.grow(&mut store, 1).expect("memory should grow");
f.call(&mut store, 0).expect("function should not trap");
let trap = f
.call(&mut store, 65536)
.expect_err("function should trap")
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
let trap = f
.call(&mut store, 65537)
.expect_err("function should trap")
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
m.grow(&mut store, 1).expect("memory should grow");
f.call(&mut store, 65536).expect("function should not trap");
m.grow(&mut store, 1)
.expect_err("memory should be at the limit");
}
Ok(())
}
#[test]
fn memory_zeroed() -> Result<()> {
if skip_pooling_allocator_tests() {
return Ok(());
}
let mut pool = crate::small_pool_config();
pool.max_memory_size(1 << 16).table_elements(0);
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(0);
config.memory_reservation(1 << 16);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (memory (export "m") 1))"#)?;
for _ in 0..10 {
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let memory = instance.get_memory(&mut store, "m").unwrap();
assert_eq!(memory.size(&store,), 1);
assert_eq!(memory.data_size(&store), 65536);
let ptr = memory.data_mut(&mut store).as_mut_ptr();
unsafe {
for i in 0..8192 {
assert_eq!(*ptr.cast::<u64>().offset(i), 0);
}
std::ptr::write_bytes(ptr, 0xFE, memory.data_size(&store));
}
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn table_limit() -> Result<()> {
const TABLE_ELEMENTS: usize = 10;
let mut pool = crate::small_pool_config();
pool.table_elements(TABLE_ELEMENTS);
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(0);
config.memory_reservation(1 << 16);
let engine = Engine::new(&config)?;
match Module::new(&engine, r#"(module (table 1 funcref) (table 1 funcref))"#) {
Ok(_) => panic!("module compilation should fail"),
Err(e) => {
e.assert_contains("defined tables count of 2 exceeds the per-instance limit of 1")
}
}
match Module::new(&engine, r#"(module (table 31 funcref))"#) {
Ok(_) => panic!("module compilation should fail"),
Err(e) => e.assert_contains(
"table index 0 has a minimum element size of 31 which exceeds the limit of 10",
),
}
let module = Module::new(
&engine,
r#"(module (table (export "t") 0 funcref) (func (export "f") (result i32) (table.grow (ref.null func) (i32.const 1))))"#,
)?;
{
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
for i in 0..TABLE_ELEMENTS {
assert_eq!(
f.call(&mut store, ()).expect("function should not trap"),
i as i32
);
}
assert_eq!(
f.call(&mut store, ()).expect("function should not trap"),
-1
);
assert_eq!(
f.call(&mut store, ()).expect("function should not trap"),
-1
);
}
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let table = instance.get_table(&mut store, "t").unwrap();
for i in 0..TABLE_ELEMENTS {
assert_eq!(table.size(&store), i as u64);
assert_eq!(
table
.grow(&mut store, 1, Ref::Func(None))
.expect("table should grow"),
i as u64
);
}
assert_eq!(table.size(&store), TABLE_ELEMENTS as u64);
assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err());
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn table_init() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memory_size(0).table_elements(6);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(table (export "t") 6 funcref)
(elem (i32.const 1) 1 2 3 4)
(elem (i32.const 0) 0)
(func)
(func (param i32))
(func (param i32 i32))
(func (param i32 i32 i32))
(func (param i32 i32 i32 i32))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let table = instance.get_table(&mut store, "t").unwrap();
for i in 0..5 {
let v = table.get(&mut store, i).expect("table should have entry");
let f = v
.as_func()
.expect("expected funcref")
.expect("expected non-null value");
assert_eq!(f.ty(&store).params().len(), i as usize);
}
assert!(
table
.get(&mut store, 5)
.expect("table should have entry")
.as_func()
.expect("expected funcref")
.is_none(),
"funcref should be null"
);
Ok(())
}
#[test]
fn table_zeroed() -> Result<()> {
if skip_pooling_allocator_tests() {
return Ok(());
}
let pool = crate::small_pool_config();
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(0);
config.memory_reservation(1 << 16);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (table (export "t") 10 funcref))"#)?;
for _ in 0..10 {
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let table = instance.get_table(&mut store, "t").unwrap();
let f = Func::wrap(&mut store, || {});
assert_eq!(table.size(&store), 10);
for i in 0..10 {
match table.get(&mut store, i).unwrap() {
Ref::Func(r) => assert!(r.is_none()),
_ => panic!("expected a funcref"),
}
table.set(&mut store, i, Ref::Func(Some(f))).unwrap();
}
}
Ok(())
}
#[test]
fn total_core_instances_limit() -> Result<()> {
const INSTANCE_LIMIT: u32 = 10;
let mut pool = crate::small_pool_config();
pool.total_core_instances(INSTANCE_LIMIT);
let mut config = Config::new();
config.allocation_strategy(pool);
config.memory_guard_size(0);
config.memory_reservation(1 << 16);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module)"#)?;
{
let mut store = Store::new(&engine, ());
for _ in 0..INSTANCE_LIMIT {
Instance::new(&mut store, &module, &[])?;
}
match Instance::new(&mut store, &module, &[]) {
Ok(_) => panic!("instantiation should fail"),
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
}
}
let mut store = Store::new(&engine, ());
for _ in 0..INSTANCE_LIMIT {
Instance::new(&mut store, &module, &[])?;
}
Ok(())
}
#[test]
fn preserve_data_segments() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.total_memories(2);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let m = Module::new(
&engine,
r#"
(module
(memory (export "mem") 1 1)
(data (i32.const 0) "foo"))
"#,
)?;
let mut store = Store::new(&engine, ());
let i = Instance::new(&mut store, &m, &[])?;
drop(m);
if !cfg!(miri) {
let mut strings = Vec::new();
for _ in 0..1000 {
let mut string = String::new();
for _ in 0..1000 {
string.push('g');
}
strings.push(string);
}
drop(strings);
}
let mem = i.get_memory(&mut store, "mem").unwrap();
assert!(mem.data(&store).starts_with(b"foo"));
Ok(())
}
#[test]
fn multi_memory_with_imported_memories() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.total_memories(2).max_memories_per_module(2);
let mut config = Config::new();
config.allocation_strategy(pool);
config.wasm_multi_memory(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"(module (import "" "m1" (memory 0)) (memory (export "m2") 1))"#,
)?;
let mut store = Store::new(&engine, ());
let m1 = Memory::new(&mut store, MemoryType::new(0, None))?;
let instance = Instance::new(&mut store, &module, &[m1.into()])?;
let m2 = instance.get_memory(&mut store, "m2").unwrap();
m2.data_mut(&mut store)[0] = 0x42;
assert_eq!(m2.data(&store)[0], 0x42);
Ok(())
}
#[test]
fn drop_externref_global_during_module_init() -> Result<()> {
struct Limiter;
impl ResourceLimiter for Limiter {
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
Ok(false)
}
fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
Ok(false)
}
}
let pool = crate::small_pool_config();
let mut config = Config::new();
config.wasm_reference_types(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(global i32 (i32.const 1))
(global i32 (i32.const 2))
(global i32 (i32.const 3))
(global i32 (i32.const 4))
(global i32 (i32.const 5))
)
"#,
)?;
let mut store = Store::new(&engine, Limiter);
Instance::new(&mut store, &module, &[])?;
drop(store);
let module = Module::new(
&engine,
r#"
(module
(memory 1)
(global (mut externref) (ref.null extern))
)
"#,
)?;
let mut store = Store::new(&engine, Limiter);
store.limiter(|s| s);
assert!(Instance::new(&mut store, &module, &[]).is_err());
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn switch_image_and_non_image() -> Result<()> {
let pool = crate::small_pool_config();
let mut c = Config::new();
c.allocation_strategy(pool);
let engine = Engine::new(&c)?;
let module1 = Module::new(
&engine,
r#"
(module
(memory 1)
(func (export "load") (param i32) (result i32)
local.get 0
i32.load
)
)
"#,
)?;
let module2 = Module::new(
&engine,
r#"
(module
(memory (export "memory") 1)
(data (i32.const 0) "1234")
)
"#,
)?;
let assert_zero = || -> Result<()> {
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module1, &[])?;
let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
assert_eq!(func.call(&mut store, 0)?, 0);
Ok(())
};
Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;
assert_zero()?;
Instance::new(&mut Store::new(&engine, ()), &module2, &[])?;
assert_zero()?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module2, &[])?;
let memory = instance.get_memory(&mut store, "memory").unwrap();
let mem = memory.data_mut(&mut store);
assert!(mem.starts_with(b"1234"));
mem[..6].copy_from_slice(b"567890");
Ok(())
}
#[test]
#[cfg(target_pointer_width = "64")]
#[cfg_attr(miri, ignore)]
fn instance_too_large() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_core_instance_size(16);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
match Module::new(&engine, "(module)") {
Ok(_) => panic!("should have failed to compile"),
Err(e) => {
e.assert_contains("exceeds the configured maximum of 16 bytes");
e.assert_contains("breakdown of allocation requirement");
e.assert_contains("instance state management");
e.assert_contains("static vmctx data");
}
}
let mut lots_of_globals = format!("(module");
for _ in 0..100 {
lots_of_globals.push_str("(global i32 i32.const 0)\n");
}
lots_of_globals.push_str(")");
match Module::new(&engine, &lots_of_globals) {
Ok(_) => panic!("should have failed to compile"),
Err(e) => {
e.assert_contains("exceeds the configured maximum of 16 bytes");
e.assert_contains("breakdown of allocation requirement");
e.assert_contains("defined globals");
e.assert_contains("instance state management");
}
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn dynamic_memory_pooling_allocator() -> Result<()> {
for guard_size in [0, 1 << 16] {
for signals_based_traps in [false, true] {
let max_size = 128 << 20;
let mut pool = crate::small_pool_config();
pool.max_memory_size(max_size as usize);
let mut config = Config::new();
config.memory_reservation(max_size);
config.memory_guard_size(guard_size);
config.allocation_strategy(pool);
config.signals_based_traps(signals_based_traps);
let engine = Engine::new(&config)?;
let Ok(module) = Module::new(
&engine,
r#"
(module
(memory (export "memory") 1)
(func (export "grow") (param i32) (result i32)
local.get 0
memory.grow)
(func (export "size") (result i32)
memory.size)
(func (export "i32.load") (param i32) (result i32)
local.get 0
i32.load)
(func (export "i32.store") (param i32 i32)
local.get 0
local.get 1
i32.store)
(data (i32.const 100) "x")
)
"#,
) else {
assert!(cfg!(target_pointer_width = "32") && signals_based_traps);
continue;
};
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
let memory = instance.get_memory(&mut store, "memory").unwrap();
assert_eq!(memory.size(&store), 1);
assert_eq!(size.call(&mut store, ())?, 1);
assert_eq!(i32_load.call(&mut store, 0)?, 0);
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'x'));
i32_store.call(&mut store, (0, 0))?;
i32_store.call(&mut store, (100, i32::from(b'y')))?;
assert_eq!(i32_load.call(&mut store, 100)?, i32::from(b'y'));
let page = 64 * 1024;
assert_eq!(grow.call(&mut store, 1)?, 1);
assert_eq!(memory.size(&store), 2);
assert_eq!(size.call(&mut store, ())?, 2);
i32_store.call(&mut store, (page, 200))?;
assert_eq!(i32_load.call(&mut store, page)?, 200);
i32_store.call(&mut store, (2, 100))?;
assert_eq!(i32_load.call(&mut store, 2)?, 100);
let too_many = max_size / (64 * 1024);
assert_eq!(grow.call(&mut store, too_many as u32)?, -1);
assert!(memory.grow(&mut store, too_many).is_err());
assert_eq!(memory.data(&store)[page as usize], 200);
store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
let memory = instance.get_memory(&mut store, "memory").unwrap();
assert!(i32_load.call(&mut store, page).is_err());
assert_eq!(memory.data_size(&store), page as usize);
if cfg!(target_os = "linux") && guard_size == 0 && !signals_based_traps {
unsafe {
let ptr = memory.data_ptr(&store);
assert_eq!(*ptr.offset(page as isize), 0);
}
}
}
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn zero_memory_pages_disallows_oob() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memory_size(0);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(memory 0)
(func (export "load") (param i32) (result i32)
local.get 0
i32.load)
(func (export "store") (param i32 )
local.get 0
local.get 0
i32.store)
)
"#,
)?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?;
for i in 0..31 {
assert!(load32.call(&mut store, 1 << i).is_err());
assert!(store32.call(&mut store, 1 << i).is_err());
}
Ok(())
}
#[test]
#[cfg(feature = "component-model")]
fn total_component_instances_limit() -> Result<()> {
const TOTAL_COMPONENT_INSTANCES: u32 = 5;
let mut pool = crate::small_pool_config();
pool.total_component_instances(TOTAL_COMPONENT_INSTANCES);
let mut config = Config::new();
config.wasm_component_model(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let linker = wasmtime::component::Linker::new(&engine);
let component = wasmtime::component::Component::new(&engine, "(component)")?;
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_COMPONENT_INSTANCES {
linker.instantiate(&mut store, &component)?;
}
match linker.instantiate(&mut store, &component) {
Ok(_) => panic!("should have hit component instance limit"),
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
}
drop(store);
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_COMPONENT_INSTANCES {
linker.instantiate(&mut store, &component)?;
}
Ok(())
}
#[test]
#[cfg(feature = "component-model")]
#[cfg(target_pointer_width = "64")] fn component_instance_size_limit() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_component_instance_size(1);
let mut config = Config::new();
config.wasm_component_model(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
match wasmtime::component::Component::new(&engine, "(component)") {
Ok(_) => panic!("should have hit limit"),
Err(e) => e.assert_contains(
"instance allocation for this component requires 64 bytes of \
`VMComponentContext` space which exceeds the configured maximum of 1 bytes",
),
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn total_tables_limit() -> Result<()> {
const TOTAL_TABLES: u32 = 5;
let mut pool = crate::small_pool_config();
pool.total_tables(TOTAL_TABLES)
.total_core_instances(TOTAL_TABLES + 1);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let linker = Linker::new(&engine);
let module = Module::new(&engine, "(module (table 0 1 funcref))")?;
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_TABLES {
linker.instantiate(&mut store, &module)?;
}
match linker.instantiate(&mut store, &module) {
Ok(_) => panic!("should have hit table limit"),
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
}
drop(store);
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_TABLES {
linker.instantiate(&mut store, &module)?;
}
Ok(())
}
#[tokio::test]
#[cfg(not(miri))]
async fn total_stacks_limit() -> Result<()> {
use super::async_functions::PollOnce;
const TOTAL_STACKS: u32 = 2;
let mut pool = crate::small_pool_config();
pool.total_stacks(TOTAL_STACKS)
.total_core_instances(TOTAL_STACKS + 1);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let mut linker = Linker::new(&engine);
linker.func_new_async(
"async",
"yield",
FuncType::new(&engine, [], []),
|_caller, _params, _results| {
Box::new(async {
tokio::task::yield_now().await;
Ok(())
})
},
)?;
let module = Module::new(
&engine,
r#"
(module
(import "async" "yield" (func $yield))
(func (export "run")
call $yield
)
(func $empty)
(start $empty)
)
"#,
)?;
let mut store1 = Store::new(&engine, ());
let instance1 = linker.instantiate_async(&mut store1, &module).await?;
let run1 = instance1.get_func(&mut store1, "run").unwrap();
let future1 = PollOnce::new(Box::pin(run1.call_async(store1, &[], &mut [])))
.await
.unwrap_err();
let mut store2 = Store::new(&engine, ());
let instance2 = linker.instantiate_async(&mut store2, &module).await?;
let run2 = instance2.get_func(&mut store2, "run").unwrap();
let future2 = PollOnce::new(Box::pin(run2.call_async(store2, &[], &mut [])))
.await
.unwrap_err();
let mut store3 = Store::new(&engine, ());
match linker.instantiate_async(&mut store3, &module).await {
Ok(_) => panic!("should have hit stack limit"),
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
}
future1.await?;
future2.await?;
let mut store1 = Store::new(&engine, ());
let instance1 = linker.instantiate_async(&mut store1, &module).await?;
let run1 = instance1.get_func(&mut store1, "run").unwrap();
let future1 = run1.call_async(&mut store1, &[], &mut []);
let mut store2 = Store::new(&engine, ());
let instance2 = linker.instantiate_async(&mut store2, &module).await?;
let run2 = instance2.get_func(&mut store2, "run").unwrap();
let future2 = run2.call_async(&mut store2, &[], &mut []);
future1.await?;
future2.await?;
drop(store1);
store2.into_data();
Ok(())
}
#[test]
#[cfg(feature = "component-model")]
fn component_core_instances_limit() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_core_instances_per_component(1);
let mut config = Config::new();
config.wasm_component_model(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m)
(core instance $a (instantiate $m))
)
"#,
)?;
match wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m)
(core instance $a (instantiate $m))
(core instance $b (instantiate $m))
)
"#,
) {
Ok(_) => panic!("should have hit limit"),
Err(e) => e.assert_contains(
"The component transitively contains 2 core module instances, which exceeds the \
configured maximum of 1",
),
}
Ok(())
}
#[test]
#[cfg(feature = "component-model")]
fn component_memories_limit() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_memories_per_component(1).total_memories(2);
let mut config = Config::new();
config.wasm_component_model(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m (memory 1 1))
(core instance $a (instantiate $m))
)
"#,
)?;
match wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m (memory 1 1))
(core instance $a (instantiate $m))
(core instance $b (instantiate $m))
)
"#,
) {
Ok(_) => panic!("should have hit limit"),
Err(e) => e.assert_contains(
"The component transitively contains 2 Wasm linear memories, which exceeds the \
configured maximum of 1",
),
}
Ok(())
}
#[test]
#[cfg(feature = "component-model")]
fn component_tables_limit() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.max_tables_per_component(1).total_tables(2);
let mut config = Config::new();
config.wasm_component_model(true);
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m (table 1 1 funcref))
(core instance $a (instantiate $m))
)
"#,
)?;
match wasmtime::component::Component::new(
&engine,
r#"
(component
(core module $m (table 1 1 funcref))
(core instance $a (instantiate $m))
(core instance $b (instantiate $m))
)
"#,
) {
Ok(_) => panic!("should have hit limit"),
Err(e) => e.assert_contains(
"The component transitively contains 2 tables, which exceeds the \
configured maximum of 1",
),
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn total_memories_limit() -> Result<()> {
const TOTAL_MEMORIES: u32 = 5;
let mut pool = crate::small_pool_config();
pool.total_memories(TOTAL_MEMORIES)
.total_core_instances(TOTAL_MEMORIES + 1)
.memory_protection_keys(Enabled::No);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let linker = Linker::new(&engine);
let module = Module::new(&engine, "(module (memory 1 1))")?;
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_MEMORIES {
linker.instantiate(&mut store, &module)?;
}
match linker.instantiate(&mut store, &module) {
Ok(_) => panic!("should have hit memory limit"),
Err(e) => assert!(e.is::<PoolConcurrencyLimitError>()),
}
drop(store);
let mut store = Store::new(&engine, ());
for _ in 0..TOTAL_MEMORIES {
linker.instantiate(&mut store, &module)?;
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn decommit_batching() -> Result<()> {
for (capacity, batch_size) in [
(10, 5),
(10, 1),
(10, 0),
(10, 99),
] {
let mut pool = crate::small_pool_config();
pool.total_memories(capacity)
.total_core_instances(capacity)
.decommit_batch_size(batch_size)
.memory_protection_keys(Enabled::No);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let linker = Linker::new(&engine);
let module = Module::new(&engine, "(module (memory 1 1))")?;
for _ in 0..3 {
let mut store = Store::new(&engine, ());
for _ in 0..capacity {
linker.instantiate(&mut store, &module)?;
}
}
}
Ok(())
}
#[test]
fn tricky_empty_table_with_empty_virtual_memory_alloc() -> Result<()> {
let mut cfg = PoolingAllocationConfig::default();
cfg.table_elements(0);
cfg.total_memories(0);
cfg.total_tables(1);
cfg.total_stacks(0);
cfg.total_core_instances(1);
cfg.max_memory_size(0);
let mut c = Config::new();
c.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
c.table_lazy_init(false);
let engine = Engine::new(&c)?;
let module = Module::new(
&engine,
r#"
(module
(table 0 funcref)
(elem (i32.const 0) func)
)
"#,
)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &module, &[])?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn shared_memory_unsupported() -> Result<()> {
if crate::threads::engine().is_none() {
return Ok(());
}
let mut config = Config::new();
let mut cfg = PoolingAllocationConfig::default();
cfg.total_memories(1);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
let engine = Engine::new(&config)?;
let err = Module::new(
&engine,
r#"
(module
(memory 5 5 shared)
)
"#,
)
.unwrap_err();
err.assert_contains(
"memory is shared which is not supported \
in the pooling allocator",
);
err.assert_contains("memory index 0");
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn custom_page_sizes_reusing_same_slot() -> Result<()> {
let mut config = Config::new();
config.wasm_custom_page_sizes(true);
let mut cfg = crate::small_pool_config();
cfg.total_memories(1);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
let engine = Engine::new(&config)?;
{
let m1 = Module::new(
&engine,
r#"
(module
(memory 5 (pagesize 1))
(data (i32.const 0) "a")
)
"#,
)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &m1, &[])?;
}
{
let m2 = Module::new(
&engine,
r#"
(module
(memory 6 (pagesize 1))
(data (i32.const 0) "a")
)
"#,
)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &m2, &[])?;
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn instantiate_non_page_aligned_sizes() -> Result<()> {
let mut config = Config::new();
config.wasm_custom_page_sizes(true);
let mut cfg = crate::small_pool_config();
cfg.total_memories(1);
cfg.max_memory_size(761927);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(memory 761927 761927 (pagesize 0x1))
)
"#,
)?;
let mut store = Store::new(&engine, ());
Instance::new(&mut store, &module, &[])?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn pagemap_scan_enabled_or_disabled() -> Result<()> {
let mut config = Config::new();
let mut cfg = crate::small_pool_config();
cfg.total_memories(1);
cfg.pagemap_scan(Enabled::Yes);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
let result = Engine::new(&config);
if PoolingAllocationConfig::is_pagemap_scan_available() {
assert!(result.is_ok());
} else {
assert!(result.is_err());
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn pooling_reuse_resets() -> Result<()> {
let mut config = Config::new();
let mut cfg = crate::small_pool_config();
cfg.total_memories(1);
cfg.max_memory_size(0x2000000);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(cfg));
config.memory_guard_size(0);
config.memory_reservation(0x2000000);
let engine = Engine::new(&config)?;
let a = Module::new(&engine, r#"(module (memory (export "m") 10 100))"#)?;
let b = Module::new(
&engine,
r#"
(module $B
(memory 5 100)
(func (export "read_oob") (result i32)
(i32.load (i32.const 983040))
)
)
"#,
)?;
{
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &a, &[])?;
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow(&mut store, 10)?;
}
{
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &b, &[])?;
let read_oob = instance.get_typed_func::<(), i32>(&mut store, "read_oob")?;
let trap: Trap = read_oob.call(&mut store, ()).unwrap_err().downcast()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
}
Ok(())
}
#[test]
fn memory_reset_if_instantiation_fails() -> Result<()> {
struct Limiter;
impl ResourceLimiter for Limiter {
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
Ok(false)
}
fn table_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
Ok(false)
}
}
let pool = crate::small_pool_config();
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let module_with_image = Module::new(
&engine,
r#"
(module
(memory 1)
(data (i32.const 0) "\aa")
)
"#,
)?;
let module_without_image = Module::new(
&engine,
r#"
(module
(memory (export "m") 1)
)
"#,
)?;
let mut store = Store::new(&engine, Limiter);
store.limiter(|s| s);
assert!(Instance::new(&mut store, &module_with_image, &[]).is_err());
drop(store);
let mut store = Store::new(&engine, Limiter);
let instance = Instance::new(&mut store, &module_without_image, &[])?;
let mem = instance.get_memory(&mut store, "m").unwrap();
let data = mem.data(&store);
assert_eq!(data[0], 0);
Ok(())
}