Move most wasmtime tests into one test suite (#1544)

* Move most wasmtime tests into one test suite

This commit moves most wasmtime tests into a single test suite which
gets compiled into one executable instead of having lots of test
executables. The goal here is to reduce disk space on CI, and this
should be achieved by having fewer executables which means fewer copies
of `libwasmtime.rlib` linked across binaries on the system. More
importantly though this means that DWARF debug information should only
be in one executable rather than duplicated across many.

* Share more build caches

Globally set `RUSTFLAGS` to `-Dwarnings` instead of individually so all
build steps share the same value.

* Allow some dead code in cranelift-codegen

Prevents having to fix all warnings for all possible feature
combinations, only the main ones which come up.

* Update some debug file paths
This commit is contained in:
Alex Crichton
2020-04-17 17:22:12 -05:00
committed by GitHub
parent a524f58dfe
commit 4c82da440a
39 changed files with 49 additions and 34 deletions

View File

@@ -29,13 +29,7 @@ wat = { version = "1.0.10", optional = true }
winapi = "0.3.7"
[dev-dependencies]
# for wasmtime.rs
wasi-common = { path = "../wasi-common", version = "0.15.0" }
pretty_env_logger = "0.4.0"
rayon = "1.2.1"
file-per-thread-logger = "0.1.1"
wat = "1.0.10"
tempfile = "3.1"
tempfile = "3.0"
[badges]
maintenance = { status = "actively-developed" }
@@ -53,7 +47,3 @@ jitdump = ["wasmtime-jit/jitdump"]
# Enables support for the `VTune` profiler
vtune = ["wasmtime-jit/vtune"]
[[test]]
name = "host-segfault"
harness = false

View File

@@ -1,112 +0,0 @@
use wasmtime::*;
#[test]
fn bad_globals() {
let ty = GlobalType::new(ValType::I32, Mutability::Var);
assert!(Global::new(&Store::default(), ty.clone(), Val::I64(0)).is_err());
assert!(Global::new(&Store::default(), ty.clone(), Val::F32(0)).is_err());
assert!(Global::new(&Store::default(), ty.clone(), Val::F64(0)).is_err());
let ty = GlobalType::new(ValType::I32, Mutability::Const);
let g = Global::new(&Store::default(), ty.clone(), Val::I32(0)).unwrap();
assert!(g.set(Val::I32(1)).is_err());
let ty = GlobalType::new(ValType::I32, Mutability::Var);
let g = Global::new(&Store::default(), ty.clone(), Val::I32(0)).unwrap();
assert!(g.set(Val::I64(0)).is_err());
}
#[test]
fn bad_tables() {
// i32 not supported yet
let ty = TableType::new(ValType::I32, Limits::new(0, Some(1)));
assert!(Table::new(&Store::default(), ty.clone(), Val::I32(0)).is_err());
// mismatched initializer
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
assert!(Table::new(&Store::default(), ty.clone(), Val::I32(0)).is_err());
// get out of bounds
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
assert!(t.get(0).is_none());
assert!(t.get(u32::max_value()).is_none());
// set out of bounds or wrong type
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
assert!(t.set(0, Val::I32(0)).is_err());
assert!(t.set(0, Val::AnyRef(AnyRef::Null)).is_ok());
assert!(t.set(1, Val::AnyRef(AnyRef::Null)).is_err());
// grow beyond max
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
assert!(t.grow(0, Val::AnyRef(AnyRef::Null)).is_ok());
assert!(t.grow(1, Val::AnyRef(AnyRef::Null)).is_err());
assert_eq!(t.size(), 1);
// grow wrong type
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(2)));
let t = Table::new(&Store::default(), ty.clone(), Val::AnyRef(AnyRef::Null)).unwrap();
assert!(t.grow(1, Val::I32(0)).is_err());
assert_eq!(t.size(), 1);
}
#[test]
fn cross_store() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let store1 = Store::new(&Engine::new(&cfg));
let store2 = Store::new(&Engine::new(&cfg));
// ============ Cross-store instantiation ==============
let func = Func::wrap(&store2, || {});
let ty = GlobalType::new(ValType::I32, Mutability::Const);
let global = Global::new(&store2, ty, Val::I32(0))?;
let ty = MemoryType::new(Limits::new(1, None));
let memory = Memory::new(&store2, ty);
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store2, ty, Val::AnyRef(AnyRef::Null))?;
let need_func = Module::new(&store1, r#"(module (import "" "" (func)))"#)?;
assert!(Instance::new(&need_func, &[func.into()]).is_err());
let need_global = Module::new(&store1, r#"(module (import "" "" (global i32)))"#)?;
assert!(Instance::new(&need_global, &[global.into()]).is_err());
let need_table = Module::new(&store1, r#"(module (import "" "" (table 1 funcref)))"#)?;
assert!(Instance::new(&need_table, &[table.into()]).is_err());
let need_memory = Module::new(&store1, r#"(module (import "" "" (memory 1)))"#)?;
assert!(Instance::new(&need_memory, &[memory.into()]).is_err());
// ============ Cross-store globals ==============
let store1val = Val::FuncRef(Func::wrap(&store1, || {}));
let store2val = Val::FuncRef(Func::wrap(&store2, || {}));
let ty = GlobalType::new(ValType::FuncRef, Mutability::Var);
assert!(Global::new(&store2, ty.clone(), store1val.clone()).is_err());
if let Ok(g) = Global::new(&store2, ty.clone(), store2val.clone()) {
assert!(g.set(store1val.clone()).is_err());
}
// ============ Cross-store tables ==============
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
assert!(Table::new(&store2, ty.clone(), store1val.clone()).is_err());
let t1 = Table::new(&store2, ty.clone(), store2val.clone())?;
assert!(t1.set(0, store1val.clone()).is_err());
assert!(t1.grow(0, store1val.clone()).is_err());
let t2 = Table::new(&store1, ty.clone(), store1val.clone())?;
assert!(Table::copy(&t1, 0, &t2, 0, 0).is_err());
// ============ Cross-store funcs ==============
// TODO: need to actually fill this out once we support anyref params/locals
// let module = Module::new(&store1, r#"(module (func (export "a") (param funcref)))"#)?;
Ok(())
}

View File

@@ -1,401 +0,0 @@
use anyhow::Result;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*;
#[test]
fn func_constructors() {
let store = Store::default();
Func::wrap(&store, || {});
Func::wrap(&store, |_: i32| {});
Func::wrap(&store, |_: i32, _: i64| {});
Func::wrap(&store, |_: f32, _: f64| {});
Func::wrap(&store, || -> i32 { 0 });
Func::wrap(&store, || -> i64 { 0 });
Func::wrap(&store, || -> f32 { 0.0 });
Func::wrap(&store, || -> f64 { 0.0 });
Func::wrap(&store, || -> Result<(), Trap> { loop {} });
Func::wrap(&store, || -> Result<i32, Trap> { loop {} });
Func::wrap(&store, || -> Result<i64, Trap> { loop {} });
Func::wrap(&store, || -> Result<f32, Trap> { loop {} });
Func::wrap(&store, || -> Result<f64, Trap> { loop {} });
}
#[test]
fn dtor_runs() {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let store = Store::default();
let a = A;
assert_eq!(HITS.load(SeqCst), 0);
Func::wrap(&store, move || {
drop(&a);
});
assert_eq!(HITS.load(SeqCst), 1);
}
#[test]
fn dtor_delayed() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let store = Store::default();
let a = A;
let func = Func::wrap(&store, move || drop(&a));
assert_eq!(HITS.load(SeqCst), 0);
let wasm = wat::parse_str(r#"(import "" "" (func))"#)?;
let module = Module::new(&store, &wasm)?;
let instance = Instance::new(&module, &[func.into()])?;
assert_eq!(HITS.load(SeqCst), 0);
drop(instance);
assert_eq!(HITS.load(SeqCst), 1);
Ok(())
}
#[test]
fn signatures_match() {
let store = Store::default();
let f = Func::wrap(&store, || {});
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[]);
let f = Func::wrap(&store, || -> i32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I32]);
let f = Func::wrap(&store, || -> i64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I64]);
let f = Func::wrap(&store, || -> f32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F32]);
let f = Func::wrap(&store, || -> f64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F64]);
let f = Func::wrap(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 {
loop {}
});
assert_eq!(
f.ty().params(),
&[
ValType::F32,
ValType::F64,
ValType::I32,
ValType::I64,
ValType::I32
]
);
assert_eq!(f.ty().results(), &[ValType::F64]);
}
#[test]
fn import_works() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
let wasm = wat::parse_str(
r#"
(import "" "" (func))
(import "" "" (func (param i32) (result i32)))
(import "" "" (func (param i32) (param i64)))
(import "" "" (func (param i32 i64 i32 f32 f64)))
(func $foo
call 0
i32.const 0
call 1
i32.const 1
i32.add
i64.const 3
call 2
i32.const 100
i64.const 200
i32.const 300
f32.const 400
f64.const 500
call 3
)
(start $foo)
"#,
)?;
let store = Store::default();
let module = Module::new(&store, &wasm)?;
Instance::new(
&module,
&[
Func::wrap(&store, || {
assert_eq!(HITS.fetch_add(1, SeqCst), 0);
})
.into(),
Func::wrap(&store, |x: i32| -> i32 {
assert_eq!(x, 0);
assert_eq!(HITS.fetch_add(1, SeqCst), 1);
1
})
.into(),
Func::wrap(&store, |x: i32, y: i64| {
assert_eq!(x, 2);
assert_eq!(y, 3);
assert_eq!(HITS.fetch_add(1, SeqCst), 2);
})
.into(),
Func::wrap(&store, |a: i32, b: i64, c: i32, d: f32, e: f64| {
assert_eq!(a, 100);
assert_eq!(b, 200);
assert_eq!(c, 300);
assert_eq!(d, 400.0);
assert_eq!(e, 500.0);
assert_eq!(HITS.fetch_add(1, SeqCst), 3);
})
.into(),
],
)?;
Ok(())
}
#[test]
fn trap_smoke() -> Result<()> {
let store = Store::default();
let f = Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(err.message(), "test");
Ok(())
}
#[test]
fn trap_import() -> Result<()> {
let wasm = wat::parse_str(
r#"
(import "" "" (func))
(start 0)
"#,
)?;
let store = Store::default();
let module = Module::new(&store, &wasm)?;
let trap = Instance::new(
&module,
&[Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()],
)
.err()
.unwrap()
.downcast::<Trap>()?;
assert_eq!(trap.message(), "foo");
Ok(())
}
#[test]
fn get_from_wrapper() {
let store = Store::default();
let f = Func::wrap(&store, || {});
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<(), ()>().is_ok());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, i32>().is_err());
assert!(f.get2::<(), (), ()>().is_ok());
assert!(f.get2::<i32, i32, ()>().is_err());
assert!(f.get2::<i32, i32, i32>().is_err());
let f = Func::wrap(&store, || -> i32 { loop {} });
assert!(f.get0::<i32>().is_ok());
let f = Func::wrap(&store, || -> f32 { loop {} });
assert!(f.get0::<f32>().is_ok());
let f = Func::wrap(&store, || -> f64 { loop {} });
assert!(f.get0::<f64>().is_ok());
let f = Func::wrap(&store, |_: i32| {});
assert!(f.get1::<i32, ()>().is_ok());
assert!(f.get1::<i64, ()>().is_err());
assert!(f.get1::<f32, ()>().is_err());
assert!(f.get1::<f64, ()>().is_err());
let f = Func::wrap(&store, |_: i64| {});
assert!(f.get1::<i64, ()>().is_ok());
let f = Func::wrap(&store, |_: f32| {});
assert!(f.get1::<f32, ()>().is_ok());
let f = Func::wrap(&store, |_: f64| {});
assert!(f.get1::<f64, ()>().is_ok());
}
#[test]
fn get_from_signature() {
let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([]));
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
let ty = FuncType::new(Box::new([ValType::I32]), Box::new([ValType::F64]));
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok());
}
#[test]
fn get_from_module() -> anyhow::Result<()> {
let store = Store::default();
let module = Module::new(
&store,
r#"
(module
(func (export "f0"))
(func (export "f1") (param i32))
(func (export "f2") (result i32)
i32.const 0)
)
"#,
)?;
let instance = Instance::new(&module, &[])?;
let f0 = instance.get_export("f0").unwrap().func().unwrap();
assert!(f0.get0::<()>().is_ok());
assert!(f0.get0::<i32>().is_err());
let f1 = instance.get_export("f1").unwrap().func().unwrap();
assert!(f1.get0::<()>().is_err());
assert!(f1.get1::<i32, ()>().is_ok());
assert!(f1.get1::<i32, f32>().is_err());
let f2 = instance.get_export("f2").unwrap().func().unwrap();
assert!(f2.get0::<()>().is_err());
assert!(f2.get0::<i32>().is_ok());
assert!(f2.get1::<i32, ()>().is_err());
assert!(f2.get1::<i32, f32>().is_err());
Ok(())
}
#[test]
fn call_wrapped_func() -> Result<()> {
let store = Store::default();
let f = Func::wrap(&store, |a: i32, b: i64, c: f32, d: f64| {
assert_eq!(a, 1);
assert_eq!(b, 2);
assert_eq!(c, 3.0);
assert_eq!(d, 4.0);
});
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?;
f.get4::<i32, i64, f32, f64, ()>()?(1, 2, 3.0, 4.0)?;
let f = Func::wrap(&store, || 1i32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
let f = Func::wrap(&store, || 2i64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
let f = Func::wrap(&store, || 3.0f32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0);
assert_eq!(f.get0::<f32>()?()?, 3.0);
let f = Func::wrap(&store, || 4.0f64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0);
Ok(())
}
#[test]
fn caller_memory() -> anyhow::Result<()> {
let store = Store::default();
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("x").is_none());
assert!(c.get_export("y").is_none());
assert!(c.get_export("z").is_none());
});
f.call(&[])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("x").is_none());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("memory").is_some());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(memory (export "memory") 1)
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("m").is_some());
assert!(c.get_export("f").is_none());
assert!(c.get_export("g").is_none());
assert!(c.get_export("t").is_none());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(memory (export "m") 1)
(func (export "f"))
(global (export "g") i32 (i32.const 0))
(table (export "t") 1 funcref)
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
Ok(())
}
#[test]
fn func_write_nothing() -> anyhow::Result<()> {
let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([ValType::I32]));
let f = Func::new(&store, ty, |_, _, _| Ok(()));
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(
err.message(),
"function attempted to return an incompatible value"
);
Ok(())
}

