diff --git a/ext/src/ruby_api/func.rs b/ext/src/ruby_api/func.rs index a63d7ff4..78514146 100644 --- a/ext/src/ruby_api/func.rs +++ b/ext/src/ruby_api/func.rs @@ -15,7 +15,7 @@ use magnus::{ use std::cell::UnsafeCell; use wasmtime::{ AsContext, AsContextMut, Caller as CallerImpl, Func as FuncImpl, StoreContext, StoreContextMut, - Trap, Val, + Val, }; /// @yard @@ -168,7 +168,7 @@ impl From<&Func<'_>> for wasmtime::Extern { pub fn make_func_closure( ty: &wasmtime::FuncType, callable: Proc, -) -> impl Fn(CallerImpl<'_, StoreData>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static +) -> impl Fn(CallerImpl<'_, StoreData>, &[Val], &mut [Val]) -> anyhow::Result<()> + Send + Sync + 'static { let ty = ty.to_owned(); let callable = ShareableProc(callable); @@ -182,9 +182,9 @@ pub fn make_func_closure( rparams.push(Value::from(wrapped_caller)).unwrap(); for (i, param) in params.iter().enumerate() { - let rparam = param.to_ruby_value(&store_context).map_err(|e| { - wasmtime::Trap::new(format!("invalid argument at index {}: {}", i, e)) - })?; + let rparam = param + .to_ruby_value(&store_context) + .map_err(|e| anyhow::anyhow!(format!("invalid argument at index {}: {}", i, e)))?; rparams.push(rparam).unwrap(); } @@ -223,7 +223,7 @@ pub fn make_func_closure( } }) .map_err(|e| { - wasmtime::Trap::new(format!( + anyhow::anyhow!(format!( "Error when calling Func {}\n Error: {}", callable.inspect(), e diff --git a/ext/src/ruby_api/store.rs b/ext/src/ruby_api/store.rs index 298b2007..ca2032ce 100644 --- a/ext/src/ruby_api/store.rs +++ b/ext/src/ruby_api/store.rs @@ -5,6 +5,7 @@ use magnus::{ Module, Object, TypedData, Value, QNIL, }; use std::cell::{RefCell, UnsafeCell}; +use std::convert::TryFrom; use wasmtime::{AsContext, AsContextMut, Store as StoreImpl, StoreContext, StoreContextMut}; #[derive(Debug)] @@ -162,10 +163,9 @@ impl<'a> StoreContextValue<'a> { pub fn handle_wasm_error(&self, error: anyhow::Error) -> Error { match self.context_mut() { Ok(mut context) => context.data_mut().take_last_error().unwrap_or_else(|| { - match error.downcast_ref::() { - Some(t) => Trap::from(t.to_owned()).into(), - _ => error!("{}", error), - } + Trap::try_from(error) + .map(|trap| trap.into()) + .unwrap_or_else(|e| error!("{}", e)) }), Err(e) => e, } diff --git a/ext/src/ruby_api/trap.rs b/ext/src/ruby_api/trap.rs index bf3e66a5..3168b2ae 100644 --- a/ext/src/ruby_api/trap.rs +++ b/ext/src/ruby_api/trap.rs @@ -1,23 +1,20 @@ +use std::convert::TryFrom; + use crate::helpers::WrappedStruct; use crate::ruby_api::{errors::base_error, root}; use magnus::Error; use magnus::{ - memoize, method, rb_sys::AsRawValue, DataTypeFunctions, ExceptionClass, Module as _, RModule, - Symbol, TypedData, Value, + memoize, method, rb_sys::AsRawValue, DataTypeFunctions, ExceptionClass, Module as _, Symbol, + TypedData, Value, }; -use wasmtime::TrapCode; pub fn trap_error() -> ExceptionClass { *memoize!(ExceptionClass: root().define_error("Trap", base_error()).unwrap()) } -pub fn trap_code() -> RModule { - *memoize!(RModule: root().define_module("TrapCode").unwrap()) -} - macro_rules! trap_const { ($trap:ident) => { - trap_code().const_get(stringify!($trap)).map(Some) + trap_error().const_get(stringify!($trap)).map(Some) }; } @@ -25,53 +22,77 @@ macro_rules! trap_const { #[magnus(class = "Wasmtime::Trap", size, free_immediatly)] /// @yard pub struct Trap { - inner: wasmtime::Trap, + trap: wasmtime::Trap, + wasm_backtrace: Option, } impl DataTypeFunctions for Trap {} impl Trap { + pub fn new(trap: wasmtime::Trap, wasm_backtrace: Option) -> Self { + Self { + trap, + wasm_backtrace, + } + } + /// @yard - /// Returns the message with backtrace. Example message: + /// Returns a textual description of the trap error, for example: /// wasm trap: wasm `unreachable` instruction executed - /// wasm backtrace: - /// 0: 0x1a - ! /// @return [String] pub fn message(&self) -> String { - self.inner.to_string() + self.trap.to_string() + } + + /// @yard + /// Returns a textual representation of the Wasm backtrce, if it exists. + /// For example: + /// error while executing at wasm backtrace: + /// 0: 0x1a - ! + /// @return [String, nil] + pub fn wasm_backtrace_message(&self) -> Option { + self.wasm_backtrace.as_ref().map(|bt| format!("{}", bt)) } /// @yard /// Returns the trap code as a Symbol, possibly nil if the trap did not /// origin from Wasm code. All possible trap codes are defined as constants on {Trap}. /// @return [Symbol, nil] - pub fn trap_code(&self) -> Result, Error> { - if let Some(code) = self.inner.trap_code() { - match code { - TrapCode::HeapMisaligned => trap_const!(HEAP_MISALIGNED), - TrapCode::TableOutOfBounds => trap_const!(TABLE_OUT_OF_BOUNDS), - TrapCode::IndirectCallToNull => trap_const!(INDIRECT_CALL_TO_NULL), - TrapCode::BadSignature => trap_const!(BAD_SIGNATURE), - TrapCode::IntegerOverflow => trap_const!(INTEGER_OVERFLOW), - TrapCode::IntegerDivisionByZero => trap_const!(INTEGER_DIVISION_BY_ZERO), - TrapCode::BadConversionToInteger => trap_const!(BAD_CONVERSION_TO_INTEGER), - TrapCode::UnreachableCodeReached => trap_const!(UNREACHABLE_CODE_REACHED), - TrapCode::Interrupt => trap_const!(INTERRUPT), - TrapCode::AlwaysTrapAdapter => trap_const!(ALWAYS_TRAP_ADAPTER), - // When adding a trap code here, define a matching constant on Wasmtime::Trap (in Ruby) - _ => trap_const!(UNKNOWN), - } - } else { - Ok(None) + pub fn code(&self) -> Result, Error> { + match self.trap { + wasmtime::Trap::HeapMisaligned => trap_const!(HEAP_MISALIGNED), + wasmtime::Trap::TableOutOfBounds => trap_const!(TABLE_OUT_OF_BOUNDS), + wasmtime::Trap::IndirectCallToNull => trap_const!(INDIRECT_CALL_TO_NULL), + wasmtime::Trap::BadSignature => trap_const!(BAD_SIGNATURE), + wasmtime::Trap::IntegerOverflow => trap_const!(INTEGER_OVERFLOW), + wasmtime::Trap::IntegerDivisionByZero => trap_const!(INTEGER_DIVISION_BY_ZERO), + wasmtime::Trap::BadConversionToInteger => trap_const!(BAD_CONVERSION_TO_INTEGER), + wasmtime::Trap::UnreachableCodeReached => trap_const!(UNREACHABLE_CODE_REACHED), + wasmtime::Trap::Interrupt => trap_const!(INTERRUPT), + wasmtime::Trap::AlwaysTrapAdapter => trap_const!(ALWAYS_TRAP_ADAPTER), + // When adding a trap code here, define a matching constant on Wasmtime::Trap (in Ruby) + _ => trap_const!(UNKNOWN), } } + // pub fn wasm_backtrace(&self) -> Option { + // self.wasm_backtrace.as_ref().map(|backtrace| { + // let array = RArray::with_capacity(backtrace.frames().len()); + // backtrace + // .frames() + // .iter() + // .for_each(|frame| array.push(frame.format()).unwrap()); + + // array + // }) + // } + pub fn inspect(rb_self: WrappedStruct) -> Result { let rs_self = rb_self.get()?; Ok(format!( "#", rb_self.to_value().as_raw(), - Value::from(rs_self.trap_code()?).inspect() + Value::from(rs_self.code()?).inspect() )) } } @@ -85,16 +106,29 @@ impl From for Error { } } -impl From for Trap { - fn from(trap: wasmtime::Trap) -> Self { - Self { inner: trap } +impl TryFrom for Trap { + type Error = anyhow::Error; + + fn try_from(value: anyhow::Error) -> Result { + match value.downcast_ref::() { + Some(trap) => { + let trap = trap.to_owned(); + let bt = value.downcast::(); + Ok(Trap::new(trap, bt.map(Some).unwrap_or(None))) + } + None => Err(value), + } } } pub fn init() -> Result<(), Error> { let class = trap_error(); class.define_method("message", method!(Trap::message, 0))?; - class.define_method("trap_code", method!(Trap::trap_code, 0))?; + class.define_method( + "wasm_backtrace_message", + method!(Trap::wasm_backtrace_message, 0), + )?; + class.define_method("code", method!(Trap::code, 0))?; class.define_method("inspect", method!(Trap::inspect, 0))?; class.define_alias("to_s", "message")?; Ok(()) diff --git a/lib/wasmtime.rb b/lib/wasmtime.rb index 4a4acaad..dbde5627 100644 --- a/lib/wasmtime.rb +++ b/lib/wasmtime.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "wasmtime/version" -require_relative "wasmtime/trap_code" # Tries to require the extension for the given Ruby version first begin @@ -16,5 +15,18 @@ class Error < StandardError; end class ConversionError < Error; end - class Trap < Error; end + class Trap < Error + STACK_OVERFLOW = :stack_overflow + HEAP_MISALIGNED = :heap_misaligned + TABLE_OUT_OF_BOUNDS = :table_out_of_bounds + INDIRECT_CALL_TO_NULL = :indirect_call_to_null + BAD_SIGNATURE = :bad_signature + INTEGER_OVERFLOW = :integer_overflow + INTEGER_DIVISION_BY_ZERO = :integer_division_by_zero + BAD_CONVERSION_TO_INTEGER = :bad_conversion_to_integer + UNREACHABLE_CODE_REACHED = :unreachable_code_reached + INTERRUPT = :interrupt + ALWAYS_TRAP_ADAPTER = :always_trap_adapter + UNKNOWN = :unknown + end end diff --git a/lib/wasmtime/trap_code.rb b/lib/wasmtime/trap_code.rb deleted file mode 100644 index 5512f7bb..00000000 --- a/lib/wasmtime/trap_code.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Wasmtime - module TrapCode - STACK_OVERFLOW = :stack_overflow - HEAP_MISALIGNED = :heap_misaligned - TABLE_OUT_OF_BOUNDS = :table_out_of_bounds - INDIRECT_CALL_TO_NULL = :indirect_call_to_null - BAD_SIGNATURE = :bad_signature - INTEGER_OVERFLOW = :integer_overflow - INTEGER_DIVISION_BY_ZERO = :integer_division_by_zero - BAD_CONVERSION_TO_INTEGER = :bad_conversion_to_integer - UNREACHABLE_CODE_REACHED = :unreachable_code_reached - INTERRUPT = :interrupt - ALWAYS_TRAP_ADAPTER = :always_trap_adapter - UNKNOWN = :unknown - end -end diff --git a/spec/unit/trap_spec.rb b/spec/unit/trap_spec.rb index 79fe0dcc..3cf1cb75 100644 --- a/spec/unit/trap_spec.rb +++ b/spec/unit/trap_spec.rb @@ -9,18 +9,28 @@ module Wasmtime end describe "#message" do - it "has the full message including backtrace" do - expect(trap.message).to eq(<<~MSG) - wasm trap: wasm `unreachable` instruction executed - wasm backtrace: + it "has a short message" do + expect(trap.message).to eq("wasm trap: wasm `unreachable` instruction executed") + end + end + + describe "#message_with_backtrace" do + it "includes the backtrace" do + expect(trap.wasm_backtrace_message).to eq(<<~MSG.rstrip) + error while executing at wasm backtrace: 0: 0x1a - ! MSG end end - describe "#trap_code" do + describe "#wasm_backtrace" do + it "returns an enumerable of trace entries" do + end + end + + describe "#code" do it "returns a symbol matching a constant" do - expect(trap.trap_code).to eq(TrapCode::UNREACHABLE_CODE_REACHED) + expect(trap.code).to eq(Trap::UNREACHABLE_CODE_REACHED) end end