Skip to main content

boa_engine/error/
mod.rs

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