quickjs_runtime/quickjs_utils/
errors.rs1use crate::jsutils::JsError;
4use crate::quickjs_utils::{objects, primitives};
5use crate::quickjsrealmadapter::QuickJsRealmAdapter;
6use crate::quickjsvalueadapter::{QuickJsValueAdapter, TAG_EXCEPTION};
7use libquickjs_sys as q;
8
9pub unsafe fn get_exception(context: *mut q::JSContext) -> Option<JsError> {
13 log::trace!("get_exception");
14 let exception_val = q::JS_GetException(context);
15 log::trace!("get_exception / 2");
16 let exception_ref =
17 QuickJsValueAdapter::new(context, exception_val, false, true, "errors::get_exception");
18
19 if exception_ref.is_null() {
20 None
21 } else {
22 let err = if exception_ref.is_exception() {
23 JsError::new_str("Could not get exception from runtime")
24 } else if exception_ref.is_object() {
25 error_to_js_error(context, &exception_ref)
26 } else {
27 JsError::new_string(format!(
28 "no clue what happened {}",
29 exception_ref.get_js_type()
30 ))
31 };
32 Some(err)
33 }
34}
35
36pub unsafe fn error_to_js_error(
40 context: *mut q::JSContext,
41 exception_ref: &QuickJsValueAdapter,
42) -> JsError {
43 log::trace!("error_to_js_error");
44 let name_ref = objects::get_property(context, exception_ref, "name")
45 .ok()
46 .unwrap();
47 let name_string = primitives::to_string(context, &name_ref).ok().unwrap();
48 let message_ref = objects::get_property(context, exception_ref, "message")
49 .ok()
50 .unwrap();
51 let message_string = primitives::to_string(context, &message_ref).ok().unwrap();
52 let stack_ref = objects::get_property(context, exception_ref, "stack")
53 .ok()
54 .unwrap();
55 let mut stack_string = "".to_string();
56
57 let stack2_ref = objects::get_property(context, exception_ref, "stack2")
58 .ok()
59 .unwrap();
60 if stack2_ref.is_string() {
61 stack_string.push_str(
62 primitives::to_string(context, &stack2_ref)
63 .ok()
64 .unwrap()
65 .as_str(),
66 );
67 }
68
69 if stack_ref.is_string() {
70 let stack_str = primitives::to_string(context, &stack_ref).ok().unwrap();
71 #[cfg(feature = "typescript")]
72 let stack_str = crate::typescript::unmap_stack_trace(stack_str.as_str());
73
74 stack_string.push_str(stack_str.as_str());
75 }
76
77 JsError::new(name_string, message_string, stack_string)
78}
79
80pub unsafe fn new_error(
84 context: *mut q::JSContext,
85 name: &str,
86 message: &str,
87 stack: &str,
88) -> Result<QuickJsValueAdapter, JsError> {
89 let obj = q::JS_NewError(context);
90 let obj_ref = QuickJsValueAdapter::new(
91 context,
92 obj,
93 false,
94 true,
95 format!("new_error {name}").as_str(),
96 );
97 objects::set_property(
98 context,
99 &obj_ref,
100 "message",
101 &primitives::from_string(context, message)?,
102 )?;
103 objects::set_property(
104 context,
105 &obj_ref,
106 "name",
107 &primitives::from_string(context, name)?,
108 )?;
109 objects::set_property(
110 context,
111 &obj_ref,
112 "stack2",
113 &primitives::from_string(context, stack)?,
114 )?;
115 Ok(obj_ref)
116}
117
118pub fn is_error_q(q_ctx: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
120 unsafe { is_error(q_ctx.context, obj_ref) }
121}
122
123pub unsafe fn is_error(context: *mut q::JSContext, obj_ref: &QuickJsValueAdapter) -> bool {
127 if obj_ref.is_object() {
128 #[cfg(feature = "bellard")]
129 {
130 let res = q::JS_IsError(context, *obj_ref.borrow_value());
131 res != 0
132 }
133 #[cfg(feature = "quickjs-ng")]
134 {
135 q::JS_IsError(context, *obj_ref.borrow_value())
136 }
137 } else {
138 false
139 }
140}
141
142pub fn get_stack(realm: &QuickJsRealmAdapter) -> Result<QuickJsValueAdapter, JsError> {
143 let e = realm.invoke_function_by_name(&[], "Error", &[])?;
144 realm.get_object_property(&e, "stack")
145}
146
147pub unsafe fn throw(context: *mut q::JSContext, error: QuickJsValueAdapter) -> q::JSValue {
151 assert!(is_error(context, &error));
152 q::JS_Throw(context, error.clone_value_incr_rc());
153 q::JSValue {
154 u: q::JSValueUnion { int32: 0 },
155 tag: TAG_EXCEPTION,
156 }
157}
158
159#[cfg(test)]
160pub mod tests {
161 use crate::facades::tests::init_test_rt;
162 use crate::jsutils::{JsError, Script};
163 use crate::quickjs_utils::functions;
164 use crate::values::{JsValueConvertable, JsValueFacade};
165 use std::thread;
166 use std::time::Duration;
167
168 #[test]
169 fn test_ex_nat() {
170 let rt = init_test_rt();
173 let res = rt.eval_sync(
174 None,
175 Script::new(
176 "ex.js",
177 "console.log('foo');\nconsole.log('bar');let a = __c_v__ * 7;",
178 ),
179 );
180 let ex = res.expect_err("script should have failed;");
181
182 #[cfg(feature = "bellard")]
183 assert_eq!(ex.get_message(), "'__c_v__' is not defined");
184 #[cfg(feature = "quickjs-ng")]
185 assert_eq!(ex.get_message(), "__c_v__ is not defined");
186 }
187
188 #[test]
189 fn test_ex0() {
190 let rt = init_test_rt();
193 let res = rt.eval_sync(
194 None,
195 Script::new(
196 "ex.js",
197 "console.log('foo');\nconsole.log('bar');let a = __c_v__ * 7;",
198 ),
199 );
200 let ex = res.expect_err("script should have failed;");
201
202 #[cfg(feature = "bellard")]
203 assert_eq!(ex.get_message(), "'__c_v__' is not defined");
204 #[cfg(feature = "quickjs-ng")]
205 assert_eq!(ex.get_message(), "__c_v__ is not defined");
206 }
207
208 #[test]
209 fn test_ex1() {
210 let rt = init_test_rt();
213 rt.set_function(&[], "test_consume", move |_realm, args| {
214 let func_jsvf = &args[0];
216 match func_jsvf {
217 JsValueFacade::JsFunction { cached_function } => {
218 let _ = cached_function.invoke_function_sync(vec![12.to_js_value_facade()]);
219 Ok(0.to_js_value_facade())
220 }
221 _ => Err(JsError::new_str("poof")),
222 }
223 })
224 .expect("could not set function");
225 let s_res = rt.eval_sync(
226 None,
227 Script::new(
228 "test_ex.es",
229 "let consumer = function() {\n
230 console.log('consuming');\n
231 throw Error('oh dear stuff failed at line 3 in consumer');\n
232 };\n
233 console.log('calling consume from line 5');test_consume(consumer);\n
234 console.log('should never reach line 7')",
235 ),
236 );
237 if s_res.is_err() {
238 let e = format!("script failed: {}", s_res.err().unwrap());
239 log::error!("{}", e);
240 }
242 std::thread::sleep(Duration::from_secs(1));
243 }
244
245 #[test]
246 fn test_ex3() {
247 let rt = init_test_rt();
248 rt.eval_sync(
249 None,
250 Script::new(
251 "test_ex3.js",
252 r#"
253async function asyncDebugStackPreserve(delegate, invokerName) {
254
255 const startStack = new Error().stack;
256 try {
257 return await delegate();
258 } catch (ex) {
259 const err = Error(ex.message);
260
261 err.fileName = ex.fileName;
262 err.lineNumber = ex.lineNumber;
263 err.columnNumber = ex.columnNumber;
264 err.cause = ex.cause;
265
266 err.stack = ex.stack + startStack;
267 throw err;
268 }
269
270}
271
272async function sleep(ms) {
273 return new Promise((res) => {
274 setTimeout(res, ms);
275 });
276}
277
278async function a(){
279
280 return new Promise(async (res, rej) => {
281 try {
282 let ret = await asyncDebugStackPreserve(async function aInner() {
283 await sleep(100);
284 let ret = await b();
285 });
286 res(ret);
287 } catch(ex) {
288 rej(ex);
289 }
290 });
291 }
292
293 async function b(){
294 throw Error("poof");
295 }
296
297 a().catch((ex) => {
298 console.error(ex);
299 });
300
301
302
303 "#,
304 ),
305 )
306 .expect("script failed");
307 thread::sleep(Duration::from_secs(1));
308 }
309
310 #[test]
311 fn test_ex_stack() {
312 let rt = init_test_rt();
313 rt.exe_rt_task_in_event_loop(|rt| {
314 let realm = rt.get_main_realm();
315 realm
316 .install_closure(
317 &[],
318 "myFunc",
319 |_rt, realm, _this, _args| crate::quickjs_utils::errors::get_stack(realm),
320 0,
321 )
322 .expect("could not install func");
323
324 let res = realm
325 .eval(Script::new(
326 "runMyFunc.js",
327 r#"
328 function a(){
329 return b();
330 }
331 function b(){
332 return myFunc();
333 }
334 a()
335 "#,
336 ))
337 .expect("script failed");
338
339 log::info!("test_ex_stack res = {}", res.to_string().unwrap());
340 });
341 }
342
343 #[test]
344 fn test_ex2() {
345 let rt = init_test_rt();
350 rt.exe_rt_task_in_event_loop(|q_js_rt| {
351 let q_ctx = q_js_rt.get_main_realm();
352
353 q_ctx
354 .eval(Script::new(
355 "test_ex2_pre.es",
356 "console.log('before ex test');",
357 ))
358 .expect("test_ex2_pre failed");
359 {
360 let func_ref1 = q_ctx
361 .eval(Script::new(
362 "test_ex2f1.es",
363 "(function(){\nconsole.log('running f1');});",
364 ))
365 .expect("script failed");
366 assert!(functions::is_function_q(q_ctx, &func_ref1));
367 let res = functions::call_function_q(q_ctx, &func_ref1, &[], None);
368 match res {
369 Ok(_) => {}
370 Err(e) => {
371 log::error!("func1 failed: {}", e);
372 }
373 }
374 }
375 let func_ref2 = q_ctx
377 .eval(Script::new(
378 "test_ex2.es",
379 r#"
380 const f = function(){
381 throw Error('poof');
382 };
383 f
384 "#,
385 ))
386 .expect("script failed");
387
388 assert!(functions::is_function_q(q_ctx, &func_ref2));
389 let res = functions::call_function_q(q_ctx, &func_ref2, &[], None);
390 match res {
391 Ok(_) => {}
392 Err(e) => {
393 log::error!("func2 failed: {}", e);
394 }
395 }
396 });
397
398 #[cfg(feature = "bellard")]
399 {
400 let mjsvf = rt
401 .eval_module_sync(
402 None,
403 Script::new(
404 "test_ex2.es",
405 r#"
406 throw Error('poof');
407 "#,
408 ),
409 )
410 .map_err(|e| {
411 log::error!("script compilation failed: {e}");
412 e
413 })
414 .expect("script compilation failed");
415 match mjsvf {
416 JsValueFacade::JsPromise { cached_promise } => {
417 let pres = cached_promise
418 .get_promise_result_sync()
419 .expect("promise timed out");
420 match pres {
421 Ok(m) => {
422 log::info!("prom resolved to {}", m.stringify())
423 }
424 Err(e) => {
425 log::info!("prom rejected to {}", e.stringify())
426 }
427 }
428 }
429 _ => {
430 panic!("not a prom")
431 }
432 }
433 }
434
435 std::thread::sleep(Duration::from_secs(1));
436 }
437}