boa_engine/
error.rs

1//! Error-related types and conversions.
2
3use crate::{
4    Context, JsString, JsValue,
5    builtins::{
6        Array,
7        error::{Error, ErrorKind},
8    },
9    js_string,
10    object::JsObject,
11    property::PropertyDescriptor,
12    realm::Realm,
13    vm::{
14        NativeSourceInfo,
15        shadow_stack::{Backtrace, ShadowEntry},
16    },
17};
18use boa_gc::{Finalize, Trace, custom_trace};
19use std::{
20    borrow::Cow,
21    error,
22    fmt::{self},
23};
24use thiserror::Error;
25
26/// Create an error object from a value or string literal. Optionally the
27/// first argument of the macro can be a type of error (such as `TypeError`,
28/// `RangeError` or `InternalError`).
29///
30/// Can be used with an expression that converts into `JsValue` or a format
31/// string with arguments.
32///
33/// # Native Errors
34///
35/// The only native error that is not buildable using this macro is
36/// `AggregateError`, which requires multiple error objects available at
37/// construction.
38///
39/// [`InternalError`][mdn] is non-standard and unsupported in Boa.
40///
41/// All other native error types can be created from the macro using their
42/// JavaScript name followed by a colon, like:
43///
44/// ```ignore
45/// js_error!(TypeError: "hello world");
46/// ```
47///
48/// # Examples
49///
50/// ```
51/// # use boa_engine::{js_str, Context, JsValue};
52/// use boa_engine::js_error;
53/// let context = &mut Context::default();
54///
55/// let error = js_error!("error!");
56/// assert!(error.as_opaque().is_some());
57/// assert_eq!(
58///     error.as_opaque().unwrap().to_string(context).unwrap(),
59///     "error!"
60/// );
61///
62/// let error = js_error!("error: {}", 5);
63/// assert_eq!(
64///     error.as_opaque().unwrap().to_string(context).unwrap(),
65///     "error: 5"
66/// );
67///
68/// // Non-string literals must be used as an expression.
69/// let error = js_error!({ true });
70/// assert_eq!(error.as_opaque().unwrap(), &JsValue::from(true));
71/// ```
72///
73/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError
74#[macro_export]
75macro_rules! js_error {
76    (Error: $value: literal) => {
77        $crate::JsError::from_native(
78            $crate::JsNativeError::ERROR.with_message($value)
79        )
80    };
81    (Error: $value: literal $(, $args: expr)* $(,)?) => {
82        $crate::JsError::from_native(
83            $crate::JsNativeError::ERROR
84                .with_message(format!($value $(, $args)*))
85        )
86    };
87
88    (TypeError: $value: literal) => {
89        $crate::JsError::from_native(
90            $crate::JsNativeError::TYP.with_message($value)
91        )
92    };
93    (TypeError: $value: literal $(, $args: expr)* $(,)?) => {
94        $crate::JsError::from_native(
95            $crate::JsNativeError::TYP
96                .with_message(format!($value $(, $args)*))
97        )
98    };
99
100    (SyntaxError: $value: literal) => {
101        $crate::JsError::from_native(
102            $crate::JsNativeError::SYNTAX.with_message($value)
103        )
104    };
105    (SyntaxError: $value: literal $(, $args: expr)* $(,)?) => {
106        $crate::JsError::from_native(
107            $crate::JsNativeError::SYNTAX.with_message(format!($value $(, $args)*))
108        )
109    };
110
111    (RangeError: $value: literal) => {
112        $crate::JsError::from_native(
113            $crate::JsNativeError::RANGE.with_message($value)
114        )
115    };
116    (RangeError: $value: literal $(, $args: expr)* $(,)?) => {
117        $crate::JsError::from_native(
118            $crate::JsNativeError::RANGE.with_message(format!($value $(, $args)*))
119        )
120    };
121
122    (EvalError: $value: literal) => {
123        $crate::JsError::from_native(
124            $crate::JsNativeError::EVAL.with_message($value)
125        )
126    };
127    (EvalError: $value: literal $(, $args: expr)* $(,)?) => {
128        $crate::JsError::from_native(
129            $crate::JsNativeError::EVAL.with_message(format!($value $(, $args)*))
130        )
131    };
132
133    (ReferenceError: $value: literal) => {
134        $crate::JsError::from_native(
135            $crate::JsNativeError::REFERENCE.with_message($value)
136        )
137    };
138    (ReferenceError: $value: literal $(, $args: expr)* $(,)?) => {
139        $crate::JsError::from_native(
140            $crate::JsNativeError::REFERENCE.with_message(format!($value $(, $args)*))
141        )
142    };
143
144    (URIError: $value: literal) => {
145        $crate::JsError::from_native(
146            $crate::JsNativeError::URI.with_message($value)
147        )
148    };
149    (URIError: $value: literal $(, $args: expr)* $(,)?) => {
150        $crate::JsError::from_native(
151            $crate::JsNativeError::URI.with_message(format!($value $(, $args)*))
152        )
153    };
154
155    ($value: literal) => {
156        $crate::JsError::from_opaque($crate::JsValue::from(
157            $crate::js_string!($value)
158        ))
159    };
160    ($value: expr) => {
161        $crate::JsError::from_opaque(
162            $crate::JsValue::from($value)
163        )
164    };
165    ($value: literal $(, $args: expr)* $(,)?) => {
166        $crate::JsError::from_opaque($crate::JsValue::from(
167            $crate::JsString::from(format!($value $(, $args)*))
168        ))
169    };
170}
171
172/// The error type returned by all operations related
173/// to the execution of Javascript code.
174///
175/// This is essentially an enum that can store either [`JsNativeError`]s (for ideal
176/// native errors)  or opaque [`JsValue`]s, since Javascript allows throwing any valid
177/// `JsValue`.
178///
179/// The implementation doesn't provide a [`From`] conversion
180/// for `JsValue`. This is with the intent of encouraging the usage of proper
181/// `JsNativeError`s instead of plain `JsValue`s. However, if you
182/// do need a proper opaque error, you can construct one using the
183/// [`JsError::from_opaque`] method.
184///
185/// # Examples
186///
187/// ```rust
188/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_string};
189/// let cause = JsError::from_opaque(js_string!("error!").into());
190///
191/// assert!(cause.as_opaque().is_some());
192/// assert_eq!(
193///     cause.as_opaque().unwrap(),
194///     &JsValue::from(js_string!("error!"))
195/// );
196///
197/// let native_error: JsError = JsNativeError::typ()
198///     .with_message("invalid type!")
199///     .with_cause(cause)
200///     .into();
201///
202/// assert!(native_error.as_native().is_some());
203///
204/// let kind = &native_error.as_native().unwrap().kind;
205/// assert!(matches!(kind, JsNativeErrorKind::Type));
206/// ```
207#[derive(Debug, Clone, Trace, Finalize)]
208#[boa_gc(unsafe_no_drop)]
209pub struct JsError {
210    inner: Repr,
211
212    pub(crate) backtrace: Option<Backtrace>,
213}
214
215impl Eq for JsError {}
216impl PartialEq for JsError {
217    /// The backtrace information is ignored.
218    #[inline]
219    fn eq(&self, other: &Self) -> bool {
220        // NOTE: We want to ignore stack trace, since that is
221        //       meta-information about the error.
222        self.inner == other.inner
223    }
224}
225
226/// Internal representation of a [`JsError`].
227///
228/// `JsError` is represented by an opaque enum because it restricts
229/// matching against `JsError` without calling `try_native` first.
230/// This allows us to provide a safe API for `Error` objects that extracts
231/// their info as a native `Rust` type ([`JsNativeError`]).
232///
233/// This should never be used outside of this module. If that's not the case,
234/// you should add methods to either `JsError` or `JsNativeError` to
235/// represent that special use case.
236#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)]
237#[boa_gc(unsafe_no_drop)]
238enum Repr {
239    Native(Box<JsNativeError>),
240    Opaque(JsValue),
241}
242
243/// The error type returned by the [`JsError::try_native`] method.
244#[derive(Debug, Clone, Error)]
245pub enum TryNativeError {
246    /// A property of the error object has an invalid type.
247    #[error("invalid type of property `{0}`")]
248    InvalidPropertyType(&'static str),
249
250    /// The message of the error object could not be decoded.
251    #[error("property `message` cannot contain unpaired surrogates")]
252    InvalidMessageEncoding,
253
254    /// The constructor property of the error object was invalid.
255    #[error("invalid `constructor` property of Error object")]
256    InvalidConstructor,
257
258    /// A property of the error object is not accessible.
259    #[error("could not access property `{property}`")]
260    InaccessibleProperty {
261        /// The name of the property that could not be accessed.
262        property: &'static str,
263
264        /// The source error.
265        source: JsError,
266    },
267
268    /// An inner error of an aggregate error is not accessible.
269    #[error("could not get element `{index}` of property `errors`")]
270    InvalidErrorsIndex {
271        /// The index of the error that could not be accessed.
272        index: u64,
273
274        /// The source error.
275        source: JsError,
276    },
277
278    /// The error value is not an error object.
279    #[error("opaque error of type `{:?}` is not an Error object", .0.get_type())]
280    NotAnErrorObject(JsValue),
281
282    /// The original realm of the error object was inaccessible.
283    #[error("could not access realm of Error object")]
284    InaccessibleRealm {
285        /// The source error.
286        source: JsError,
287    },
288}
289
290impl error::Error for JsError {
291    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
292        match &self.inner {
293            Repr::Native(err) => err.source(),
294            Repr::Opaque(_) => None,
295        }
296    }
297}
298
299impl JsError {
300    /// Creates a new `JsError` from a native error `err`.
301    ///
302    /// # Examples
303    ///
304    /// ```rust
305    /// # use boa_engine::{JsError, JsNativeError};
306    /// let error = JsError::from_native(JsNativeError::syntax());
307    ///
308    /// assert!(error.as_native().is_some());
309    /// ```
310    #[must_use]
311    pub fn from_native(err: JsNativeError) -> Self {
312        Self {
313            inner: Repr::Native(Box::new(err)),
314            backtrace: None,
315        }
316    }
317
318    /// Creates a new `JsError` from a Rust standard error `err`.
319    /// This will create a new `JsNativeError` with the message of the standard error.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// # use boa_engine::JsError;
325    /// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!");
326    /// let js_error: JsError = JsError::from_rust(error);
327    ///
328    /// assert_eq!(js_error.as_native().unwrap().message(), "oh no!");
329    /// assert!(js_error.as_native().unwrap().cause().is_none());
330    /// ```
331    #[must_use]
332    pub fn from_rust(err: impl error::Error) -> Self {
333        let mut native_err = JsNativeError::error().with_message(err.to_string());
334        if let Some(source) = err.source() {
335            native_err = native_err.with_cause(Self::from_rust(source));
336        }
337
338        Self::from_native(native_err)
339    }
340
341    /// Creates a new `JsError` from an opaque error `value`.
342    ///
343    /// # Examples
344    ///
345    /// ```rust
346    /// # use boa_engine::JsError;
347    /// let error = JsError::from_opaque(5.0f64.into());
348    ///
349    /// assert!(error.as_opaque().is_some());
350    /// ```
351    #[must_use]
352    pub const fn from_opaque(value: JsValue) -> Self {
353        Self {
354            inner: Repr::Opaque(value),
355            backtrace: None,
356        }
357    }
358
359    /// Converts the error to an opaque `JsValue` error
360    ///
361    /// Unwraps the inner `JsValue` if the error is already an opaque error.
362    ///
363    /// # Examples
364    ///
365    /// ```rust
366    /// # use boa_engine::{Context, JsError, JsNativeError};
367    /// # use boa_engine::builtins::error::Error;
368    /// let context = &mut Context::default();
369    /// let error: JsError =
370    ///     JsNativeError::eval().with_message("invalid script").into();
371    /// let error_val = error.to_opaque(context);
372    ///
373    /// assert!(error_val.as_object().unwrap().is::<Error>());
374    /// ```
375    pub fn to_opaque(&self, context: &mut Context) -> JsValue {
376        match &self.inner {
377            Repr::Native(e) => e.to_opaque(context).into(),
378            Repr::Opaque(v) => v.clone(),
379        }
380    }
381
382    /// Unwraps the inner error if this contains a native error.
383    /// Otherwise, inspects the opaque error and tries to extract the
384    /// necessary information to construct a native error similar to the provided
385    /// opaque error. If the conversion fails, returns a [`TryNativeError`]
386    /// with the cause of the failure.
387    ///
388    /// # Note 1
389    ///
390    /// This method won't try to make any conversions between JS types.
391    /// In other words, for this conversion to succeed:
392    /// - `message` **MUST** be a `JsString` value.
393    /// - `errors` (in the case of `AggregateError`s) **MUST** be an `Array` object.
394    ///
395    /// # Note 2
396    ///
397    /// This operation should be considered a lossy conversion, since it
398    /// won't store any additional properties of the opaque
399    /// error, other than `message`, `cause` and `errors` (in the case of
400    /// `AggregateError`s). If you cannot affort a lossy conversion, clone
401    /// the object before calling [`from_opaque`][JsError::from_opaque]
402    /// to preserve its original properties.
403    ///
404    /// # Examples
405    ///
406    /// ```rust
407    /// # use boa_engine::{Context, JsError, JsNativeError, JsNativeErrorKind};
408    /// let context = &mut Context::default();
409    ///
410    /// // create a new, opaque Error object
411    /// let error: JsError = JsNativeError::typ().with_message("type error!").into();
412    /// let error_val = error.to_opaque(context);
413    ///
414    /// // then, try to recover the original
415    /// let error = JsError::from_opaque(error_val).try_native(context).unwrap();
416    ///
417    /// assert!(matches!(error.kind, JsNativeErrorKind::Type));
418    /// assert_eq!(error.message(), "type error!");
419    /// ```
420    pub fn try_native(&self, context: &mut Context) -> Result<JsNativeError, TryNativeError> {
421        match &self.inner {
422            Repr::Native(e) => Ok(e.as_ref().clone()),
423            Repr::Opaque(val) => {
424                let obj = val
425                    .as_object()
426                    .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?;
427                let error_data: Error = obj
428                    .downcast_ref::<Error>()
429                    .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?
430                    .clone();
431
432                let try_get_property = |key: JsString, name, context: &mut Context| {
433                    obj.try_get(key, context)
434                        .map_err(|e| TryNativeError::InaccessibleProperty {
435                            property: name,
436                            source: e,
437                        })
438                };
439
440                let message = if let Some(msg) =
441                    try_get_property(js_string!("message"), "message", context)?
442                {
443                    Cow::Owned(
444                        msg.as_string()
445                            .as_ref()
446                            .map(JsString::to_std_string)
447                            .transpose()
448                            .map_err(|_| TryNativeError::InvalidMessageEncoding)?
449                            .ok_or(TryNativeError::InvalidPropertyType("message"))?,
450                    )
451                } else {
452                    Cow::Borrowed("")
453                };
454
455                let cause = try_get_property(js_string!("cause"), "cause", context)?;
456
457                let position = error_data.position.clone();
458                let kind = match error_data.tag {
459                    ErrorKind::Error => JsNativeErrorKind::Error,
460                    ErrorKind::Eval => JsNativeErrorKind::Eval,
461                    ErrorKind::Type => JsNativeErrorKind::Type,
462                    ErrorKind::Range => JsNativeErrorKind::Range,
463                    ErrorKind::Reference => JsNativeErrorKind::Reference,
464                    ErrorKind::Syntax => JsNativeErrorKind::Syntax,
465                    ErrorKind::Uri => JsNativeErrorKind::Uri,
466                    ErrorKind::Aggregate => {
467                        let errors = obj.get(js_string!("errors"), context).map_err(|e| {
468                            TryNativeError::InaccessibleProperty {
469                                property: "errors",
470                                source: e,
471                            }
472                        })?;
473                        let mut error_list = Vec::new();
474                        match errors.as_object() {
475                            Some(errors) if errors.is_array() => {
476                                let length = errors.length_of_array_like(context).map_err(|e| {
477                                    TryNativeError::InaccessibleProperty {
478                                        property: "errors.length",
479                                        source: e,
480                                    }
481                                })?;
482                                for i in 0..length {
483                                    error_list.push(Self::from_opaque(
484                                        errors.get(i, context).map_err(|e| {
485                                            TryNativeError::InvalidErrorsIndex {
486                                                index: i,
487                                                source: e,
488                                            }
489                                        })?,
490                                    ));
491                                }
492                            }
493                            _ => return Err(TryNativeError::InvalidPropertyType("errors")),
494                        }
495
496                        JsNativeErrorKind::Aggregate(error_list)
497                    }
498                };
499
500                let realm = try_get_property(js_string!("constructor"), "constructor", context)?
501                    .as_ref()
502                    .and_then(JsValue::as_constructor)
503                    .ok_or(TryNativeError::InvalidConstructor)?
504                    .get_function_realm(context)
505                    .map_err(|err| TryNativeError::InaccessibleRealm { source: err })?;
506
507                Ok(JsNativeError {
508                    kind,
509                    message,
510                    cause: cause.map(|v| Box::new(Self::from_opaque(v))),
511                    realm: Some(realm),
512                    position,
513                })
514            }
515        }
516    }
517
518    /// Gets the inner [`JsValue`] if the error is an opaque error,
519    /// or `None` otherwise.
520    ///
521    /// # Examples
522    ///
523    /// ```rust
524    /// # use boa_engine::{JsError, JsNativeError};
525    /// let error: JsError = JsNativeError::reference()
526    ///     .with_message("variable not found!")
527    ///     .into();
528    ///
529    /// assert!(error.as_opaque().is_none());
530    ///
531    /// let error = JsError::from_opaque(256u32.into());
532    ///
533    /// assert!(error.as_opaque().is_some());
534    /// ```
535    #[must_use]
536    pub const fn as_opaque(&self) -> Option<&JsValue> {
537        match self.inner {
538            Repr::Native(_) => None,
539            Repr::Opaque(ref v) => Some(v),
540        }
541    }
542
543    /// Gets the inner [`JsNativeError`] if the error is a native
544    /// error, or `None` otherwise.
545    ///
546    /// # Examples
547    ///
548    /// ```rust
549    /// # use boa_engine::{JsError, JsNativeError, JsValue};
550    /// let error: JsError =
551    ///     JsNativeError::error().with_message("Unknown error").into();
552    ///
553    /// assert!(error.as_native().is_some());
554    ///
555    /// let error = JsError::from_opaque(JsValue::undefined());
556    ///
557    /// assert!(error.as_native().is_none());
558    /// ```
559    #[must_use]
560    pub const fn as_native(&self) -> Option<&JsNativeError> {
561        match &self.inner {
562            Repr::Native(e) => Some(e),
563            Repr::Opaque(_) => None,
564        }
565    }
566
567    /// Converts this error into its thread-safe, erased version.
568    ///
569    /// Even though this operation is lossy, converting into a `JsErasedError`
570    /// is useful since it implements `Send` and `Sync`, making it compatible with
571    /// error reporting frameworks such as `anyhow`, `eyre` or `miette`.
572    ///
573    /// # Examples
574    ///
575    /// ```rust
576    /// # use boa_engine::{js_string, Context, JsError, JsNativeError, JsSymbol, JsValue};
577    /// # use std::error::Error;
578    /// let context = &mut Context::default();
579    /// let cause = JsError::from_opaque(JsSymbol::new(Some(js_string!("error!"))).unwrap().into());
580    ///
581    /// let native_error: JsError = JsNativeError::typ()
582    ///     .with_message("invalid type!")
583    ///     .with_cause(cause)
584    ///     .into();
585    ///
586    /// let erased_error = native_error.into_erased(context);
587    ///
588    /// assert_eq!(erased_error.to_string(), "TypeError: invalid type!");
589    ///
590    /// let send_sync_error: Box<dyn Error + Send + Sync> = Box::new(erased_error);
591    ///
592    /// assert_eq!(
593    ///     send_sync_error.source().unwrap().to_string(),
594    ///     "Symbol(error!)"
595    /// );
596    /// ```
597    pub fn into_erased(self, context: &mut Context) -> JsErasedError {
598        let Ok(native) = self.try_native(context) else {
599            return JsErasedError {
600                inner: ErasedRepr::Opaque(Cow::Owned(self.to_string())),
601            };
602        };
603
604        let JsNativeError {
605            kind,
606            message,
607            cause,
608            ..
609        } = native;
610
611        let cause = cause.map(|err| Box::new(err.into_erased(context)));
612
613        let kind = match kind {
614            JsNativeErrorKind::Aggregate(errors) => JsErasedNativeErrorKind::Aggregate(
615                errors
616                    .into_iter()
617                    .map(|err| err.into_erased(context))
618                    .collect(),
619            ),
620            JsNativeErrorKind::Error => JsErasedNativeErrorKind::Error,
621            JsNativeErrorKind::Eval => JsErasedNativeErrorKind::Eval,
622            JsNativeErrorKind::Range => JsErasedNativeErrorKind::Range,
623            JsNativeErrorKind::Reference => JsErasedNativeErrorKind::Reference,
624            JsNativeErrorKind::Syntax => JsErasedNativeErrorKind::Syntax,
625            JsNativeErrorKind::Type => JsErasedNativeErrorKind::Type,
626            JsNativeErrorKind::Uri => JsErasedNativeErrorKind::Uri,
627            JsNativeErrorKind::RuntimeLimit => JsErasedNativeErrorKind::RuntimeLimit,
628            #[cfg(feature = "fuzz")]
629            JsNativeErrorKind::NoInstructionsRemain => unreachable!(
630                "The NoInstructionsRemain native error cannot be converted to an erased kind."
631            ),
632        };
633
634        JsErasedError {
635            inner: ErasedRepr::Native(JsErasedNativeError {
636                kind,
637                message,
638                cause,
639            }),
640        }
641    }
642
643    /// Injects a realm on the `realm` field of a native error.
644    ///
645    /// This is a no-op if the error is not native or if the `realm` field of the error is already
646    /// set.
647    pub(crate) fn inject_realm(mut self, realm: Realm) -> Self {
648        match &mut self.inner {
649            Repr::Native(err) if err.realm.is_none() => {
650                err.realm = Some(realm);
651            }
652            _ => {}
653        }
654        self
655    }
656
657    /// Is the [`JsError`] catchable in JavaScript.
658    #[inline]
659    pub(crate) fn is_catchable(&self) -> bool {
660        self.as_native().is_none_or(JsNativeError::is_catchable)
661    }
662}
663
664impl From<boa_parser::Error> for JsError {
665    #[cfg_attr(feature = "native-backtrace", track_caller)]
666    fn from(err: boa_parser::Error) -> Self {
667        Self::from(JsNativeError::from(err))
668    }
669}
670
671impl From<JsNativeError> for JsError {
672    fn from(error: JsNativeError) -> Self {
673        Self {
674            inner: Repr::Native(Box::new(error)),
675            backtrace: None,
676        }
677    }
678}
679
680impl fmt::Display for JsError {
681    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
682        match &self.inner {
683            Repr::Native(e) => e.fmt(f)?,
684            Repr::Opaque(v) => v.display().fmt(f)?,
685        }
686
687        if let Some(shadow_stack) = &self.backtrace {
688            for entry in shadow_stack.iter().rev() {
689                write!(f, "\n    at ")?;
690                match entry {
691                    ShadowEntry::Native {
692                        function_name,
693                        source_info,
694                    } => {
695                        if let Some(function_name) = function_name {
696                            write!(f, "{}", function_name.to_std_string_escaped())?;
697                        } else {
698                            f.write_str("<anonymous>")?;
699                        }
700
701                        if let Some(loc) = source_info.as_location() {
702                            write!(
703                                f,
704                                " (native at {}:{}:{})",
705                                loc.file(),
706                                loc.line(),
707                                loc.column()
708                            )?;
709                        } else {
710                            f.write_str(" (native)")?;
711                        }
712                    }
713                    ShadowEntry::Bytecode { pc, source_info } => {
714                        let has_function_name = !source_info.function_name().is_empty();
715                        if has_function_name {
716                            write!(f, "{}", source_info.function_name().to_std_string_escaped(),)?;
717                        } else {
718                            f.write_str("<anonymous>")?;
719                        }
720
721                        f.write_str(" (")?;
722                        source_info.map().path().fmt(f)?;
723
724                        if let Some(position) = source_info.map().find(*pc) {
725                            write!(
726                                f,
727                                ":{}:{}",
728                                position.line_number(),
729                                position.column_number()
730                            )?;
731                        } else {
732                            f.write_str(":?:?")?;
733                        }
734                        f.write_str(")")?;
735                    }
736                }
737            }
738        }
739        Ok(())
740    }
741}
742
743/// Helper struct that ignores equality operator.
744#[derive(Debug, Clone, Finalize)]
745pub(crate) struct IgnoreEq<T>(pub(crate) T);
746
747impl<T> Eq for IgnoreEq<T> {}
748
749impl<T> PartialEq for IgnoreEq<T> {
750    #[inline]
751    fn eq(&self, _: &Self) -> bool {
752        true
753    }
754}
755
756impl<T> std::hash::Hash for IgnoreEq<T> {
757    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
758}
759
760impl<T> From<T> for IgnoreEq<T> {
761    #[inline]
762    fn from(value: T) -> Self {
763        Self(value)
764    }
765}
766
767/// Native representation of an ideal `Error` object from Javascript.
768///
769/// This representation is more space efficient than its [`JsObject`] equivalent,
770/// since it doesn't need to create a whole new `JsObject` to be instantiated.
771/// Prefer using this over [`JsError`] when you don't need to throw
772/// plain [`JsValue`]s as errors, or when you need to inspect the error type
773/// of a `JsError`.
774///
775/// # Examples
776///
777/// ```rust
778/// # use boa_engine::{JsNativeError, JsNativeErrorKind};
779/// let native_error = JsNativeError::uri().with_message("cannot decode uri");
780///
781/// match native_error.kind {
782///     JsNativeErrorKind::Uri => { /* handle URI error*/ }
783///     _ => unreachable!(),
784/// }
785///
786/// assert_eq!(native_error.message(), "cannot decode uri");
787/// ```
788#[derive(Clone, Finalize, Error, PartialEq, Eq)]
789pub struct JsNativeError {
790    /// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.)
791    pub kind: JsNativeErrorKind,
792    message: Cow<'static, str>,
793    #[source]
794    cause: Option<Box<JsError>>,
795    realm: Option<Realm>,
796    position: IgnoreEq<Option<ShadowEntry>>,
797}
798
799impl fmt::Display for JsNativeError {
800    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801        write!(f, "{}", self.kind)?;
802
803        let message = self.message.trim();
804        if !message.is_empty() {
805            write!(f, ": {message}")?;
806        }
807
808        if let Some(position) = &self.position.0 {
809            position.fmt(f)?;
810        }
811
812        Ok(())
813    }
814}
815
816// SAFETY: just mirroring the default derive to allow destructuring.
817unsafe impl Trace for JsNativeError {
818    custom_trace!(this, mark, {
819        mark(&this.kind);
820        mark(&this.cause);
821        mark(&this.realm);
822    });
823}
824
825impl fmt::Debug for JsNativeError {
826    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827        f.debug_struct("JsNativeError")
828            .field("kind", &self.kind)
829            .field("message", &self.message)
830            .field("cause", &self.cause)
831            .finish_non_exhaustive()
832    }
833}
834
835impl JsNativeError {
836    /// Default `AggregateError` kind `JsNativeError`.
837    pub const AGGREGATE: Self = Self::aggregate(Vec::new());
838    /// Default `Error` kind `JsNativeError`.
839    pub const ERROR: Self = Self::error();
840    /// Default `EvalError` kind `JsNativeError`.
841    pub const EVAL: Self = Self::eval();
842    /// Default `RangeError` kind `JsNativeError`.
843    pub const RANGE: Self = Self::range();
844    /// Default `ReferenceError` kind `JsNativeError`.
845    pub const REFERENCE: Self = Self::reference();
846    /// Default `SyntaxError` kind `JsNativeError`.
847    pub const SYNTAX: Self = Self::syntax();
848    /// Default `error` kind `JsNativeError`.
849    pub const TYP: Self = Self::typ();
850    /// Default `UriError` kind `JsNativeError`.
851    pub const URI: Self = Self::uri();
852    #[cfg(feature = "fuzz")]
853    /// Default `NoInstructionsRemain` kind `JsNativeError`.
854    pub const NO_INSTRUCTIONS_REMAIN: Self = Self::no_instructions_remain();
855    /// Default `error` kind `JsNativeError`.
856    pub const RUNTIME_LIMIT: Self = Self::runtime_limit();
857
858    /// Creates a new `JsNativeError` from its `kind`, `message` and (optionally) its `cause`.
859    #[cfg_attr(feature = "native-backtrace", track_caller)]
860    const fn new(
861        kind: JsNativeErrorKind,
862        message: Cow<'static, str>,
863        cause: Option<Box<JsError>>,
864    ) -> Self {
865        Self {
866            kind,
867            message,
868            cause,
869            realm: None,
870            position: IgnoreEq(Some(ShadowEntry::Native {
871                function_name: None,
872                source_info: NativeSourceInfo::caller(),
873            })),
874        }
875    }
876
877    /// Creates a new `JsNativeError` of kind `AggregateError` from a list of [`JsError`]s, with
878    /// empty `message` and undefined `cause`.
879    ///
880    /// # Examples
881    ///
882    /// ```rust
883    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
884    /// let inner_errors = vec![
885    ///     JsNativeError::typ().into(),
886    ///     JsNativeError::syntax().into()
887    /// ];
888    /// let error = JsNativeError::aggregate(inner_errors);
889    ///
890    /// assert!(matches!(
891    ///     error.kind,
892    ///     JsNativeErrorKind::Aggregate(ref errors) if errors.len() == 2
893    /// ));
894    /// ```
895    #[must_use]
896    #[inline]
897    #[cfg_attr(feature = "native-backtrace", track_caller)]
898    pub const fn aggregate(errors: Vec<JsError>) -> Self {
899        Self::new(
900            JsNativeErrorKind::Aggregate(errors),
901            Cow::Borrowed(""),
902            None,
903        )
904    }
905
906    /// Check if it's a [`JsNativeErrorKind::Aggregate`].
907    #[must_use]
908    #[inline]
909    pub const fn is_aggregate(&self) -> bool {
910        matches!(self.kind, JsNativeErrorKind::Aggregate(_))
911    }
912
913    /// Creates a new `JsNativeError` of kind `Error`, with empty `message` and undefined `cause`.
914    ///
915    /// # Examples
916    ///
917    /// ```rust
918    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
919    /// let error = JsNativeError::error();
920    ///
921    /// assert!(matches!(error.kind, JsNativeErrorKind::Error));
922    /// ```
923    #[must_use]
924    #[inline]
925    #[cfg_attr(feature = "native-backtrace", track_caller)]
926    pub const fn error() -> Self {
927        Self::new(JsNativeErrorKind::Error, Cow::Borrowed(""), None)
928    }
929
930    /// Check if it's a [`JsNativeErrorKind::Error`].
931    #[must_use]
932    #[inline]
933    pub const fn is_error(&self) -> bool {
934        matches!(self.kind, JsNativeErrorKind::Error)
935    }
936
937    /// Creates a new `JsNativeError` of kind `EvalError`, with empty `message` and undefined `cause`.
938    ///
939    /// # Examples
940    ///
941    /// ```rust
942    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
943    /// let error = JsNativeError::eval();
944    ///
945    /// assert!(matches!(error.kind, JsNativeErrorKind::Eval));
946    /// ```
947    #[must_use]
948    #[inline]
949    #[cfg_attr(feature = "native-backtrace", track_caller)]
950    pub const fn eval() -> Self {
951        Self::new(JsNativeErrorKind::Eval, Cow::Borrowed(""), None)
952    }
953
954    /// Check if it's a [`JsNativeErrorKind::Eval`].
955    #[must_use]
956    #[inline]
957    pub const fn is_eval(&self) -> bool {
958        matches!(self.kind, JsNativeErrorKind::Eval)
959    }
960
961    /// Creates a new `JsNativeError` of kind `RangeError`, with empty `message` and undefined `cause`.
962    ///
963    /// # Examples
964    ///
965    /// ```rust
966    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
967    /// let error = JsNativeError::range();
968    ///
969    /// assert!(matches!(error.kind, JsNativeErrorKind::Range));
970    /// ```
971    #[must_use]
972    #[inline]
973    #[cfg_attr(feature = "native-backtrace", track_caller)]
974    pub const fn range() -> Self {
975        Self::new(JsNativeErrorKind::Range, Cow::Borrowed(""), None)
976    }
977
978    /// Check if it's a [`JsNativeErrorKind::Range`].
979    #[must_use]
980    #[inline]
981    pub const fn is_range(&self) -> bool {
982        matches!(self.kind, JsNativeErrorKind::Range)
983    }
984
985    /// Creates a new `JsNativeError` of kind `ReferenceError`, with empty `message` and undefined `cause`.
986    ///
987    /// # Examples
988    ///
989    /// ```rust
990    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
991    /// let error = JsNativeError::reference();
992    ///
993    /// assert!(matches!(error.kind, JsNativeErrorKind::Reference));
994    /// ```
995    #[must_use]
996    #[inline]
997    #[cfg_attr(feature = "native-backtrace", track_caller)]
998    pub const fn reference() -> Self {
999        Self::new(JsNativeErrorKind::Reference, Cow::Borrowed(""), None)
1000    }
1001
1002    /// Check if it's a [`JsNativeErrorKind::Reference`].
1003    #[must_use]
1004    #[inline]
1005    pub const fn is_reference(&self) -> bool {
1006        matches!(self.kind, JsNativeErrorKind::Reference)
1007    }
1008
1009    /// Creates a new `JsNativeError` of kind `SyntaxError`, with empty `message` and undefined `cause`.
1010    ///
1011    /// # Examples
1012    ///
1013    /// ```rust
1014    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
1015    /// let error = JsNativeError::syntax();
1016    ///
1017    /// assert!(matches!(error.kind, JsNativeErrorKind::Syntax));
1018    /// ```
1019    #[must_use]
1020    #[inline]
1021    #[cfg_attr(feature = "native-backtrace", track_caller)]
1022    pub const fn syntax() -> Self {
1023        Self::new(JsNativeErrorKind::Syntax, Cow::Borrowed(""), None)
1024    }
1025
1026    /// Check if it's a [`JsNativeErrorKind::Syntax`].
1027    #[must_use]
1028    #[inline]
1029    pub const fn is_syntax(&self) -> bool {
1030        matches!(self.kind, JsNativeErrorKind::Syntax)
1031    }
1032
1033    /// Creates a new `JsNativeError` of kind `TypeError`, with empty `message` and undefined `cause`.
1034    ///
1035    /// # Examples
1036    ///
1037    /// ```rust
1038    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
1039    /// let error = JsNativeError::typ();
1040    ///
1041    /// assert!(matches!(error.kind, JsNativeErrorKind::Type));
1042    /// ```
1043    #[must_use]
1044    #[inline]
1045    #[cfg_attr(feature = "native-backtrace", track_caller)]
1046    pub const fn typ() -> Self {
1047        Self::new(JsNativeErrorKind::Type, Cow::Borrowed(""), None)
1048    }
1049
1050    /// Check if it's a [`JsNativeErrorKind::Type`].
1051    #[must_use]
1052    #[inline]
1053    pub const fn is_type(&self) -> bool {
1054        matches!(self.kind, JsNativeErrorKind::Type)
1055    }
1056
1057    /// Creates a new `JsNativeError` of kind `UriError`, with empty `message` and undefined `cause`.
1058    ///
1059    /// # Examples
1060    ///
1061    /// ```rust
1062    /// # use boa_engine::{JsNativeError, JsNativeErrorKind};
1063    /// let error = JsNativeError::uri();
1064    ///
1065    /// assert!(matches!(error.kind, JsNativeErrorKind::Uri));
1066    /// ```
1067    #[must_use]
1068    #[inline]
1069    #[cfg_attr(feature = "native-backtrace", track_caller)]
1070    pub const fn uri() -> Self {
1071        Self::new(JsNativeErrorKind::Uri, Cow::Borrowed(""), None)
1072    }
1073
1074    /// Check if it's a [`JsNativeErrorKind::Uri`].
1075    #[must_use]
1076    #[inline]
1077    pub const fn is_uri(&self) -> bool {
1078        matches!(self.kind, JsNativeErrorKind::Uri)
1079    }
1080
1081    /// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This
1082    /// is only used in a fuzzing context.
1083    #[cfg(feature = "fuzz")]
1084    #[must_use]
1085    #[cfg_attr(feature = "native-backtrace", track_caller)]
1086    pub const fn no_instructions_remain() -> Self {
1087        Self::new(
1088            JsNativeErrorKind::NoInstructionsRemain,
1089            Cow::Borrowed(""),
1090            None,
1091        )
1092    }
1093
1094    /// Check if it's a [`JsNativeErrorKind::NoInstructionsRemain`].
1095    #[must_use]
1096    #[inline]
1097    #[cfg(feature = "fuzz")]
1098    pub const fn is_no_instructions_remain(&self) -> bool {
1099        matches!(self.kind, JsNativeErrorKind::NoInstructionsRemain)
1100    }
1101
1102    /// Creates a new `JsNativeError` that indicates that the context exceeded the runtime limits.
1103    #[must_use]
1104    #[inline]
1105    #[cfg_attr(feature = "native-backtrace", track_caller)]
1106    pub const fn runtime_limit() -> Self {
1107        Self::new(JsNativeErrorKind::RuntimeLimit, Cow::Borrowed(""), None)
1108    }
1109
1110    /// Check if it's a [`JsNativeErrorKind::RuntimeLimit`].
1111    #[must_use]
1112    #[inline]
1113    pub const fn is_runtime_limit(&self) -> bool {
1114        matches!(self.kind, JsNativeErrorKind::RuntimeLimit)
1115    }
1116
1117    /// Sets the message of this error.
1118    ///
1119    /// # Examples
1120    ///
1121    /// ```rust
1122    /// # use boa_engine::JsNativeError;
1123    /// let error = JsNativeError::range().with_message("number too large");
1124    ///
1125    /// assert_eq!(error.message(), "number too large");
1126    /// ```
1127    #[must_use]
1128    #[inline]
1129    pub fn with_message<S>(mut self, message: S) -> Self
1130    where
1131        S: Into<Cow<'static, str>>,
1132    {
1133        self.message = message.into();
1134        self
1135    }
1136
1137    /// Sets the cause of this error.
1138    ///
1139    /// # Examples
1140    ///
1141    /// ```rust
1142    /// # use boa_engine::JsNativeError;
1143    /// let cause = JsNativeError::syntax();
1144    /// let error = JsNativeError::error().with_cause(cause);
1145    ///
1146    /// assert!(error.cause().unwrap().as_native().is_some());
1147    /// ```
1148    #[must_use]
1149    #[inline]
1150    pub fn with_cause<V>(mut self, cause: V) -> Self
1151    where
1152        V: Into<JsError>,
1153    {
1154        self.cause = Some(Box::new(cause.into()));
1155        self
1156    }
1157
1158    /// Gets the `message` of this error.
1159    ///
1160    /// This is equivalent to the [`NativeError.prototype.message`][spec]
1161    /// property.
1162    ///
1163    /// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message
1164    ///
1165    /// # Examples
1166    ///
1167    /// ```rust
1168    /// # use boa_engine::JsNativeError;
1169    /// let error = JsNativeError::range().with_message("number too large");
1170    ///
1171    /// assert_eq!(error.message(), "number too large");
1172    /// ```
1173    #[must_use]
1174    #[inline]
1175    pub fn message(&self) -> &str {
1176        &self.message
1177    }
1178
1179    /// Gets the `cause` of this error.
1180    ///
1181    /// This is equivalent to the [`NativeError.prototype.cause`][spec]
1182    /// property.
1183    ///
1184    /// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
1185    ///
1186    /// # Examples
1187    ///
1188    /// ```rust
1189    /// # use boa_engine::JsNativeError;
1190    /// let cause = JsNativeError::syntax();
1191    /// let error = JsNativeError::error().with_cause(cause);
1192    ///
1193    /// assert!(error.cause().unwrap().as_native().is_some());
1194    /// ```
1195    #[must_use]
1196    #[inline]
1197    pub fn cause(&self) -> Option<&JsError> {
1198        self.cause.as_deref()
1199    }
1200
1201    /// Converts this native error to its opaque representation as a [`JsObject`].
1202    ///
1203    /// # Examples
1204    ///
1205    /// ```rust
1206    /// # use boa_engine::{Context, JsError, JsNativeError, js_string};
1207    /// # use boa_engine::builtins::error::Error;
1208    /// let context = &mut Context::default();
1209    ///
1210    /// let error = JsNativeError::error().with_message("error!");
1211    /// let error_obj = error.to_opaque(context);
1212    ///
1213    /// assert!(error_obj.is::<Error>());
1214    /// assert_eq!(
1215    ///     error_obj.get(js_string!("message"), context).unwrap(),
1216    ///     js_string!("error!").into()
1217    /// )
1218    /// ```
1219    ///
1220    /// # Panics
1221    ///
1222    /// If converting a [`JsNativeErrorKind::RuntimeLimit`] to an opaque object.
1223    #[inline]
1224    pub fn to_opaque(&self, context: &mut Context) -> JsObject {
1225        let Self {
1226            kind,
1227            message,
1228            cause,
1229            realm,
1230            position,
1231        } = self;
1232        let constructors = realm.as_ref().map_or_else(
1233            || context.intrinsics().constructors(),
1234            |realm| realm.intrinsics().constructors(),
1235        );
1236        let (prototype, tag) = match kind {
1237            JsNativeErrorKind::Aggregate(_) => (
1238                constructors.aggregate_error().prototype(),
1239                ErrorKind::Aggregate,
1240            ),
1241            JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorKind::Error),
1242            JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorKind::Eval),
1243            JsNativeErrorKind::Range => (constructors.range_error().prototype(), ErrorKind::Range),
1244            JsNativeErrorKind::Reference => (
1245                constructors.reference_error().prototype(),
1246                ErrorKind::Reference,
1247            ),
1248            JsNativeErrorKind::Syntax => {
1249                (constructors.syntax_error().prototype(), ErrorKind::Syntax)
1250            }
1251            JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type),
1252            JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri),
1253            #[cfg(feature = "fuzz")]
1254            JsNativeErrorKind::NoInstructionsRemain => {
1255                unreachable!(
1256                    "The NoInstructionsRemain native error cannot be converted to an opaque type."
1257                )
1258            }
1259            JsNativeErrorKind::RuntimeLimit => {
1260                panic!("The RuntimeLimit native error cannot be converted to an opaque type.")
1261            }
1262        };
1263
1264        let o = JsObject::from_proto_and_data_with_shared_shape(
1265            context.root_shape(),
1266            prototype,
1267            Error::with_shadow_entry(tag, position.0.clone()),
1268        );
1269
1270        o.create_non_enumerable_data_property_or_throw(
1271            js_string!("message"),
1272            js_string!(message.as_ref()),
1273            context,
1274        );
1275
1276        if let Some(cause) = cause {
1277            o.create_non_enumerable_data_property_or_throw(
1278                js_string!("cause"),
1279                cause.to_opaque(context),
1280                context,
1281            );
1282        }
1283
1284        if let JsNativeErrorKind::Aggregate(errors) = kind {
1285            let errors = errors
1286                .iter()
1287                .map(|e| e.to_opaque(context))
1288                .collect::<Vec<_>>();
1289            let errors = Array::create_array_from_list(errors, context);
1290            o.define_property_or_throw(
1291                js_string!("errors"),
1292                PropertyDescriptor::builder()
1293                    .configurable(true)
1294                    .enumerable(false)
1295                    .writable(true)
1296                    .value(errors),
1297                context,
1298            )
1299            .expect("The spec guarantees this succeeds for a newly created object ");
1300        }
1301        o
1302    }
1303
1304    /// Sets the realm of this error.
1305    pub(crate) fn with_realm(mut self, realm: Realm) -> Self {
1306        self.realm = Some(realm);
1307        self
1308    }
1309
1310    /// Is the [`JsNativeError`] catchable in JavaScript.
1311    #[inline]
1312    pub(crate) fn is_catchable(&self) -> bool {
1313        self.kind.is_catchable()
1314    }
1315}
1316
1317impl From<boa_parser::Error> for JsNativeError {
1318    #[cfg_attr(feature = "native-backtrace", track_caller)]
1319    fn from(err: boa_parser::Error) -> Self {
1320        Self::syntax().with_message(err.to_string())
1321    }
1322}
1323
1324/// The list of possible error types a [`JsNativeError`] can be.
1325///
1326/// More information:
1327/// - [ECMAScript reference][spec]
1328/// - [MDN documentation][mdn]
1329///
1330/// [spec]: https://tc39.es/ecma262/#sec-error-objects
1331/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
1332#[derive(Debug, Clone, Finalize, PartialEq, Eq)]
1333#[non_exhaustive]
1334pub enum JsNativeErrorKind {
1335    /// A collection of errors wrapped in a single error.
1336    ///
1337    /// More information:
1338    /// - [ECMAScript reference][spec]
1339    /// - [MDN documentation][mdn]
1340    ///
1341    /// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects
1342    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
1343    Aggregate(Vec<JsError>),
1344    /// A generic error. Commonly used as the base for custom exceptions.
1345    ///
1346    /// More information:
1347    /// - [ECMAScript reference][spec]
1348    /// - [MDN documentation][mdn]
1349    ///
1350    /// [spec]: https://tc39.es/ecma262/#sec-error-constructor
1351    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
1352    Error,
1353    /// An error related to the global function [`eval()`][eval].
1354    ///
1355    /// More information:
1356    /// - [ECMAScript reference][spec]
1357    /// - [MDN documentation][mdn]
1358    ///
1359    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
1360    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
1361    /// [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
1362    Eval,
1363    /// An error thrown when a value is outside its valid range.
1364    ///
1365    /// More information:
1366    /// - [ECMAScript reference][spec]
1367    /// - [MDN documentation][mdn]
1368    ///
1369    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
1370    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
1371    Range,
1372    /// An error representing an invalid de-reference of a variable.
1373    ///
1374    /// More information:
1375    /// - [ECMAScript reference][spec]
1376    /// - [MDN documentation][mdn]
1377    ///
1378    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
1379    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
1380    Reference,
1381    /// An error representing an invalid syntax in the Javascript language.
1382    ///
1383    /// More information:
1384    /// - [ECMAScript reference][spec]
1385    /// - [MDN documentation][mdn]
1386    ///
1387    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
1388    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
1389    Syntax,
1390    /// An error thrown when a variable or argument is not of a valid type.
1391    ///
1392    /// More information:
1393    /// - [ECMAScript reference][spec]
1394    /// - [MDN documentation][mdn]
1395    ///
1396    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror
1397    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
1398    Type,
1399    /// An error thrown when the [`encodeURI()`][e_uri] and [`decodeURI()`][d_uri] functions receive
1400    /// invalid parameters.
1401    ///
1402    /// More information:
1403    /// - [ECMAScript reference][spec]
1404    /// - [MDN documentation][mdn]
1405    ///
1406    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
1407    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
1408    /// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
1409    /// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
1410    Uri,
1411
1412    /// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS
1413    /// error variant.
1414    #[cfg(feature = "fuzz")]
1415    NoInstructionsRemain,
1416
1417    /// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant.
1418    RuntimeLimit,
1419}
1420
1421// SAFETY: just mirroring the default derive to allow destructuring.
1422unsafe impl Trace for JsNativeErrorKind {
1423    custom_trace!(
1424        this,
1425        mark,
1426        match &this {
1427            Self::Aggregate(errors) => mark(errors),
1428            Self::Error
1429            | Self::Eval
1430            | Self::Range
1431            | Self::Reference
1432            | Self::Syntax
1433            | Self::Type
1434            | Self::Uri
1435            | Self::RuntimeLimit => {}
1436            #[cfg(feature = "fuzz")]
1437            Self::NoInstructionsRemain => {}
1438        }
1439    );
1440}
1441
1442impl JsNativeErrorKind {
1443    /// Is the [`JsNativeErrorKind`] catchable in JavaScript.
1444    #[inline]
1445    pub(crate) fn is_catchable(&self) -> bool {
1446        match self {
1447            Self::Aggregate(_)
1448            | Self::Error
1449            | Self::Eval
1450            | Self::Range
1451            | Self::Reference
1452            | Self::Syntax
1453            | Self::Type
1454            | Self::Uri => true,
1455            Self::RuntimeLimit => false,
1456            #[cfg(feature = "fuzz")]
1457            Self::NoInstructionsRemain => false,
1458        }
1459    }
1460}
1461
1462impl PartialEq<ErrorKind> for JsNativeErrorKind {
1463    fn eq(&self, other: &ErrorKind) -> bool {
1464        matches!(
1465            (self, other),
1466            (Self::Aggregate(_), ErrorKind::Aggregate)
1467                | (Self::Error, ErrorKind::Error)
1468                | (Self::Eval, ErrorKind::Eval)
1469                | (Self::Range, ErrorKind::Range)
1470                | (Self::Reference, ErrorKind::Reference)
1471                | (Self::Syntax, ErrorKind::Syntax)
1472                | (Self::Type, ErrorKind::Type)
1473                | (Self::Uri, ErrorKind::Uri)
1474        )
1475    }
1476}
1477
1478impl fmt::Display for JsNativeErrorKind {
1479    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1480        match self {
1481            Self::Aggregate(_) => "AggregateError",
1482            Self::Error => "Error",
1483            Self::Eval => "EvalError",
1484            Self::Range => "RangeError",
1485            Self::Reference => "ReferenceError",
1486            Self::Syntax => "SyntaxError",
1487            Self::Type => "TypeError",
1488            Self::Uri => "UriError",
1489            Self::RuntimeLimit => "RuntimeLimit",
1490            #[cfg(feature = "fuzz")]
1491            Self::NoInstructionsRemain => "NoInstructionsRemain",
1492        }
1493        .fmt(f)
1494    }
1495}
1496
1497/// Erased version of [`JsError`].
1498///
1499/// This is mainly useful to convert a `JsError` into an `Error` that also
1500/// implements `Send + Sync`, which makes it compatible with error reporting tools
1501/// such as `anyhow`, `eyre` or `miette`.
1502///
1503/// Generally, the conversion from `JsError` to `JsErasedError` is unidirectional,
1504/// since any `JsError` that is a [`JsValue`] is converted to its string representation
1505/// instead. This will lose information if that value was an object, a symbol or a big int.
1506#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
1507pub struct JsErasedError {
1508    inner: ErasedRepr,
1509}
1510
1511#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
1512enum ErasedRepr {
1513    Native(JsErasedNativeError),
1514    Opaque(Cow<'static, str>),
1515}
1516
1517impl fmt::Display for JsErasedError {
1518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1519        match &self.inner {
1520            ErasedRepr::Native(e) => e.fmt(f),
1521            ErasedRepr::Opaque(v) => fmt::Display::fmt(v, f),
1522        }
1523    }
1524}
1525
1526impl error::Error for JsErasedError {
1527    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
1528        match &self.inner {
1529            ErasedRepr::Native(err) => err.source(),
1530            ErasedRepr::Opaque(_) => None,
1531        }
1532    }
1533}
1534
1535impl JsErasedError {
1536    /// Gets the inner [`str`] if the error is an opaque error,
1537    /// or `None` otherwise.
1538    #[must_use]
1539    pub fn as_opaque(&self) -> Option<&str> {
1540        match self.inner {
1541            ErasedRepr::Native(_) => None,
1542            ErasedRepr::Opaque(ref v) => Some(v),
1543        }
1544    }
1545
1546    /// Gets the inner [`JsErasedNativeError`] if the error is a native
1547    /// error, or `None` otherwise.
1548    #[must_use]
1549    pub const fn as_native(&self) -> Option<&JsErasedNativeError> {
1550        match &self.inner {
1551            ErasedRepr::Native(e) => Some(e),
1552            ErasedRepr::Opaque(_) => None,
1553        }
1554    }
1555}
1556
1557/// Erased version of [`JsNativeError`].
1558#[derive(Debug, Clone, Trace, Finalize, Error, PartialEq, Eq)]
1559pub struct JsErasedNativeError {
1560    /// The kind of native error (e.g. `TypeError`, `SyntaxError`, etc.)
1561    pub kind: JsErasedNativeErrorKind,
1562    message: Cow<'static, str>,
1563    #[source]
1564    cause: Option<Box<JsErasedError>>,
1565}
1566
1567impl fmt::Display for JsErasedNativeError {
1568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1569        write!(f, "{}", self.kind)?;
1570
1571        let message = self.message.trim();
1572        if !message.is_empty() {
1573            write!(f, ": {message}")?;
1574        }
1575
1576        Ok(())
1577    }
1578}
1579
1580/// Erased version of [`JsNativeErrorKind`]
1581#[derive(Debug, Clone, Trace, Finalize, PartialEq, Eq)]
1582#[non_exhaustive]
1583pub enum JsErasedNativeErrorKind {
1584    /// A collection of errors wrapped in a single error.
1585    ///
1586    /// More information:
1587    /// - [ECMAScript reference][spec]
1588    /// - [MDN documentation][mdn]
1589    ///
1590    /// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects
1591    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
1592    Aggregate(Vec<JsErasedError>),
1593    /// A generic error. Commonly used as the base for custom exceptions.
1594    ///
1595    /// More information:
1596    /// - [ECMAScript reference][spec]
1597    /// - [MDN documentation][mdn]
1598    ///
1599    /// [spec]: https://tc39.es/ecma262/#sec-error-constructor
1600    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
1601    Error,
1602    /// An error related to the global function [`eval()`][eval].
1603    ///
1604    /// More information:
1605    /// - [ECMAScript reference][spec]
1606    /// - [MDN documentation][mdn]
1607    ///
1608    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
1609    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
1610    /// [eval]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
1611    Eval,
1612    /// An error thrown when a value is outside its valid range.
1613    ///
1614    /// More information:
1615    /// - [ECMAScript reference][spec]
1616    /// - [MDN documentation][mdn]
1617    ///
1618    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
1619    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
1620    Range,
1621    /// An error representing an invalid de-reference of a variable.
1622    ///
1623    /// More information:
1624    /// - [ECMAScript reference][spec]
1625    /// - [MDN documentation][mdn]
1626    ///
1627    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
1628    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
1629    Reference,
1630    /// An error representing an invalid syntax in the Javascript language.
1631    ///
1632    /// More information:
1633    /// - [ECMAScript reference][spec]
1634    /// - [MDN documentation][mdn]
1635    ///
1636    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
1637    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
1638    Syntax,
1639    /// An error thrown when a variable or argument is not of a valid type.
1640    ///
1641    /// More information:
1642    /// - [ECMAScript reference][spec]
1643    /// - [MDN documentation][mdn]
1644    ///
1645    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror
1646    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
1647    Type,
1648    /// An error thrown when the [`encodeURI()`][e_uri] and [`decodeURI()`][d_uri] functions receive
1649    /// invalid parameters.
1650    ///
1651    /// More information:
1652    /// - [ECMAScript reference][spec]
1653    /// - [MDN documentation][mdn]
1654    ///
1655    /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
1656    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
1657    /// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
1658    /// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
1659    Uri,
1660
1661    /// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant.
1662    RuntimeLimit,
1663}
1664
1665impl fmt::Display for JsErasedNativeErrorKind {
1666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1667        match &self {
1668            Self::Aggregate(errors) => {
1669                return write!(f, "AggregateError(error count: {})", errors.len());
1670            }
1671            Self::Error => "Error",
1672            Self::Eval => "EvalError",
1673            Self::Range => "RangeError",
1674            Self::Reference => "ReferenceError",
1675            Self::Syntax => "SyntaxError",
1676            Self::Type => "TypeError",
1677            Self::Uri => "UriError",
1678            Self::RuntimeLimit => "RuntimeLimit",
1679        }
1680        .fmt(f)
1681    }
1682}