use crate::HostError;
use alloc::{boxed::Box, string::String};
use core::fmt::{self, Display};
#[cfg(feature = "std")]
use std::error::Error as StdError;
#[derive(Debug)]
pub struct Trap {
reason: Box<TrapReason>,
}
#[test]
fn trap_size() {
assert_eq!(
core::mem::size_of::<Trap>(),
core::mem::size_of::<*const ()>()
);
}
#[derive(Debug)]
enum TrapReason {
InstructionTrap(TrapCode),
I32Exit(i32),
Message(Box<str>),
Host(Box<dyn HostError>),
}
impl TrapReason {
pub fn i32_exit_status(&self) -> Option<i32> {
if let Self::I32Exit(status) = self {
return Some(*status);
}
None
}
#[inline]
pub fn as_host(&self) -> Option<&dyn HostError> {
if let Self::Host(host_error) = self {
return Some(&**host_error);
}
None
}
#[inline]
pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> {
if let Self::Host(host_error) = self {
return Some(&mut **host_error);
}
None
}
#[inline]
pub fn into_host(self) -> Option<Box<dyn HostError>> {
if let Self::Host(host_error) = self {
return Some(host_error);
}
None
}
#[inline]
pub fn trap_code(&self) -> Option<TrapCode> {
if let Self::InstructionTrap(trap_code) = self {
return Some(*trap_code);
}
None
}
}
impl Trap {
fn with_reason(reason: TrapReason) -> Self {
Self {
reason: Box::new(reason),
}
}
#[cold] pub fn new<T>(message: T) -> Self
where
T: Into<String>,
{
Self::with_reason(TrapReason::Message(message.into().into_boxed_str()))
}
#[inline]
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: HostError,
{
self.reason
.as_host()
.and_then(<(dyn HostError + 'static)>::downcast_ref)
}
#[inline]
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: HostError,
{
self.reason
.as_host_mut()
.and_then(<(dyn HostError + 'static)>::downcast_mut)
}
#[inline]
pub fn downcast<T>(self) -> Option<T>
where
T: HostError,
{
self.reason
.into_host()
.and_then(|error| error.downcast().ok())
.map(|boxed| *boxed)
}
#[cold] pub fn i32_exit(status: i32) -> Self {
Self::with_reason(TrapReason::I32Exit(status))
}
#[inline]
pub fn i32_exit_status(&self) -> Option<i32> {
self.reason.i32_exit_status()
}
#[inline]
pub fn trap_code(&self) -> Option<TrapCode> {
self.reason.trap_code()
}
}
impl From<TrapCode> for Trap {
#[cold] fn from(error: TrapCode) -> Self {
Self::with_reason(TrapReason::InstructionTrap(error))
}
}
impl<E> From<E> for Trap
where
E: HostError,
{
#[inline]
#[cold] fn from(host_error: E) -> Self {
Self::with_reason(TrapReason::Host(Box::new(host_error)))
}
}
impl Display for TrapReason {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InstructionTrap(trap_code) => Display::fmt(trap_code, f),
Self::I32Exit(status) => write!(f, "Exited with i32 exit status {status}"),
Self::Message(message) => write!(f, "{message}"),
Self::Host(host_error) => Display::fmt(host_error, f),
}
}
}
impl Display for Trap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<TrapReason as Display>::fmt(&self.reason, f)
}
}
#[cfg(feature = "std")]
impl StdError for Trap {
fn description(&self) -> &str {
self.trap_code().map_or("", |code| code.trap_message())
}
}
#[derive(Debug, Copy, Clone)]
pub enum TrapCode {
UnreachableCodeReached,
MemoryOutOfBounds,
TableOutOfBounds,
IndirectCallToNull,
IntegerDivisionByZero,
IntegerOverflow,
BadConversionToInteger,
StackOverflow,
BadSignature,
OutOfFuel,
}
impl TrapCode {
pub fn trap_message(&self) -> &'static str {
match self {
Self::UnreachableCodeReached => "wasm `unreachable` instruction executed",
Self::MemoryOutOfBounds => "out of bounds memory access",
Self::TableOutOfBounds => "undefined element: out of bounds table access",
Self::IndirectCallToNull => "uninitialized element 2", Self::IntegerDivisionByZero => "integer divide by zero",
Self::IntegerOverflow => "integer overflow",
Self::BadConversionToInteger => "invalid conversion to integer",
Self::StackOverflow => "call stack exhausted",
Self::BadSignature => "indirect call type mismatch",
Self::OutOfFuel => "all fuel consumed by WebAssembly",
}
}
}
impl Display for TrapCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.trap_message())
}
}