diff --git a/CHANGELOG.md b/CHANGELOG.md index fd93984f40b..7a25fe171cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1133](https://github.com/wasmerio/wasmer/pull/1133) New `wasmer_trap` function in the C API, to properly error from within a host function - [#1147](https://github.com/wasmerio/wasmer/pull/1147) Remove `log` and `trace` macros from `wasmer-runtime-core`, remove `debug` and `trace` features from `wasmer-*` crates, use the `log` crate for logging and use `fern` in the Wasmer CLI binary to output log messages. Colorized output will be enabled automatically if printing to a terminal, to force colorization on or off, set the `WASMER_COLOR` environment variable to `true` or `false`. - [#1128](https://github.com/wasmerio/wasmer/pull/1128) Fix a crash when a host function is missing and the `allow_missing_functions` flag is enabled - [#1099](https://github.com/wasmerio/wasmer/pull/1099) Remove `backend::Backend` from `wasmer_runtime_core` diff --git a/lib/runtime-c-api/src/export.rs b/lib/runtime-c-api/src/export.rs index 40d370111b5..5f33748ce3b 100644 --- a/lib/runtime-c-api/src/export.rs +++ b/lib/runtime-c-api/src/export.rs @@ -456,6 +456,7 @@ pub unsafe extern "C" fn wasmer_export_func_call( let instance = &*named_export.instance; let result = instance.call(&named_export.name, ¶ms[..]); + match result { Ok(results_vec) => { if !results_vec.is_empty() { diff --git a/lib/runtime-c-api/src/import/mod.rs b/lib/runtime-c-api/src/import/mod.rs index 1c3c1521c8f..a2044c06041 100644 --- a/lib/runtime-c-api/src/import/mod.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -4,13 +4,20 @@ use crate::{ error::{update_last_error, CApiError}, export::{wasmer_import_export_kind, wasmer_import_export_value}, + instance::wasmer_instance_context_t, module::wasmer_module_t, value::wasmer_value_tag, wasmer_byte_array, wasmer_result_t, }; use libc::c_uint; -use std::{convert::TryFrom, ffi::c_void, ptr, slice, sync::Arc}; -use wasmer_runtime::{Global, Memory, Module, Table}; +use std::{ + convert::TryFrom, + ffi::{c_void, CStr}, + os::raw::c_char, + ptr, slice, + sync::Arc, +}; +use wasmer_runtime::{Ctx, Global, Memory, Module, Table}; use wasmer_runtime_core::{ export::{Context, Export, FuncPointer}, import::{ImportObject, ImportObjectIterator}, @@ -636,9 +643,23 @@ pub unsafe extern "C" fn wasmer_import_func_params_arity( } } -/// Creates new func +/// Creates new host function, aka imported function. `func` is a +/// function pointer, where the first argument is the famous `vm::Ctx` +/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments +/// must be typed with compatible WebAssembly native types: +/// +/// | WebAssembly type | C/C++ type | +/// | ---------------- | ---------- | +/// | `i32` | `int32_t` | +/// | `i64` | `int64_t` | +/// | `f32` | `float` | +/// | `f64` | `double` | +/// +/// The function pointer must have a lifetime greater than the +/// WebAssembly instance lifetime. /// -/// The caller owns the object and should call `wasmer_import_func_destroy` to free it. +/// The caller owns the object and should call +/// `wasmer_import_func_destroy` to free it. #[no_mangle] #[allow(clippy::cast_ptr_alignment)] pub unsafe extern "C" fn wasmer_import_func_new( @@ -661,6 +682,59 @@ pub unsafe extern "C" fn wasmer_import_func_new( Box::into_raw(export) as *mut wasmer_import_func_t } +/// Stop the execution of a host function, aka imported function. The +/// function must be used _only_ inside a host function. +/// +/// The pointer to `wasmer_instance_context_t` is received by the host +/// function as its first argument. Just passing it to `ctx` is fine. +/// +/// The error message must have a greater lifetime than the host +/// function itself since the error is read outside the host function +/// with `wasmer_last_error_message`. +/// +/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or +/// `error_message` are null. +/// +/// This function never returns otherwise. +#[no_mangle] +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn wasmer_trap( + ctx: *const wasmer_instance_context_t, + error_message: *const c_char, +) -> wasmer_result_t { + if ctx.is_null() { + update_last_error(CApiError { + msg: "ctx ptr is null in wasmer_import_trap".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + if error_message.is_null() { + update_last_error(CApiError { + msg: "error_message is null in wasmer_import_trap".to_string(), + }); + + return wasmer_result_t::WASMER_ERROR; + } + + let ctx = &*(ctx as *const Ctx); + let error_message = CStr::from_ptr(error_message).to_str().unwrap(); + + (&*ctx.module) + .runnable_module + .do_early_trap(Box::new(error_message)); // never returns + + // cbindgen does not generate a binding for a function that + // returns `!`. Since we also need to error in some cases, the + // output type of `wasmer_trap` is `wasmer_result_t`. But the OK + // case is not reachable because `do_early_trap` never + // returns. That's a compromise to satisfy the Rust type system, + // cbindgen, and get an acceptable clean code. + #[allow(unreachable_code)] + wasmer_result_t::WASMER_OK +} + /// Sets the params buffer to the parameter types of the given wasmer_import_func_t /// /// Returns `wasmer_result_t::WASMER_OK` upon success. diff --git a/lib/runtime-c-api/tests/.gitignore b/lib/runtime-c-api/tests/.gitignore index b62699da34d..722e91641c0 100644 --- a/lib/runtime-c-api/tests/.gitignore +++ b/lib/runtime-c-api/tests/.gitignore @@ -15,6 +15,7 @@ test-exported-memory test-exports test-globals test-import-function +test-import-trap test-import-object test-imports test-instantiate diff --git a/lib/runtime-c-api/tests/CMakeLists.txt b/lib/runtime-c-api/tests/CMakeLists.txt index f9aecd4d694..58e94077bd1 100644 --- a/lib/runtime-c-api/tests/CMakeLists.txt +++ b/lib/runtime-c-api/tests/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(test-exported-memory test-exported-memory.c) add_executable(test-exports test-exports.c) add_executable(test-globals test-globals.c) add_executable(test-import-function test-import-function.c) +add_executable(test-import-trap test-import-trap.c) add_executable(test-imports test-imports.c) add_executable(test-import-object test-import-object.c) add_executable(test-instantiate test-instantiate.c) @@ -64,6 +65,10 @@ target_link_libraries(test-import-function general ${WASMER_LIB}) target_compile_options(test-import-function PRIVATE ${COMPILER_OPTIONS}) add_test(test-import-function test-import-function) +target_link_libraries(test-import-trap general ${WASMER_LIB}) +target_compile_options(test-import-trap PRIVATE ${COMPILER_OPTIONS}) +add_test(test-import-trap test-import-trap) + target_link_libraries(test-imports general ${WASMER_LIB}) target_compile_options(test-imports PRIVATE ${COMPILER_OPTIONS}) add_test(test-imports test-imports) diff --git a/lib/runtime-c-api/tests/test-import-function.c b/lib/runtime-c-api/tests/test-import-function.c index a6bea2a54c4..a53dec75f99 100644 --- a/lib/runtime-c-api/tests/test-import-function.c +++ b/lib/runtime-c-api/tests/test-import-function.c @@ -31,7 +31,7 @@ void print_str(wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) actual_str[idx] = mem_bytes[ptr + idx]; } actual_str[13] = '\0'; - printf("In print_str, memory len: %d, ptr_len: %d\n, str %s", mem_len, len, actual_str); + printf("In print_str, memory len: %d, ptr_len: %d, str %s\n", mem_len, len, actual_str); print_str_called = true; memory_len = mem_len; ptr_len = len; diff --git a/lib/runtime-c-api/tests/test-import-trap.c b/lib/runtime-c-api/tests/test-import-trap.c new file mode 100644 index 00000000000..0e28208eccc --- /dev/null +++ b/lib/runtime-c-api/tests/test-import-trap.c @@ -0,0 +1,80 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static const char *trap_error_message = "Hello"; + +void print_str(wasmer_instance_context_t *ctx, int32_t _ptr, int32_t _len) +{ + wasmer_trap(ctx, trap_error_message); +} + +int main() +{ + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + + printf("Creating new func\n"); + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0); + + char *module_name = "env"; + wasmer_byte_array module_name_bytes = { + .bytes = (const uint8_t *) module_name, + .bytes_len = strlen(module_name), + }; + + char *import_name = "print_str"; + wasmer_byte_array import_name_bytes = { + .bytes = (const uint8_t *) import_name, + .bytes_len = strlen(import_name), + }; + + wasmer_import_t import = { + .module_name = module_name_bytes, + .import_name = import_name_bytes, + .tag = WASM_FUNCTION, + .value.func = func, + }; + + wasmer_import_t imports[] = {import}; + + // Read the wasm file bytes + FILE *file = fopen("assets/wasm_sample_app.wasm", "r"); + fseek(file, 0, SEEK_END); + long len = ftell(file); + uint8_t *bytes = malloc(len); + fseek(file, 0, SEEK_SET); + fread(bytes, 1, len, file); + fclose(file); + + printf("Instantiating\n"); + wasmer_instance_t *instance = NULL; + wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1); + printf("Compile result: %d\n", compile_result); + + assert(compile_result == WASMER_OK); + + wasmer_value_t params[] = {}; + wasmer_value_t results[] = {}; + wasmer_result_t call_result = wasmer_instance_call(instance, "hello_wasm", params, 0, results, 0); + printf("Call result: %d\n", call_result); + + assert(call_result == WASMER_ERROR); + + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char *error_str = malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); + + assert(0 == strcmp(error_str, "Call error: \"Hello\"")); + + printf("Destroying func\n"); + wasmer_import_func_destroy(func); + printf("Destroy instance\n"); + wasmer_instance_destroy(instance); + + return 0; +} diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 1c3bf4c7ea4..c085f8d841a 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -47,7 +47,7 @@ enum Version { Unknown = 0, /** * Latest version. See `wasmer_wasi::WasiVersion::Latest` to - * leran more. + * learn more. */ Latest = 1, /** @@ -537,9 +537,23 @@ unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports) void wasmer_import_func_destroy(wasmer_import_func_t *func); /** - * Creates new func + * Creates new host function, aka imported function. `func` is a + * function pointer, where the first argument is the famous `vm::Ctx` + * (in Rust), or `wasmer_instance_context_t` (in C). All arguments + * must be typed with compatible WebAssembly native types: * - * The caller owns the object and should call `wasmer_import_func_destroy` to free it. + * | WebAssembly type | C/C++ type | + * | ---------------- | ---------- | + * | `i32` | `int32_t` | + * | `i64` | `int64_t` | + * | `f32` | `float` | + * | `f64` | `double` | + * + * The function pointer must have a lifetime greater than the + * WebAssembly instance lifetime. + * + * The caller owns the object and should call + * `wasmer_import_func_destroy` to free it. */ wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), const wasmer_value_tag *params, @@ -966,6 +980,24 @@ const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(cons void *wasmer_trampoline_get_context(void); #endif +/** + * Stop the execution of a host function, aka imported function. The + * function must be used _only_ inside a host function. + * + * The pointer to `wasmer_instance_context_t` is received by the host + * function as its first argument. Just passing it to `ctx` is fine. + * + * The error message must have a greater lifetime than the host + * function itself since the error is read outside the host function + * with `wasmer_last_error_message`. + * + * This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or + * `error_message` are null. + * + * This function never returns otherwise. + */ +wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message); + /** * Returns true for valid wasm bytes and false for invalid bytes */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 0f4f44441a0..12bc0a9b8a9 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -44,7 +44,7 @@ enum class Version : uint8_t { /// Version cannot be detected or is unknown. Unknown = 0, /// Latest version. See `wasmer_wasi::WasiVersion::Latest` to - /// leran more. + /// learn more. Latest = 1, /// `wasi_unstable`. Snapshot0 = 2, @@ -431,9 +431,23 @@ unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports) /// Frees memory for the given Func void wasmer_import_func_destroy(wasmer_import_func_t *func); -/// Creates new func +/// Creates new host function, aka imported function. `func` is a +/// function pointer, where the first argument is the famous `vm::Ctx` +/// (in Rust), or `wasmer_instance_context_t` (in C). All arguments +/// must be typed with compatible WebAssembly native types: /// -/// The caller owns the object and should call `wasmer_import_func_destroy` to free it. +/// | WebAssembly type | C/C++ type | +/// | ---------------- | ---------- | +/// | `i32` | `int32_t` | +/// | `i64` | `int64_t` | +/// | `f32` | `float` | +/// | `f64` | `double` | +/// +/// The function pointer must have a lifetime greater than the +/// WebAssembly instance lifetime. +/// +/// The caller owns the object and should call +/// `wasmer_import_func_destroy` to free it. wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), const wasmer_value_tag *params, unsigned int params_len, @@ -763,6 +777,22 @@ const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(cons void *wasmer_trampoline_get_context(); #endif +/// Stop the execution of a host function, aka imported function. The +/// function must be used _only_ inside a host function. +/// +/// The pointer to `wasmer_instance_context_t` is received by the host +/// function as its first argument. Just passing it to `ctx` is fine. +/// +/// The error message must have a greater lifetime than the host +/// function itself since the error is read outside the host function +/// with `wasmer_last_error_message`. +/// +/// This function returns `wasmer_result_t::WASMER_ERROR` if `ctx` or +/// `error_message` are null. +/// +/// This function never returns otherwise. +wasmer_result_t wasmer_trap(const wasmer_instance_context_t *ctx, const char *error_message); + /// Returns true for valid wasm bytes and false for invalid bytes bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); diff --git a/lib/runtime-core/src/backend.rs b/lib/runtime-core/src/backend.rs index d98e9442aee..607e72ee3b0 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -149,10 +149,11 @@ pub trait RunnableModule: Send + Sync { } /// A wasm trampoline contains the necessary data to dynamically call an exported wasm function. - /// Given a particular signature index, we are returned a trampoline that is matched with that + /// Given a particular signature index, we return a trampoline that is matched with that /// signature and an invoke function that can call the trampoline. fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option; + /// Trap an error. unsafe fn do_early_trap(&self, data: Box) -> !; /// Returns the machine code associated with this module.