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