View File

@@ -1,93 +0,0 @@
use wasmtime::*;
#[test]
fn smoke() -> anyhow::Result<()> {
let store = Store::default();
let g = Global::new(
&store,
GlobalType::new(ValType::I32, Mutability::Const),
0.into(),
)?;
assert_eq!(g.get().i32(), Some(0));
assert!(g.set(0.into()).is_err());
let g = Global::new(
&store,
GlobalType::new(ValType::I32, Mutability::Const),
1i32.into(),
)?;
assert_eq!(g.get().i32(), Some(1));
let g = Global::new(
&store,
GlobalType::new(ValType::I64, Mutability::Const),
2i64.into(),
)?;
assert_eq!(g.get().i64(), Some(2));
let g = Global::new(
&store,
GlobalType::new(ValType::F32, Mutability::Const),
3.0f32.into(),
)?;
assert_eq!(g.get().f32(), Some(3.0));
let g = Global::new(
&store,
GlobalType::new(ValType::F64, Mutability::Const),
4.0f64.into(),
)?;
assert_eq!(g.get().f64(), Some(4.0));
Ok(())
}
#[test]
fn mutability() -> anyhow::Result<()> {
let store = Store::default();
let g = Global::new(
&store,
GlobalType::new(ValType::I32, Mutability::Var),
0.into(),
)?;
assert_eq!(g.get().i32(), Some(0));
g.set(1.into())?;
assert_eq!(g.get().i32(), Some(1));
Ok(())
}
// Make sure that a global is still usable after its original instance is
// dropped. This is a bit of a weird test and really only fails depending on the
// implementation, but for now should hopefully be resilient enough to catch at
// least some cases of heap corruption.
#[test]
fn use_after_drop() -> anyhow::Result<()> {
let store = Store::default();
let module = Module::new(
&store,
r#"
(module
(global (export "foo") (mut i32) (i32.const 100)))
"#,
)?;
let instance = Instance::new(&module, &[])?;
let g = instance.exports()[0].global().unwrap().clone();
assert_eq!(g.get().i32(), Some(100));
g.set(101.into())?;
drop(instance);
assert_eq!(g.get().i32(), Some(101));
Instance::new(&module, &[])?;
assert_eq!(g.get().i32(), Some(101));
drop(module);
assert_eq!(g.get().i32(), Some(101));
drop(store);
assert_eq!(g.get().i32(), Some(101));
// spray some heap values
let mut x = Vec::new();
for _ in 0..100 {
x.push("xy".to_string());
}
drop(x);
assert_eq!(g.get().i32(), Some(101));
Ok(())
}

