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

124
tests/all/cli_tests.rs Normal file
View File

@@ -0,0 +1,124 @@
use anyhow::{bail, Result};
use std::io::Write;
use std::path::Path;
use std::process::{Command, Output};
use tempfile::NamedTempFile;
// Run the wasmtime CLI with the provided args and return the `Output`.
fn run_wasmtime_for_output(args: &[&str]) -> Result<Output> {
let mut me = std::env::current_exe()?;
me.pop(); // chop off the file name
me.pop(); // chop off `deps`
me.push("wasmtime");
Command::new(&me).args(args).output().map_err(Into::into)
}
// Run the wasmtime CLI with the provided args and, if it succeeds, return
// the standard output in a `String`.
fn run_wasmtime(args: &[&str]) -> Result<String> {
let output = run_wasmtime_for_output(args)?;
if !output.status.success() {
bail!(
"Failed to execute wasmtime with: {:?}\n{}",
args,
String::from_utf8_lossy(&output.stderr)
);
}
Ok(String::from_utf8(output.stdout).unwrap())
}
fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
let mut wasm_file = NamedTempFile::new()?;
let wasm = wat::parse_file(wat_path)?;
wasm_file.write(&wasm)?;
Ok(wasm_file)
}
// Very basic use case: compile binary wasm file and run specific function with arguments.
#[test]
fn run_wasmtime_simple() -> Result<()> {
let wasm = build_wasm("tests/wasm/simple.wat")?;
run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--invoke",
"simple",
"--disable-cache",
"4",
])?;
Ok(())
}
// Wasmtime shakk when not enough arguments were provided.
#[test]
fn run_wasmtime_simple_fail_no_args() -> Result<()> {
let wasm = build_wasm("tests/wasm/simple.wat")?;
assert!(
run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--disable-cache",
"--invoke",
"simple",
])
.is_err(),
"shall fail"
);
Ok(())
}
// Running simple wat
#[test]
fn run_wasmtime_simple_wat() -> Result<()> {
let wasm = build_wasm("tests/wasm/simple.wat")?;
run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--invoke",
"simple",
"--disable-cache",
"4",
])?;
Ok(())
}
// Running a wat that traps.
#[test]
fn run_wasmtime_unreachable_wat() -> Result<()> {
let wasm = build_wasm("tests/wasm/unreachable.wat")?;
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
assert_ne!(output.stderr, b"");
assert_eq!(output.stdout, b"");
assert!(!output.status.success());
let code = output
.status
.code()
.expect("wasmtime process should exit normally");
// Test for the specific error code Wasmtime uses to indicate a trap return.
#[cfg(unix)]
assert_eq!(code, 128 + libc::SIGABRT);
#[cfg(windows)]
assert_eq!(code, 3);
Ok(())
}
// Run a simple WASI hello world, snapshot0 edition.
#[test]
fn hello_wasi_snapshot0() -> Result<()> {
let wasm = build_wasm("tests/wasm/hello_wasi_snapshot0.wat")?;
let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
assert_eq!(stdout, "Hello, world!\n");
Ok(())
}
// Run a simple WASI hello world, snapshot1 edition.
#[test]
fn hello_wasi_snapshot1() -> Result<()> {
let wasm = build_wasm("tests/wasm/hello_wasi_snapshot1.wat")?;
let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
assert_eq!(stdout, "Hello, world!\n");
Ok(())
}

View File

