Change proc_exit to unwind the stack rather than exiting the host process. (#1646)
* Remove Cranelift's OutOfBounds trap, which is no longer used. * Change proc_exit to unwind instead of exit the host process. This implements the semantics in https://github.com/WebAssembly/WASI/pull/235. Fixes #783. Fixes #993. * Fix exit-status tests on Windows. * Revert the wiggle changes and re-introduce the wasi-common implementations. * Move `wasi_proc_exit` into the wasmtime-wasi crate. * Revert the spec_testsuite change. * Remove the old proc_exit implementations. * Make `TrapReason` an implementation detail. * Allow exit status 2 on Windows too. * Fix a documentation link. * Really fix a documentation link.
This commit is contained in:
@@ -27,9 +27,6 @@ pub enum TrapCode {
|
|||||||
/// A `table_addr` instruction detected an out-of-bounds error.
|
/// A `table_addr` instruction detected an out-of-bounds error.
|
||||||
TableOutOfBounds,
|
TableOutOfBounds,
|
||||||
|
|
||||||
/// Other bounds checking error.
|
|
||||||
OutOfBounds,
|
|
||||||
|
|
||||||
/// Indirect call to a null table entry.
|
/// Indirect call to a null table entry.
|
||||||
IndirectCallToNull,
|
IndirectCallToNull,
|
||||||
|
|
||||||
@@ -63,7 +60,6 @@ impl Display for TrapCode {
|
|||||||
StackOverflow => "stk_ovf",
|
StackOverflow => "stk_ovf",
|
||||||
HeapOutOfBounds => "heap_oob",
|
HeapOutOfBounds => "heap_oob",
|
||||||
TableOutOfBounds => "table_oob",
|
TableOutOfBounds => "table_oob",
|
||||||
OutOfBounds => "oob",
|
|
||||||
IndirectCallToNull => "icall_null",
|
IndirectCallToNull => "icall_null",
|
||||||
BadSignature => "bad_sig",
|
BadSignature => "bad_sig",
|
||||||
IntegerOverflow => "int_ovf",
|
IntegerOverflow => "int_ovf",
|
||||||
@@ -86,7 +82,6 @@ impl FromStr for TrapCode {
|
|||||||
"stk_ovf" => Ok(StackOverflow),
|
"stk_ovf" => Ok(StackOverflow),
|
||||||
"heap_oob" => Ok(HeapOutOfBounds),
|
"heap_oob" => Ok(HeapOutOfBounds),
|
||||||
"table_oob" => Ok(TableOutOfBounds),
|
"table_oob" => Ok(TableOutOfBounds),
|
||||||
"oob" => Ok(OutOfBounds),
|
|
||||||
"icall_null" => Ok(IndirectCallToNull),
|
"icall_null" => Ok(IndirectCallToNull),
|
||||||
"bad_sig" => Ok(BadSignature),
|
"bad_sig" => Ok(BadSignature),
|
||||||
"int_ovf" => Ok(IntegerOverflow),
|
"int_ovf" => Ok(IntegerOverflow),
|
||||||
@@ -106,11 +101,10 @@ mod tests {
|
|||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
|
|
||||||
// Everything but user-defined codes.
|
// Everything but user-defined codes.
|
||||||
const CODES: [TrapCode; 11] = [
|
const CODES: [TrapCode; 10] = [
|
||||||
TrapCode::StackOverflow,
|
TrapCode::StackOverflow,
|
||||||
TrapCode::HeapOutOfBounds,
|
TrapCode::HeapOutOfBounds,
|
||||||
TrapCode::TableOutOfBounds,
|
TrapCode::TableOutOfBounds,
|
||||||
TrapCode::OutOfBounds,
|
|
||||||
TrapCode::IndirectCallToNull,
|
TrapCode::IndirectCallToNull,
|
||||||
TrapCode::BadSignature,
|
TrapCode::BadSignature,
|
||||||
TrapCode::IntegerOverflow,
|
TrapCode::IntegerOverflow,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ block201:
|
|||||||
return
|
return
|
||||||
|
|
||||||
block202:
|
block202:
|
||||||
trap oob
|
trap heap_oob
|
||||||
}
|
}
|
||||||
; check: v1 = ifcmp_imm v0, 17
|
; check: v1 = ifcmp_imm v0, 17
|
||||||
; check: brif eq v1, block201
|
; check: brif eq v1, block201
|
||||||
@@ -56,7 +56,7 @@ block201:
|
|||||||
return
|
return
|
||||||
|
|
||||||
block202:
|
block202:
|
||||||
trap oob
|
trap heap_oob
|
||||||
}
|
}
|
||||||
; check: v2 = ffcmp v0, v1
|
; check: v2 = ffcmp v0, v1
|
||||||
; check: brff eq v2, block201
|
; check: brff eq v2, block201
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ function %cond_traps(i32) {
|
|||||||
block0(v0: i32):
|
block0(v0: i32):
|
||||||
trapz v0, stk_ovf
|
trapz v0, stk_ovf
|
||||||
v1 = ifcmp_imm v0, 5
|
v1 = ifcmp_imm v0, 5
|
||||||
trapif ugt v1, oob
|
trapif ugt v1, heap_oob
|
||||||
v2 = bitcast.f32 v1
|
v2 = bitcast.f32 v1
|
||||||
v3 = ffcmp v2, v2
|
v3 = ffcmp v2, v2
|
||||||
trapff uno v3, int_ovf
|
trapff uno v3, int_ovf
|
||||||
@@ -233,7 +233,7 @@ block0(v0: i32):
|
|||||||
; nextln: block0(v0: i32):
|
; nextln: block0(v0: i32):
|
||||||
; nextln: trapz v0, stk_ovf
|
; nextln: trapz v0, stk_ovf
|
||||||
; nextln: v1 = ifcmp_imm v0, 5
|
; nextln: v1 = ifcmp_imm v0, 5
|
||||||
; nextln: trapif ugt v1, oob
|
; nextln: trapif ugt v1, heap_oob
|
||||||
; nextln: v2 = bitcast.f32 v1
|
; nextln: v2 = bitcast.f32 v1
|
||||||
; nextln: v3 = ffcmp v2, v2
|
; nextln: v3 = ffcmp v2, v2
|
||||||
; nextln: trapff uno v3, int_ovf
|
; nextln: trapff uno v3, int_ovf
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ block0(v0: i64):
|
|||||||
jump block4
|
jump block4
|
||||||
|
|
||||||
block4:
|
block4:
|
||||||
trap oob
|
trap heap_oob
|
||||||
|
|
||||||
block2:
|
block2:
|
||||||
v8 = load.i64 v5+8
|
v8 = load.i64 v5+8
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ block0(v0: i32):
|
|||||||
br_table v0, block4, jt0
|
br_table v0, block4, jt0
|
||||||
|
|
||||||
block4:
|
block4:
|
||||||
trap oob
|
trap heap_oob
|
||||||
|
|
||||||
block1:
|
block1:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -342,13 +342,6 @@ pub(crate) struct FdEventData<'a> {
|
|||||||
pub(crate) userdata: wasi::__wasi_userdata_t,
|
pub(crate) userdata: wasi::__wasi_userdata_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn proc_exit(_wasi_ctx: &WasiCtx, _memory: &mut [u8], rval: wasi::__wasi_exitcode_t) {
|
|
||||||
trace!("proc_exit(rval={:?})", rval);
|
|
||||||
// TODO: Rather than call std::process::exit here, we should trigger a
|
|
||||||
// stack unwind similar to a trap.
|
|
||||||
std::process::exit(rval as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn proc_raise(
|
pub(crate) fn proc_raise(
|
||||||
_wasi_ctx: &WasiCtx,
|
_wasi_ctx: &WasiCtx,
|
||||||
_memory: &mut [u8],
|
_memory: &mut [u8],
|
||||||
|
|||||||
@@ -813,13 +813,10 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
|||||||
Ok(nevents)
|
Ok(nevents)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just a temporary to ignore the warning which becomes a hard error
|
fn proc_exit(&self, _rval: types::Exitcode) -> std::result::Result<(), ()> {
|
||||||
// in the CI. Once we figure out non-returns in `wiggle`, this should be gone.
|
// proc_exit is special in that it's expected to unwind the stack, which
|
||||||
#[allow(unreachable_code)]
|
// typically requires runtime-specific logic.
|
||||||
fn proc_exit(&self, rval: types::Exitcode) -> std::result::Result<(), ()> {
|
unimplemented!("runtimes are expected to override this implementation")
|
||||||
// TODO: Rather than call std::process::exit here, we should trigger a
|
|
||||||
// stack unwind similar to a trap.
|
|
||||||
std::process::exit(rval as i32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
|
fn proc_raise(&self, _sig: types::Signal) -> Result<()> {
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ pub fn define(args: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
for module in doc.modules() {
|
for module in doc.modules() {
|
||||||
for func in module.funcs() {
|
for func in module.funcs() {
|
||||||
|
// `proc_exit` is special; it's essentially an unwinding primitive,
|
||||||
|
// so we implement it in the runtime rather than use the implementation
|
||||||
|
// in wasi-common.
|
||||||
|
if func.name.as_str() == "proc_exit" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ret.extend(generate_wrappers(&func, old));
|
ret.extend(generate_wrappers(&func, old));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
|
|||||||
linker_add.push(quote! {
|
linker_add.push(quote! {
|
||||||
linker.define(#module_name, #name, self.#name_ident.clone())?;
|
linker.define(#module_name, #name, self.#name_ident.clone())?;
|
||||||
});
|
});
|
||||||
|
// `proc_exit` is special; it's essentially an unwinding primitive,
|
||||||
|
// so we implement it in the runtime rather than use the implementation
|
||||||
|
// in wasi-common.
|
||||||
|
if name == "proc_exit" {
|
||||||
|
ctor_externs.push(quote! {
|
||||||
|
let #name_ident = wasmtime::Func::wrap(store, crate::wasi_proc_exit);
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut shim_arg_decls = Vec::new();
|
let mut shim_arg_decls = Vec::new();
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
@@ -291,6 +300,15 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
|
|||||||
linker_add.push(quote! {
|
linker_add.push(quote! {
|
||||||
linker.define(#module_name, #name, self.#name_ident.clone())?;
|
linker.define(#module_name, #name, self.#name_ident.clone())?;
|
||||||
});
|
});
|
||||||
|
// `proc_exit` is special; it's essentially an unwinding primitive,
|
||||||
|
// so we implement it in the runtime rather than use the implementation
|
||||||
|
// in wasi-common.
|
||||||
|
if name == "proc_exit" {
|
||||||
|
ctor_externs.push(quote! {
|
||||||
|
let #name_ident = wasmtime::Func::wrap(store, crate::wasi_proc_exit);
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut shim_arg_decls = Vec::new();
|
let mut shim_arg_decls = Vec::new();
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use wasmtime::Trap;
|
||||||
|
|
||||||
pub mod old;
|
pub mod old;
|
||||||
|
|
||||||
pub use wasi_common::{WasiCtx, WasiCtxBuilder};
|
pub use wasi_common::{WasiCtx, WasiCtxBuilder};
|
||||||
@@ -12,3 +14,17 @@ pub fn is_wasi_module(name: &str) -> bool {
|
|||||||
// trick.
|
// trick.
|
||||||
name.starts_with("wasi")
|
name.starts_with("wasi")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the WASI `proc_exit` function. This function is implemented here
|
||||||
|
/// instead of in wasi-common so that we can use the runtime to perform an
|
||||||
|
/// unwind rather than exiting the host process.
|
||||||
|
fn wasi_proc_exit(status: i32) -> Result<(), Trap> {
|
||||||
|
// Check that the status is within WASI's range.
|
||||||
|
if status >= 0 && status < 126 {
|
||||||
|
Err(Trap::i32_exit(status))
|
||||||
|
} else {
|
||||||
|
Err(Trap::new(
|
||||||
|
"exit with invalid exit status outside of [0..126)",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ impl Table {
|
|||||||
let src_table = src_table.instance.get_defined_table(src_table_index);
|
let src_table = src_table.instance.get_defined_table(src_table_index);
|
||||||
|
|
||||||
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
|
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
|
||||||
.map_err(Trap::from_jit)?;
|
.map_err(Trap::from_runtime)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use std::mem;
|
|||||||
use std::panic::{self, AssertUnwindSafe};
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Weak;
|
use std::rc::Weak;
|
||||||
|
use wasmtime_runtime::{raise_user_trap, ExportFunction, VMTrampoline};
|
||||||
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
|
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
|
||||||
use wasmtime_runtime::{ExportFunction, VMTrampoline};
|
|
||||||
|
|
||||||
/// A WebAssembly function which can be called.
|
/// A WebAssembly function which can be called.
|
||||||
///
|
///
|
||||||
@@ -747,7 +747,7 @@ pub(crate) fn catch_traps(
|
|||||||
signalhandler.as_deref(),
|
signalhandler.as_deref(),
|
||||||
closure,
|
closure,
|
||||||
)
|
)
|
||||||
.map_err(Trap::from_jit)
|
.map_err(Trap::from_runtime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,7 +941,7 @@ unsafe impl<T: WasmTy> WasmRet for Result<T, Trap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_trap(trap: Trap) -> ! {
|
fn handle_trap(trap: Trap) -> ! {
|
||||||
unsafe { wasmtime_runtime::raise_user_trap(Box::new(trap)) }
|
unsafe { raise_user_trap(trap.into()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,9 +1133,9 @@ macro_rules! impl_into_func {
|
|||||||
R::push(&mut ret);
|
R::push(&mut ret);
|
||||||
let ty = FuncType::new(_args.into(), ret.into());
|
let ty = FuncType::new(_args.into(), ret.into());
|
||||||
let store_weak = store.weak();
|
let store_weak = store.weak();
|
||||||
unsafe {
|
let trampoline = trampoline::<$($args,)* R>;
|
||||||
let trampoline = trampoline::<$($args,)* R>;
|
let (instance, export) = unsafe {
|
||||||
let (instance, export) = crate::trampoline::generate_raw_func_export(
|
crate::trampoline::generate_raw_func_export(
|
||||||
&ty,
|
&ty,
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(
|
||||||
shim::<F, $($args,)* R> as *mut _,
|
shim::<F, $($args,)* R> as *mut _,
|
||||||
@@ -1145,12 +1145,12 @@ macro_rules! impl_into_func {
|
|||||||
store,
|
store,
|
||||||
Box::new((self, store_weak)),
|
Box::new((self, store_weak)),
|
||||||
)
|
)
|
||||||
.expect("failed to generate export");
|
.expect("failed to generate export")
|
||||||
Func {
|
};
|
||||||
instance,
|
Func {
|
||||||
export,
|
instance,
|
||||||
trampoline,
|
export,
|
||||||
}
|
trampoline,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fn instantiate(
|
|||||||
.map_err(|e| -> Error {
|
.map_err(|e| -> Error {
|
||||||
match e {
|
match e {
|
||||||
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
|
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
|
||||||
Trap::from_jit(trap).into()
|
Trap::from_runtime(trap).into()
|
||||||
}
|
}
|
||||||
other => other.into(),
|
other => other.into(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ unsafe extern "C" fn stub_fn(
|
|||||||
|
|
||||||
// If a trap was raised (an error returned from the imported function)
|
// If a trap was raised (an error returned from the imported function)
|
||||||
// then we smuggle the trap through `Box<dyn Error>` through to the
|
// then we smuggle the trap through `Box<dyn Error>` through to the
|
||||||
// call-site, which gets unwrapped in `Trap::from_jit` later on as we
|
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
|
||||||
// convert from the internal `Trap` type to our own `Trap` type in this
|
// convert from the internal `Trap` type to our own `Trap` type in this
|
||||||
// crate.
|
// crate.
|
||||||
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
|
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
|
||||||
|
|||||||
@@ -12,8 +12,27 @@ pub struct Trap {
|
|||||||
inner: Arc<TrapInner>,
|
inner: Arc<TrapInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// State describing the occasion which evoked a trap.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TrapReason {
|
||||||
|
/// An error message describing a trap.
|
||||||
|
Message(String),
|
||||||
|
|
||||||
|
/// An `i32` exit status describing an explicit program exit.
|
||||||
|
I32Exit(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TrapReason {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
TrapReason::Message(s) => write!(f, "{}", s),
|
||||||
|
TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TrapInner {
|
struct TrapInner {
|
||||||
message: String,
|
reason: TrapReason,
|
||||||
wasm_trace: Vec<FrameInfo>,
|
wasm_trace: Vec<FrameInfo>,
|
||||||
native_trace: Backtrace,
|
native_trace: Backtrace,
|
||||||
}
|
}
|
||||||
@@ -34,9 +53,21 @@ impl Trap {
|
|||||||
Trap::new_with_trace(&info, None, message.into(), Backtrace::new_unresolved())
|
Trap::new_with_trace(&info, None, message.into(), Backtrace::new_unresolved())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_jit(jit: wasmtime_runtime::Trap) -> Self {
|
/// Creates a new `Trap` representing an explicit program exit with a classic `i32`
|
||||||
|
/// exit status value.
|
||||||
|
pub fn i32_exit(status: i32) -> Self {
|
||||||
|
Trap {
|
||||||
|
inner: Arc::new(TrapInner {
|
||||||
|
reason: TrapReason::I32Exit(status),
|
||||||
|
wasm_trace: Vec::new(),
|
||||||
|
native_trace: Backtrace::from(Vec::new()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self {
|
||||||
let info = FRAME_INFO.read().unwrap();
|
let info = FRAME_INFO.read().unwrap();
|
||||||
match jit {
|
match runtime_trap {
|
||||||
wasmtime_runtime::Trap::User(error) => {
|
wasmtime_runtime::Trap::User(error) => {
|
||||||
// Since we're the only one using the wasmtime internals (in
|
// Since we're the only one using the wasmtime internals (in
|
||||||
// theory) we should only see user errors which were originally
|
// theory) we should only see user errors which were originally
|
||||||
@@ -85,7 +116,6 @@ impl Trap {
|
|||||||
StackOverflow => "call stack exhausted",
|
StackOverflow => "call stack exhausted",
|
||||||
HeapOutOfBounds => "out of bounds memory access",
|
HeapOutOfBounds => "out of bounds memory access",
|
||||||
TableOutOfBounds => "undefined element: out of bounds table access",
|
TableOutOfBounds => "undefined element: out of bounds table access",
|
||||||
OutOfBounds => "out of bounds",
|
|
||||||
IndirectCallToNull => "uninitialized element",
|
IndirectCallToNull => "uninitialized element",
|
||||||
BadSignature => "indirect call type mismatch",
|
BadSignature => "indirect call type mismatch",
|
||||||
IntegerOverflow => "integer overflow",
|
IntegerOverflow => "integer overflow",
|
||||||
@@ -128,7 +158,7 @@ impl Trap {
|
|||||||
}
|
}
|
||||||
Trap {
|
Trap {
|
||||||
inner: Arc::new(TrapInner {
|
inner: Arc::new(TrapInner {
|
||||||
message,
|
reason: TrapReason::Message(message),
|
||||||
wasm_trace,
|
wasm_trace,
|
||||||
native_trace,
|
native_trace,
|
||||||
}),
|
}),
|
||||||
@@ -136,8 +166,23 @@ impl Trap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference the `message` stored in `Trap`.
|
/// Returns a reference the `message` stored in `Trap`.
|
||||||
|
///
|
||||||
|
/// In the case of an explicit exit, the exit status can be obtained by
|
||||||
|
/// calling `i32_exit_status`.
|
||||||
pub fn message(&self) -> &str {
|
pub fn message(&self) -> &str {
|
||||||
&self.inner.message
|
match &self.inner.reason {
|
||||||
|
TrapReason::Message(message) => message,
|
||||||
|
TrapReason::I32Exit(_) => "explicitly exited",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the trap was the result of an explicit program exit with a classic
|
||||||
|
/// `i32` exit status value, return the value, otherwise return `None`.
|
||||||
|
pub fn i32_exit_status(&self) -> Option<i32> {
|
||||||
|
match self.inner.reason {
|
||||||
|
TrapReason::I32Exit(status) => Some(status),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of function frames in WebAssembly code that led to this
|
/// Returns a list of function frames in WebAssembly code that led to this
|
||||||
@@ -150,7 +195,7 @@ impl Trap {
|
|||||||
impl fmt::Debug for Trap {
|
impl fmt::Debug for Trap {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Trap")
|
f.debug_struct("Trap")
|
||||||
.field("message", &self.inner.message)
|
.field("reason", &self.inner.reason)
|
||||||
.field("wasm_trace", &self.inner.wasm_trace)
|
.field("wasm_trace", &self.inner.wasm_trace)
|
||||||
.field("native_trace", &self.inner.native_trace)
|
.field("native_trace", &self.inner.native_trace)
|
||||||
.finish()
|
.finish()
|
||||||
@@ -159,7 +204,7 @@ impl fmt::Debug for Trap {
|
|||||||
|
|
||||||
impl fmt::Display for Trap {
|
impl fmt::Display for Trap {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.inner.message)?;
|
write!(f, "{}", self.inner.reason)?;
|
||||||
let trace = self.trace();
|
let trace = self.trace();
|
||||||
if trace.is_empty() {
|
if trace.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ impl WastContext {
|
|||||||
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> {
|
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> {
|
||||||
let instance = match self.instantiate(module)? {
|
let instance = match self.instantiate(module)? {
|
||||||
Outcome::Ok(i) => i,
|
Outcome::Ok(i) => i,
|
||||||
Outcome::Trap(e) => bail!("instantiation failed with: {}", e.message()),
|
Outcome::Trap(e) => bail!("instantiation failed: {}", e.message()),
|
||||||
};
|
};
|
||||||
if let Some(name) = instance_name {
|
if let Some(name) = instance_name {
|
||||||
self.linker.instance(name, &instance)?;
|
self.linker.instance(name, &instance)?;
|
||||||
|
|||||||
@@ -142,13 +142,24 @@ impl RunCommand {
|
|||||||
{
|
{
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// If the program exited because of a trap, return an error code
|
// If the program exited because of a non-zero exit status, print
|
||||||
// to the outside environment indicating a more severe problem
|
// a message and exit.
|
||||||
// than a simple failure.
|
if let Some(trap) = e.downcast_ref::<Trap>() {
|
||||||
if e.is::<Trap>() {
|
|
||||||
// Print the error message in the usual way.
|
// Print the error message in the usual way.
|
||||||
eprintln!("Error: {:?}", e);
|
eprintln!("Error: {:?}", e);
|
||||||
|
|
||||||
|
if let Some(status) = trap.i32_exit_status() {
|
||||||
|
// On Windows, exit status 3 indicates an abort (see below),
|
||||||
|
// so return 1 indicating a non-zero status to avoid ambiguity.
|
||||||
|
if cfg!(windows) && status >= 3 {
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
process::exit(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the program exited because of a trap, return an error code
|
||||||
|
// to the outside environment indicating a more severe problem
|
||||||
|
// than a simple failure.
|
||||||
if cfg!(unix) {
|
if cfg!(unix) {
|
||||||
// On Unix, return the error code of an abort.
|
// On Unix, return the error code of an abort.
|
||||||
process::exit(128 + libc::SIGABRT);
|
process::exit(128 + libc::SIGABRT);
|
||||||
|
|||||||
@@ -182,3 +182,77 @@ fn timeout_in_invoke() -> Result<()> {
|
|||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exit with a valid non-zero exit code, snapshot0 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit2_wasi_snapshot0() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit2_wasi_snapshot0.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
assert_eq!(output.status.code().unwrap(), 2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with a valid non-zero exit code, snapshot1 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit2_wasi_snapshot1() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit2_wasi_snapshot1.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
assert_eq!(output.status.code().unwrap(), 2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with a valid non-zero exit code, snapshot0 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit125_wasi_snapshot0() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit125_wasi_snapshot0.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
if cfg!(windows) {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 1);
|
||||||
|
} else {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 125);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with a valid non-zero exit code, snapshot1 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit125_wasi_snapshot1() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit125_wasi_snapshot1.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
if cfg!(windows) {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 1);
|
||||||
|
} else {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 125);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with an invalid non-zero exit code, snapshot0 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit126_wasi_snapshot0() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit126_wasi_snapshot0.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
if cfg!(windows) {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 3);
|
||||||
|
} else {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT);
|
||||||
|
}
|
||||||
|
assert!(output.stdout.is_empty());
|
||||||
|
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with an invalid non-zero exit code, snapshot1 edition.
|
||||||
|
#[test]
|
||||||
|
fn exit126_wasi_snapshot1() -> Result<()> {
|
||||||
|
let wasm = build_wasm("tests/wasm/exit126_wasi_snapshot1.wat")?;
|
||||||
|
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
|
||||||
|
if cfg!(windows) {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 3);
|
||||||
|
} else {
|
||||||
|
assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT);
|
||||||
|
}
|
||||||
|
assert!(output.stdout.is_empty());
|
||||||
|
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ fn trap_smoke() -> Result<()> {
|
|||||||
let f = Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
|
let f = Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
|
||||||
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
|
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
|
||||||
assert_eq!(err.message(), "test");
|
assert_eq!(err.message(), "test");
|
||||||
|
assert!(err.i32_exit_status().is_none());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ fn loops_interruptable() -> anyhow::Result<()> {
|
|||||||
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
|
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
|
||||||
store.interrupt_handle()?.interrupt();
|
store.interrupt_handle()?.interrupt();
|
||||||
let trap = iloop().unwrap_err();
|
let trap = iloop().unwrap_err();
|
||||||
assert!(trap.message().contains("wasm trap: interrupt"));
|
assert!(trap.to_string().contains("wasm trap: interrupt"));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ fn functions_interruptable() -> anyhow::Result<()> {
|
|||||||
store.interrupt_handle()?.interrupt();
|
store.interrupt_handle()?.interrupt();
|
||||||
let trap = iloop().unwrap_err();
|
let trap = iloop().unwrap_err();
|
||||||
assert!(
|
assert!(
|
||||||
trap.message().contains("wasm trap: interrupt"),
|
trap.to_string().contains("wasm trap: interrupt"),
|
||||||
"{}",
|
"{}",
|
||||||
trap.message()
|
trap.to_string()
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -91,9 +91,9 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> {
|
|||||||
let trap = iloop().unwrap_err();
|
let trap = iloop().unwrap_err();
|
||||||
thread.join().unwrap();
|
thread.join().unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
trap.message().contains("wasm trap: interrupt"),
|
trap.to_string().contains("wasm trap: interrupt"),
|
||||||
"bad message: {}",
|
"bad message: {}",
|
||||||
trap.message()
|
trap.to_string()
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -127,9 +127,9 @@ fn function_interrupt_from_afar() -> anyhow::Result<()> {
|
|||||||
let trap = iloop().unwrap_err();
|
let trap = iloop().unwrap_err();
|
||||||
thread.join().unwrap();
|
thread.join().unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
trap.message().contains("wasm trap: interrupt"),
|
trap.to_string().contains("wasm trap: interrupt"),
|
||||||
"bad message: {}",
|
"bad message: {}",
|
||||||
trap.message()
|
trap.to_string()
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ fn host_always_has_some_stack() -> anyhow::Result<()> {
|
|||||||
// has been exhausted.
|
// has been exhausted.
|
||||||
let trap = foo().unwrap_err();
|
let trap = foo().unwrap_err();
|
||||||
assert!(
|
assert!(
|
||||||
trap.message().contains("call stack exhausted"),
|
trap.to_string().contains("call stack exhausted"),
|
||||||
"{}",
|
"{}",
|
||||||
trap.message()
|
trap.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Additionally, however, and this is the crucial test, make sure that the
|
// Additionally, however, and this is the crucial test, make sure that the
|
||||||
|
|||||||
8
tests/wasm/exit125_wasi_snapshot0.wat
Normal file
8
tests/wasm/exit125_wasi_snapshot0.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_unstable" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 125))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
8
tests/wasm/exit125_wasi_snapshot1.wat
Normal file
8
tests/wasm/exit125_wasi_snapshot1.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_snapshot_preview1" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 125))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
8
tests/wasm/exit126_wasi_snapshot0.wat
Normal file
8
tests/wasm/exit126_wasi_snapshot0.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_unstable" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 126))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
8
tests/wasm/exit126_wasi_snapshot1.wat
Normal file
8
tests/wasm/exit126_wasi_snapshot1.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_snapshot_preview1" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 126))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
8
tests/wasm/exit2_wasi_snapshot0.wat
Normal file
8
tests/wasm/exit2_wasi_snapshot0.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_unstable" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 2))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
8
tests/wasm/exit2_wasi_snapshot1.wat
Normal file
8
tests/wasm/exit2_wasi_snapshot1.wat
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(module
|
||||||
|
(import "wasi_snapshot_preview1" "proc_exit"
|
||||||
|
(func $__wasi_proc_exit (param i32)))
|
||||||
|
(func $_start
|
||||||
|
(call $__wasi_proc_exit (i32.const 2))
|
||||||
|
)
|
||||||
|
(export "_start" (func $_start))
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user