When `Linker` was first created it was attempted to be created with the ability to instantiate any wasm modules, including those with duplicate import strings of different types. In an effort to support this a `Linker` supports defining the same names twice so long as they're defined with differently-typed values. This ended up causing wast testsuite failures module linking is enabled, however, because the wrong error message is returned. While it would be possible to fix this there's already the possibility for confusing error messages today due to the `Linker` trying to take on this type-level complexity. In a way this is yet-another type checker for wasm imports, but sort of a bad one because it only supports things like globals/functions, and otherwise you can only define one `Memory`, for example, with a particular name. This commit completely removes this feature from `Linker` to simplify the implementation and make error messages more straightforward. This means that any error message coming from a `Linker` is purely "this thing wasn't defined" rather than a hybrid of "maybe the types didn't match?". I think this also better aligns with the direction that we see conventional wasm modules going which is that duplicate imports are not ever present.
262 lines
8.0 KiB
Rust
262 lines
8.0 KiB
Rust
use anyhow::Result;
|
|
use std::cell::Cell;
|
|
use std::rc::Rc;
|
|
use wasmtime::*;
|
|
|
|
#[test]
|
|
fn link_undefined() -> Result<()> {
|
|
let store = Store::default();
|
|
let linker = Linker::new(&store);
|
|
let module = Module::new(store.engine(), r#"(module (import "" "" (func)))"#)?;
|
|
assert!(linker.instantiate(&module).is_err());
|
|
let module = Module::new(store.engine(), r#"(module (import "" "" (global i32)))"#)?;
|
|
assert!(linker.instantiate(&module).is_err());
|
|
let module = Module::new(store.engine(), r#"(module (import "" "" (memory 1)))"#)?;
|
|
assert!(linker.instantiate(&module).is_err());
|
|
let module = Module::new(
|
|
store.engine(),
|
|
r#"(module (import "" "" (table 1 funcref)))"#,
|
|
)?;
|
|
assert!(linker.instantiate(&module).is_err());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn link_twice_bad() -> Result<()> {
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
|
|
// functions
|
|
linker.func("f", "", || {})?;
|
|
assert!(linker.func("f", "", || {}).is_err());
|
|
assert!(linker
|
|
.func("f", "", || -> Result<(), Trap> { loop {} })
|
|
.is_err());
|
|
|
|
// globals
|
|
let ty = GlobalType::new(ValType::I32, Mutability::Const);
|
|
let global = Global::new(&store, ty, Val::I32(0))?;
|
|
linker.define("g", "1", global.clone())?;
|
|
assert!(linker.define("g", "1", global.clone()).is_err());
|
|
|
|
let ty = GlobalType::new(ValType::I32, Mutability::Var);
|
|
let global = Global::new(&store, ty, Val::I32(0))?;
|
|
linker.define("g", "2", global.clone())?;
|
|
assert!(linker.define("g", "2", global.clone()).is_err());
|
|
|
|
let ty = GlobalType::new(ValType::I64, Mutability::Const);
|
|
let global = Global::new(&store, ty, Val::I64(0))?;
|
|
linker.define("g", "3", global.clone())?;
|
|
assert!(linker.define("g", "3", global.clone()).is_err());
|
|
|
|
// memories
|
|
let ty = MemoryType::new(Limits::new(1, None));
|
|
let memory = Memory::new(&store, ty);
|
|
linker.define("m", "", memory.clone())?;
|
|
assert!(linker.define("m", "", memory.clone()).is_err());
|
|
let ty = MemoryType::new(Limits::new(2, None));
|
|
let memory = Memory::new(&store, ty);
|
|
assert!(linker.define("m", "", memory.clone()).is_err());
|
|
|
|
// tables
|
|
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
|
|
let table = Table::new(&store, ty, Val::FuncRef(None))?;
|
|
linker.define("t", "", table.clone())?;
|
|
assert!(linker.define("t", "", table.clone()).is_err());
|
|
let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
|
|
let table = Table::new(&store, ty, Val::FuncRef(None))?;
|
|
assert!(linker.define("t", "", table.clone()).is_err());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn function_interposition() -> Result<()> {
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
linker.allow_shadowing(true);
|
|
let mut module = Module::new(
|
|
store.engine(),
|
|
r#"(module (func (export "green") (result i32) (i32.const 7)))"#,
|
|
)?;
|
|
for _ in 0..4 {
|
|
let instance = linker.instantiate(&module)?;
|
|
linker.define(
|
|
"red",
|
|
"green",
|
|
instance.get_export("green").unwrap().clone(),
|
|
)?;
|
|
module = Module::new(
|
|
store.engine(),
|
|
r#"(module
|
|
(import "red" "green" (func (result i32)))
|
|
(func (export "green") (result i32) (i32.mul (call 0) (i32.const 2)))
|
|
)"#,
|
|
)?;
|
|
}
|
|
let instance = linker.instantiate(&module)?;
|
|
let func = instance.get_export("green").unwrap().into_func().unwrap();
|
|
let func = func.typed::<(), i32>()?;
|
|
assert_eq!(func.call(())?, 112);
|
|
Ok(())
|
|
}
|
|
|
|
// Same as `function_interposition`, but the linker's name for the function
|
|
// differs from the module's name.
|
|
#[test]
|
|
fn function_interposition_renamed() -> Result<()> {
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
linker.allow_shadowing(true);
|
|
let mut module = Module::new(
|
|
store.engine(),
|
|
r#"(module (func (export "export") (result i32) (i32.const 7)))"#,
|
|
)?;
|
|
for _ in 0..4 {
|
|
let instance = linker.instantiate(&module)?;
|
|
linker.define(
|
|
"red",
|
|
"green",
|
|
instance.get_export("export").unwrap().clone(),
|
|
)?;
|
|
module = Module::new(
|
|
store.engine(),
|
|
r#"(module
|
|
(import "red" "green" (func (result i32)))
|
|
(func (export "export") (result i32) (i32.mul (call 0) (i32.const 2)))
|
|
)"#,
|
|
)?;
|
|
}
|
|
let instance = linker.instantiate(&module)?;
|
|
let func = instance.get_func("export").unwrap();
|
|
let func = func.typed::<(), i32>()?;
|
|
assert_eq!(func.call(())?, 112);
|
|
Ok(())
|
|
}
|
|
|
|
// Similar to `function_interposition`, but use `Linker::instance` instead of
|
|
// `Linker::define`.
|
|
#[test]
|
|
fn module_interposition() -> Result<()> {
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
linker.allow_shadowing(true);
|
|
let mut module = Module::new(
|
|
store.engine(),
|
|
r#"(module (func (export "export") (result i32) (i32.const 7)))"#,
|
|
)?;
|
|
for _ in 0..4 {
|
|
let instance = linker.instantiate(&module)?;
|
|
linker.instance("instance", &instance)?;
|
|
module = Module::new(
|
|
store.engine(),
|
|
r#"(module
|
|
(import "instance" "export" (func (result i32)))
|
|
(func (export "export") (result i32) (i32.mul (call 0) (i32.const 2)))
|
|
)"#,
|
|
)?;
|
|
}
|
|
let instance = linker.instantiate(&module)?;
|
|
let func = instance.get_export("export").unwrap().into_func().unwrap();
|
|
let func = func.typed::<(), i32>()?;
|
|
assert_eq!(func.call(())?, 112);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn no_leak() -> Result<()> {
|
|
struct DropMe(Rc<Cell<bool>>);
|
|
|
|
impl Drop for DropMe {
|
|
fn drop(&mut self) {
|
|
self.0.set(true);
|
|
}
|
|
}
|
|
|
|
let flag = Rc::new(Cell::new(false));
|
|
{
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
let drop_me = DropMe(flag.clone());
|
|
linker.func("", "", move || drop(&drop_me))?;
|
|
let module = Module::new(
|
|
store.engine(),
|
|
r#"
|
|
(module
|
|
(func (export "_start"))
|
|
)
|
|
"#,
|
|
)?;
|
|
linker.module("a", &module)?;
|
|
}
|
|
assert!(flag.get(), "store was leaked");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn no_leak_with_imports() -> Result<()> {
|
|
struct DropMe(Rc<Cell<bool>>);
|
|
|
|
impl Drop for DropMe {
|
|
fn drop(&mut self) {
|
|
self.0.set(true);
|
|
}
|
|
}
|
|
|
|
let flag = Rc::new(Cell::new(false));
|
|
{
|
|
let store = Store::default();
|
|
let mut linker = Linker::new(&store);
|
|
let drop_me = DropMe(flag.clone());
|
|
linker.func("", "", move || drop(&drop_me))?;
|
|
let module = Module::new(
|
|
store.engine(),
|
|
r#"
|
|
(module
|
|
(import "" "" (func))
|
|
(func (export "_start"))
|
|
)
|
|
"#,
|
|
)?;
|
|
linker.module("a", &module)?;
|
|
}
|
|
assert!(flag.get(), "store was leaked");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn get_host_function() -> Result<()> {
|
|
let mut config = Config::default();
|
|
config.wrap_host_func("mod", "f1", || {});
|
|
|
|
let engine = Engine::new(&config)?;
|
|
let module = Module::new(&engine, r#"(module (import "mod" "f1" (func)))"#)?;
|
|
let store = Store::new(&engine);
|
|
|
|
let linker = Linker::new(&store);
|
|
assert!(linker.get(&module.imports().nth(0).unwrap()).is_some());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn shadowing_host_function() -> Result<()> {
|
|
let mut config = Config::default();
|
|
config.wrap_host_func("mod", "f1", || {});
|
|
|
|
let engine = Engine::new(&config)?;
|
|
let store = Store::new(&engine);
|
|
|
|
let mut linker = Linker::new(&store);
|
|
assert!(linker
|
|
.define("mod", "f1", Func::wrap(&store, || {}))
|
|
.is_err());
|
|
linker.define("mod", "f2", Func::wrap(&store, || {}))?;
|
|
|
|
let mut linker = Linker::new(&store);
|
|
linker.allow_shadowing(true);
|
|
linker.define("mod", "f1", Func::wrap(&store, || {}))?;
|
|
linker.define("mod", "f2", Func::wrap(&store, || {}))?;
|
|
|
|
Ok(())
|
|
}
|