View File

@@ -1,100 +0,0 @@
// To handle out-of-bounds reads and writes we use segfaults right now. We only
// want to catch a subset of segfaults, however, rather than all segfaults
// happening everywhere. The purpose of this test is to ensure that we *don't*
// catch segfaults if it happens in a random place in the code, but we instead
// bail out of our segfault handler early.
//
// This is sort of hard to test for but the general idea here is that we confirm
// that execution made it to our `segfault` function by printing something, and
// then we also make sure that stderr is empty to confirm that no weird panics
// happened or anything like that.
use std::env;
use std::process::{Command, ExitStatus};
use wasmtime::*;
const VAR_NAME: &str = "__TEST_TO_RUN";
const CONFIRM: &str = "well at least we ran up to the segfault\n";
fn segfault() -> ! {
unsafe {
print!("{}", CONFIRM);
*(0x4 as *mut i32) = 3;
unreachable!()
}
}
fn main() {
let tests: &[(&str, fn())] = &[
("normal segfault", || segfault()),
("make instance then segfault", || {
let store = Store::default();
let module = Module::new(&store, "(module)").unwrap();
let _instance = Instance::new(&module, &[]).unwrap();
segfault();
}),
];
match env::var(VAR_NAME) {
Ok(s) => {
let test = tests
.iter()
.find(|p| p.0 == s)
.expect("failed to find test")
.1;
test();
}
Err(_) => {
for (name, _test) in tests {
runtest(name);
}
}
}
}
fn runtest(name: &str) {
let me = env::current_exe().unwrap();
let mut cmd = Command::new(me);
cmd.env(VAR_NAME, name);
let output = cmd.output().expect("failed to spawn subprocess");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let mut desc = format!("got status: {}", output.status);
if !stdout.trim().is_empty() {
desc.push_str("\nstdout: ----\n");
desc.push_str(" ");
desc.push_str(&stdout.replace("\n", "\n "));
}
if !stderr.trim().is_empty() {
desc.push_str("\nstderr: ----\n");
desc.push_str(" ");
desc.push_str(&stderr.replace("\n", "\n "));
}
if is_segfault(&output.status) {
assert!(
stdout.ends_with(CONFIRM) && stderr.is_empty(),
"failed to find confirmation in test `{}`\n{}",
name,
desc
);
} else {
panic!("\n\nexpected a segfault on `{}`\n{}\n\n", name, desc);
}
}
#[cfg(unix)]
fn is_segfault(status: &ExitStatus) -> bool {
use std::os::unix::prelude::*;
match status.signal() {
Some(libc::SIGSEGV) | Some(libc::SIGBUS) => true,
_ => false,
}
}
#[cfg(windows)]
fn is_segfault(status: &ExitStatus) -> bool {
match status.code().map(|s| s as u32) {
Some(0xc0000005) => true,
_ => false,
}
}

