use core::{
error::Error as StdError,
ffi::FromBytesWithNulError,
fmt::{self, Display, Formatter, Result as FmtResult},
panic::UnwindSafe,
str::{FromStr, Utf8Error},
};
use alloc::{
ffi::{CString, NulError},
string::{FromUtf8Error, ToString as _},
};
#[cfg(feature = "std")]
use std::io::Error as IoError;
#[cfg(feature = "futures")]
use crate::context::AsyncContext;
use crate::value::array_buffer::AsSliceError;
use crate::{
atom::PredefinedAtom, qjs, runtime::UserDataError, value::exception::ERROR_FORMAT_STR, Context,
Ctx, Exception, Object, StdResult, StdString, Type, Value,
};
pub type Result<T> = StdResult<T, Error>;
pub type CaughtResult<'js, T> = StdResult<T, CaughtError<'js>>;
#[derive(Debug)]
pub enum BorrowError {
NotWritable,
AlreadyBorrowed,
AlreadyUsed,
}
impl fmt::Display for BorrowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
BorrowError::NotWritable => write!(f, "tried to borrow a value which is not writable"),
BorrowError::AlreadyBorrowed => {
write!(f, "can't borrow a value as it is already borrowed")
}
BorrowError::AlreadyUsed => {
write!(
f,
"tried to use a value, which can only be used once, again."
)
}
}
}
}
impl core::error::Error for BorrowError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Allocation,
DuplicateExports,
InvalidExport,
InvalidString(NulError),
InvalidCStr(FromBytesWithNulError),
Utf8(Utf8Error),
#[cfg(feature = "std")]
Io(IoError),
ClassBorrow(BorrowError),
FunctionBorrow(BorrowError),
Exception,
FromJs {
from: &'static str,
to: &'static str,
message: Option<StdString>,
},
IntoJs {
from: &'static str,
to: &'static str,
message: Option<StdString>,
},
MissingArgs {
expected: usize,
given: usize,
},
TooManyArgs {
expected: usize,
given: usize,
},
#[cfg(feature = "loader")]
Resolving {
base: StdString,
name: StdString,
message: Option<StdString>,
},
#[cfg(feature = "loader")]
Loading {
name: StdString,
message: Option<StdString>,
},
AsSlice(AsSliceError),
UnrelatedRuntime,
WouldBlock,
UserData(UserDataError<()>),
Unknown,
}
impl Error {
#[cfg(feature = "loader")]
pub fn new_resolving<B, N>(base: B, name: N) -> Self
where
StdString: From<B> + From<N>,
{
Error::Resolving {
base: base.into(),
name: name.into(),
message: None,
}
}
#[cfg(feature = "loader")]
pub fn new_resolving_message<B, N, M>(base: B, name: N, msg: M) -> Self
where
StdString: From<B> + From<N> + From<M>,
{
Error::Resolving {
base: base.into(),
name: name.into(),
message: Some(msg.into()),
}
}
#[cfg(feature = "loader")]
pub fn is_resolving(&self) -> bool {
matches!(self, Error::Resolving { .. })
}
#[cfg(feature = "loader")]
pub fn new_loading<N>(name: N) -> Self
where
StdString: From<N>,
{
Error::Loading {
name: name.into(),
message: None,
}
}
#[cfg(feature = "loader")]
pub fn new_loading_message<N, M>(name: N, msg: M) -> Self
where
StdString: From<N> + From<M>,
{
Error::Loading {
name: name.into(),
message: Some(msg.into()),
}
}
#[cfg(feature = "loader")]
pub fn is_loading(&self) -> bool {
matches!(self, Error::Loading { .. })
}
pub fn is_exception(&self) -> bool {
matches!(self, Error::Exception)
}
pub fn new_from_js(from: &'static str, to: &'static str) -> Self {
Error::FromJs {
from,
to,
message: None,
}
}
pub fn new_from_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
where
StdString: From<M>,
{
Error::FromJs {
from,
to,
message: Some(msg.into()),
}
}
pub fn new_into_js(from: &'static str, to: &'static str) -> Self {
Error::IntoJs {
from,
to,
message: None,
}
}
pub fn new_into_js_message<M>(from: &'static str, to: &'static str, msg: M) -> Self
where
StdString: From<M>,
{
Error::IntoJs {
from,
to,
message: Some(msg.into()),
}
}
pub fn is_from_js(&self) -> bool {
matches!(self, Self::FromJs { .. })
}
pub fn is_from_js_to_js(&self) -> bool {
matches!(self, Self::FromJs { to, .. } if Type::from_str(to).is_ok())
}
pub fn is_into_js(&self) -> bool {
matches!(self, Self::IntoJs { .. })
}
pub fn is_num_args(&self) -> bool {
matches!(self, Self::TooManyArgs { .. } | Self::MissingArgs { .. })
}
pub(crate) fn to_cstring(&self) -> CString {
let mut message = alloc::format!("{self}\0").into_bytes();
message.pop();
unsafe { CString::from_vec_unchecked(message) }
}
pub(crate) fn throw(&self, ctx: &Ctx) -> qjs::JSValue {
use Error::*;
match self {
Exception => qjs::JS_EXCEPTION,
Allocation => unsafe { qjs::JS_ThrowOutOfMemory(ctx.as_ptr()) },
InvalidString(_)
| Utf8(_)
| FromJs { .. }
| IntoJs { .. }
| TooManyArgs { .. }
| MissingArgs { .. } => {
let message = self.to_cstring();
unsafe {
qjs::JS_ThrowTypeError(
ctx.as_ptr(),
ERROR_FORMAT_STR.as_ptr(),
message.as_ptr(),
)
}
}
AsSlice(_) => {
let message = self.to_cstring();
unsafe {
qjs::JS_ThrowReferenceError(
ctx.as_ptr(),
ERROR_FORMAT_STR.as_ptr(),
message.as_ptr(),
)
}
}
#[cfg(feature = "loader")]
Resolving { .. } | Loading { .. } => {
let message = self.to_cstring();
unsafe {
qjs::JS_ThrowReferenceError(
ctx.as_ptr(),
ERROR_FORMAT_STR.as_ptr(),
message.as_ptr(),
)
}
}
Unknown => {
let message = self.to_cstring();
unsafe {
qjs::JS_ThrowInternalError(
ctx.as_ptr(),
ERROR_FORMAT_STR.as_ptr(),
message.as_ptr(),
)
}
}
error => {
unsafe {
let value = qjs::JS_NewError(ctx.as_ptr());
if qjs::JS_VALUE_GET_NORM_TAG(value) == qjs::JS_TAG_EXCEPTION {
return value;
}
let obj = Object::from_js_value(ctx.clone(), value);
match obj.set(PredefinedAtom::Message, error.to_string()) {
Ok(_) => {}
Err(Error::Exception) => return qjs::JS_EXCEPTION,
Err(e) => {
panic!("generated error while throwing error: {}", e);
}
}
let js_val = (obj).into_js_value();
qjs::JS_Throw(ctx.as_ptr(), js_val)
}
}
}
}
}
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Error::Allocation => "Allocation failed while creating object".fmt(f)?,
Error::DuplicateExports => {
"Tried to export two values with the same name from one module".fmt(f)?
}
Error::InvalidExport => {
"Tried to export a value which was not previously declared".fmt(f)?
}
Error::InvalidString(error) => {
"String contained internal null bytes: ".fmt(f)?;
error.fmt(f)?;
}
Error::InvalidCStr(error) => {
"CStr didn't end in a null byte: ".fmt(f)?;
error.fmt(f)?;
}
Error::Utf8(error) => {
"Conversion from string failed: ".fmt(f)?;
error.fmt(f)?;
}
Error::Unknown => "QuickJS library created a unknown error".fmt(f)?,
Error::Exception => "Exception generated by QuickJS".fmt(f)?,
Error::FromJs { from, to, message } => {
"Error converting from js '".fmt(f)?;
from.fmt(f)?;
"' into type '".fmt(f)?;
to.fmt(f)?;
"'".fmt(f)?;
if let Some(message) = message {
if !message.is_empty() {
": ".fmt(f)?;
message.fmt(f)?;
}
}
}
Error::IntoJs { from, to, message } => {
"Error converting from '".fmt(f)?;
from.fmt(f)?;
"' into js '".fmt(f)?;
to.fmt(f)?;
"'".fmt(f)?;
if let Some(message) = message {
if !message.is_empty() {
": ".fmt(f)?;
message.fmt(f)?;
}
}
}
Error::MissingArgs { expected, given } => {
"Error calling function with ".fmt(f)?;
given.fmt(f)?;
" argument(s) while ".fmt(f)?;
expected.fmt(f)?;
" where expected".fmt(f)?;
}
Error::TooManyArgs { expected, given } => {
"Error calling function with ".fmt(f)?;
given.fmt(f)?;
" argument(s), function is exhaustive and cannot be called with more then "
.fmt(f)?;
expected.fmt(f)?;
" arguments".fmt(f)?;
}
#[cfg(feature = "loader")]
Error::Resolving {
base,
name,
message,
} => {
"Error resolving module '".fmt(f)?;
name.fmt(f)?;
"' from '".fmt(f)?;
base.fmt(f)?;
"'".fmt(f)?;
if let Some(message) = message {
if !message.is_empty() {
": ".fmt(f)?;
message.fmt(f)?;
}
}
}
#[cfg(feature = "loader")]
Error::Loading { name, message } => {
"Error loading module '".fmt(f)?;
name.fmt(f)?;
"'".fmt(f)?;
if let Some(message) = message {
if !message.is_empty() {
": ".fmt(f)?;
message.fmt(f)?;
}
}
}
#[cfg(feature = "std")]
Error::Io(error) => {
"IO Error: ".fmt(f)?;
error.fmt(f)?;
}
Error::ClassBorrow(x) => {
"Error borrowing class: ".fmt(f)?;
x.fmt(f)?;
}
Error::FunctionBorrow(x) => {
"Error borrowing function: ".fmt(f)?;
x.fmt(f)?;
}
Error::WouldBlock => "Error blocking on a promise resulted in a dead lock".fmt(f)?,
Error::UserData(x) => x.fmt(f)?,
Error::AsSlice(x) => {
"Could not convert array buffer to slice: ".fmt(f)?;
x.fmt(f)?;
}
Error::UnrelatedRuntime => "Restoring Persistent in an unrelated runtime".fmt(f)?,
}
Ok(())
}
}
macro_rules! from_impls {
($($type:ty => $variant:ident,)*) => {
$(
impl From<$type> for Error {
fn from(error: $type) -> Self {
Error::$variant(error)
}
}
)*
};
}
from_impls! {
NulError => InvalidString,
FromBytesWithNulError => InvalidCStr,
Utf8Error => Utf8,
}
#[cfg(feature = "std")]
from_impls! {
IoError => Io,
}
impl From<FromUtf8Error> for Error {
fn from(error: FromUtf8Error) -> Self {
Error::Utf8(error.utf8_error())
}
}
impl<T> From<UserDataError<T>> for Error {
fn from(_: UserDataError<T>) -> Self {
Error::UserData(UserDataError(()))
}
}
impl From<AsSliceError> for Error {
fn from(value: AsSliceError) -> Self {
Error::AsSlice(value)
}
}
#[derive(Debug)]
pub enum CaughtError<'js> {
Error(Error),
Exception(Exception<'js>),
Value(Value<'js>),
}
impl<'js> Display for CaughtError<'js> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
CaughtError::Error(ref e) => e.fmt(f),
CaughtError::Exception(ref e) => e.fmt(f),
CaughtError::Value(ref e) => {
writeln!(f, "Exception generated by quickjs: {e:?}")
}
}
}
}
impl<'js> StdError for CaughtError<'js> {}
impl<'js> CaughtError<'js> {
pub fn from_error(ctx: &Ctx<'js>, error: Error) -> Self {
if let Error::Exception = error {
let value = ctx.catch();
if let Some(ex) = value
.as_object()
.and_then(|x| Exception::from_object(x.clone()))
{
CaughtError::Exception(ex)
} else {
CaughtError::Value(value)
}
} else {
CaughtError::Error(error)
}
}
pub fn catch<T>(ctx: &Ctx<'js>, error: Result<T>) -> CaughtResult<'js, T> {
error.map_err(|error| Self::from_error(ctx, error))
}
pub fn throw(self, ctx: &Ctx<'js>) -> Error {
match self {
CaughtError::Error(e) => e,
CaughtError::Exception(ex) => ctx.throw(ex.into_value()),
CaughtError::Value(ex) => ctx.throw(ex),
}
}
pub fn is_exception(&self) -> bool {
matches!(self, CaughtError::Exception(_))
}
pub fn is_js_error(&self) -> bool {
matches!(self, CaughtError::Exception(_) | CaughtError::Value(_))
}
}
pub trait CatchResultExt<'js, T> {
fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T>;
}
impl<'js, T> CatchResultExt<'js, T> for Result<T> {
fn catch(self, ctx: &Ctx<'js>) -> CaughtResult<'js, T> {
CaughtError::catch(ctx, self)
}
}
pub trait ThrowResultExt<'js, T> {
fn throw(self, ctx: &Ctx<'js>) -> Result<T>;
}
impl<'js, T> ThrowResultExt<'js, T> for CaughtResult<'js, T> {
fn throw(self, ctx: &Ctx<'js>) -> Result<T> {
self.map_err(|e| e.throw(ctx))
}
}
#[derive(Clone)]
pub struct JobException(pub Context);
impl fmt::Debug for JobException {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_tuple("JobException")
.field(&"TODO: Context")
.finish()
}
}
impl Display for JobException {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "Job raised an exception")?;
Ok(())
}
}
#[cfg(feature = "futures")]
#[derive(Clone)]
pub struct AsyncJobException(pub AsyncContext);
#[cfg(feature = "futures")]
impl fmt::Debug for AsyncJobException {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_tuple("AsyncJobException")
.field(&"TODO: Context")
.finish()
}
}
#[cfg(feature = "futures")]
impl Display for AsyncJobException {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "Async job raised an exception")?;
Ok(())
}
}
impl<'js> Ctx<'js> {
pub(crate) fn handle_panic<F>(&self, f: F) -> qjs::JSValue
where
F: FnOnce() -> qjs::JSValue + UnwindSafe,
{
match crate::util::catch_unwind(f) {
Ok(x) => x,
Err(e) => unsafe {
self.get_opaque().set_panic(e);
qjs::JS_Throw(self.as_ptr(), qjs::JS_MKVAL(qjs::JS_TAG_EXCEPTION, 0))
},
}
}
pub(crate) unsafe fn handle_exception(&self, js_val: qjs::JSValue) -> Result<qjs::JSValue> {
if qjs::JS_VALUE_GET_NORM_TAG(js_val) != qjs::JS_TAG_EXCEPTION {
Ok(js_val)
} else {
if let Some(x) = self.get_opaque().take_panic() {
crate::util::resume_unwind(x);
}
Err(Error::Exception)
}
}
pub(crate) fn raise_exception(&self) -> Error {
unsafe {
if let Some(x) = self.get_opaque().take_panic() {
crate::util::resume_unwind(x);
}
Error::Exception
}
}
}