diff --git a/Cargo.lock b/Cargo.lock index acfc8bf474..79da3e985e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2061,6 +2061,7 @@ dependencies = [ "pretty_env_logger", "rayon", "region", + "rustc-demangle", "target-lexicon", "wasi-common", "wasmparser 0.47.0", diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 66d2e42024..a5f559bc4f 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -19,6 +19,7 @@ region = "2.0.0" libc = "0.2" cfg-if = "0.1.9" backtrace = "0.3.42" +rustc-demangle = "0.1.16" [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index fb4cdaf760..653fd0abe2 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -78,7 +78,25 @@ impl fmt::Debug for Trap { impl fmt::Display for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.message.fmt(f) + write!(f, "{}", self.inner.message)?; + let trace = self.trace(); + if trace.is_empty() { + return Ok(()); + } + writeln!(f, "\nwasm backtrace:")?; + for (i, frame) in self.trace().iter().enumerate() { + let name = frame.module_name().unwrap_or(""); + write!(f, " {}: {}!", i, name)?; + match frame.func_name() { + Some(name) => match rustc_demangle::try_demangle(name) { + Ok(name) => write!(f, "{}", name)?, + Err(_) => write!(f, "{}", name)?, + }, + None => write!(f, "", frame.func_index)?, + } + writeln!(f, "")?; + } + Ok(()) } } diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index 0c93ca3f82..159aebce5f 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -1,9 +1,9 @@ +use anyhow::Result; use std::rc::Rc; use wasmtime::*; -use wat::parse_str; #[test] -fn test_trap_return() -> Result<(), String> { +fn test_trap_return() -> Result<()> { struct HelloCallback; impl Callable for HelloCallback { @@ -13,24 +13,20 @@ fn test_trap_return() -> Result<(), String> { } let store = Store::default(); - let binary = parse_str( + let binary = wat::parse_str( r#" (module (func $hello (import "" "hello")) (func (export "run") (call $hello)) ) "#, - ) - .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + )?; - let module = - Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let module = Module::new(&store, &binary)?; let hello_type = FuncType::new(Box::new([]), Box::new([])); let hello_func = Func::new(&store, hello_type, Rc::new(HelloCallback)); - let imports = vec![hello_func.into()]; - let instance = Instance::new(&module, &imports) - .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let instance = Instance::new(&module, &[hello_func.into()])?; let run_func = instance.exports()[0] .func() .expect("expected function export"); @@ -43,22 +39,19 @@ fn test_trap_return() -> Result<(), String> { } #[test] -fn test_trap_trace() -> Result<(), String> { +fn test_trap_trace() -> Result<()> { let store = Store::default(); - let binary = parse_str( + let binary = wat::parse_str( r#" (module $hello_mod (func (export "run") (call $hello)) (func $hello (unreachable)) ) "#, - ) - .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + )?; - let module = - Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; - let instance = Instance::new(&module, &[]) - .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let module = Module::new(&store, &binary)?; + let instance = Instance::new(&module, &[])?; let run_func = instance.exports()[0] .func() .expect("expected function export"); @@ -79,7 +72,7 @@ fn test_trap_trace() -> Result<(), String> { } #[test] -fn test_trap_trace_cb() -> Result<(), String> { +fn test_trap_trace_cb() -> Result<()> { struct ThrowCallback; impl Callable for ThrowCallback { @@ -89,7 +82,7 @@ fn test_trap_trace_cb() -> Result<(), String> { } let store = Store::default(); - let binary = parse_str( + let binary = wat::parse_str( r#" (module $hello_mod (import "" "throw" (func $throw)) @@ -97,16 +90,13 @@ fn test_trap_trace_cb() -> Result<(), String> { (func $hello (call $throw)) ) "#, - ) - .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + )?; let fn_type = FuncType::new(Box::new([]), Box::new([])); let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback)); - let module = - Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; - let instance = Instance::new(&module, &[fn_func.into()]) - .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let module = Module::new(&store, &binary)?; + let instance = Instance::new(&module, &[fn_func.into()])?; let run_func = instance.exports()[0] .func() .expect("expected function export"); @@ -125,21 +115,18 @@ fn test_trap_trace_cb() -> Result<(), String> { } #[test] -fn test_trap_stack_overflow() -> Result<(), String> { +fn test_trap_stack_overflow() -> Result<()> { let store = Store::default(); - let binary = parse_str( + let binary = wat::parse_str( r#" (module $rec_mod (func $run (export "run") (call $run)) ) "#, - ) - .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + )?; - let module = - Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; - let instance = Instance::new(&module, &[]) - .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let module = Module::new(&store, &binary)?; + let instance = Instance::new(&module, &[])?; let run_func = instance.exports()[0] .func() .expect("expected function export"); @@ -151,9 +138,94 @@ fn test_trap_stack_overflow() -> Result<(), String> { 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[1].func_name(), Some("run")); + 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 binary = wat::parse_str( + r#" + (module $m + (func $die unreachable) + (func call $die) + (func $foo call 1) + (func (export "bar") call $foo) + ) + "#, + )?; + + let module = Module::new(&store, &binary)?; + 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, source location: @0023 +wasm backtrace: + 0: m!die + 1: m! + 2: m!foo + 3: m! +" + ); + Ok(()) +} + +#[test] +fn trap_display_multi_module() -> Result<()> { + let store = Store::default(); + let binary = wat::parse_str( + r#" + (module $a + (func $die unreachable) + (func call $die) + (func $foo call 1) + (func (export "bar") call $foo) + ) + "#, + )?; + + let module = Module::new(&store, &binary)?; + let instance = Instance::new(&module, &[])?; + let bar = instance.exports()[0].clone(); + + let binary = wat::parse_str( + r#" + (module $b + (import "" "" (func $bar)) + (func $middle call $bar) + (func (export "bar2") call $middle) + ) + "#, + )?; + let module = Module::new(&store, &binary)?; + 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, source location: @0023 +wasm backtrace: + 0: a!die + 1: a! + 2: a!foo + 3: a! + 4: b!middle + 5: b! +" + ); + Ok(()) +} diff --git a/crates/interface-types/src/lib.rs b/crates/interface-types/src/lib.rs index 4934e7370c..76752041e7 100644 --- a/crates/interface-types/src/lib.rs +++ b/crates/interface-types/src/lib.rs @@ -147,21 +147,19 @@ impl ModuleData { .into_iter() .map(|rv| rv.into()) .collect::>(); - let wasm_results = match f.call(&wasm_args) { - Ok(values) => values - .to_vec() - .into_iter() - .map(|v: wasmtime::Val| match v { - wasmtime::Val::I32(i) => RuntimeValue::I32(i), - wasmtime::Val::I64(i) => RuntimeValue::I64(i), - wasmtime::Val::F32(i) => RuntimeValue::F32(i), - wasmtime::Val::F64(i) => RuntimeValue::F64(i), - wasmtime::Val::V128(i) => RuntimeValue::V128(i.to_le_bytes()), - _ => panic!("unsupported value {:?}", v), - }) - .collect::>(), - Err(trap) => bail!("trapped: {:?}", trap), - }; + let wasm_results = f + .call(&wasm_args)? + .to_vec() + .into_iter() + .map(|v: wasmtime::Val| match v { + wasmtime::Val::I32(i) => RuntimeValue::I32(i), + wasmtime::Val::I64(i) => RuntimeValue::I64(i), + wasmtime::Val::F32(i) => RuntimeValue::F32(i), + wasmtime::Val::F64(i) => RuntimeValue::F64(i), + wasmtime::Val::V128(i) => RuntimeValue::V128(i.to_le_bytes()), + _ => panic!("unsupported value {:?}", v), + }) + .collect::>(); translate_outgoing(&mut cx, &outgoing, &wasm_results) } @@ -333,10 +331,7 @@ impl TranslateContext for InstanceTranslateContext { .ok_or_else(|| format_err!("`{}` is not a (alloc) function", alloc_func_name))? .clone(); let alloc_args = vec![wasmtime::Val::I32(len)]; - let results = match alloc.call(&alloc_args) { - Ok(values) => values, - Err(trap) => bail!("trapped: {:?}", trap), - }; + let results = alloc.call(&alloc_args)?; if results.len() != 1 { bail!("allocator function wrong number of results"); } diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index 28eef2878d..7c674efde4 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -71,13 +71,10 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any .context("expected a _start export")? .clone(); - if let Err(trap) = export + export .func() .context("expected export to be a func")? - .call(&[]) - { - bail!("trapped: {:?}", trap); - } + .call(&[])?; Ok(()) }