1use core::{
2 error::Error as StdError,
3 ffi::FromBytesWithNulError,
4 fmt::{self, Display, Formatter, Result as FmtResult},
5 panic::UnwindSafe,
6 str::{FromStr, Utf8Error},
7};
8
9use alloc::{
10 ffi::{CString, NulError},
11 string::{FromUtf8Error, ToString as _},
12};
13
14#[cfg(feature = "std")]
15use std::io::Error as IoError;
16
17#[cfg(feature = "futures")]
18use crate::context::AsyncContext;
19use crate::value::array_buffer::AsSliceError;
20use crate::{
21 atom::PredefinedAtom, qjs, runtime::UserDataError, value::exception::ERROR_FORMAT_STR, Context,
22 Ctx, Exception, Object, StdResult, StdString, Type, Value,
23};
24
25pub type Result<T> = StdResult<T, Error>;
27
28pub type CaughtResult<'js, T> = StdResult<T, CaughtError<'js>>;
30
31#[derive(Debug)]
32pub enum BorrowError {
33 NotWritable,
35 AlreadyBorrowed,
37 AlreadyUsed,
39}
40
41impl fmt::Display for BorrowError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match *self {
44 BorrowError::NotWritable => write!(f, "tried to borrow a value which is not writable"),
45 BorrowError::AlreadyBorrowed => {
46 write!(f, "can't borrow a value as it is already borrowed")
47 }
48 BorrowError::AlreadyUsed => {
49 write!(
50 f,
51 "tried to use a value, which can only be used once, again."
52 )
53 }
54 }
55 }
56}
57
58impl core::error::Error for BorrowError {}
59
60#[derive(Debug)]
62#[non_exhaustive]
63pub enum Error {
64 Allocation,
67 DuplicateExports,
69 InvalidExport,
71 InvalidClass {
73 class: &'static str,
74 message: StdString,
75 },
76 InvalidString(NulError),
79 InvalidCStr(FromBytesWithNulError),
82 Utf8(Utf8Error),
84 #[cfg(feature = "std")]
86 Io(IoError),
87 ClassBorrow(BorrowError),
89 FunctionBorrow(BorrowError),
91 Exception,
97 FromJs {
99 from: &'static str,
100 to: &'static str,
101 message: Option<StdString>,
102 },
103 IntoJs {
105 from: &'static str,
106 to: &'static str,
107 message: Option<StdString>,
108 },
109 MissingArgs {
111 expected: usize,
112 given: usize,
113 },
114 TooManyArgs {
115 expected: usize,
116 given: usize,
117 },
118 #[cfg(feature = "loader")]
119 Resolving {
121 base: StdString,
122 name: StdString,
123 message: Option<StdString>,
124 },
125 #[cfg(feature = "loader")]
126 Loading {
128 name: StdString,
129 message: Option<StdString>,
130 },
131 AsSlice(AsSliceError),
132 UnrelatedRuntime,
134 WouldBlock,
137 UserData(UserDataError<()>),
139 Unknown,
142}
143
144impl Error {
145 #[cfg(feature = "loader")]
146 pub fn new_resolving<B, N>(base: B, name: N) -> Self
148 where
149 StdString: From<B> + From<N>,
150 {
151 Error::Resolving {
152 base: base.into(),
153 name: name.into(),
154 message: None,
155 }
156 }
157
158 #[cfg(feature = "loader")]
159 pub fn new_resolving_message<B, N, M>(base: B, name: N, msg: M) -> Self
161 where
162 StdString: From<B> + From<N> + From<M>,
163 {
164 Error::Resolving {
165 base: base.into(),
166 name: name.into(),
167 message: Some(msg.into()),
168 }
169 }
170
171 #[cfg(feature = "loader")]
172 pub fn is_resolving(&self) -> bool {
174 matches!(self, Error::Resolving { .. })
175 }
176
177 #[cfg(feature = "loader")]
178 pub fn new_loading<N>(name: N) -> Self
180 where
181 StdString: From<N>,
182 {
183 Error::Loading {
184 name: name.into(),
185 message: None,
186 }
187 }
188
189 #[cfg(feature = "loader")]
190 pub fn new_loading_message<N, M>(name: N, msg: M) -> Self
192 where
193 StdString: From<N> + From<M>,
194 {
195 Error::Loading {
196 name: name.into(),
197 message: Some(msg.into()),
198 }
199 }
200
201 #[cfg(feature = "loader")]
202 pub fn is_loading(&self) -> bool {
204 matches!(self, Error::Loading { .. })
205 }
206
207 pub fn is_exception(&self) -> bool {
209 matches!(self, Error::Exception)
210 }
211
212 pub fn new_from_js(from: &'static str, to: &'static str) -> Self {
214 Error::FromJs {
215 from,
216 to,
217 message: None,
218 }
219 }
220
221 pub fn new_from_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
223 where
224 StdString: From<M>,
225 {
226 Error::FromJs {
227 from,
228 to,
229 message: Some(msg.into()),
230 }
231 }
232
233 pub fn new_into_js(from: &'static str, to: &'static str) -> Self {
235 Error::IntoJs {
236 from,
237 to,
238 message: None,
239 }
240 }
241
242 pub fn new_into_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
244 where
245 StdString: From<M>,
246 {
247 Error::IntoJs {
248 from,
249 to,
250 message: Some(msg.into()),
251 }
252 }
253
254 pub fn is_from_js(&self) -> bool {
256 matches!(self, Self::FromJs { .. })
257 }
258
259 pub fn is_from_js_to_js(&self) -> bool {
261 matches!(self, Self::FromJs { to, .. } if Type::from_str(to).is_ok())
262 }
263
264 pub fn is_into_js(&self) -> bool {
266 matches!(self, Self::IntoJs { .. })
267 }
268
269 pub fn is_num_args(&self) -> bool {
271 matches!(self, Self::TooManyArgs { .. } | Self::MissingArgs { .. })
272 }
273
274 pub(crate) fn to_cstring(&self) -> CString {
276 let mut message = alloc::format!("{self}\0").into_bytes();
278
279 message.pop(); unsafe { CString::from_vec_unchecked(message) }
283 }
284
285 pub(crate) fn throw(&self, ctx: &Ctx) -> qjs::JSValue {
287 use Error::*;
288 match self {
289 Exception => qjs::JS_EXCEPTION,
290 Allocation => unsafe { qjs::JS_ThrowOutOfMemory(ctx.as_ptr()) },
291 InvalidString(_)
292 | Utf8(_)
293 | FromJs { .. }
294 | IntoJs { .. }
295 | TooManyArgs { .. }
296 | MissingArgs { .. } => {
297 let message = self.to_cstring();
298 unsafe {
299 qjs::JS_ThrowTypeError(
300 ctx.as_ptr(),
301 ERROR_FORMAT_STR.as_ptr(),
302 message.as_ptr(),
303 )
304 }
305 }
306 AsSlice(_) => {
307 let message = self.to_cstring();
308 unsafe {
309 qjs::JS_ThrowReferenceError(
310 ctx.as_ptr(),
311 ERROR_FORMAT_STR.as_ptr(),
312 message.as_ptr(),
313 )
314 }
315 }
316 #[cfg(feature = "loader")]
317 Resolving { .. } | Loading { .. } => {
318 let message = self.to_cstring();
319 unsafe {
320 qjs::JS_ThrowReferenceError(
321 ctx.as_ptr(),
322 ERROR_FORMAT_STR.as_ptr(),
323 message.as_ptr(),
324 )
325 }
326 }
327 Unknown => {
328 let message = self.to_cstring();
329 unsafe {
330 qjs::JS_ThrowInternalError(
331 ctx.as_ptr(),
332 ERROR_FORMAT_STR.as_ptr(),
333 message.as_ptr(),
334 )
335 }
336 }
337 error => {
338 unsafe {
339 let value = qjs::JS_NewError(ctx.as_ptr());
340 if qjs::JS_VALUE_GET_NORM_TAG(value) == qjs::JS_TAG_EXCEPTION {
341 return value;
344 }
345 let obj = Object::from_js_value(ctx.clone(), value);
346 match obj.set(PredefinedAtom::Message, error.to_string()) {
347 Ok(_) => {}
348 Err(Error::Exception) => return qjs::JS_EXCEPTION,
349 Err(e) => {
350 panic!("generated error while throwing error: {}", e);
351 }
352 }
353 let js_val = (obj).into_js_value();
354 qjs::JS_Throw(ctx.as_ptr(), js_val)
355 }
356 }
357 }
358 }
359}
360
361impl StdError for Error {}
362
363impl Display for Error {
364 fn fmt(&self, f: &mut Formatter) -> FmtResult {
365 match self {
366 Error::Allocation => "Allocation failed while creating object".fmt(f)?,
367 Error::DuplicateExports => {
368 "Tried to export two values with the same name from one module".fmt(f)?
369 }
370 Error::InvalidExport => {
371 "Tried to export a value which was not previously declared".fmt(f)?
372 }
373 Error::InvalidClass { class, message } => {
374 "Invalid class '".fmt(f)?;
375 class.fmt(f)?;
376 "': ".fmt(f)?;
377 message.fmt(f)?;
378 }
379 Error::InvalidString(error) => {
380 "String contained internal null bytes: ".fmt(f)?;
381 error.fmt(f)?;
382 }
383 Error::InvalidCStr(error) => {
384 "CStr didn't end in a null byte: ".fmt(f)?;
385 error.fmt(f)?;
386 }
387 Error::Utf8(error) => {
388 "Conversion from string failed: ".fmt(f)?;
389 error.fmt(f)?;
390 }
391 Error::Unknown => "QuickJS library created a unknown error".fmt(f)?,
392 Error::Exception => "Exception generated by QuickJS".fmt(f)?,
393 Error::FromJs { from, to, message } => {
394 "Error converting from js '".fmt(f)?;
395 from.fmt(f)?;
396 "' into type '".fmt(f)?;
397 to.fmt(f)?;
398 "'".fmt(f)?;
399 if let Some(message) = message {
400 if !message.is_empty() {
401 ": ".fmt(f)?;
402 message.fmt(f)?;
403 }
404 }
405 }
406 Error::IntoJs { from, to, message } => {
407 "Error converting from '".fmt(f)?;
408 from.fmt(f)?;
409 "' into js '".fmt(f)?;
410 to.fmt(f)?;
411 "'".fmt(f)?;
412 if let Some(message) = message {
413 if !message.is_empty() {
414 ": ".fmt(f)?;
415 message.fmt(f)?;
416 }
417 }
418 }
419 Error::MissingArgs { expected, given } => {
420 "Error calling function with ".fmt(f)?;
421 given.fmt(f)?;
422 " argument(s) while ".fmt(f)?;
423 expected.fmt(f)?;
424 " where expected".fmt(f)?;
425 }
426 Error::TooManyArgs { expected, given } => {
427 "Error calling function with ".fmt(f)?;
428 given.fmt(f)?;
429 " argument(s), function is exhaustive and cannot be called with more then "
430 .fmt(f)?;
431 expected.fmt(f)?;
432 " arguments".fmt(f)?;
433 }
434 #[cfg(feature = "loader")]
435 Error::Resolving {
436 base,
437 name,
438 message,
439 } => {
440 "Error resolving module '".fmt(f)?;
441 name.fmt(f)?;
442 "' from '".fmt(f)?;
443 base.fmt(f)?;
444 "'".fmt(f)?;
445 if let Some(message) = message {
446 if !message.is_empty() {
447 ": ".fmt(f)?;
448 message.fmt(f)?;
449 }
450 }
451 }
452 #[cfg(feature = "loader")]
453 Error::Loading { name, message } => {
454 "Error loading module '".fmt(f)?;
455 name.fmt(f)?;
456 "'".fmt(f)?;
457 if let Some(message) = message {
458 if !message.is_empty() {
459 ": ".fmt(f)?;
460 message.fmt(f)?;
461 }
462 }
463 }
464 #[cfg(feature = "std")]
465 Error::Io(error) => {
466 "IO Error: ".fmt(f)?;
467 error.fmt(f)?;
468 }
469 Error::ClassBorrow(x) => {
470 "Error borrowing class: ".fmt(f)?;
471 x.fmt(f)?;
472 }
473 Error::FunctionBorrow(x) => {
474 "Error borrowing function: ".fmt(f)?;
475 x.fmt(f)?;
476 }
477 Error::WouldBlock => "Error blocking on a promise resulted in a dead lock".fmt(f)?,
478 Error::UserData(x) => x.fmt(f)?,
479 Error::AsSlice(x) => {
480 "Could not convert array buffer to slice: ".fmt(f)?;
481 x.fmt(f)?;
482 }
483 Error::UnrelatedRuntime => "Restoring Persistent in an unrelated runtime".fmt(f)?,
484 }
485 Ok(())
486 }
487}
488
489macro_rules! from_impls {
490 ($($type:ty => $variant:ident,)*) => {
491 $(
492 impl From<$type> for Error {
493 fn from(error: $type) -> Self {
494 Error::$variant(error)
495 }
496 }
497 )*
498 };
499}
500
501from_impls! {
502 NulError => InvalidString,
503 FromBytesWithNulError => InvalidCStr,
504 Utf8Error => Utf8,
505}
506
507#[cfg(feature = "std")]
508from_impls! {
509 IoError => Io,
510}
511
512impl From<FromUtf8Error> for Error {
513 fn from(error: FromUtf8Error) -> Self {
514 Error::Utf8(error.utf8_error())
515 }
516}
517
518impl<T> From<UserDataError<T>> for Error {
519 fn from(_: UserDataError<T>) -> Self {
520 Error::UserData(UserDataError(()))
521 }
522}
523
524impl From<AsSliceError> for Error {
525 fn from(value: AsSliceError) -> Self {
526 Error::AsSlice(value)
527 }
528}
529
530#[derive(Debug)]
532pub enum CaughtError<'js> {
533 Error(Error),
535 Exception(Exception<'js>),
537 Value(Value<'js>),
539}
540
541impl<'js> Display for CaughtError<'js> {
542 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
543 match *self {
544 CaughtError::Error(ref e) => e.fmt(f),
545 CaughtError::Exception(ref e) => e.fmt(f),
546 CaughtError::Value(ref e) => {
547 writeln!(f, "Exception generated by quickjs: {e:?}")
548 }
549 }
550 }
551}
552
553impl<'js> StdError for CaughtError<'js> {}
554
555impl<'js> CaughtError<'js> {
556 pub fn from_error(ctx: &Ctx<'js>, error: Error) -> Self {
559 if let Error::Exception = error {
560 let value = ctx.catch();
561 if let Some(ex) = value
562 .as_object()
563 .and_then(|x| Exception::from_object(x.clone()))
564 {
565 CaughtError::Exception(ex)
566 } else {
567 CaughtError::Value(value)
568 }
569 } else {
570 CaughtError::Error(error)
571 }
572 }
573
574 pub fn catch<T>(ctx: &Ctx<'js>, error: Result<T>) -> CaughtResult<'js, T> {
577 error.map_err(|error| Self::from_error(ctx, error))
578 }
579
580 pub fn throw(self, ctx: &Ctx<'js>) -> Error {
582 match self {
583 CaughtError::Error(e) => e,
584 CaughtError::Exception(ex) => ctx.throw(ex.into_value()),
585 CaughtError::Value(ex) => ctx.throw(ex),
586 }
587 }
588
589 pub fn is_exception(&self) -> bool {
591 matches!(self, CaughtError::Exception(_))
592 }
593
594 pub fn is_js_error(&self) -> bool {
596 matches!(self, CaughtError::Exception(_) | CaughtError::Value(_))
597 }
598}
599
600pub trait CatchResultExt<'js, T> {
617 fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T>;
618}
619
620impl<'js, T> CatchResultExt<'js, T> for Result<T> {
621 fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T> {
622 CaughtError::catch(ctx, self)
623 }
624}
625
626pub trait ThrowResultExt<'js, T> {
631 fn throw(self, ctx: &Ctx<'js>) -> Result<T>;
632}
633
634impl<'js, T> ThrowResultExt<'js, T> for CaughtResult<'js, T> {
635 fn throw(self, ctx: &Ctx<'js>) -> Result<T> {
636 self.map_err(|e| e.throw(ctx))
637 }
638}
639
640#[derive(Clone)]
645pub struct JobException(pub Context);
646
647impl fmt::Debug for JobException {
648 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
649 f.debug_tuple("JobException")
650 .field(&"TODO: Context")
651 .finish()
652 }
653}
654
655impl Display for JobException {
656 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
657 write!(f, "Job raised an exception")?;
658 Ok(())
660 }
661}
662
663#[cfg(feature = "futures")]
668#[derive(Clone)]
669pub struct AsyncJobException(pub AsyncContext);
670
671#[cfg(feature = "futures")]
672impl fmt::Debug for AsyncJobException {
673 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
674 f.debug_tuple("AsyncJobException")
675 .field(&"TODO: Context")
676 .finish()
677 }
678}
679
680#[cfg(feature = "futures")]
681impl Display for AsyncJobException {
682 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
683 write!(f, "Async job raised an exception")?;
684 Ok(())
686 }
687}
688
689impl<'js> Ctx<'js> {
690 fn handle_panic_inner<R, F>(&self, f: F, err_val: R) -> R
691 where
692 F: FnOnce() -> R + UnwindSafe,
693 {
694 match crate::util::catch_unwind(f) {
695 Ok(x) => x,
696 Err(e) => unsafe {
697 self.get_opaque().set_panic(e);
698 qjs::JS_Throw(self.as_ptr(), qjs::JS_MKVAL(qjs::JS_TAG_EXCEPTION, 0));
699 err_val
700 },
701 }
702 }
703
704 pub(crate) fn handle_panic<F>(&self, f: F) -> qjs::JSValue
705 where
706 F: FnOnce() -> qjs::JSValue + UnwindSafe,
707 {
708 self.handle_panic_inner(f, qjs::JS_EXCEPTION)
710 }
711
712 pub(crate) fn handle_panic_exotic<F>(&self, f: F) -> qjs::c_int
713 where
714 F: FnOnce() -> qjs::c_int + UnwindSafe,
715 {
716 self.handle_panic_inner(f, -1)
717 }
718
719 pub(crate) unsafe fn handle_exception(&self, js_val: qjs::JSValue) -> Result<qjs::JSValue> {
725 if qjs::JS_VALUE_GET_NORM_TAG(js_val) != qjs::JS_TAG_EXCEPTION {
726 Ok(js_val)
727 } else {
728 if let Some(x) = self.get_opaque().take_panic() {
729 crate::util::resume_unwind(x);
730 }
731 Err(Error::Exception)
732 }
733 }
734
735 pub(crate) fn raise_exception(&self) -> Error {
738 unsafe {
740 if let Some(x) = self.get_opaque().take_panic() {
741 crate::util::resume_unwind(x);
742 }
743 Error::Exception
744 }
745 }
746}