Skip to content

Commit

Permalink
Add Wasmtime-specific C API functions to return errors (#1467)
Browse files Browse the repository at this point in the history
* Add Wasmtime-specific C API functions to return errors

This commit adds new `wasmtime_*` symbols to the C API, many of which
mirror the existing counterparts in the `wasm.h` header. These APIs are
enhanced in a number of respects:

* Detailed error information is now available through a
  `wasmtime_error_t`. Currently this only exposes one function which is
  to extract a string version of the error.

* There is a distinction now between traps and errors during
  instantiation and function calling. Traps only happen if wasm traps,
  and errors can happen for things like runtime type errors when
  interacting with the API.

* APIs have improved safety with respect to embedders where the lengths
  of arrays are now taken as explicit parameters rather than assumed
  from other parameters.

* Handle trap updates

* Update C examples

* Fix memory.c compile on MSVC

* Update test assertions

* Refactor C slightly

* Bare-bones .NET update

* Remove bogus nul handling
  • Loading branch information
alexcrichton authored Apr 6, 2020
1 parent 78c548d commit bd374fd
Show file tree
Hide file tree
Showing 30 changed files with 816 additions and 402 deletions.
23 changes: 23 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

--------------------------------------------------------------------------------

## 0.16.0

Unreleased

### Added

* The C API has a number of new `wasmtime_*` functions which return error
objects to get detailed error information when an API fails.
[#TODO](https://github.com/bytecodealliance/wasmtime/pull/TODO)

### Changed

* The `Func::call` API has changed its error type from `Trap` to `anyhow::Error`
to distinguish between wasm traps and runtiem violations (like the wrong
number of parameters).
[#TODO](https://github.com/bytecodealliance/wasmtime/pull/TODO)

* A number of `wasmtime_linker_*` and `wasmtime_config_*` C APIs have new type
signatures which reflect returning errors.
[#TODO](https://github.com/bytecodealliance/wasmtime/pull/TODO)

--------------------------------------------------------------------------------

## 0.15.0

Unreleased
Expand Down
16 changes: 7 additions & 9 deletions crates/api/src/func.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
use anyhow::{ensure, Context as _};
use anyhow::{bail, ensure, Context as _, Result};
use std::cmp::max;
use std::fmt;
use std::mem;
Expand Down Expand Up @@ -493,18 +493,18 @@ impl Func {
///
/// This function should not panic unless the underlying function itself
/// initiates a panic.
pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>, Trap> {
pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>> {
// We need to perform a dynamic check that the arguments given to us
// match the signature of this function and are appropriate to pass to
// this function. This involves checking to make sure we have the right
// number and types of arguments as well as making sure everything is
// from the same `Store`.
if self.ty.params().len() != params.len() {
return Err(Trap::new(format!(
bail!(
"expected {} arguments, got {}",
self.ty.params().len(),
params.len()
)));
);
}

let mut values_vec = vec![0; max(params.len(), self.ty.results().len())];
Expand All @@ -513,12 +513,10 @@ impl Func {
let param_tys = self.ty.params().iter();
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
if arg.ty() != *ty {
return Err(Trap::new("argument type mismatch"));
bail!("argument type mismatch");
}
if !arg.comes_from_same_store(&self.store) {
return Err(Trap::new(
"cross-`Store` values are not currently supported",
));
bail!("cross-`Store` values are not currently supported");
}
unsafe {
arg.write_value_to(slot);
Expand All @@ -536,7 +534,7 @@ impl Func {
)
})
} {
return Err(Trap::from_jit(error));
return Err(Trap::from_jit(error).into());
}

// Load the return values out of `values_vec`.
Expand Down
7 changes: 4 additions & 3 deletions crates/api/tests/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,12 @@ fn import_works() -> Result<()> {
}

#[test]
fn trap_smoke() {
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();
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(err.message(), "test");
Ok(())
}

#[test]
Expand Down Expand Up @@ -391,7 +392,7 @@ 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();
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(
err.message(),
"function attempted to return an incompatible value"
Expand Down
14 changes: 9 additions & 5 deletions crates/api/tests/import_calling_export.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::Result;
use std::cell::RefCell;
use std::rc::Rc;
use wasmtime::*;
Expand Down Expand Up @@ -57,7 +58,7 @@ fn test_import_calling_export() {
}

#[test]
fn test_returns_incorrect_type() {
fn test_returns_incorrect_type() -> Result<()> {
const WAT: &str = r#"
(module
(import "env" "evil" (func $evil (result i32)))
Expand All @@ -68,7 +69,7 @@ fn test_returns_incorrect_type() {
"#;

let store = Store::default();
let module = Module::new(&store, WAT).expect("failed to create module");
let module = Module::new(&store, WAT)?;

let callback_func = Func::new(
&store,
Expand All @@ -81,8 +82,7 @@ fn test_returns_incorrect_type() {
);

let imports = vec![callback_func.into()];
let instance =
Instance::new(&module, imports.as_slice()).expect("failed to instantiate module");
let instance = Instance::new(&module, imports.as_slice())?;

let exports = instance.exports();
assert!(!exports.is_empty());
Expand All @@ -91,9 +91,13 @@ fn test_returns_incorrect_type() {
.func()
.expect("expected a run func in the module");

let trap = run_func.call(&[]).expect_err("the execution should fail");
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(())
}
30 changes: 23 additions & 7 deletions crates/api/tests/traps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ fn test_trap_return() -> Result<()> {
.func()
.expect("expected function export");

let e = run_func.call(&[]).err().expect("error calling function");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;

assert_eq!(e.message(), "test 123");

Expand All @@ -44,7 +48,11 @@ fn test_trap_trace() -> Result<()> {
.func()
.expect("expected function export");

let e = run_func.call(&[]).err().expect("error calling function");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;

let trace = e.trace();
assert_eq!(trace.len(), 2);
Expand Down Expand Up @@ -83,7 +91,11 @@ fn test_trap_trace_cb() -> Result<()> {
.func()
.expect("expected function export");

let e = run_func.call(&[]).err().expect("error calling function");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;

let trace = e.trace();
assert_eq!(trace.len(), 2);
Expand Down Expand Up @@ -111,7 +123,11 @@ fn test_trap_stack_overflow() -> Result<()> {
.func()
.expect("expected function export");

let e = run_func.call(&[]).err().expect("error calling function");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;

let trace = e.trace();
assert!(trace.len() >= 32);
Expand Down Expand Up @@ -315,17 +331,17 @@ fn mismatched_arguments() -> Result<()> {
let instance = Instance::new(&module, &[])?;
let func = instance.exports()[0].func().unwrap().clone();
assert_eq!(
func.call(&[]).unwrap_err().message(),
func.call(&[]).unwrap_err().to_string(),
"expected 1 arguments, got 0"
);
assert_eq!(
func.call(&[Val::F32(0)]).unwrap_err().message(),
func.call(&[Val::F32(0)]).unwrap_err().to_string(),
"argument type mismatch",
);
assert_eq!(
func.call(&[Val::I32(0), Val::I32(1)])
.unwrap_err()
.message(),
.to_string(),
"expected 1 arguments, got 2"
);
Ok(())
Expand Down
Loading

0 comments on commit bd374fd

Please sign in to comment.