@@ -0,0 +1,285 @@
#[cfg(not(target_os = "windows"))]
mod tests {
use anyhow::Result;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use wasmtime::unix::InstanceExt;
use wasmtime::*;
const WAT1: &str = r#"
(module
(func $read (export "read") (result i32)
(i32.load (i32.const 0))
)
(func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
(i32.load
(i32.mul
;; memory size in Wasm pages
(memory.size)
;; Wasm page size
(i32.const 65536)
)
)
)
(func $start
(i32.store (i32.const 0) (i32.const 123))
)
(start $start)
(memory (export "memory") 1 4)
)
"#;
const WAT2: &str = r#"
(module
(import "other_module" "read" (func $other_module.read (result i32)))
(func $run (export "run") (result i32)
call $other_module.read)
)
"#;
fn invoke_export(instance: &Instance, func_name: &str) -> Result<Box<[Val]>> {
let ret = instance
.get_export(func_name)
.unwrap()
.func()
.unwrap()
.call(&[])?;
Ok(ret)
}
// Locate "memory" export, get base address and size and set memory protection to PROT_NONE
fn set_up_memory(instance: &Instance) -> (*mut u8, usize) {
let mem_export = instance.get_export("memory").unwrap().memory().unwrap();
let base = mem_export.data_ptr();
let length = mem_export.data_size();
// So we can later trigger SIGSEGV by performing a read
unsafe {
libc::mprotect(base as *mut libc::c_void, length, libc::PROT_NONE);
}
println!("memory: base={:?}, length={}", base, length);
(base, length)
}
fn handle_sigsegv(
base: *mut u8,
length: usize,
signum: libc::c_int,
siginfo: *const libc::siginfo_t,
) -> bool {
println!("Hello from instance signal handler!");
// SIGSEGV on Linux, SIGBUS on Mac
if libc::SIGSEGV == signum || libc::SIGBUS == signum {
let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
// Any signal from within module's memory we handle ourselves
let result = (si_addr as u64) < (base as u64) + (length as u64);
// Remove protections so the execution may resume
unsafe {
libc::mprotect(
base as *mut libc::c_void,
length,
libc::PROT_READ | libc::PROT_WRITE,
);
}
println!("signal handled: {}", result);
result
} else {
// Otherwise, we forward to wasmtime's signal handler.
false
}
}
#[test]
fn test_custom_signal_handler_single_instance() -> Result<()> {
let engine = Engine::new(&Config::default());
let store = Store::new(&engine);
let module = Module::new(&store, WAT1)?;
let instance = Instance::new(&module, &[])?;
let (base, length) = set_up_memory(&instance);
unsafe {
instance.set_signal_handler(move |signum, siginfo, _| {
handle_sigsegv(base, length, signum, siginfo)
});
}
let exports = instance.exports();
assert!(!exports.is_empty());
// these invoke wasmtime_call_trampoline from action.rs
{
println!("calling read...");
let result = invoke_export(&instance, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
}
{
println!("calling read_out_of_bounds...");
let trap = invoke_export(&instance, "read_out_of_bounds")
.unwrap_err()
.downcast::<Trap>()?;
assert!(
trap.message()
.starts_with("wasm trap: out of bounds memory access"),
"bad trap message: {:?}",
trap.message()
);
}
// these invoke wasmtime_call_trampoline from callable.rs
{
let read_func = exports[0]
.func()
.expect("expected a 'read' func in the module");
println!("calling read...");
let result = read_func.call(&[]).expect("expected function not to trap");
assert_eq!(123i32, result[0].clone().unwrap_i32());
}
{
let read_out_of_bounds_func = exports[1]
.func()
.expect("expected a 'read_out_of_bounds' func in the module");
println!("calling read_out_of_bounds...");
let trap = read_out_of_bounds_func
.call(&[])
.unwrap_err()
.downcast::<Trap>()?;
assert!(trap
.message()
.starts_with("wasm trap: out of bounds memory access"));
}
Ok(())
}
#[test]
fn test_custom_signal_handler_multiple_instances() -> Result<()> {
let engine = Engine::new(&Config::default());
let store = Store::new(&engine);
let module = Module::new(&store, WAT1)?;
// Set up multiple instances
let instance1 = Instance::new(&module, &[])?;
let instance1_handler_triggered = Rc::new(AtomicBool::new(false));
unsafe {
let (base1, length1) = set_up_memory(&instance1);
instance1.set_signal_handler({
let instance1_handler_triggered = instance1_handler_triggered.clone();
move |_signum, _siginfo, _context| {
// Remove protections so the execution may resume
libc::mprotect(
base1 as *mut libc::c_void,
length1,
libc::PROT_READ | libc::PROT_WRITE,
);
instance1_handler_triggered.store(true, Ordering::SeqCst);
println!(
"Hello from instance1 signal handler! {}",
instance1_handler_triggered.load(Ordering::SeqCst)
);
true
}
});
}
let instance2 = Instance::new(&module, &[]).expect("failed to instantiate module");
let instance2_handler_triggered = Rc::new(AtomicBool::new(false));
unsafe {
let (base2, length2) = set_up_memory(&instance2);
instance2.set_signal_handler({
let instance2_handler_triggered = instance2_handler_triggered.clone();
move |_signum, _siginfo, _context| {
// Remove protections so the execution may resume
libc::mprotect(
base2 as *mut libc::c_void,
length2,
libc::PROT_READ | libc::PROT_WRITE,
);
instance2_handler_triggered.store(true, Ordering::SeqCst);
println!(
"Hello from instance2 signal handler! {}",
instance2_handler_triggered.load(Ordering::SeqCst)
);
true
}
});
}
// Invoke both instances and trigger both signal handlers
// First instance1
{
let exports1 = instance1.exports();
assert!(!exports1.is_empty());
println!("calling instance1.read...");
let result = invoke_export(&instance1, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(
instance1_handler_triggered.load(Ordering::SeqCst),
true,
"instance1 signal handler has been triggered"
);
}
// And then instance2
{
let exports2 = instance2.exports();
assert!(!exports2.is_empty());
println!("calling instance2.read...");
let result = invoke_export(&instance2, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(
instance2_handler_triggered.load(Ordering::SeqCst),
true,
"instance1 signal handler has been triggered"
);
}
Ok(())
}
#[test]
fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
let engine = Engine::new(&Config::default());
let store = Store::new(&engine);
// instance1 which defines 'read'
let module1 = Module::new(&store, WAT1)?;
let instance1 = Instance::new(&module1, &[])?;
let (base1, length1) = set_up_memory(&instance1);
unsafe {
instance1.set_signal_handler(move |signum, siginfo, _| {
println!("instance1");
handle_sigsegv(base1, length1, signum, siginfo)
});
}
let instance1_exports = instance1.exports();
assert!(!instance1_exports.is_empty());
let instance1_read = instance1_exports[0].clone();
// instance2 wich calls 'instance1.read'
let module2 = Module::new(&store, WAT2)?;
let instance2 = Instance::new(&module2, &[instance1_read])?;
// since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
// SIGSEGV originating from within the memory of instance1
unsafe {
instance2.set_signal_handler(move |signum, siginfo, _| {
handle_sigsegv(base1, length1, signum, siginfo)
});
}
println!("calling instance2.run");
let result = invoke_export(&instance2, "run")?;
assert_eq!(123, result[0].unwrap_i32());
Ok(())
}
}

30
tests/all/debug/dump.rs Normal file
View File

@@ -0,0 +1,30 @@
use anyhow::{bail, Result};
use std::env;
use std::process::Command;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum DwarfDumpSection {
DebugInfo,
DebugLine,
}
pub fn get_dwarfdump(obj: &str, section: DwarfDumpSection) -> Result<String> {
let dwarfdump = env::var("DWARFDUMP").unwrap_or("llvm-dwarfdump".to_string());
let section_flag = match section {
DwarfDumpSection::DebugInfo => "-debug-info",
DwarfDumpSection::DebugLine => "-debug-line",
};
let output = Command::new(&dwarfdump)
.args(&[section_flag, obj])
.output()
.expect("success");
if !output.status.success() {
bail!(
"failed to execute {}: {}",
dwarfdump,
String::from_utf8_lossy(&output.stderr),
);
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}

128
tests/all/debug/lldb.rs Normal file
View File

@@ -0,0 +1,128 @@
#![allow(dead_code)]
use anyhow::{bail, format_err, Result};
use filecheck::{CheckerBuilder, NO_VARIABLES};
use std::env;
use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
fn lldb_with_script(args: &[&str], script: &str) -> Result<String> {
let lldb_path = env::var("LLDB").unwrap_or("lldb".to_string());
let mut cmd = Command::new(&lldb_path);
cmd.arg("--batch");
if cfg!(target_os = "macos") {
cmd.args(&["-o", "settings set plugin.jit-loader.gdb.enable on"]);
}
let mut script_file = NamedTempFile::new()?;
script_file.write(script.as_bytes())?;
let script_path = script_file.path().to_str().unwrap();
cmd.args(&["-s", &script_path]);
let mut me = std::env::current_exe().expect("current_exe specified");
me.pop(); // chop off the file name
me.pop(); // chop off `deps`
me.push("wasmtime");
cmd.arg(me);
cmd.arg("--");
cmd.args(args);
let output = cmd.output().expect("success");
if !output.status.success() {
bail!(
"failed to execute {:?}: {}",
cmd,
String::from_utf8_lossy(&output.stderr),
);
}
Ok(String::from_utf8(output.stdout)?)
}
fn check_lldb_output(output: &str, directives: &str) -> Result<()> {
let mut builder = CheckerBuilder::new();
builder
.text(directives)
.map_err(|e| format_err!("unable to build checker: {:?}", e))?;
let checker = builder.finish();
let check = checker
.explain(output, NO_VARIABLES)
.map_err(|e| format_err!("{:?}", e))?;
assert!(check.0, "didn't pass check {}", check.1);
Ok(())
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
pub fn test_debug_dwarf_lldb() -> Result<()> {
let output = lldb_with_script(
&[
"-g",
"tests/all/debug/testsuite/fib-wasm.wasm",
"--invoke",
"fib",
"3",
],
r#"b fib
r
fr v
c"#,
)?;
check_lldb_output(
&output,
r#"
check: Breakpoint 1: no locations (pending)
check: Unable to resolve breakpoint to any actual locations.
check: 1 location added to breakpoint 1
check: stop reason = breakpoint 1.1
check: frame #0
sameln: JIT
sameln: fib(n=3)
check: n = 3
check: a = 0
check: resuming
check: exited with status
"#,
)?;
Ok(())
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
pub fn test_debug_dwarf_ptr() -> Result<()> {
let output = lldb_with_script(
&[
"-g",
"--opt-level",
"0",
"tests/all/debug/testsuite/reverse-str.wasm",
],
r#"b reverse-str.c:9
r
p __vmctx->set(),&*s
c"#,
)?;
check_lldb_output(
&output,
r#"
check: Breakpoint 1: no locations (pending)
check: stop reason = breakpoint 1.1
check: frame #0
sameln: reverse(s=(__ptr =
check: "Hello, world."
check: resuming
"#,
)?;
Ok(())
}

5
tests/all/debug/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
mod dump;
mod lldb;
mod obj;
mod simulate;
mod translate;

34
tests/all/debug/obj.rs Normal file
View File

@@ -0,0 +1,34 @@
use anyhow::{Context as _, Result};
use std::fs::File;
use std::path::Path;
use target_lexicon::Triple;
use wasmtime::Strategy;
use wasmtime_cli::compile_to_obj;
use wasmtime_environ::CacheConfig;
pub fn compile_cranelift(
wasm: &[u8],
target: Option<Triple>,
output: impl AsRef<Path>,
) -> Result<()> {
let obj = compile_to_obj(
wasm,
target.as_ref(),
Strategy::Cranelift,
false,
wasmtime::OptLevel::None,
true,
output
.as_ref()
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
&CacheConfig::new_cache_disabled(),
)?;
let file = File::create(output).context("failed to create object file")?;
obj.write(file).context("failed to write object file")?;
Ok(())
}

View File

@@ -0,0 +1,99 @@
use super::dump::{get_dwarfdump, DwarfDumpSection};
use super::obj::compile_cranelift;
use anyhow::{format_err, Result};
use filecheck::{CheckerBuilder, NO_VARIABLES};
use tempfile::NamedTempFile;
use wat::parse_str;
#[allow(dead_code)]
fn check_wat(wat: &str) -> Result<()> {
let wasm = parse_str(wat)?;
let obj_file = NamedTempFile::new()?;
let obj_path = obj_file.path().to_str().unwrap();
compile_cranelift(&wasm, None, obj_path)?;
let dump = get_dwarfdump(obj_path, DwarfDumpSection::DebugInfo)?;
let mut builder = CheckerBuilder::new();
builder
.text(wat)
.map_err(|e| format_err!("unable to build checker: {:?}", e))?;
let checker = builder.finish();
let check = checker
.explain(&dump, NO_VARIABLES)
.map_err(|e| format_err!("{:?}", e))?;
assert!(check.0, "didn't pass check {}", check.1);
Ok(())
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
fn test_debug_dwarf_simulate_simple_x86_64() -> Result<()> {
check_wat(
r#"
;; check: DW_TAG_compile_unit
(module
;; check: DW_TAG_subprogram
;; check: DW_AT_name ("wasm-function[0]")
;; check: DW_TAG_formal_parameter
;; check: DW_AT_name ("var0")
;; check: DW_AT_type
;; sameln: "i32"
;; check: DW_TAG_variable
;; check: DW_AT_name ("var1")
;; check: DW_AT_type
;; sameln: "i32"
(func (param i32) (result i32)
(local i32)
local.get 0
local.set 1
local.get 1
)
)"#,
)
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
fn test_debug_dwarf_simulate_with_imports_x86_64() -> Result<()> {
check_wat(
r#"
;; check: DW_TAG_compile_unit
(module
;; check: DW_TAG_subprogram
;; check: DW_AT_name ("func1")
(import "foo" "bar" (func $import1) )
(func $func1 (result i32)
i32.const 1
)
)"#,
)
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
fn test_debug_dwarf_simulate_with_invalid_name_x86_64() -> Result<()> {
check_wat(
r#"
;; check: DW_TAG_compile_unit
(module (@name "\00")
;; check: DW_TAG_subprogram
;; check: DW_AT_name ("wasm-function[1]")
(import "foo" "bar" (func $import1) )
(func (@name "\00f") (result i32)
(local (@name "l\00") i32)
i32.const 1
)
)"#,
)
}

View File

@@ -0,0 +1,13 @@
// Compile with:
// clang --target=wasm32 fib-wasm.c -o fib-wasm.wasm -g \
// -Wl,--no-entry,--export=fib -nostdlib -fdebug-prefix-map=$PWD=.
int fib(int n) {
int i, t, a = 0, b = 1;
for (i = 0; i < n; i++) {
t = a;
a = b;
b += t;
}
return b;
}

Binary file not shown.

View File

@@ -0,0 +1,21 @@
// Compile with:
// clang --target=wasm32 reverse-str.c -o reverse-str.wasm -g \
// -O0 -nostdlib -fdebug-prefix-map=$PWD=.
#include <stdlib.h>
void reverse(char *s, size_t len)
{
if (!len) return;
size_t i = 0, j = len - 1;
while (i < j) {
char t = s[i];
s[i++] = s[j];
s[j--] = t;
}
}
void _start()
{
char hello[] = "Hello, world.";
reverse(hello, 13);
}

Binary file not shown.

View File

@@ -0,0 +1,57 @@
use super::dump::{get_dwarfdump, DwarfDumpSection};
use super::obj::compile_cranelift;
use anyhow::{format_err, Result};
use filecheck::{CheckerBuilder, NO_VARIABLES};
use std::fs::read;
use tempfile::NamedTempFile;
#[allow(dead_code)]
fn check_wasm(wasm_path: &str, directives: &str) -> Result<()> {
let wasm = read(wasm_path)?;
let obj_file = NamedTempFile::new()?;
let obj_path = obj_file.path().to_str().unwrap();
compile_cranelift(&wasm, None, obj_path)?;
let dump = get_dwarfdump(obj_path, DwarfDumpSection::DebugInfo)?;
let mut builder = CheckerBuilder::new();
builder
.text(directives)
.map_err(|e| format_err!("unable to build checker: {:?}", e))?;
let checker = builder.finish();
let check = checker
.explain(&dump, NO_VARIABLES)
.map_err(|e| format_err!("{:?}", e))?;
assert!(check.0, "didn't pass check {}", check.1);
Ok(())
}
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
fn test_debug_dwarf_translate() -> Result<()> {
check_wasm(
"tests/all/debug/testsuite/fib-wasm.wasm",
r##"
check: DW_TAG_compile_unit
# We have "fib" function
check: DW_TAG_subprogram
check: DW_AT_name ("fib")
# Accepts one parameter
check: DW_TAG_formal_parameter
check: DW_AT_name ("n")
check: DW_AT_decl_line (5)
# Has four locals: i, t, a, b
check: DW_TAG_variable
check: DW_AT_name ("i")
check: DW_AT_decl_line (6)
check: DW_TAG_variable
check: DW_AT_name ("t")
check: DW_TAG_variable
check: DW_AT_name ("a")
check: DW_TAG_variable
check: DW_AT_name ("b")
"##,
)
}

112
tests/all/externals.rs Normal file
View File

@@ -0,0 +1,112 @@
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(())
}

401
tests/all/func.rs Normal file
View File

@@ -0,0 +1,401 @@
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(())
}

29
tests/all/fuzzing.rs Normal file
View File

@@ -0,0 +1,29 @@
//! Regression tests for bugs found via fuzzing.
//!
//! The `#[test]` goes in here, the Wasm binary goes in
//! `./fuzzing/some-descriptive-name.wasm`, and then the `#[test]` should
//! use the Wasm binary by including it via
//! `include_bytes!("./fuzzing/some-descriptive-name.wasm")`.
use wasmtime::{Config, Strategy};
use wasmtime_fuzzing::oracles;
#[test]
fn instantiate_empty_module() {
let data = wat::parse_str(include_str!("./fuzzing/empty.wat")).unwrap();
oracles::instantiate(&data, Strategy::Auto);
}
#[test]
fn instantiate_empty_module_with_memory() {
let data = wat::parse_str(include_str!("./fuzzing/empty_with_memory.wat")).unwrap();
oracles::instantiate(&data, Strategy::Auto);
}
#[test]
fn instantiate_module_that_compiled_to_x64_has_register_32() {
let mut config = Config::new();
config.debug_info(true);
let data = wat::parse_str(include_str!("./fuzzing/issue694.wat")).unwrap();
oracles::instantiate_with_config(&data, config);
}

View File

@@ -0,0 +1,2 @@
This directory contains `.wasm` binaries generated during fuzzing that uncovered
a bug, and which we now use as regression tests in `../fuzzing.rs`.

View File

@@ -0,0 +1 @@
(module)

View File

@@ -0,0 +1 @@
(module (memory 1))

View File

@@ -0,0 +1,49 @@
(module
(type (;0;) (func))
(type (;1;) (func (param i64)))
(func (;0;) (type 0))
(func (;1;) (type 0))
(func (;2;) (type 0))
(func (;3;) (type 0))
(func (;4;) (type 1) (param i64)
(local f32 f32 f32)
loop (result i64) ;; label = @1
global.get 0
if ;; label = @2
local.get 1
return
end
block (result i64) ;; label = @2
loop ;; label = @3
block ;; label = @4
global.get 0
if ;; label = @5
i32.const 5
if (result f32) ;; label = @6
block (result f32) ;; label = @7
call 0
i32.const 7
if (result f32) ;; label = @8
local.get 2
else
f32.const 0x1p+0 (;=1;)
end
end
else
f32.const 0x1p+0 (;=1;)
end
local.tee 1
local.set 3
end
end
end
i32.const 8
br_if 1 (;@1;)
i64.const 4
end
end
return)
(memory (;0;) 1)
(global (;0;) i32 (i32.const 0))
)

93
tests/all/globals.rs Normal file
View File

@@ -0,0 +1,93 @@
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

@@ -0,0 +1,103 @@
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

@@ -0,0 +1,54 @@
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(())
}

13
tests/all/instance.rs Normal file
View File

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,32 @@
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(())
}

97
tests/all/linker.rs Normal file
View File

@@ -0,0 +1,97 @@
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(())
}

16
tests/all/main.rs Normal file
View File

@@ -0,0 +1,16 @@
mod cli_tests;
mod custom_signal_handler;
mod debug;
mod externals;
mod func;
mod fuzzing;
mod globals;
mod import_calling_export;
mod import_indexes;
mod instance;
mod invoke_func_via_table;
mod linker;
mod memory_creator;
mod name;
mod traps;
mod wast;

192
tests/all/memory_creator.rs Normal file
View File

@@ -0,0 +1,192 @@
#[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(())
}
}

34
tests/all/name.rs Normal file
View File

@@ -0,0 +1,34 @@
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(())
}

435
tests/all/traps.rs Normal file
View File

@@ -0,0 +1,435 @@
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);
}
}

