rquickjs_core/value/
exception.rs1use 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#[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
28fn 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 (str.as_bytes()[max] & 0b1100_0000) == 0b1000_0000 {
38 max -= 1;
39 }
40 str = &str[..max];
41 }
42 unsafe {
43 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 pub fn into_object(self) -> Object<'js> {
53 self.0
54 }
55
56 pub fn as_object(&self) -> &Object<'js> {
58 &self.0
59 }
60
61 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 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 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 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 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 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 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 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 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 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 pub fn throw_dom(ctx: &Ctx<'js>, name: &str, message: &str) -> Error {
199 let mut name_buffer = [MaybeUninit::uninit(); 256];
200 truncate_cstr_into(&mut name_buffer, name);
201 let mut message_buffer = [MaybeUninit::uninit(); 256];
202 truncate_cstr_into(&mut message_buffer, message);
203 unsafe {
204 let res = qjs::JS_ThrowDOMException(
205 ctx.as_ptr(),
206 name_buffer.as_ptr().cast(),
207 ERROR_FORMAT_STR.as_ptr(),
208 message_buffer.as_mut_ptr(),
209 );
210 debug_assert_eq!(qjs::JS_VALUE_GET_NORM_TAG(res), qjs::JS_TAG_EXCEPTION);
211 }
212 Error::Exception
213 }
214
215 pub fn throw(self) -> Error {
217 let ctx = self.ctx().clone();
218 ctx.throw(self.0.into_value())
219 }
220}
221
222impl fmt::Display for Exception<'_> {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 "Error:".fmt(f)?;
225 if let Some(message) = self.message() {
226 ' '.fmt(f)?;
227 message.fmt(f)?;
228 }
229 if let Some(stack) = self.stack() {
230 '\n'.fmt(f)?;
231 stack.fmt(f)?;
232 }
233 Ok(())
234 }
235}
236
237#[cfg(test)]
238mod test {
239 use crate::{
240 atom::PredefinedAtom, context::intrinsic, Context, Error, Exception, Object, Runtime,
241 };
242
243 #[test]
244 fn test_throw_dom() {
245 let rt = Runtime::new().unwrap();
246 let ctx = Context::custom::<intrinsic::DOMException>(&rt).unwrap();
247 ctx.with(|ctx| {
248 let e = Exception::throw_dom(&ctx, "SyntaxError", "oh no");
249 assert!(matches!(e, Error::Exception), "{e}");
250 let exception = ctx.catch().into_object().unwrap();
251 assert!(exception.is_instance_of(
252 ctx.globals()
253 .get::<_, Object>(PredefinedAtom::DOMException)
254 .unwrap()
255 ));
256 assert_eq!(
257 exception.get::<_, i32>("code").unwrap(),
258 12
260 );
261 });
262 }
263}