diff --git a/src/commands/run.rs b/src/commands/run.rs index 7407f83c49f1..c0e9ca13c0fd 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -6,10 +6,11 @@ use std::{ ffi::{OsStr, OsString}, fs::File, path::{Component, Path, PathBuf}, + process, }; use structopt::{clap::AppSettings, StructOpt}; use wasi_common::preopen_dir; -use wasmtime::{Engine, Instance, Module, Store}; +use wasmtime::{Engine, Instance, Module, Store, Trap}; use wasmtime_interface_types::ModuleData; use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi}; @@ -113,8 +114,31 @@ impl RunCommand { } // Load the main wasm module. - self.handle_module(&store, &module_registry) - .with_context(|| format!("failed to run main module `{}`", self.module.display()))?; + match self + .handle_module(&store, &module_registry) + .with_context(|| format!("failed to run main module `{}`", self.module.display())) + { + Ok(()) => (), + Err(e) => { + // 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 e.is::() { + // Print the error message in the usual way. + eprintln!("Error: {:?}", e); + + if cfg!(unix) { + // On Unix, return the error code of an abort. + process::exit(128 + libc::SIGABRT); + } else if cfg!(windows) { + // On Windows, return 3. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 + process::exit(3); + } + } + return Err(e); + } + } Ok(()) } diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs index 3387158d5632..d13970504fd0 100644 --- a/tests/cli_tests.rs +++ b/tests/cli_tests.rs @@ -1,15 +1,22 @@ use anyhow::{bail, Result}; use std::io::Write; use std::path::Path; -use std::process::Command; +use std::process::{Command, Output}; use tempfile::NamedTempFile; -fn run_wasmtime(args: &[&str]) -> Result { +// Run the wasmtime CLI with the provided args and return the `Output`. +fn run_wasmtime_for_output(args: &[&str]) -> Result { let mut me = std::env::current_exe()?; me.pop(); // chop off the file name me.pop(); // chop off `deps` me.push("wasmtime"); - let output = Command::new(&me).args(args).output()?; + Command::new(&me).args(args).output().map_err(Into::into) +} + +// Run the wasmtime CLI with the provided args and, if it succeeds, return +// the standard output in a `String`. +fn run_wasmtime(args: &[&str]) -> Result { + let output = run_wasmtime_for_output(args)?; if !output.status.success() { bail!( "Failed to execute wasmtime with: {:?}\n{}", @@ -74,3 +81,27 @@ fn run_wasmtime_simple_wat() -> Result<()> { ])?; Ok(()) } + +// Running a wat that traps. +#[test] +fn run_wasmtime_unreachable_wat() -> Result<()> { + let wasm = build_wasm("tests/wasm/unreachable.wat")?; + let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; + + assert_ne!(output.stderr, b""); + assert_eq!(output.stdout, b""); + assert!(!output.status.success()); + + let code = output + .status + .code() + .expect("wasmtime process should exit normally"); + + // Test for the specific error code Wasmtime uses to indicate a trap return. + #[cfg(unix)] + assert_eq!(code, 128 + libc::SIGABRT); + #[cfg(windows)] + assert_eq!(code, 3); + + Ok(()) +} diff --git a/tests/wasm/unreachable.wat b/tests/wasm/unreachable.wat new file mode 100644 index 000000000000..0a81de37f178 --- /dev/null +++ b/tests/wasm/unreachable.wat @@ -0,0 +1,5 @@ +(module + (func (export "_start") + unreachable + ) +)