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:
124
tests/all/cli_tests.rs
Normal file
124
tests/all/cli_tests.rs
Normal 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(())
|
||||
}
|
||||
285
tests/all/custom_signal_handler.rs
Normal file
285
tests/all/custom_signal_handler.rs
Normal 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
30
tests/all/debug/dump.rs
Normal 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
128
tests/all/debug/lldb.rs
Normal 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
5
tests/all/debug/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod dump;
|
||||
mod lldb;
|
||||
mod obj;
|
||||
mod simulate;
|
||||
mod translate;
|
||||
34
tests/all/debug/obj.rs
Normal file
34
tests/all/debug/obj.rs
Normal 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(())
|
||||
}
|
||||
99
tests/all/debug/simulate.rs
Normal file
99
tests/all/debug/simulate.rs
Normal 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
|
||||
)
|
||||
)"#,
|
||||
)
|
||||
}
|
||||
13
tests/all/debug/testsuite/fib-wasm.c
Normal file
13
tests/all/debug/testsuite/fib-wasm.c
Normal 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;
|
||||
}
|
||||
BIN
tests/all/debug/testsuite/fib-wasm.wasm
Executable file
BIN
tests/all/debug/testsuite/fib-wasm.wasm
Executable file
Binary file not shown.
21
tests/all/debug/testsuite/reverse-str.c
Normal file
21
tests/all/debug/testsuite/reverse-str.c
Normal 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);
|
||||
}
|
||||
BIN
tests/all/debug/testsuite/reverse-str.wasm
Executable file
BIN
tests/all/debug/testsuite/reverse-str.wasm
Executable file
Binary file not shown.
57
tests/all/debug/translate.rs
Normal file
57
tests/all/debug/translate.rs
Normal 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
112
tests/all/externals.rs
Normal 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
401
tests/all/func.rs
Normal 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
29
tests/all/fuzzing.rs
Normal 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);
|
||||
}
|
||||
2
tests/all/fuzzing/README.md
Normal file
2
tests/all/fuzzing/README.md
Normal 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`.
|
||||
1
tests/all/fuzzing/empty.wat
Normal file
1
tests/all/fuzzing/empty.wat
Normal file
@@ -0,0 +1 @@
|
||||
(module)
|
||||
1
tests/all/fuzzing/empty_with_memory.wat
Normal file
1
tests/all/fuzzing/empty_with_memory.wat
Normal file
@@ -0,0 +1 @@
|
||||
(module (memory 1))
|
||||
49
tests/all/fuzzing/issue694.wat
Normal file
49
tests/all/fuzzing/issue694.wat
Normal 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
93
tests/all/globals.rs
Normal 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(())
|
||||
}
|
||||
103
tests/all/import_calling_export.rs
Normal file
103
tests/all/import_calling_export.rs
Normal 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(())
|
||||
}
|
||||
54
tests/all/import_indexes.rs
Normal file
54
tests/all/import_indexes.rs
Normal 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
13
tests/all/instance.rs
Normal 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(())
|
||||
}
|
||||
32
tests/all/invoke_func_via_table.rs
Normal file
32
tests/all/invoke_func_via_table.rs
Normal 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
97
tests/all/linker.rs
Normal 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
16
tests/all/main.rs
Normal 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
192
tests/all/memory_creator.rs
Normal 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
34
tests/all/name.rs
Normal 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
435
tests/all/traps.rs
Normal 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
41
tests/all/wast.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user