View File

@@ -1,54 +0,0 @@
use wasmtime::*;
#[test]
fn same_import_names_still_distinct() -> anyhow::Result<()> {
const WAT: &str = r#"
(module
(import "" "" (func $a (result i32)))
(import "" "" (func $b (result f32)))
(func (export "foo") (result i32)
call $a
call $b
i32.trunc_f32_u
i32.add)
)
"#;
let store = Store::default();
let module = Module::new(&store, WAT)?;
let imports = [
Func::new(
&store,
FuncType::new(Box::new([]), Box::new([ValType::I32])),
|_, params, results| {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 1i32.into();
Ok(())
},
)
.into(),
Func::new(
&store,
FuncType::new(Box::new([]), Box::new([ValType::F32])),
|_, params, results| {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 2.0f32.into();
Ok(())
},
)
.into(),
];
let instance = Instance::new(&module, &imports)?;
let func = instance.get_export("foo").unwrap().func().unwrap();
let results = func.call(&[])?;
assert_eq!(results.len(), 1);
match results[0] {
Val::I32(n) => assert_eq!(n, 3),
_ => panic!("unexpected type of return"),
}
Ok(())
}

View File

@@ -1,103 +0,0 @@
use anyhow::Result;
use std::cell::RefCell;
use std::rc::Rc;
use wasmtime::*;
#[test]
fn test_import_calling_export() {
const WAT: &str = r#"
(module
(type $t0 (func))
(import "" "imp" (func $.imp (type $t0)))
(func $run call $.imp)
(func $other)
(export "run" (func $run))
(export "other" (func $other))
)
"#;
let store = Store::default();
let module = Module::new(&store, WAT).expect("failed to create module");
let other = Rc::new(RefCell::new(None::<Func>));
let other2 = other.clone();
let callback_func = Func::new(
&store,
FuncType::new(Box::new([]), Box::new([])),
move |_, _, _| {
other2
.borrow()
.as_ref()
.expect("expected a function ref")
.call(&[])
.expect("expected function not to trap");
Ok(())
},
);
let imports = vec![callback_func.into()];
let instance =
Instance::new(&module, imports.as_slice()).expect("failed to instantiate module");
let exports = instance.exports();
assert!(!exports.is_empty());
let run_func = exports[0]
.func()
.expect("expected a run func in the module");
*other.borrow_mut() = Some(
exports[1]
.func()
.expect("expected an other func in the module")
.clone(),
);
run_func.call(&[]).expect("expected function not to trap");
}
#[test]
fn test_returns_incorrect_type() -> Result<()> {
const WAT: &str = r#"
(module
(import "env" "evil" (func $evil (result i32)))
(func (export "run") (result i32)
(call $evil)
)
)
"#;
let store = Store::default();
let module = Module::new(&store, WAT)?;
let callback_func = Func::new(
&store,
FuncType::new(Box::new([]), Box::new([ValType::I32])),
|_, _, results| {
// Evil! Returns I64 here instead of promised in the signature I32.
results[0] = Val::I64(228);
Ok(())
},
);
let imports = vec![callback_func.into()];
let instance = Instance::new(&module, imports.as_slice())?;
let exports = instance.exports();
assert!(!exports.is_empty());
let run_func = exports[0]
.func()
.expect("expected a run func in the module");
let trap = run_func
.call(&[])
.expect_err("the execution should fail")
.downcast::<Trap>()?;
assert_eq!(
trap.message(),
"function attempted to return an incompatible value"
);
Ok(())
}

