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