Files
wasmtime/tests/all/module_linking.rs
Peter Huene f12b4c467c Add resource limiting to the Wasmtime API. (#2736)
* Add resource limiting to the Wasmtime API.

This commit adds a `ResourceLimiter` trait to the Wasmtime API.

When used in conjunction with `Store::new_with_limiter`, this can be used to
monitor and prevent WebAssembly code from growing linear memories and tables.

This is particularly useful when hosts need to take into account host resource
usage to determine if WebAssembly code can consume more resources.

A simple `StaticResourceLimiter` is also included with these changes that will
simply limit the size of linear memories or tables for all instances created in
the store based on static values.

* Code review feedback.

* Implemented `StoreLimits` and `StoreLimitsBuilder`.
* Moved `max_instances`, `max_memories`, `max_tables` out of `Config` and into
  `StoreLimits`.
* Moved storage of the limiter in the runtime into `Memory` and `Table`.
* Made `InstanceAllocationRequest` use a reference to the limiter.
* Updated docs.
* Made `ResourceLimiterProxy` generic to remove a level of indirection.
* Fixed the limiter not being used for `wasmtime::Memory` and
  `wasmtime::Table`.

* Code review feedback and bug fix.

* `Memory::new` now returns `Result<Self>` so that an error can be returned if
  the initial requested memory exceeds any limits placed on the store.

* Changed an `Arc` to `Rc` as the `Arc` wasn't necessary.

* Removed `Store` from the `ResourceLimiter` callbacks. Custom resource limiter
  implementations are free to capture any context they want, so no need to
  unnecessarily store a weak reference to `Store` from the proxy type.

* Fixed a bug in the pooling instance allocator where an instance would be
  leaked from the pool. Previously, this would only have happened if the OS was
  unable to make the necessary linear memory available for the instance. With
  these changes, however, the instance might not be created due to limits
  placed on the store. We now properly deallocate the instance on error.

* Added more tests, including one that covers the fix mentioned above.

* Code review feedback.

* Add another memory to `test_pooling_allocator_initial_limits_exceeded` to
  ensure a partially created instance is successfully deallocated.
* Update some doc comments for better documentation of `Store` and
  `ResourceLimiter`.
2021-04-19 09:19:20 -05:00

296 lines
8.7 KiB
Rust

use anyhow::Result;
use wasmtime::*;
fn engine() -> Engine {
let mut config = Config::new();
config.wasm_module_linking(true);
Engine::new(&config).unwrap()
}
#[test]
fn compile() -> Result<()> {
let engine = engine();
Module::new(&engine, "(module (module))")?;
Module::new(&engine, "(module (module) (module))")?;
Module::new(&engine, "(module (module (module)))")?;
Module::new(
&engine,
"
(module
(func)
(module (func))
(module (func))
)
",
)?;
let m = Module::new(
&engine,
"
(module
(global i32 (i32.const 0))
(func)
(module (memory 1) (func))
(module (memory 2) (func))
(module (table 2 funcref) (func))
(module (global i64 (i64.const 0)) (func))
)
",
)?;
assert_eq!(m.imports().len(), 0);
assert_eq!(m.exports().len(), 0);
let bytes = m.serialize()?;
Module::new(&engine, &bytes)?;
assert_eq!(m.imports().len(), 0);
assert_eq!(m.exports().len(), 0);
Ok(())
}
#[test]
fn types() -> Result<()> {
let engine = engine();
Module::new(&engine, "(module (type (module)))")?;
Module::new(&engine, "(module (type (instance)))")?;
Ok(())
}
#[test]
fn imports_exports() -> Result<()> {
let engine = engine();
// empty module type
let module = Module::new(&engine, "(module (module (export \"\")))")?;
let mut e = module.exports();
assert_eq!(e.len(), 1);
let export = e.next().unwrap();
assert_eq!(export.name(), "");
let module_ty = match export.ty() {
ExternType::Module(m) => m,
_ => panic!("unexpected type"),
};
assert_eq!(module_ty.imports().len(), 0);
assert_eq!(module_ty.exports().len(), 0);
// empty instance type
let module = Module::new(
&engine,
"
(module
(module)
(instance (export \"\") (instantiate 0)))
",
)?;
let mut e = module.exports();
assert_eq!(e.len(), 1);
let export = e.next().unwrap();
assert_eq!(export.name(), "");
let instance_ty = match export.ty() {
ExternType::Instance(i) => i,
_ => panic!("unexpected type"),
};
assert_eq!(instance_ty.exports().len(), 0);
// full module type
let module = Module::new(
&engine,
"
(module
(import \"\" \"a\" (module
(import \"a\" (func))
(export \"\" (global i32))
))
)
",
)?;
let mut i = module.imports();
assert_eq!(i.len(), 1);
let import = i.next().unwrap();
assert_eq!(import.module(), "");
assert_eq!(import.name(), None);
let instance_ty = match import.ty() {
ExternType::Instance(t) => t,
_ => panic!("unexpected type"),
};
assert_eq!(instance_ty.exports().len(), 1);
let module_ty = match instance_ty.exports().next().unwrap().ty() {
ExternType::Module(m) => m,
_ => panic!("unexpected type"),
};
assert_eq!(module_ty.imports().len(), 1);
assert_eq!(module_ty.exports().len(), 1);
let import = module_ty.imports().next().unwrap();
assert_eq!(import.module(), "a");
assert_eq!(import.name(), None);
match import.ty() {
ExternType::Func(f) => {
assert_eq!(f.results().len(), 0);
assert_eq!(f.params().len(), 0);
}
_ => panic!("unexpected type"),
}
let export = module_ty.exports().next().unwrap();
assert_eq!(export.name(), "");
match export.ty() {
ExternType::Global(g) => {
assert_eq!(*g.content(), ValType::I32);
assert_eq!(g.mutability(), Mutability::Const);
}
_ => panic!("unexpected type"),
}
// full instance type
let module = Module::new(
&engine,
"
(module
(import \"\" \"b\" (instance
(export \"m\" (memory 1))
(export \"t\" (table 1 funcref))
))
)
",
)?;
let mut i = module.imports();
assert_eq!(i.len(), 1);
let import = i.next().unwrap();
assert_eq!(import.module(), "");
assert_eq!(import.name(), None);
let instance_ty = match import.ty() {
ExternType::Instance(t) => t,
_ => panic!("unexpected type"),
};
assert_eq!(instance_ty.exports().len(), 1);
let instance_ty = match instance_ty.exports().next().unwrap().ty() {
ExternType::Instance(m) => m,
_ => panic!("unexpected type"),
};
assert_eq!(instance_ty.exports().len(), 2);
let mem_export = instance_ty.exports().nth(0).unwrap();
assert_eq!(mem_export.name(), "m");
match mem_export.ty() {
ExternType::Memory(m) => {
assert_eq!(m.limits().min(), 1);
assert_eq!(m.limits().max(), None);
}
_ => panic!("unexpected type"),
}
let table_export = instance_ty.exports().nth(1).unwrap();
assert_eq!(table_export.name(), "t");
match table_export.ty() {
ExternType::Table(t) => {
assert_eq!(t.limits().min(), 1);
assert_eq!(t.limits().max(), None);
assert_eq!(*t.element(), ValType::FuncRef);
}
_ => panic!("unexpected type"),
}
Ok(())
}
#[test]
fn limit_instances() -> Result<()> {
let mut config = Config::new();
config.wasm_module_linking(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module $PARENT
(module $m0)
(module $m1
(instance (instantiate (module outer $PARENT $m0)))
(instance (instantiate (module outer $PARENT $m0))))
(module $m2
(instance (instantiate (module outer $PARENT $m1)))
(instance (instantiate (module outer $PARENT $m1))))
(module $m3
(instance (instantiate (module outer $PARENT $m2)))
(instance (instantiate (module outer $PARENT $m2))))
(module $m4
(instance (instantiate (module outer $PARENT $m3)))
(instance (instantiate (module outer $PARENT $m3))))
(module $m5
(instance (instantiate (module outer $PARENT $m4)))
(instance (instantiate (module outer $PARENT $m4))))
(instance (instantiate $m5))
)
"#,
)?;
let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().instances(10).build());
let err = Instance::new(&store, &module, &[]).err().unwrap();
assert!(
err.to_string().contains("resource limit exceeded"),
"bad error: {}",
err
);
Ok(())
}
#[test]
fn limit_memories() -> Result<()> {
let mut config = Config::new();
config.wasm_module_linking(true);
config.wasm_multi_memory(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(module $m0
(memory 1 1)
(memory 1 1)
(memory 1 1)
(memory 1 1)
(memory 1 1)
)
(instance (instantiate $m0))
(instance (instantiate $m0))
(instance (instantiate $m0))
(instance (instantiate $m0))
)
"#,
)?;
let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().memories(10).build());
let err = Instance::new(&store, &module, &[]).err().unwrap();
assert!(
err.to_string().contains("resource limit exceeded"),
"bad error: {}",
err
);
Ok(())
}
#[test]
fn limit_tables() -> Result<()> {
let mut config = Config::new();
config.wasm_module_linking(true);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
(module
(module $m0
(table 1 1 funcref)
(table 1 1 funcref)
(table 1 1 funcref)
(table 1 1 funcref)
(table 1 1 funcref)
)
(instance (instantiate $m0))
(instance (instantiate $m0))
(instance (instantiate $m0))
(instance (instantiate $m0))
)
"#,
)?;
let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().tables(10).build());
let err = Instance::new(&store, &module, &[]).err().unwrap();
assert!(
err.to_string().contains("resource limit exceeded"),
"bad error: {}",
err
);
Ok(())
}