Skip to main content

rquickjs_core/value/
exception.rs

1use alloc::string::String;
2use core::mem::{self, MaybeUninit};
3use core::{error::Error as ErrorTrait, ffi::CStr, fmt};
4
5use crate::{atom::PredefinedAtom, convert::Coerced, qjs, Ctx, Error, Object, Result, Value};
6
7/// A JavaScript instance of Error
8///
9/// Will turn into a error when converted to JavaScript but won't automatically be thrown.
10#[repr(transparent)]
11#[derive(Clone, Eq, PartialEq, Hash)]
12pub struct Exception<'js>(pub(crate) Object<'js>);
13
14impl<'js> ErrorTrait for Exception<'js> {}
15
16impl fmt::Debug for Exception<'_> {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        f.debug_struct("Exception")
19            .field("message", &self.message())
20            .field("stack", &self.stack())
21            .finish()
22    }
23}
24
25pub(crate) static ERROR_FORMAT_STR: &CStr =
26    unsafe { CStr::from_bytes_with_nul_unchecked("%s\0".as_bytes()) };
27
28/// Writes as many characters of `str` as will fit into `buf`, followed by a nul byte.
29///
30/// QuickJS implementation doesn't allow error strings longer than 256 anyway so
31/// truncating here is fine.
32fn truncate_cstr_into(buf: &mut [MaybeUninit<u8>; 256], mut str: &str) {
33    let mut max = buf.len() - 1;
34    if str.len() > max {
35        // while the byte at len is a continue byte shorten the byte.
36        // TODO: use floor_char_boundary when MSRV is at least 1.91
37        while (str.as_bytes()[max] & 0b1100_0000) == 0b1000_0000 {
38            max -= 1;
39        }
40        str = &str[..max];
41    }
42    unsafe {
43        // SAFETY: these slice types have the same layout
44        buf[..str.len()]
45            .copy_from_slice(mem::transmute::<&[u8], &[MaybeUninit<u8>]>(str.as_bytes()));
46    }
47    buf[str.len()].write(0u8);
48}
49
50impl<'js> Exception<'js> {
51    /// Turns the exception into the underlying object.
52    pub fn into_object(self) -> Object<'js> {
53        self.0
54    }
55
56    /// Returns a reference to the underlying object.
57    pub fn as_object(&self) -> &Object<'js> {
58        &self.0
59    }
60
61    /// Creates an exception from an object if it is an instance of error.
62    pub fn from_object(obj: Object<'js>) -> Option<Self> {
63        if obj.is_error() {
64            Some(Self(obj))
65        } else {
66            None
67        }
68    }
69
70    /// Creates a new exception with a given message.
71    pub fn from_message(ctx: Ctx<'js>, message: &str) -> Result<Self> {
72        let obj = unsafe {
73            let value = ctx.handle_exception(qjs::JS_NewError(ctx.as_ptr()))?;
74            Value::from_js_value(ctx, value)
75                .into_object()
76                .expect("`JS_NewError` did not return an object")
77        };
78        obj.set(PredefinedAtom::Message, message)?;
79        Ok(Exception(obj))
80    }
81
82    /// Returns the message of the error.
83    ///
84    /// Same as retrieving `error.message` in JavaScript.
85    pub fn message(&self) -> Option<String> {
86        self.get::<_, Option<Coerced<String>>>(PredefinedAtom::Message)
87            .ok()
88            .and_then(|x| x)
89            .map(|x| x.0)
90    }
91
92    /// Returns the error stack.
93    ///
94    /// Same as retrieving `error.stack` in JavaScript.
95    pub fn stack(&self) -> Option<String> {
96        self.get::<_, Option<Coerced<String>>>(PredefinedAtom::Stack)
97            .ok()
98            .and_then(|x| x)
99            .map(|x| x.0)
100    }
101
102    /// Throws a new generic error.
103    ///
104    /// Equivalent to:
105    /// ```rust
106    /// # use rquickjs::{Runtime,Context,Exception};
107    /// # let rt = Runtime::new().unwrap();
108    /// # let ctx = Context::full(&rt).unwrap();
109    /// # ctx.with(|ctx|{
110    /// # let _ = {
111    /// # let message = "";
112    /// let (Ok(e) | Err(e)) = Exception::from_message(ctx, message).map(|x| x.throw());
113    /// e
114    /// # };
115    /// # })
116    /// ```
117    pub fn throw_message(ctx: &Ctx<'js>, message: &str) -> Error {
118        let (Ok(e) | Err(e)) = Self::from_message(ctx.clone(), message).map(|x| x.throw());
119        e
120    }
121
122    /// Throws a new syntax error.
123    pub fn throw_syntax(ctx: &Ctx<'js>, message: &str) -> Error {
124        let mut buffer = [MaybeUninit::uninit(); 256];
125        truncate_cstr_into(&mut buffer, message);
126        unsafe {
127            let res = qjs::JS_ThrowSyntaxError(
128                ctx.as_ptr(),
129                ERROR_FORMAT_STR.as_ptr(),
130                buffer.as_mut_ptr(),
131            );
132            debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
133        }
134        Error::Exception
135    }
136
137    /// Throws a new type error.
138    pub fn throw_type(ctx: &Ctx<'js>, message: &str) -> Error {
139        let mut buffer = [MaybeUninit::uninit(); 256];
140        truncate_cstr_into(&mut buffer, message);
141        unsafe {
142            let res = qjs::JS_ThrowTypeError(
143                ctx.as_ptr(),
144                ERROR_FORMAT_STR.as_ptr(),
145                buffer.as_mut_ptr(),
146            );
147            debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
148        }
149        Error::Exception
150    }
151
152    /// Throws a new reference error.
153    pub fn throw_reference(ctx: &Ctx<'js>, message: &str) -> Error {
154        let mut buffer = [MaybeUninit::uninit(); 256];
155        truncate_cstr_into(&mut buffer, message);
156        unsafe {
157            let res = qjs::JS_ThrowReferenceError(
158                ctx.as_ptr(),
159                ERROR_FORMAT_STR.as_ptr(),
160                buffer.as_mut_ptr(),
161            );
162            debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
163        }
164        Error::Exception
165    }
166
167    /// Throws a new range error.
168    pub fn throw_range(ctx: &Ctx<'js>, message: &str) -> Error {
169        let mut buffer = [MaybeUninit::uninit(); 256];
170        truncate_cstr_into(&mut buffer, message);
171        unsafe {
172            let res = qjs::JS_ThrowRangeError(
173                ctx.as_ptr(),
174                ERROR_FORMAT_STR.as_ptr(),
175                buffer.as_mut_ptr(),
176            );
177            debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
178        }
179        Error::Exception
180    }
181
182    /// Throws a new internal error.
183    pub fn throw_internal(ctx: &Ctx<'js>, message: &str) -> Error {
184        let mut buffer = [MaybeUninit::uninit(); 256];
185        truncate_cstr_into(&mut buffer, message);
186        unsafe {
187            let res = qjs::JS_ThrowInternalError(
188                ctx.as_ptr(),
189                ERROR_FORMAT_STR.as_ptr(),
190                buffer.as_mut_ptr(),
191            );
192            debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
193        }
194        Error::Exception
195    }
196
197    /// Sets the exception as the current error an returns `Error::Exception`
198    pub fn throw(self) -> Error {
199        let ctx = self.ctx().clone();
200        ctx.throw(self.0.into_value())
201    }
202}
203
204impl fmt::Display for Exception<'_> {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        "Error:".fmt(f)?;
207        if let Some(message) = self.message() {
208            ' '.fmt(f)?;
209            message.fmt(f)?;
210        }
211        if let Some(stack) = self.stack() {
212            '\n'.fmt(f)?;
213            stack.fmt(f)?;
214        }
215        Ok(())
216    }
217}