use wasmtime::*;
const WASM_PAGE_SIZE: usize = wasmtime_environ::Memory::DEFAULT_PAGE_SIZE as usize;
#[test]
#[cfg_attr(miri, ignore)]
fn test_limits() -> Result<()> {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"(module
(memory $m (export "m") 0)
(table (export "t") 0 funcref)
(func (export "grow") (param i32) (result i32)
(memory.grow $m (local.get 0)))
)"#,
)?;
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(10 * WASM_PAGE_SIZE)
.table_elements(5)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
for memory in IntoIterator::into_iter([
instance.get_memory(&mut store, "m").unwrap(),
Memory::new(&mut store, MemoryType::new(0, None))?,
]) {
memory.grow(&mut store, 3)?;
memory.grow(&mut store, 5)?;
memory.grow(&mut store, 2)?;
assert_eq!(
memory
.grow(&mut store, 1)
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow memory by `1`"
);
}
for table in IntoIterator::into_iter([
instance.get_table(&mut store, "t").unwrap(),
Table::new(
&mut store,
TableType::new(RefType::FUNCREF, 0, None),
Ref::Func(None),
)?,
]) {
table.grow(&mut store, 2, Ref::Func(None))?;
table.grow(&mut store, 1, Ref::Func(None))?;
table.grow(&mut store, 2, Ref::Func(None))?;
assert_eq!(
table
.grow(&mut store, 1, Ref::Func(None))
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow table by `1`"
);
}
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(10 * WASM_PAGE_SIZE)
.table_elements(5)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
let grow = instance.get_func(&mut store, "grow").unwrap();
let grow = grow.typed::<i32, i32>(&store).unwrap();
grow.call(&mut store, 3).unwrap();
grow.call(&mut store, 5).unwrap();
grow.call(&mut store, 2).unwrap();
assert_eq!(grow.call(&mut store, 1).unwrap(), -1);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn test_limits_async() -> Result<()> {
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config).unwrap();
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
)?;
struct LimitsAsync {
memory_size: usize,
table_elements: usize,
}
#[async_trait::async_trait]
impl ResourceLimiterAsync for LimitsAsync {
async fn memory_growing(
&mut self,
_current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
Ok(desired <= self.memory_size)
}
async fn table_growing(
&mut self,
_current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
Ok(desired <= self.table_elements)
}
}
let mut store = Store::new(
&engine,
LimitsAsync {
memory_size: 10 * WASM_PAGE_SIZE,
table_elements: 5,
},
);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = Instance::new_async(&mut store, &module, &[]).await?;
for memory in IntoIterator::into_iter([
instance.get_memory(&mut store, "m").unwrap(),
Memory::new_async(&mut store, MemoryType::new(0, None)).await?,
]) {
memory.grow_async(&mut store, 3).await?;
memory.grow_async(&mut store, 5).await?;
memory.grow_async(&mut store, 2).await?;
assert_eq!(
memory
.grow_async(&mut store, 1)
.await
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow memory by `1`"
);
}
for table in IntoIterator::into_iter([
instance.get_table(&mut store, "t").unwrap(),
Table::new_async(
&mut store,
TableType::new(RefType::FUNCREF, 0, None),
Ref::Func(None),
)
.await?,
]) {
table.grow_async(&mut store, 2, Ref::Func(None)).await?;
table.grow_async(&mut store, 1, Ref::Func(None)).await?;
table.grow_async(&mut store, 2, Ref::Func(None)).await?;
assert_eq!(
table
.grow_async(&mut store, 1, Ref::Func(None))
.await
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow table by `1`"
);
}
Ok(())
}
#[test]
fn test_limits_memory_only() -> Result<()> {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
)?;
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(10 * WASM_PAGE_SIZE)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
for memory in IntoIterator::into_iter([
instance.get_memory(&mut store, "m").unwrap(),
Memory::new(&mut store, MemoryType::new(0, None))?,
]) {
memory.grow(&mut store, 3)?;
memory.grow(&mut store, 5)?;
memory.grow(&mut store, 2)?;
assert_eq!(
memory
.grow(&mut store, 1)
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow memory by `1`"
);
}
for table in IntoIterator::into_iter([
instance.get_table(&mut store, "t").unwrap(),
Table::new(
&mut store,
TableType::new(RefType::FUNCREF, 0, None),
Ref::Func(None),
)?,
]) {
table.grow(&mut store, 2, Ref::Func(None))?;
table.grow(&mut store, 1, Ref::Func(None))?;
table.grow(&mut store, 2, Ref::Func(None))?;
table.grow(&mut store, 1, Ref::Func(None))?;
}
Ok(())
}
#[test]
fn test_initial_memory_limits_exceeded() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, r#"(module (memory (export "m") 11))"#)?;
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(10 * WASM_PAGE_SIZE)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"memory minimum size of 11 pages exceeds memory limits"
),
}
match Memory::new(&mut store, MemoryType::new(25, None)) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"memory minimum size of 25 pages exceeds memory limits"
),
}
Ok(())
}
#[test]
fn test_limits_table_only() -> Result<()> {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
)?;
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(5).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
for memory in IntoIterator::into_iter([
instance.get_memory(&mut store, "m").unwrap(),
Memory::new(&mut store, MemoryType::new(0, None))?,
]) {
memory.grow(&mut store, 3)?;
memory.grow(&mut store, 5)?;
memory.grow(&mut store, 2)?;
memory.grow(&mut store, 1)?;
}
for table in IntoIterator::into_iter([
instance.get_table(&mut store, "t").unwrap(),
Table::new(
&mut store,
TableType::new(RefType::FUNCREF, 0, None),
Ref::Func(None),
)?,
]) {
table.grow(&mut store, 2, Ref::Func(None))?;
table.grow(&mut store, 1, Ref::Func(None))?;
table.grow(&mut store, 2, Ref::Func(None))?;
assert_eq!(
table
.grow(&mut store, 1, Ref::Func(None))
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow table by `1`"
);
}
Ok(())
}
#[test]
fn test_initial_table_limits_exceeded() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, r#"(module (table (export "t") 23 funcref))"#)?;
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(4).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"table minimum size of 23 elements exceeds table limits"
),
}
match Table::new(
&mut store,
TableType::new(RefType::FUNCREF, 99, None),
Ref::Func(None),
) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"table minimum size of 99 elements exceeds table limits"
),
}
Ok(())
}
#[test]
fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> {
let mut pool = crate::small_pool_config();
pool.total_memories(2)
.max_memories_per_module(2)
.max_memory_size(5 << 16)
.memory_protection_keys(Enabled::No);
let mut config = Config::new();
config.wasm_multi_memory(true);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"(module (memory (export "m1") 2) (memory (export "m2") 5))"#,
)?;
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(3 * WASM_PAGE_SIZE)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"memory minimum size of 5 pages exceeds memory limits"
),
}
let module = Module::new(&engine, r#"(module (memory (export "m") 2))"#)?;
Instance::new(&mut store, &module, &[])?;
Ok(())
}
struct MemoryContext {
host_memory_used: usize,
wasm_memory_used: usize,
memory_limit: usize,
limit_exceeded: bool,
}
impl ResourceLimiter for MemoryContext {
fn memory_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
assert!(desired < maximum.unwrap_or(usize::MAX));
assert_eq!(current, self.wasm_memory_used);
if desired + self.host_memory_used > self.memory_limit {
self.limit_exceeded = true;
return Ok(false);
}
self.wasm_memory_used = desired;
Ok(true)
}
fn table_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
Ok(true)
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_custom_memory_limiter() -> Result<()> {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
linker.func_wrap(
"",
"alloc",
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
let ctx = caller.data_mut();
let size = size as usize;
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
ctx.host_memory_used += size;
return 1;
}
ctx.limit_exceeded = true;
0
},
)?;
let module = Module::new(
&engine,
r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#,
)?;
let context = MemoryContext {
host_memory_used: 0,
wasm_memory_used: 0,
memory_limit: 1 << 20,
limit_exceeded: false,
};
let mut store = Store::new(&engine, context);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module)?;
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow(&mut store, 3)?;
memory.grow(&mut store, 5)?;
memory.grow(&mut store, 2)?;
assert!(!store.data().limit_exceeded);
let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
assert_eq!(f.call(&mut store, 1 * 0x10000)?, 1);
assert_eq!(f.call(&mut store, 3 * 0x10000)?, 1);
assert_eq!(f.call(&mut store, 2 * 0x10000)?, 1);
assert!(!store.data().limit_exceeded);
assert_eq!(
memory
.grow(&mut store, 1)
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow memory by `1`"
);
assert!(store.data().limit_exceeded);
assert_eq!(f.call(&mut store, 1)?, 0);
assert!(store.data().limit_exceeded);
drop(store);
Ok(())
}
#[async_trait::async_trait]
impl ResourceLimiterAsync for MemoryContext {
async fn memory_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
assert!(desired < maximum.unwrap_or(usize::MAX));
assert_eq!(current, self.wasm_memory_used);
if desired + self.host_memory_used > self.memory_limit {
self.limit_exceeded = true;
return Ok(false);
}
self.wasm_memory_used = desired;
Ok(true)
}
async fn table_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
Ok(true)
}
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn test_custom_memory_limiter_async() -> Result<()> {
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config).unwrap();
let mut linker = Linker::new(&engine);
linker.func_wrap(
"",
"alloc",
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
let ctx = caller.data_mut();
let size = size as usize;
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
ctx.host_memory_used += size;
return 1;
}
ctx.limit_exceeded = true;
0
},
)?;
let module = Module::new(
&engine,
r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#,
)?;
let context = MemoryContext {
host_memory_used: 0,
wasm_memory_used: 0,
memory_limit: 1 << 20,
limit_exceeded: false,
};
let mut store = Store::new(&engine, context);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = linker.instantiate_async(&mut store, &module).await?;
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow_async(&mut store, 3).await?;
memory.grow_async(&mut store, 5).await?;
memory.grow_async(&mut store, 2).await?;
assert!(!store.data().limit_exceeded);
let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
assert_eq!(f.call_async(&mut store, 1 * 0x10000).await?, 1);
assert_eq!(f.call_async(&mut store, 3 * 0x10000).await?, 1);
assert_eq!(f.call_async(&mut store, 2 * 0x10000).await?, 1);
assert!(!store.data().limit_exceeded);
assert_eq!(
memory
.grow_async(&mut store, 1)
.await
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow memory by `1`"
);
assert!(store.data().limit_exceeded);
assert_eq!(f.call_async(&mut store, 1).await?, 0);
assert!(store.data().limit_exceeded);
drop(store);
Ok(())
}
struct TableContext {
elements_used: usize,
element_limit: usize,
limit_exceeded: bool,
}
impl ResourceLimiter for TableContext {
fn memory_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
Ok(true)
}
fn table_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
assert!(desired < maximum.unwrap_or(usize::MAX));
assert_eq!(current, self.elements_used);
Ok(if desired > self.element_limit {
self.limit_exceeded = true;
false
} else {
self.elements_used = desired;
true
})
}
}
#[test]
fn test_custom_table_limiter() -> Result<()> {
let engine = Engine::default();
let linker = Linker::new(&engine);
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#)?;
let context = TableContext {
elements_used: 0,
element_limit: 10,
limit_exceeded: false,
};
let mut store = Store::new(&engine, context);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module)?;
let table = instance.get_table(&mut store, "t").unwrap();
table.grow(&mut store, 3, Ref::Func(None))?;
table.grow(&mut store, 5, Ref::Func(None))?;
table.grow(&mut store, 2, Ref::Func(None))?;
assert!(!store.data().limit_exceeded);
assert!(!store.data().limit_exceeded);
assert_eq!(
table
.grow(&mut store, 1, Ref::Func(None))
.map_err(|e| e.to_string())
.unwrap_err(),
"failed to grow table by `1`"
);
assert!(store.data().limit_exceeded);
Ok(())
}
#[derive(Default)]
struct FailureDetector {
memory_current: usize,
memory_desired: usize,
memory_error: Option<String>,
table_current: usize,
table_desired: usize,
table_error: Option<String>,
}
impl ResourceLimiter for FailureDetector {
fn memory_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
self.memory_current = current;
self.memory_desired = desired;
Ok(true)
}
fn memory_grow_failed(&mut self, err: anyhow::Error) -> Result<()> {
self.memory_error = Some(err.to_string());
Ok(())
}
fn table_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
self.table_current = current;
self.table_desired = desired;
Ok(true)
}
fn table_grow_failed(&mut self, err: anyhow::Error) -> Result<()> {
self.table_error = Some(err.to_string());
Ok(())
}
}
#[test]
fn custom_limiter_detect_grow_failure() -> Result<()> {
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
return Ok(());
}
let mut pool = crate::small_pool_config();
pool.max_memory_size(10 << 16).table_elements(10);
let mut config = Config::new();
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
let engine = Engine::new(&config).unwrap();
let linker = Linker::new(&engine);
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
)?;
let context = FailureDetector::default();
let mut store = Store::new(&engine, context);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module)?;
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow(&mut store, 10)?;
assert!(store.data().memory_error.is_none());
assert_eq!(store.data().memory_current, 0);
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
assert_eq!(
memory.grow(&mut store, 1).unwrap_err().to_string(),
"failed to grow memory by `1`"
);
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
assert_eq!(
store.data().memory_error.as_ref().unwrap(),
"Memory maximum size exceeded"
);
let table = instance.get_table(&mut store, "t").unwrap();
table.grow(&mut store, 10, Ref::Func(None))?;
assert!(store.data().table_error.is_none());
assert_eq!(store.data().table_current, 0);
assert_eq!(store.data().table_desired, 10);
assert_eq!(
table
.grow(&mut store, 1, Ref::Func(None))
.unwrap_err()
.to_string(),
"failed to grow table by `1`"
);
assert_eq!(store.data().table_current, 10);
assert_eq!(store.data().table_desired, 11);
assert_eq!(
store.data().table_error.as_ref().unwrap(),
"Table maximum size exceeded"
);
drop(store);
Ok(())
}
#[async_trait::async_trait]
impl ResourceLimiterAsync for FailureDetector {
async fn memory_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
self.memory_current = current;
self.memory_desired = desired;
Ok(true)
}
fn memory_grow_failed(&mut self, err: anyhow::Error) -> Result<()> {
self.memory_error = Some(err.to_string());
Ok(())
}
async fn table_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
self.table_current = current;
self.table_desired = desired;
Ok(true)
}
fn table_grow_failed(&mut self, err: anyhow::Error) -> Result<()> {
self.table_error = Some(err.to_string());
Ok(())
}
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn custom_limiter_async_detect_grow_failure() -> Result<()> {
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
return Ok(());
}
let mut pool = crate::small_pool_config();
pool.max_memory_size(10 << 16).table_elements(10);
let mut config = Config::new();
config.async_support(true);
config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));
let engine = Engine::new(&config).unwrap();
let linker = Linker::<FailureDetector>::new(&engine);
let module = Module::new(
&engine,
r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
)?;
let context = FailureDetector::default();
let mut store = Store::new(&engine, context);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = linker.instantiate_async(&mut store, &module).await?;
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow_async(&mut store, 10).await?;
assert!(store.data().memory_error.is_none());
assert_eq!(store.data().memory_current, 0);
assert_eq!(store.data().memory_desired, 10 * 64 * 1024);
assert_eq!(
memory
.grow_async(&mut store, 1)
.await
.unwrap_err()
.to_string(),
"failed to grow memory by `1`"
);
assert_eq!(store.data().memory_current, 10 * 64 * 1024);
assert_eq!(store.data().memory_desired, 11 * 64 * 1024);
assert_eq!(
store.data().memory_error.as_ref().unwrap(),
"Memory maximum size exceeded"
);
let table = instance.get_table(&mut store, "t").unwrap();
table.grow_async(&mut store, 10, Ref::Func(None)).await?;
assert!(store.data().table_error.is_none());
assert_eq!(store.data().table_current, 0);
assert_eq!(store.data().table_desired, 10);
assert_eq!(
table
.grow_async(&mut store, 1, Ref::Func(None))
.await
.unwrap_err()
.to_string(),
"failed to grow table by `1`"
);
assert_eq!(store.data().table_current, 10);
assert_eq!(store.data().table_desired, 11);
assert_eq!(
store.data().table_error.as_ref().unwrap(),
"Table maximum size exceeded"
);
drop(store);
Ok(())
}
struct Panic;
impl ResourceLimiter for Panic {
fn memory_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
panic!("resource limiter memory growing");
}
fn table_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
panic!("resource limiter table growing");
}
}
#[async_trait::async_trait]
impl ResourceLimiterAsync for Panic {
async fn memory_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
panic!("async resource limiter memory growing");
}
async fn table_growing(
&mut self,
_current: usize,
_desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
panic!("async resource limiter table growing");
}
}
#[test]
#[should_panic(expected = "resource limiter memory growing")]
fn panic_in_memory_limiter() {
let engine = Engine::default();
let linker = Linker::new(&engine);
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module).unwrap();
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow(&mut store, 3).unwrap();
}
#[test]
#[should_panic(expected = "resource limiter memory growing")]
#[cfg_attr(miri, ignore)]
fn panic_in_memory_limiter_wasm_stack() {
let engine = Engine::default();
let linker = Linker::new(&engine);
let module = Module::new(
&engine,
r#"
(module
(memory $m (export "m") 0)
(func (export "grow") (param i32) (result i32)
(memory.grow $m (local.get 0)))
)"#,
)
.unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module).unwrap();
let grow = instance.get_func(&mut store, "grow").unwrap();
let grow = grow.typed::<i32, i32>(&store).unwrap();
grow.call(&mut store, 3).unwrap();
}
#[test]
#[should_panic(expected = "resource limiter table growing")]
fn panic_in_table_limiter() {
let engine = Engine::default();
let linker = Linker::new(&engine);
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module).unwrap();
let table = instance.get_table(&mut store, "t").unwrap();
table.grow(&mut store, 3, Ref::Func(None)).unwrap();
}
#[tokio::test]
#[should_panic(expected = "async resource limiter memory growing")]
#[cfg_attr(miri, ignore)]
async fn panic_in_async_memory_limiter() {
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config).unwrap();
let linker = Linker::<Panic>::new(&engine);
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
let memory = instance.get_memory(&mut store, "m").unwrap();
memory.grow_async(&mut store, 3).await.unwrap();
}
#[tokio::test]
#[should_panic(expected = "async resource limiter memory growing")]
#[cfg_attr(miri, ignore)]
async fn panic_in_async_memory_limiter_wasm_stack() {
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config).unwrap();
let linker = Linker::<Panic>::new(&engine);
let module = Module::new(
&engine,
r#"
(module
(memory $m (export "m") 0)
(func (export "grow") (param i32) (result i32)
(memory.grow $m (local.get 0)))
)"#,
)
.unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
let grow = instance.get_func(&mut store, "grow").unwrap();
let grow = grow.typed::<i32, i32>(&store).unwrap();
grow.call_async(&mut store, 3).await.unwrap();
}
#[tokio::test]
#[should_panic(expected = "async resource limiter table growing")]
#[cfg_attr(miri, ignore)]
async fn panic_in_async_table_limiter() {
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config).unwrap();
let linker = Linker::<Panic>::new(&engine);
let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
let mut store = Store::new(&engine, Panic);
store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
let table = instance.get_table(&mut store, "t").unwrap();
table
.grow_async(&mut store, 3, Ref::Func(None))
.await
.unwrap();
}
#[test]
#[cfg_attr(miri, ignore)]
fn growth_trap() -> Result<()> {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"(module
(memory $m (export "m") 0)
(table (export "t") 0 funcref)
(func (export "grow") (param i32) (result i32)
(memory.grow $m (local.get 0)))
)"#,
)?;
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_size(WASM_PAGE_SIZE)
.table_elements(1)
.trap_on_grow_failure(true)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
for memory in [
instance.get_memory(&mut store, "m").unwrap(),
Memory::new(&mut store, MemoryType::new(0, None))?,
] {
memory.grow(&mut store, 1)?;
assert!(memory.grow(&mut store, 1).is_err());
}
for table in [
instance.get_table(&mut store, "t").unwrap(),
Table::new(
&mut store,
TableType::new(RefType::FUNCREF, 0, None),
Ref::Func(None),
)?,
] {
table.grow(&mut store, 1, Ref::Func(None))?;
assert!(table.grow(&mut store, 1, Ref::Func(None)).is_err());
}
let mut store = Store::new(&engine, store.data().clone());
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
let grow = instance.get_func(&mut store, "grow").unwrap();
let grow = grow.typed::<i32, i32>(&store).unwrap();
grow.call(&mut store, 1)?;
assert!(grow.call(&mut store, 1).is_err());
Ok(())
}