rquickjs_core/value/
exception.rs

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