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