#[cfg(test)]
mod tests;
use crate::{
Context, JsResult, JsString, JsValue,
builtins::{
Array,
error::{Error, ErrorKind},
},
js_string,
object::JsObject,
property::PropertyDescriptor,
realm::Realm,
vm::{
NativeSourceInfo,
shadow_stack::{Backtrace, ErrorStack, ShadowEntry},
},
};
use boa_gc::{Finalize, Trace, custom_trace};
use std::{borrow::Cow, error, fmt};
use thiserror::Error;
#[macro_export]
macro_rules! js_error {
(Error: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::error().with_message($value)
)
};
(Error: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::error()
.with_message(format!($value $(, $args)*))
)
};
(TypeError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::typ().with_message($value)
)
};
(TypeError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::typ()
.with_message(format!($value $(, $args)*))
)
};
(SyntaxError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::syntax().with_message($value)
)
};
(SyntaxError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::syntax().with_message(format!($value $(, $args)*))
)
};
(RangeError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::range().with_message($value)
)
};
(RangeError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::range().with_message(format!($value $(, $args)*))
)
};
(EvalError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::eval().with_message($value)
)
};
(EvalError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::eval().with_message(format!($value $(, $args)*))
)
};
(ReferenceError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::reference().with_message($value)
)
};
(ReferenceError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::reference().with_message(format!($value $(, $args)*))
)
};
(URIError: $value: literal) => {
$crate::JsError::from_native(
$crate::JsNativeError::uri().with_message($value)
)
};
(URIError: $value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_native(
$crate::JsNativeError::uri().with_message(format!($value $(, $args)*))
)
};
($value: literal) => {
$crate::JsError::from_opaque($crate::JsValue::from(
$crate::js_string!($value)
))
};
($value: expr) => {
$crate::JsError::from_opaque(
$crate::JsValue::from($value)
)
};
($value: literal $(, $args: expr)* $(,)?) => {
$crate::JsError::from_opaque($crate::JsValue::from(
$crate::JsString::from(format!($value $(, $args)*))
))
};
}
#[derive(Debug, Clone, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct JsError {
inner: Repr,
pub(crate) backtrace: Option<Backtrace>,
}
impl Eq for JsError {}
impl PartialEq for JsError {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
enum Repr {
Opaque(JsValue),
Native(Box<JsNativeError>),
Engine(EngineError),
}
impl error::Error for JsError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self.inner {
Repr::Native(err) => err.source(),
Repr::Opaque(_) => None,
Repr::Engine(err) => err.source(),
}
}
}
#[derive(Debug, Clone, Error)]
pub enum TryNativeError {
#[error("invalid type of property `{0}`")]
InvalidPropertyType(&'static str),
#[error("property `message` cannot contain unpaired surrogates")]
InvalidMessageEncoding,
#[error("invalid `constructor` property of Error object")]
InvalidConstructor,
#[error("could not access property `{property}`")]
InaccessibleProperty {
property: &'static str,
source: JsError,
},
#[error("could not get element `{index}` of property `errors`")]
InvalidErrorsIndex {
index: u64,
source: JsError,
},
#[error("opaque error of type `{:?}` is not an Error object", .0.get_type())]
NotAnErrorObject(JsValue),
#[error("could not access realm of Error object")]
InaccessibleRealm {
source: JsError,
},
#[error("could not convert engine error into a native error")]
EngineError {
source: EngineError,
},
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Error, Trace, Finalize)]
#[boa_gc(empty_trace)]
pub enum RuntimeLimitError {
#[error("reached the maximum number of iteration loops on this execution")]
LoopIteration,
#[error("reached the maximum number of recursive calls on this execution")]
Recursion,
#[error("reached the maximum stack size on this execution")]
StackSize,
}
#[derive(Debug, Clone, Error, Eq, PartialEq, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
#[error("{message}")]
#[must_use]
pub struct PanicError {
message: Box<str>,
source: Option<Box<JsError>>,
}
impl PanicError {
pub fn new<S: Into<Box<str>>>(message: S) -> Self {
PanicError {
message: message.into(),
source: None,
}
}
pub fn with_source<E: Into<JsError>>(mut self, source: E) -> Self {
self.source = Some(Box::new(source.into()));
self
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl From<PanicError> for JsError {
fn from(err: PanicError) -> Self {
EngineError::from(err).into()
}
}
#[derive(Debug, Clone, Error, Eq, PartialEq, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
#[allow(variant_size_differences)]
pub enum EngineError {
#[cfg(feature = "fuzz")]
#[error("NoInstructionsRemainError: instruction budget was exhausted")]
NoInstructionsRemain,
#[error("RuntimeLimitError: {0}")]
RuntimeLimit(#[from] RuntimeLimitError),
#[error("EnginePanic: {0}")]
Panic(#[from] PanicError),
}
impl EngineError {
fn into_erased(self, context: &mut Context) -> ErasedEngineError {
match self {
#[cfg(feature = "fuzz")]
EngineError::NoInstructionsRemain => ErasedEngineError::NoInstructionsRemain,
EngineError::RuntimeLimit(err) => ErasedEngineError::RuntimeLimit(err),
EngineError::Panic(err) => ErasedEngineError::Panic(ErasedPanicError {
message: err.message,
source: err.source.map(|err| Box::new(err.into_erased(context))),
}),
}
}
}
impl JsError {
#[must_use]
pub fn from_native(err: JsNativeError) -> Self {
Self {
inner: Repr::Native(Box::new(err)),
backtrace: None,
}
}
#[must_use]
pub fn from_rust(err: impl error::Error) -> Self {
let mut native_err = JsNativeError::error().with_message(err.to_string());
if let Some(source) = err.source() {
native_err = native_err.with_cause(Self::from_rust(source));
}
Self::from_native(native_err)
}
#[must_use]
pub fn from_opaque(value: JsValue) -> Self {
let backtrace = value.as_object().and_then(|obj| {
let error = obj.downcast_ref::<Error>()?;
error.stack.0.backtrace().cloned()
});
Self {
inner: Repr::Opaque(value),
backtrace,
}
}
pub fn into_opaque(self, context: &mut Context) -> JsResult<JsValue> {
match self.inner {
Repr::Native(e) => {
let obj = e.into_opaque(context);
if let Some(backtrace) = self.backtrace
&& let Some(mut error) = obj.downcast_mut::<Error>()
{
error.stack = IgnoreEq(ErrorStack::Backtrace(backtrace));
}
Ok(obj.into())
}
Repr::Opaque(v) => {
if let Some(backtrace) = self.backtrace
&& let Some(obj) = v.as_object()
&& let Some(mut error) = obj.downcast_mut::<Error>()
&& !error.stack.0.is_backtrace()
{
error.stack = IgnoreEq(ErrorStack::Backtrace(backtrace));
}
Ok(v.clone())
}
Repr::Engine(_) => Err(self),
}
}
pub fn try_native(&self, context: &mut Context) -> Result<JsNativeError, TryNativeError> {
match &self.inner {
Repr::Engine(e) => Err(TryNativeError::EngineError { source: e.clone() }),
Repr::Native(e) => Ok(e.as_ref().clone()),
Repr::Opaque(val) => {
let obj = val
.as_object()
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?;
let error_data: Error = obj
.downcast_ref::<Error>()
.ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?
.clone();
let try_get_property = |key: JsString, name, context: &mut Context| {
obj.try_get(key, context)
.map_err(|e| TryNativeError::InaccessibleProperty {
property: name,
source: e,
})
};
let message = if let Some(msg) =
try_get_property(js_string!("message"), "message", context)?
{
Cow::Owned(
msg.as_string()
.as_ref()
.map(JsString::to_std_string)
.transpose()
.map_err(|_| TryNativeError::InvalidMessageEncoding)?
.ok_or(TryNativeError::InvalidPropertyType("message"))?,
)
} else {
Cow::Borrowed("")
};
let cause = try_get_property(js_string!("cause"), "cause", context)?;
let location = error_data.stack.clone();
let kind = match error_data.tag {
ErrorKind::Error => JsNativeErrorKind::Error,
ErrorKind::Eval => JsNativeErrorKind::Eval,
ErrorKind::Type => JsNativeErrorKind::Type,
ErrorKind::Range => JsNativeErrorKind::Range,
ErrorKind::Reference => JsNativeErrorKind::Reference,
ErrorKind::Syntax => JsNativeErrorKind::Syntax,
ErrorKind::Uri => JsNativeErrorKind::Uri,
ErrorKind::Aggregate => {
let errors = obj.get(js_string!("errors"), context).map_err(|e| {
TryNativeError::InaccessibleProperty {
property: "errors",
source: e,
}
})?;
let mut error_list = Vec::new();
match errors.as_object() {
Some(errors) if errors.is_array() => {
let length = errors.length_of_array_like(context).map_err(|e| {
TryNativeError::InaccessibleProperty {
property: "errors.length",
source: e,
}
})?;
error_list.reserve(length as usize);
for i in 0..length {
error_list.push(Self::from_opaque(
errors.get(i, context).map_err(|e| {
TryNativeError::InvalidErrorsIndex {
index: i,
source: e,
}
})?,
));
}
}
_ => return Err(TryNativeError::InvalidPropertyType("errors")),
}
JsNativeErrorKind::Aggregate(error_list)
}
};
let realm = try_get_property(js_string!("constructor"), "constructor", context)?
.as_ref()
.and_then(JsValue::as_constructor)
.ok_or(TryNativeError::InvalidConstructor)?
.get_function_realm(context)
.map_err(|err| TryNativeError::InaccessibleRealm { source: err })?;
Ok(JsNativeError {
kind,
message,
cause: cause.map(|v| Box::new(Self::from_opaque(v))),
realm: Some(realm),
stack: location,
})
}
}
}
#[must_use]
pub const fn as_opaque(&self) -> Option<&JsValue> {
match self.inner {
Repr::Native(_) | Repr::Engine(_) => None,
Repr::Opaque(ref v) => Some(v),
}
}
#[must_use]
pub const fn as_native(&self) -> Option<&JsNativeError> {
match &self.inner {
Repr::Native(e) => Some(e),
Repr::Opaque(_) | Repr::Engine(_) => None,
}
}
#[must_use]
pub const fn as_native_mut(&mut self) -> Option<&mut JsNativeError> {
match &mut self.inner {
Repr::Native(e) => Some(e),
Repr::Opaque(_) | Repr::Engine(_) => None,
}
}
#[must_use]
pub const fn as_engine(&self) -> Option<&EngineError> {
match &self.inner {
Repr::Opaque(_) | Repr::Native(_) => None,
Repr::Engine(err) => Some(err),
}
}
pub fn into_erased(self, context: &mut Context) -> JsErasedError {
let native = match self.try_native(context) {
Ok(native) => native,
Err(TryNativeError::EngineError { source }) => {
return JsErasedError {
inner: ErasedRepr::Engine(source.into_erased(context)),
};
}
Err(_) => {
return JsErasedError {
inner: ErasedRepr::Opaque(Cow::Owned(self.to_string())),
};
}
};
let JsNativeError {
kind,
message,
cause,
..
} = native;
let cause = cause.map(|err| Box::new(err.into_erased(context)));
let kind = match kind {
JsNativeErrorKind::Aggregate(errors) => JsErasedNativeErrorKind::Aggregate(
errors
.into_iter()
.map(|err| err.into_erased(context))
.collect(),
),
JsNativeErrorKind::Error => JsErasedNativeErrorKind::Error,
JsNativeErrorKind::Eval => JsErasedNativeErrorKind::Eval,
JsNativeErrorKind::Range => JsErasedNativeErrorKind::Range,
JsNativeErrorKind::Reference => JsErasedNativeErrorKind::Reference,
JsNativeErrorKind::Syntax => JsErasedNativeErrorKind::Syntax,
JsNativeErrorKind::Type => JsErasedNativeErrorKind::Type,
JsNativeErrorKind::Uri => JsErasedNativeErrorKind::Uri,
};
JsErasedError {
inner: ErasedRepr::Native(JsErasedNativeError {
kind,
message,
cause,
}),
}
}
pub(crate) fn inject_realm(mut self, realm: Realm) -> Self {
match &mut self.inner {
Repr::Native(err) if err.realm.is_none() => {
err.realm = Some(realm);
}
_ => {}
}
self
}
#[inline]
pub(crate) const fn is_catchable(&self) -> bool {
self.as_engine().is_none()
}
}
impl From<boa_parser::Error> for JsError {
#[cfg_attr(feature = "native-backtrace", track_caller)]
fn from(err: boa_parser::Error) -> Self {
Self::from(JsNativeError::from(err))
}
}
impl From<JsNativeError> for JsError {
fn from(error: JsNativeError) -> Self {
Self {
inner: Repr::Native(Box::new(error)),
backtrace: None,
}
}
}
impl From<EngineError> for JsError {
fn from(value: EngineError) -> Self {
Self {
inner: Repr::Engine(value),
backtrace: None,
}
}
}
impl From<RuntimeLimitError> for JsError {
fn from(value: RuntimeLimitError) -> Self {
EngineError::from(value).into()
}
}
impl fmt::Display for JsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
Repr::Native(e) => e.fmt(f)?,
Repr::Engine(e) => e.fmt(f)?,
Repr::Opaque(v) => v.display().fmt(f)?,
}
if let Some(shadow_stack) = &self.backtrace {
for entry in shadow_stack.iter().rev() {
write!(f, "\n at {}", entry.display(true))?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, Finalize)]
pub(crate) struct IgnoreEq<T>(pub(crate) T);
impl<T> Eq for IgnoreEq<T> {}
impl<T> PartialEq for IgnoreEq<T> {
#[inline]
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> std::hash::Hash for IgnoreEq<T> {
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
}
impl<T> From<T> for IgnoreEq<T> {
#[inline]
fn from(value: T) -> Self {
Self(value)
}
}
#[derive(Clone, Finalize, Error, PartialEq, Eq)]
pub struct JsNativeError {
kind: JsNativeErrorKind,
message: Cow<'static, str>,
#[source]
cause: Option<Box<JsError>>,
realm: Option<Realm>,
pub(crate) stack: IgnoreEq<ErrorStack>,
}
impl fmt::Display for JsNativeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
let message = self.message.trim();
if !message.is_empty() {
write!(f, ": {message}")?;
}
if let Some(entry) = self.stack.0.position() {
write!(f, "{}", entry.display(false))?;
}
Ok(())
}
}
unsafe impl Trace for JsNativeError {
custom_trace!(this, mark, {
mark(&this.kind);
mark(&this.cause);
mark(&this.realm);
});
}
impl fmt::Debug for JsNativeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JsNativeError")
.field("kind", &self.kind)
.field("message", &self.message)
.field("cause", &self.cause)
.finish_non_exhaustive()
}
}
impl JsNativeError {
#[must_use]
#[inline]
pub const fn kind(&self) -> &JsNativeErrorKind {
&self.kind
}
#[cfg_attr(feature = "native-backtrace", track_caller)]
const fn new(
kind: JsNativeErrorKind,
message: Cow<'static, str>,
cause: Option<Box<JsError>>,
) -> Self {
if let Some(cause) = &cause
&& cause.as_engine().is_some()
{
panic!("engine errors cannot be used as the cause of another error");
}
Self {
kind,
message,
cause,
realm: None,
stack: IgnoreEq(ErrorStack::Position(ShadowEntry::Native {
function_name: None,
source_info: NativeSourceInfo::caller(),
})),
}
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn aggregate(errors: Vec<JsError>) -> Self {
Self::new(
JsNativeErrorKind::Aggregate(errors),
Cow::Borrowed(""),
None,
)
}
#[must_use]
#[inline]
pub const fn is_aggregate(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Aggregate(_))
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn error() -> Self {
Self::new(JsNativeErrorKind::Error, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_error(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Error)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn eval() -> Self {
Self::new(JsNativeErrorKind::Eval, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_eval(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Eval)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn range() -> Self {
Self::new(JsNativeErrorKind::Range, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_range(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Range)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn reference() -> Self {
Self::new(JsNativeErrorKind::Reference, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_reference(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Reference)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn syntax() -> Self {
Self::new(JsNativeErrorKind::Syntax, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_syntax(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Syntax)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn typ() -> Self {
Self::new(JsNativeErrorKind::Type, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_type(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Type)
}
#[must_use]
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub const fn uri() -> Self {
Self::new(JsNativeErrorKind::Uri, Cow::Borrowed(""), None)
}
#[must_use]
#[inline]
pub const fn is_uri(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Uri)
}
#[must_use]
#[inline]
pub fn with_message<S>(mut self, message: S) -> Self
where
S: Into<Cow<'static, str>>,
{
self.message = message.into();
self
}
#[must_use]
#[inline]
pub fn with_cause<V>(mut self, cause: V) -> Self
where
V: Into<JsError>,
{
let err = cause.into();
assert!(
err.is_catchable(),
"uncatchable errors cannot be used as the cause of another error",
);
self.cause = Some(Box::new(err));
self
}
#[must_use]
#[inline]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
#[inline]
pub fn cause(&self) -> Option<&JsError> {
self.cause.as_deref()
}
#[inline]
pub fn into_opaque(self, context: &mut Context) -> JsObject {
let Self {
kind,
message,
cause,
realm,
stack,
} = self;
let constructors = realm.as_ref().map_or_else(
|| context.intrinsics().constructors(),
|realm| realm.intrinsics().constructors(),
);
let (prototype, tag) = match kind {
JsNativeErrorKind::Aggregate(_) => (
constructors.aggregate_error().prototype(),
ErrorKind::Aggregate,
),
JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorKind::Error),
JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorKind::Eval),
JsNativeErrorKind::Range => (constructors.range_error().prototype(), ErrorKind::Range),
JsNativeErrorKind::Reference => (
constructors.reference_error().prototype(),
ErrorKind::Reference,
),
JsNativeErrorKind::Syntax => {
(constructors.syntax_error().prototype(), ErrorKind::Syntax)
}
JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type),
JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri),
};
let o = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
Error::with_stack(tag, stack.0.clone()),
)
.upcast();
o.create_non_enumerable_data_property_or_throw(
js_string!("message"),
js_string!(message.as_ref()),
context,
);
if let Some(cause) = cause {
o.create_non_enumerable_data_property_or_throw(
js_string!("cause"),
cause
.into_opaque(context)
.expect("engine errors cannot be the cause of another error"),
context,
);
}
if let JsNativeErrorKind::Aggregate(errors) = kind {
let errors = errors
.into_iter()
.map(|e| {
e.into_opaque(context)
.expect("engine errors cannot be the cause of another error")
})
.collect::<Vec<_>>();
let errors = Array::create_array_from_list(errors, context);
o.define_property_or_throw(
js_string!("errors"),
PropertyDescriptor::builder()
.configurable(true)
.enumerable(false)
.writable(true)
.value(errors),
context,
)
.expect("The spec guarantees this succeeds for a newly created object ");
}
o
}
pub(crate) fn with_realm(mut self, realm: Realm) -> Self {
self.realm = Some(realm);
self
}
}
impl From<boa_parser::Error> for JsNativeError {
#[cfg_attr(feature = "native-backtrace", track_caller)]
fn from(err: boa_parser::Error) -> Self {
Self::syntax().with_message(err.to_string())
}
}
#[derive(Debug, Clone, Finalize, PartialEq, Eq)]
#[non_exhaustive]
pub enum JsNativeErrorKind {
Aggregate(Vec<JsError>),
Error,
Eval,
Range,
Reference,
Syntax,
Type,
Uri,
}
unsafe impl Trace for JsNativeErrorKind {
custom_trace!(
this,
mark,
match &this {
Self::Aggregate(errors) => mark(errors),
Self::Error
| Self::Eval
| Self::Range
| Self::Reference
| Self::Syntax
| Self::Type
| Self::Uri => {}
}
);
}
impl PartialEq<ErrorKind> for JsNativeErrorKind {
fn eq(&self, other: &ErrorKind) -> bool {
matches!(
(self, other),
(Self::Aggregate(_), ErrorKind::Aggregate)
| (Self::Error, ErrorKind::Error)
| (Self::Eval, ErrorKind::Eval)
| (Self::Range, ErrorKind::Range)
| (Self::Reference, ErrorKind::Reference)
| (Self::Syntax, ErrorKind::Syntax)
| (Self::Type, ErrorKind::Type)
| (Self::Uri, ErrorKind::Uri)
)
}
}
impl fmt::Display for JsNativeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Aggregate(_) => "AggregateError",
Self::Error => "Error",
Self::Eval => "EvalError",
Self::Range => "RangeError",
Self::Reference => "ReferenceError",
Self::Syntax => "SyntaxError",
Self::Type => "TypeError",
Self::Uri => "UriError",
}
.fmt(f)
}
}
#[derive(Debug, Clone, Error, Eq, PartialEq, Trace, Finalize)]
#[error("{message}")]
#[must_use]
pub struct ErasedPanicError {
message: Box<str>,
source: Option<Box<JsErasedError>>,
}
impl ErasedPanicError {
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Debug, Clone, Error, Eq, PartialEq, Trace, Finalize)]
#[allow(variant_size_differences)]
pub enum ErasedEngineError {
#[cfg(feature = "fuzz")]
#[error("NoInstructionsRemainError: instruction budget was exhausted")]
NoInstructionsRemain,
#[error("RuntimeLimitError: {0}")]
RuntimeLimit(#[from] RuntimeLimitError),
#[error("EnginePanic: {0}")]
Panic(#[from] ErasedPanicError),
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
pub struct JsErasedError {
inner: ErasedRepr,
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
enum ErasedRepr {
Native(JsErasedNativeError),
Opaque(Cow<'static, str>),
Engine(ErasedEngineError),
}
impl fmt::Display for JsErasedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
ErasedRepr::Native(e) => e.fmt(f),
ErasedRepr::Opaque(v) => v.fmt(f),
ErasedRepr::Engine(e) => e.fmt(f),
}
}
}
impl error::Error for JsErasedError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self.inner {
ErasedRepr::Native(err) => err.source(),
ErasedRepr::Opaque(_) => None,
ErasedRepr::Engine(err) => err.source(),
}
}
}
impl JsErasedError {
#[must_use]
pub fn as_opaque(&self) -> Option<&str> {
match &self.inner {
ErasedRepr::Native(_) | ErasedRepr::Engine(_) => None,
ErasedRepr::Opaque(v) => Some(v),
}
}
#[must_use]
pub const fn as_native(&self) -> Option<&JsErasedNativeError> {
match &self.inner {
ErasedRepr::Native(e) => Some(e),
ErasedRepr::Opaque(_) | ErasedRepr::Engine(_) => None,
}
}
#[must_use]
pub const fn as_engine(&self) -> Option<&ErasedEngineError> {
match &self.inner {
ErasedRepr::Engine(e) => Some(e),
ErasedRepr::Opaque(_) | ErasedRepr::Native(_) => None,
}
}
}
#[derive(Debug, Clone, Trace, Finalize, Error, PartialEq, Eq)]
pub struct JsErasedNativeError {
pub kind: JsErasedNativeErrorKind,
message: Cow<'static, str>,
#[source]
cause: Option<Box<JsErasedError>>,
}
impl fmt::Display for JsErasedNativeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
let message = self.message.trim();
if !message.is_empty() {
write!(f, ": {message}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
#[non_exhaustive]
pub enum JsErasedNativeErrorKind {
Aggregate(Vec<JsErasedError>),
Error,
Eval,
Range,
Reference,
Syntax,
Type,
Uri,
RuntimeLimit,
}
impl fmt::Display for JsErasedNativeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Self::Aggregate(errors) => {
return write!(f, "AggregateError(error count: {})", errors.len());
}
Self::Error => "Error",
Self::Eval => "EvalError",
Self::Range => "RangeError",
Self::Reference => "ReferenceError",
Self::Syntax => "SyntaxError",
Self::Type => "TypeError",
Self::Uri => "UriError",
Self::RuntimeLimit => "RuntimeLimit",
}
.fmt(f)
}
}