41
tests/all/wast.rs Normal file
View File

@@ -0,0 +1,41 @@
use std::path::Path;
use wasmtime::{Config, Engine, OptLevel, Store, Strategy};
use wasmtime_wast::WastContext;
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
// Each of the tests included from `wast_testsuite_tests` will call this
// function which actually executes the `wast` test suite given the `strategy`
// to compile it.
fn run_wast(wast: &str, strategy: Strategy) -> anyhow::Result<()> {
let wast = Path::new(wast);
let simd = wast.iter().any(|s| s == "simd");
let bulk_mem = wast.iter().any(|s| s == "bulk-memory-operations");
// Some simd tests assume support for multiple tables, which are introduced
// by reference types.
let reftypes = simd || wast.iter().any(|s| s == "reference-types");
let multi_val = wast.iter().any(|s| s == "multi-value");
let mut cfg = Config::new();
cfg.wasm_simd(simd)
.wasm_bulk_memory(bulk_mem)
.wasm_reference_types(reftypes)
.wasm_multi_value(multi_val)
.strategy(strategy)?
.cranelift_debug_verifier(true);
// FIXME: https://github.com/bytecodealliance/wasmtime/issues/1186
if simd {
cfg.cranelift_opt_level(OptLevel::None);
}
let store = Store::new(&Engine::new(&cfg));
let mut wast_context = WastContext::new(store);
wast_context.register_spectest()?;
wast_context.run_file(wast)?;
Ok(())
}