rquickjs_core/value/
exception.rs

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