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