Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(c-api) Trap's messages are always null terminated #2444

Merged
merged 4 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/c-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Looking for changes to the Wasmer CLI and the Rust API? See our [Primary Changel

## **[Unreleased]**

### Fixed
- [#2444](https://github.com/wasmerio/wasmer/pull/2444) Trap's messages are always null terminated.

## 2.0.0 - 2020/06/16

## 2.0.0-rc1 - 2020/06/02

### Added
Expand Down
2 changes: 1 addition & 1 deletion lib/c-api/src/wasm_c_api/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub unsafe extern "C" fn wasm_instance_new(
///
/// # Example
///
/// See `wasm_instance_new`.
/// See [`wasm_instance_new`].
#[no_mangle]
pub unsafe extern "C" fn wasm_instance_delete(_instance: Option<Box<wasm_instance_t>>) {}

Expand Down
44 changes: 44 additions & 0 deletions lib/c-api/src/wasm_c_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,50 @@ pub mod module;
/// cbindgen:ignore
pub mod store;

/// A trap represents an error which stores trace message with
/// backtrace.
///
/// # Example
///
/// ```rust
/// # use inline_c::assert_c;
/// # fn main() {
/// # (assert_c! {
/// # #include "tests/wasmer.h"
/// #
/// int main() {
/// // Create an engine and a store.
/// wasm_engine_t* engine = wasm_engine_new();
/// wasm_store_t* store = wasm_store_new(engine);
///
/// // Create the trap message.
/// wasm_message_t message;
/// wasm_name_new_from_string_nt(&message, "foobar");
///
/// // Create the trap with its message.
/// // The backtrace will be generated automatically.
/// wasm_trap_t* trap = wasm_trap_new(store, &message);
/// assert(trap);
///
/// wasm_name_delete(&message);
///
/// // Do something with the trap.
///
/// // Free everything.
/// wasm_trap_delete(trap);
/// wasm_store_delete(store);
/// wasm_engine_delete(engine);
///
/// return 0;
/// }
/// # })
/// # .success();
/// # }
/// ```
///
/// Usually, a trap is returned from a host function (an imported
/// function).
///
/// cbindgen:ignore
pub mod trap;

Expand Down
156 changes: 152 additions & 4 deletions lib/c-api/src/wasm_c_api/trap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::store::wasm_store_t;
use super::types::{wasm_byte_vec_t, wasm_frame_t, wasm_frame_vec_t, wasm_message_t};
use std::str;
use std::ffi::CString;
use wasmer::RuntimeError;

// opaque type which is a `RuntimeError`
Expand All @@ -15,22 +15,98 @@ impl From<RuntimeError> for wasm_trap_t {
}
}

/// Create a new trap message.
///
/// Be careful, the message is typed with `wasm_message_t` which
/// represents a null-terminated string.
///
/// # Example
///
/// See the module's documentation for a complete example.
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_new(
_store: &mut wasm_store_t,
message: &wasm_message_t,
) -> Option<Box<wasm_trap_t>> {
let message_bytes = message.into_slice()?;
let message_str = c_try!(str::from_utf8(message_bytes));
let runtime_error = RuntimeError::new(message_str);

// The trap message is typed with `wasm_message_t` which is a
// typeref to `wasm_name_t` with the exception that it's a
// null-terminated string. `RuntimeError` must contain a valid
// Rust `String` that doesn't contain a null byte. We must ensure
// this behavior.
let runtime_error = match CString::new(message_bytes) {
// The string is well-formed and doesn't contain a nul byte.
Ok(cstring) => RuntimeError::new(cstring.into_string().ok()?),

// The string is well-formed but is nul-terminated. Let's
// create a `String` which is null-terminated too.
Err(nul_error) if nul_error.nul_position() + 1 == message_bytes.len() => {
let mut vec = nul_error.into_vec();
vec.pop();

RuntimeError::new(String::from_utf8(vec).ok()?)
}

// The string not well-formed.
Err(_) => return None,
};

let trap = runtime_error.into();

Some(Box::new(trap))
}

/// Deletes a trap.
///
/// # Example
///
/// See the module's documentation for a complete example.
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_delete(_trap: Option<Box<wasm_trap_t>>) {}

/// Gets the message attached to the trap.
///
/// # Example
///
/// ```rust
/// # use inline_c::assert_c;
/// # fn main() {
/// # (assert_c! {
/// # #include "tests/wasmer.h"
/// #
/// int main() {
/// // Create an engine and a store.
/// wasm_engine_t* engine = wasm_engine_new();
/// wasm_store_t* store = wasm_store_new(engine);
///
/// // Create the trap message.
/// wasm_message_t message;
/// wasm_name_new_from_string_nt(&message, "foobar");
///
/// // Create the trap with its message.
/// // The backtrace will be generated automatically.
/// wasm_trap_t* trap = wasm_trap_new(store, &message);
/// assert(trap);
///
/// // Get the trap's message back.
/// wasm_message_t retrieved_message;
/// wasm_trap_message(trap, &retrieved_message);
/// assert(retrieved_message.size == message.size);
///
/// // Free everything.
/// wasm_name_delete(&message);
/// wasm_name_delete(&retrieved_message);
/// wasm_trap_delete(trap);
/// wasm_store_delete(store);
/// wasm_engine_delete(engine);
///
/// return 0;
/// }
/// # })
/// # .success();
/// # }
/// ```
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_message(
trap: &wasm_trap_t,
Expand All @@ -39,18 +115,21 @@ pub unsafe extern "C" fn wasm_trap_message(
) {
let message = trap.inner.message();
let mut byte_vec = message.into_bytes();
byte_vec.push(0); // append NUL
byte_vec.push(0);

let byte_vec: wasm_byte_vec_t = byte_vec.into();

out.size = byte_vec.size;
out.data = byte_vec.data;
}

/// Gets the origin frame attached to the trap.
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_origin(trap: &wasm_trap_t) -> Option<Box<wasm_frame_t>> {
trap.inner.trace().first().map(Into::into).map(Box::new)
}

/// Gets the trace (as a list of frames) attached to the trap.
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_trace(
trap: &wasm_trap_t,
Expand All @@ -63,3 +142,72 @@ pub unsafe extern "C" fn wasm_trap_trace(
out.size = frame_vec.size;
out.data = frame_vec.data;
}

#[cfg(test)]
mod tests {
use inline_c::assert_c;

#[test]
fn test_trap_message_null_terminated() {
(assert_c! {
#include "tests/wasmer.h"

int main() {
wasm_engine_t* engine = wasm_engine_new();
wasm_store_t* store = wasm_store_new(engine);

wasm_message_t original_message;
wasm_name_new_from_string_nt(&original_message, "foobar");
assert(original_message.size == 7); // 6 for `foobar` + 1 for nul byte.

wasm_trap_t* trap = wasm_trap_new(store, &original_message);
assert(trap);

wasm_message_t retrieved_message;
wasm_trap_message(trap, &retrieved_message);
assert(retrieved_message.size == 7);

wasm_name_delete(&original_message);
wasm_name_delete(&retrieved_message);
wasm_trap_delete(trap);
wasm_store_delete(store);
wasm_engine_delete(engine);

return 0;
}
})
.success();
}

#[test]
fn test_trap_message_not_null_terminated() {
(assert_c! {
#include "tests/wasmer.h"

int main() {
wasm_engine_t* engine = wasm_engine_new();
wasm_store_t* store = wasm_store_new(engine);

wasm_message_t original_message;
wasm_name_new_from_string(&original_message, "foobar");
assert(original_message.size == 6); // 6 for `foobar` + 0 for nul byte.

wasm_trap_t* trap = wasm_trap_new(store, &original_message);
assert(trap);

wasm_message_t retrieved_message;
wasm_trap_message(trap, &retrieved_message);
assert(retrieved_message.size == 7);

wasm_name_delete(&original_message);
wasm_name_delete(&retrieved_message);
wasm_trap_delete(trap);
wasm_store_delete(store);
wasm_engine_delete(engine);

return 0;
}
})
.success();
}
}
2 changes: 1 addition & 1 deletion lib/engine/src/trap/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl RuntimeError {

/// Returns a reference the `message` stored in `Trap`.
pub fn message(&self) -> String {
format!("{}", self.inner.source)
self.inner.source.to_string()
}

/// Returns a list of function frames in WebAssembly code that led to this
Expand Down