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 match exception_ref.to_string() {
28 Ok(s) => JsError::new_string(s),
29 Err(ex) => {
30 JsError::new_string(format!("Could not determine error due to error: {ex:?}"))
31 }
32 }
33 };
34 Some(err)
35 }
36}
37
38pub unsafe fn error_to_js_error(
42 context: *mut q::JSContext,
43 exception_ref: &QuickJsValueAdapter,
44) -> JsError {
45 log::trace!("error_to_js_error");
46 let name_ref = objects::get_property(context, exception_ref, "name")
47 .ok()
48 .unwrap();
49 let name_string = primitives::to_string(context, &name_ref).ok().unwrap();
50 let message_ref = objects::get_property(context, exception_ref, "message")
51 .ok()
52 .unwrap();
53 let message_string = primitives::to_string(context, &message_ref).ok().unwrap();
54 let stack_ref = objects::get_property(context, exception_ref, "stack")
55 .ok()
56 .unwrap();
57 let mut stack_string = "".to_string();
58
59 let stack2_ref = objects::get_property(context, exception_ref, "stack2")
60 .ok()
61 .unwrap();
62 if stack2_ref.is_string() {
63 stack_string.push_str(
64 primitives::to_string(context, &stack2_ref)
65 .ok()
66 .unwrap()
67 .as_str(),
68 );
69 }
70
71 if stack_ref.is_string() {
72 let stack_str = primitives::to_string(context, &stack_ref).ok().unwrap();
73 #[cfg(feature = "typescript")]
74 let stack_str = crate::typescript::unmap_stack_trace(stack_str.as_str());
75
76 stack_string.push_str(stack_str.as_str());
77 }
78
79 let cause_ref = objects::get_property(context, exception_ref, "cause")
80 .ok()
81 .unwrap();
82 if cause_ref.is_null_or_undefined() {
85 JsError::new(name_string, message_string, stack_string)
86 } else {
87 QuickJsRealmAdapter::with_context(context, |realm| {
88 let cause_str = cause_ref.to_string().ok().unwrap();
89 let cause_jsvf = realm.to_js_value_facade(&cause_ref).ok().unwrap();
90
91 stack_string.push_str("Caused by: ");
92 stack_string.push_str(cause_str.as_str());
93
94 JsError::new2(name_string, message_string, stack_string, cause_jsvf)
95 })
96 }
97}
98
99pub unsafe fn new_error(
103 context: *mut q::JSContext,
104 name: &str,
105 message: &str,
106 stack: &str,
107) -> Result<QuickJsValueAdapter, JsError> {
108 let obj = q::JS_NewError(context);
109 let obj_ref = QuickJsValueAdapter::new(
110 context,
111 obj,
112 false,
113 true,
114 format!("new_error {name}").as_str(),
115 );
116 objects::set_property(
117 context,
118 &obj_ref,
119 "message",
120 &primitives::from_string(context, message)?,
121 )?;
122 objects::set_property(
123 context,
124 &obj_ref,
125 "name",
126 &primitives::from_string(context, name)?,
127 )?;
128 objects::set_property(
129 context,
130 &obj_ref,
131 "stack2",
132 &primitives::from_string(context, stack)?,
133 )?;
134 Ok(obj_ref)
135}
136
137pub fn is_error_q(q_ctx: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
139 unsafe { is_error(q_ctx.context, obj_ref) }
140}
141
142#[allow(unused_variables)]
146pub unsafe fn is_error(context: *mut q::JSContext, obj_ref: &QuickJsValueAdapter) -> bool {
147 if obj_ref.is_object() {
148 #[cfg(feature = "bellard")]
149 {
150 let res = q::JS_IsError(context, *obj_ref.borrow_value());
151 res != 0
152 }
153 #[cfg(feature = "quickjs-ng")]
154 {
155 q::JS_IsError(*obj_ref.borrow_value())
156 }
157 } else {
158 false
159 }
160}
161
162pub fn get_stack(realm: &QuickJsRealmAdapter) -> Result<QuickJsValueAdapter, JsError> {
163 let e = realm.invoke_function_by_name(&[], "Error", &[])?;
164 realm.get_object_property(&e, "stack")
165}
166
167pub unsafe fn throw(context: *mut q::JSContext, error: QuickJsValueAdapter) -> q::JSValue {
171 assert!(is_error(context, &error));
172 q::JS_Throw(context, error.clone_value_incr_rc());
173 q::JSValue {
174 #[cfg(feature = "bellard")]
175 u: q::JSValueUnion { uint64: 0 },
176 #[cfg(feature = "quickjs-ng")]
177 u: q::JSValueUnion { int32: 0 },
178 tag: TAG_EXCEPTION,
179 }
180}
181
182#[cfg(test)]
183pub mod tests {
184 use crate::facades::tests::init_test_rt;
185 use crate::jsutils::{JsError, Script};
186 use crate::quickjs_utils::functions;
187 use crate::values::{JsValueConvertable, JsValueFacade};
188 use std::thread;
189 use std::time::Duration;
190
191 #[test]
192 fn test_ex_nat() {
193 let rt = init_test_rt();
196 let res = rt.eval_sync(
197 None,
198 Script::new(
199 "ex.js",
200 "console.log('foo');\nconsole.log('bar');let a = __c_v__ * 7;",
201 ),
202 );
203 let ex = res.expect_err("script should have failed;");
204
205 #[cfg(feature = "bellard")]
206 assert_eq!(ex.get_message(), "'__c_v__' is not defined");
207 #[cfg(feature = "quickjs-ng")]
208 assert_eq!(ex.get_message(), "__c_v__ is not defined");
209 }
210
211 #[test]
212 fn test_ex_cause() {
213 let rt = init_test_rt();
216 let res = rt.eval_sync(
217 None,
218 Script::new(
219 "ex.ts",
220 r#"
221 let a = 2;
222 let b = 3;
223
224 function f1(a, b) {
225 throw new Error('Could not f1', { cause: 'Sabotage here' });
226 }
227 function f2() {
228 try {
229 let r = f1(a, b);
230 } catch(ex) {
231 throw new Error('could not f2', { cause: ex});
232 }
233 }
234 f2()
235 "#,
236 ),
237 );
238 let ex = res.expect_err("script should have failed;");
239
240 assert_eq!(ex.get_message(), "could not f2");
241
242 let complete_err = format!("{ex}");
243 assert!(complete_err.contains("Caused by: Error: Could not f1"));
244 assert!(complete_err.contains("Caused by: Sabotage here"));
245 }
246
247 #[test]
248 fn test_ex0() {
249 let rt = init_test_rt();
252 let res = rt.eval_sync(
253 None,
254 Script::new(
255 "ex.js",
256 "console.log('foo');\nconsole.log('bar');let a = __c_v__ * 7;",
257 ),
258 );
259 let ex = res.expect_err("script should have failed;");
260
261 #[cfg(feature = "bellard")]
262 assert_eq!(ex.get_message(), "'__c_v__' is not defined");
263 #[cfg(feature = "quickjs-ng")]
264 assert_eq!(ex.get_message(), "__c_v__ is not defined");
265 }
266
267 #[test]
268 fn test_ex1() {
269 let rt = init_test_rt();
272 rt.set_function(&[], "test_consume", move |_realm, args| {
273 let func_jsvf = &args[0];
275 match func_jsvf {
276 JsValueFacade::JsFunction { cached_function } => {
277 let _ = cached_function.invoke_function_sync(vec![12.to_js_value_facade()])?;
278 Ok(0.to_js_value_facade())
279 }
280 _ => Err(JsError::new_str("poof")),
281 }
282 })
283 .expect("could not set function");
284 let s_res = rt.eval_sync(
285 None,
286 Script::new(
287 "test_ex34245.js",
288 "let consumer = function() {
289 console.log('consuming');
290 throw new Error('oh dear stuff failed at line 3 in consumer');
291 };
292 console.log('calling consume from line 6');
293 let a = test_consume(consumer);
294 console.log('should never reach line 7 %s', a)",
295 ),
296 );
297 match s_res {
298 Ok(o) => {
299 log::info!("o = {}", o.stringify());
300 }
301 Err(e) => {
302 log::error!("script failed: {}", e);
303 log::error!("{}", e);
304 }
305 }
306
307 std::thread::sleep(Duration::from_secs(1));
308 }
309
310 #[test]
311 fn test_ex3() {
312 let rt = init_test_rt();
313 rt.eval_sync(
314 None,
315 Script::new(
316 "test_ex3.js",
317 r#"
318
319async function sleep(ms) {
320 return await new Promise((res) => {
321 //setTimeout(res, ms);
322 res();
323 });
324}
325
326async function a() {
327 await b();
328}
329
330async function b() {
331 await sleep(50);
332 return new Promise((res) => {
333 res(c());
334 });
335}
336
337async function c() {
338 await sleep(10);
339 await sleep(10);
340 await sleep(10);
341 await sleep(10);
342 await d();
343}
344
345async function d() {
346 throw Error("poof");
347}
348
349const ap = a();
350ap.catch((ex) => {
351 console.error("The error = %s, stack:%s", ex.message, ex.stack);
352});
353 "#,
354 ),
355 )
356 .expect("script failed");
357 thread::sleep(Duration::from_secs(1));
358 }
359
360 #[test]
361 fn test_ex_stack() {
362 let rt = init_test_rt();
363 rt.exe_rt_task_in_event_loop(|rt| {
364 let realm = rt.get_main_realm();
365 realm
366 .install_closure(
367 &[],
368 "myFunc",
369 |_rt, realm, _this, _args| crate::quickjs_utils::errors::get_stack(realm),
370 0,
371 )
372 .expect("could not install func");
373
374 let res = realm
375 .eval(Script::new(
376 "runMyFunc.js",
377 r#"
378 function a(){
379 return b();
380 }
381 function b(){
382 return myFunc();
383 }
384 a()
385 "#,
386 ))
387 .expect("script failed");
388
389 log::info!("test_ex_stack res = {}", res.to_string().unwrap());
390 });
391 }
392
393 #[test]
394 fn test_ex2() {
395 let rt = init_test_rt();
400 rt.exe_rt_task_in_event_loop(|q_js_rt| {
401 let q_ctx = q_js_rt.get_main_realm();
402
403 q_ctx
404 .eval(Script::new(
405 "test_ex2_pre.es",
406 "console.log('before ex test');",
407 ))
408 .expect("test_ex2_pre failed");
409 {
410 let func_ref1 = q_ctx
411 .eval(Script::new(
412 "test_ex2f1.es",
413 "(function(){\nconsole.log('running f1');});",
414 ))
415 .expect("script failed");
416 assert!(functions::is_function_q(q_ctx, &func_ref1));
417 let res = functions::call_function_q(q_ctx, &func_ref1, &[], None);
418 match res {
419 Ok(_) => {}
420 Err(e) => {
421 log::error!("func1 failed: {}", e);
422 }
423 }
424 }
425 let func_ref2 = q_ctx
427 .eval(Script::new(
428 "test_ex2.es",
429 r#"
430 const f = function(){
431 throw Error('poof');
432 };
433 f
434 "#,
435 ))
436 .expect("script failed");
437
438 assert!(functions::is_function_q(q_ctx, &func_ref2));
439 let res = functions::call_function_q(q_ctx, &func_ref2, &[], None);
440 match res {
441 Ok(_) => {}
442 Err(e) => {
443 log::error!("func2 failed: {}", e);
444 }
445 }
446 });
447
448 #[cfg(feature = "bellard")]
449 {
450 let mjsvf = rt
451 .eval_module_sync(
452 None,
453 Script::new(
454 "test_ex2.es",
455 r#"
456 throw Error('poof');
457 "#,
458 ),
459 )
460 .map_err(|e| {
461 log::error!("script compilation failed: {e}");
462 e
463 })
464 .expect("script compilation failed");
465 match mjsvf {
466 JsValueFacade::JsPromise { cached_promise } => {
467 let pres = cached_promise
468 .get_promise_result_sync()
469 .expect("promise timed out");
470 match pres {
471 Ok(m) => {
472 log::info!("prom resolved to {}", m.stringify())
473 }
474 Err(e) => {
475 log::info!("prom rejected to {}", e.stringify())
476 }
477 }
478 }
479 _ => {
480 panic!("not a prom")
481 }
482 }
483 }
484
485 std::thread::sleep(Duration::from_secs(1));
486 }
487}