View File

@@ -1,13 +0,0 @@
use anyhow::Result;
use wasmtime::*;
#[test]
fn wrong_import_numbers() -> Result<()> {
let store = Store::default();
let module = Module::new(&store, r#"(module (import "" "" (func)))"#)?;
assert!(Instance::new(&module, &[]).is_err());
let func = Func::wrap(&store, || {});
assert!(Instance::new(&module, &[func.clone().into(), func.into()]).is_err());
Ok(())
}

View File

@@ -1,32 +0,0 @@
use anyhow::{Context as _, Result};
use wasmtime::*;
#[test]
fn test_invoke_func_via_table() -> Result<()> {
let store = Store::default();
let wat = r#"
(module
(func $f (result i64) (i64.const 42))
(table (export "table") 1 1 anyfunc)
(elem (i32.const 0) $f)
)
"#;
let module = Module::new(&store, wat).context("> Error compiling module!")?;
let instance = Instance::new(&module, &[]).context("> Error instantiating module!")?;
let f = instance
.get_export("table")
.unwrap()
.table()
.unwrap()
.get(0)
.unwrap()
.funcref()
.unwrap()
.clone();
let result = f.call(&[]).unwrap();
assert_eq!(result[0].unwrap_i64(), 42);
Ok(())
}

View File

@@ -1,97 +0,0 @@
use anyhow::Result;
use wasmtime::*;
#[test]
fn link_undefined() -> Result<()> {
let store = Store::default();
let linker = Linker::new(&store);
let module = Module::new(&store, r#"(module (import "" "" (func)))"#)?;
assert!(linker.instantiate(&module).is_err());
let module = Module::new(&store, r#"(module (import "" "" (global i32)))"#)?;
assert!(linker.instantiate(&module).is_err());
let module = Module::new(&store, r#"(module (import "" "" (memory 1)))"#)?;
assert!(linker.instantiate(&module).is_err());
let module = Module::new(&store, 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("", "", || {})?;
assert!(linker.func("", "", || {}).is_err());
assert!(linker
.func("", "", || -> Result<(), Trap> { loop {} })
.is_err());
linker.func("", "", |_: i32| {})?;
// globals
let ty = GlobalType::new(ValType::I32, Mutability::Const);
let global = Global::new(&store, ty, Val::I32(0))?;
linker.define("", "", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err());
let ty = GlobalType::new(ValType::I32, Mutability::Var);
let global = Global::new(&store, ty, Val::I32(0))?;
linker.define("", "", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err());
let ty = GlobalType::new(ValType::I64, Mutability::Const);
let global = Global::new(&store, ty, Val::I64(0))?;
linker.define("", "", global.clone())?;
assert!(linker.define("", "", global.clone()).is_err());
// memories
let ty = MemoryType::new(Limits::new(1, None));
let memory = Memory::new(&store, ty);
linker.define("", "", memory.clone())?;
assert!(linker.define("", "", memory.clone()).is_err());
let ty = MemoryType::new(Limits::new(2, None));
let memory = Memory::new(&store, ty);
assert!(linker.define("", "", memory.clone()).is_err());
// tables
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
let table = Table::new(&store, ty, Val::AnyRef(AnyRef::Null))?;
linker.define("", "", table.clone())?;
assert!(linker.define("", "", table.clone()).is_err());
let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
let table = Table::new(&store, ty, Val::AnyRef(AnyRef::Null))?;
assert!(linker.define("", "", table.clone()).is_err());
Ok(())
}
#[test]
fn interposition() -> Result<()> {
let store = Store::default();
let mut linker = Linker::new(&store);
linker.allow_shadowing(true);
let mut module = Module::new(
&store,
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,
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_export("export").unwrap().func().unwrap();
let func = func.get0::<i32>()?;
assert_eq!(func()?, 112);
Ok(())
}

View File

@@ -1,192 +0,0 @@
#[cfg(not(target_os = "windows"))]
mod not_for_windows {
use wasmtime::*;
use wasmtime_environ::{WASM_MAX_PAGES, WASM_PAGE_SIZE};
use libc::c_void;
use libc::MAP_FAILED;
use libc::{mmap, mprotect, munmap};
use libc::{sysconf, _SC_PAGESIZE};
use libc::{MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
use std::cell::RefCell;
use std::io::Error;
use std::ptr::null_mut;
use std::sync::{Arc, Mutex};
struct CustomMemory {
mem: *mut c_void,
size: usize,
used_wasm_pages: RefCell<u32>,
glob_page_counter: Arc<Mutex<u64>>,
}
impl CustomMemory {
unsafe fn new(
num_wasm_pages: u32,
max_wasm_pages: u32,
glob_counter: Arc<Mutex<u64>>,
) -> Self {
let page_size = sysconf(_SC_PAGESIZE) as usize;
let guard_size = page_size;
let size = max_wasm_pages as usize * WASM_PAGE_SIZE as usize + guard_size;
let used_size = num_wasm_pages as usize * WASM_PAGE_SIZE as usize;
assert_eq!(size % page_size, 0); // we rely on WASM_PAGE_SIZE being multiple of host page size
let mem = mmap(null_mut(), size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
assert_ne!(mem, MAP_FAILED, "mmap failed: {}", Error::last_os_error());
let r = mprotect(mem, used_size, PROT_READ | PROT_WRITE);
assert_eq!(r, 0, "mprotect failed: {}", Error::last_os_error());
*glob_counter.lock().unwrap() += num_wasm_pages as u64;
Self {
mem,
size,
used_wasm_pages: RefCell::new(num_wasm_pages),
glob_page_counter: glob_counter,
}
}
}
impl Drop for CustomMemory {
fn drop(&mut self) {
let n = *self.used_wasm_pages.borrow() as u64;
*self.glob_page_counter.lock().unwrap() -= n;
let r = unsafe { munmap(self.mem, self.size) };
assert_eq!(r, 0, "munmap failed: {}", Error::last_os_error());
}
}
unsafe impl LinearMemory for CustomMemory {
fn size(&self) -> u32 {
*self.used_wasm_pages.borrow()
}
fn grow(&self, delta: u32) -> Option<u32> {
let delta_size = (delta as usize).checked_mul(WASM_PAGE_SIZE as usize)?;
let prev_pages = *self.used_wasm_pages.borrow();
let prev_size = (prev_pages as usize).checked_mul(WASM_PAGE_SIZE as usize)?;
let new_pages = prev_pages.checked_add(delta)?;
let new_size = (new_pages as usize).checked_mul(WASM_PAGE_SIZE as usize)?;
let guard_size = unsafe { sysconf(_SC_PAGESIZE) as usize };
if new_size > self.size - guard_size {
return None;
}
unsafe {
let start = (self.mem as *mut u8).add(prev_size) as _;
let r = mprotect(start, delta_size, PROT_READ | PROT_WRITE);
assert_eq!(r, 0, "mprotect failed: {}", Error::last_os_error());
}
*self.glob_page_counter.lock().unwrap() += delta as u64;
*self.used_wasm_pages.borrow_mut() = new_pages;
Some(prev_pages)
}
fn as_ptr(&self) -> *mut u8 {
self.mem as *mut u8
}
}
struct CustomMemoryCreator {
pub num_created_memories: Mutex<usize>,
pub num_total_pages: Arc<Mutex<u64>>,
}
impl CustomMemoryCreator {
pub fn new() -> Self {
Self {
num_created_memories: Mutex::new(0),
num_total_pages: Arc::new(Mutex::new(0)),
}
}
}
unsafe impl MemoryCreator for CustomMemoryCreator {
fn new_memory(&self, ty: MemoryType) -> Result<Box<dyn LinearMemory>, String> {
let max = ty.limits().max().unwrap_or(WASM_MAX_PAGES);
unsafe {
let mem = Box::new(CustomMemory::new(
ty.limits().min(),
max,
self.num_total_pages.clone(),
));
*self.num_created_memories.lock().unwrap() += 1;
Ok(mem)
}
}
}
#[test]
fn host_memory() -> anyhow::Result<()> {
let mem_creator = Arc::new(CustomMemoryCreator::new());
let mut config = Config::default();
config.with_host_memory(mem_creator.clone());
let engine = Engine::new(&config);
let store = Store::new(&engine);
let module = Module::new(
&store,
r#"
(module
(memory (export "memory") 1)
)
"#,
)?;
Instance::new(&module, &[])?;
assert_eq!(*mem_creator.num_created_memories.lock().unwrap(), 1);
Ok(())
}
#[test]
fn host_memory_grow() -> anyhow::Result<()> {
let mem_creator = Arc::new(CustomMemoryCreator::new());
let mut config = Config::default();
config.with_host_memory(mem_creator.clone());
let engine = Engine::new(&config);
let store = Store::new(&engine);
let module = Module::new(
&store,
r#"
(module
(func $f (drop (memory.grow (i32.const 1))))
(memory (export "memory") 1 2)
(start $f)
)
"#,
)?;
let instance1 = Instance::new(&module, &[])?;
let instance2 = Instance::new(&module, &[])?;
assert_eq!(*mem_creator.num_created_memories.lock().unwrap(), 2);
assert_eq!(
instance2
.get_export("memory")
.unwrap()
.memory()
.unwrap()
.size(),
2
);
// we take the lock outside the assert, so it won't get poisoned on assert failure
let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
assert_eq!(tot_pages, 4);
drop(instance1);
let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
assert_eq!(tot_pages, 2);
Ok(())
}
}

View File

@@ -1,34 +0,0 @@
use wasmtime::*;
#[test]
fn test_module_no_name() -> anyhow::Result<()> {
let store = Store::default();
let wat = r#"
(module
(func (export "run") (nop))
)
"#;
let module = Module::new(&store, wat)?;
assert_eq!(module.name(), None);
Ok(())
}
#[test]
fn test_module_name() -> anyhow::Result<()> {
let store = Store::default();
let wat = r#"
(module $from_name_section
(func (export "run") (nop))
)
"#;
let module = Module::new(&store, wat)?;
assert_eq!(module.name(), Some("from_name_section"));
let module = Module::new_with_name(&store, wat, "override")?;
assert_eq!(module.name(), Some("override"));
Ok(())
}

View File

@@ -1,435 +0,0 @@
use anyhow::Result;
use std::panic::{self, AssertUnwindSafe};
use wasmtime::*;
#[test]
fn test_trap_return() -> Result<()> {
let store = Store::default();
let wat = r#"
(module
(func $hello (import "" "hello"))
(func (export "run") (call $hello))
)
"#;
let module = Module::new(&store, wat)?;
let hello_type = FuncType::new(Box::new([]), Box::new([]));
let hello_func = Func::new(&store, hello_type, |_, _, _| Err(Trap::new("test 123")));
let instance = Instance::new(&module, &[hello_func.into()])?;
let run_func = instance.exports()[0]
.func()
.expect("expected function export");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
assert_eq!(e.message(), "test 123");
Ok(())
}
#[test]
fn test_trap_trace() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $hello_mod
(func (export "run") (call $hello))
(func $hello (unreachable))
)
"#;
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[])?;
let run_func = instance.exports()[0]
.func()
.expect("expected function export");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace();
assert_eq!(trace.len(), 2);
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
assert_eq!(trace[0].func_index(), 1);
assert_eq!(trace[0].func_name(), Some("hello"));
assert_eq!(trace[0].func_offset(), 1);
assert_eq!(trace[0].module_offset(), 0x26);
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
assert_eq!(trace[1].func_index(), 0);
assert_eq!(trace[1].func_name(), None);
assert_eq!(trace[1].func_offset(), 1);
assert_eq!(trace[1].module_offset(), 0x21);
assert!(
e.message().contains("unreachable"),
"wrong message: {}",
e.message()
);
Ok(())
}
#[test]
fn test_trap_trace_cb() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $hello_mod
(import "" "throw" (func $throw))
(func (export "run") (call $hello))
(func $hello (call $throw))
)
"#;
let fn_type = FuncType::new(Box::new([]), Box::new([]));
let fn_func = Func::new(&store, fn_type, |_, _, _| Err(Trap::new("cb throw")));
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[fn_func.into()])?;
let run_func = instance.exports()[0]
.func()
.expect("expected function export");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace();
assert_eq!(trace.len(), 2);
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
assert_eq!(trace[0].func_index(), 2);
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
assert_eq!(trace[1].func_index(), 1);
assert_eq!(e.message(), "cb throw");
Ok(())
}
#[test]
fn test_trap_stack_overflow() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $rec_mod
(func $run (export "run") (call $run))
)
"#;
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[])?;
let run_func = instance.exports()[0]
.func()
.expect("expected function export");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace();
assert!(trace.len() >= 32);
for i in 0..trace.len() {
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
assert_eq!(trace[i].func_index(), 0);
assert_eq!(trace[i].func_name(), Some("run"));
}
assert!(e.message().contains("call stack exhausted"));
Ok(())
}
#[test]
fn trap_display_pretty() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $m
(func $die unreachable)
(func call $die)
(func $foo call 1)
(func (export "bar") call $foo)
)
"#;
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[])?;
let run_func = instance.exports()[0]
.func()
.expect("expected function export");
let e = run_func.call(&[]).err().expect("error calling function");
assert_eq!(
e.to_string(),
"\
wasm trap: unreachable
wasm backtrace:
0: 0x23 - m!die
1: 0x27 - m!<wasm function 1>
2: 0x2c - m!foo
3: 0x31 - m!<wasm function 3>
"
);
Ok(())
}
#[test]
fn trap_display_multi_module() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $a
(func $die unreachable)
(func call $die)
(func $foo call 1)
(func (export "bar") call $foo)
)
"#;
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[])?;
let bar = instance.exports()[0].clone();
let wat = r#"
(module $b
(import "" "" (func $bar))
(func $middle call $bar)
(func (export "bar2") call $middle)
)
"#;
let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[bar])?;
let bar2 = instance.exports()[0]
.func()
.expect("expected function export");
let e = bar2.call(&[]).err().expect("error calling function");
assert_eq!(
e.to_string(),
"\
wasm trap: unreachable
wasm backtrace:
0: 0x23 - a!die
1: 0x27 - a!<wasm function 1>
2: 0x2c - a!foo
3: 0x31 - a!<wasm function 3>
4: 0x29 - b!middle
5: 0x2e - b!<wasm function 2>
"
);
Ok(())
}
#[test]
fn trap_start_function_import() -> Result<()> {
let store = Store::default();
let binary = wat::parse_str(
r#"
(module $a
(import "" "" (func $foo))
(start $foo)
)
"#,
)?;
let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, |_, _, _| Err(Trap::new("user trap")));
let err = Instance::new(&module, &[func.into()]).err().unwrap();
assert_eq!(err.downcast_ref::<Trap>().unwrap().message(), "user trap");
Ok(())
}
#[test]
fn rust_panic_import() -> Result<()> {
let store = Store::default();
let binary = wat::parse_str(
r#"
(module $a
(import "" "" (func $foo))
(import "" "" (func $bar))
(func (export "foo") call $foo)
(func (export "bar") call $bar)
)
"#,
)?;
let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, |_, _, _| panic!("this is a panic"));
let instance = Instance::new(
&module,
&[
func.into(),
Func::wrap(&store, || panic!("this is another panic")).into(),
],
)?;
let func = instance.exports()[0].func().unwrap().clone();
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(func.call(&[]));
}))
.unwrap_err();
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
let func = instance.exports()[1].func().unwrap().clone();
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(func.call(&[]));
}))
.unwrap_err();
assert_eq!(
err.downcast_ref::<&'static str>(),
Some(&"this is another panic")
);
Ok(())
}
#[test]
fn rust_panic_start_function() -> Result<()> {
let store = Store::default();
let binary = wat::parse_str(
r#"
(module $a
(import "" "" (func $foo))
(start $foo)
)
"#,
)?;
let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, |_, _, _| panic!("this is a panic"));
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(Instance::new(&module, &[func.into()]));
}))
.unwrap_err();
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
let func = Func::wrap(&store, || panic!("this is another panic"));
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(Instance::new(&module, &[func.into()]));
}))
.unwrap_err();
assert_eq!(
err.downcast_ref::<&'static str>(),
Some(&"this is another panic")
);
Ok(())
}
#[test]
fn mismatched_arguments() -> Result<()> {
let store = Store::default();
let binary = wat::parse_str(
r#"
(module $a
(func (export "foo") (param i32))
)
"#,
)?;
let module = Module::new(&store, &binary)?;
let instance = Instance::new(&module, &[])?;
let func = instance.exports()[0].func().unwrap().clone();
assert_eq!(
func.call(&[]).unwrap_err().to_string(),
"expected 1 arguments, got 0"
);
assert_eq!(
func.call(&[Val::F32(0)]).unwrap_err().to_string(),
"argument type mismatch",
);
assert_eq!(
func.call(&[Val::I32(0), Val::I32(1)])
.unwrap_err()
.to_string(),
"expected 1 arguments, got 2"
);
Ok(())
}
#[test]
fn call_signature_mismatch() -> Result<()> {
let store = Store::default();
let binary = wat::parse_str(
r#"
(module $a
(func $foo
i32.const 0
call_indirect)
(func $bar (param i32))
(start $foo)
(table 1 anyfunc)
(elem (i32.const 0) 1)
)
"#,
)?;
let module = Module::new(&store, &binary)?;
let err = Instance::new(&module, &[])
.err()
.unwrap()
.downcast::<Trap>()
.unwrap();
assert_eq!(err.message(), "wasm trap: indirect call type mismatch");
Ok(())
}
#[test]
fn start_trap_pretty() -> Result<()> {
let store = Store::default();
let wat = r#"
(module $m
(func $die unreachable)
(func call $die)
(func $foo call 1)
(func $start call $foo)
(start $start)
)
"#;
let module = Module::new(&store, wat)?;
let e = match Instance::new(&module, &[]) {
Ok(_) => panic!("expected failure"),
Err(e) => e.downcast::<Trap>()?,
};
assert_eq!(
e.to_string(),
"\
wasm trap: unreachable
wasm backtrace:
0: 0x1d - m!die
1: 0x21 - m!<wasm function 1>
2: 0x26 - m!foo
3: 0x2b - m!start
"
);
Ok(())
}
#[test]
fn present_after_module_drop() -> Result<()> {
let store = Store::default();
let module = Module::new(&store, r#"(func (export "foo") unreachable)"#)?;
let instance = Instance::new(&module, &[])?;
let func = instance.exports()[0].func().unwrap().clone();
println!("asserting before we drop modules");
assert_trap(func.call(&[]).unwrap_err().downcast()?);
drop((instance, module));
println!("asserting after drop");
assert_trap(func.call(&[]).unwrap_err().downcast()?);
return Ok(());
fn assert_trap(t: Trap) {
println!("{}", t);
assert_eq!(t.trace().len(), 1);
assert_eq!(t.trace()[0].func_index(), 0);
}
}