1use std::ops::DerefMut;
2use super::*;
3
4macro_rules! try_exec_success {
5 ($res:expr) => {
6 if $res != DUK_EXEC_SUCCESS {
7 return Err($res)
8 }
9 }
10}
11
12#[derive(Debug)]
14pub struct DukContext {
15 pub (crate) ctx: *mut duk_context,
16}
17
18impl DukContext {
19 pub (crate) unsafe fn from_raw(ctx: *mut duk_context) -> Self {
20 Self { ctx }
21 }
22
23 #[inline]
24 pub fn normalize_index(&self, index: i32) -> i32 {
25 unsafe {
26 duk_normalize_index(self.ctx, index)
27 }
28 }
29
30 #[inline]
31 pub fn get_top(&self) -> i32 {
32 unsafe { duk_get_top(self.ctx) }
33 }
34
35 #[inline]
36 pub fn dup(&self, index: i32) {
37 unsafe {
38 duk_dup(self.ctx, index);
39 }
40 }
41
42 #[inline]
43 pub fn remove(&self, index: i32) {
44 unsafe {
45 duk_remove(self.ctx, index);
46 }
47 }
48
49 #[inline]
50 pub fn pop(&self) {
51 unsafe {
52 duk_pop(self.ctx);
53 }
54 }
55
56 #[inline]
57 pub fn pop_n(&self, n: i32) {
58 unsafe {
59 duk_pop_n(self.ctx, n);
60 }
61 }
62
63 #[inline]
64 pub fn swap(&self, idx1: i32, idx2: i32) {
65 unsafe {
66 duk_swap(self.ctx, idx1, idx2);
67 }
68 }
69
70 #[inline]
71 pub fn push_this(&self) {
72 unsafe { duk_push_this(self.ctx); }
73 }
74
75 #[inline]
76 pub fn push_thread(&self) -> i32 {
77 unsafe { duk_push_thread_raw(self.ctx, 0) }
78 }
79
80 #[inline]
81 pub fn push_thread_new_globalenv(&self) -> i32 {
82 unsafe { duk_push_thread_raw(self.ctx, DukThreadFlags::DUK_THREAD_NEW_GLOBAL_ENV.bits()) }
83 }
84
85 #[inline]
86 pub fn push_global_object(&self) {
87 unsafe { duk_push_global_object(self.ctx); }
88 }
89
90 #[inline]
91 pub fn push_boolean(&self, value: bool) {
92 unsafe { duk_push_boolean(self.ctx, value as i32) }
93 }
94
95 #[inline]
96 pub fn push_null(&self) {
97 unsafe { duk_push_null(self.ctx) }
98 }
99
100 #[inline]
101 pub fn push_undefined(&self) {
102 unsafe { duk_push_undefined(self.ctx) }
103 }
104
105 #[inline]
106 pub fn push_i32(&self, value: i32) {
107 unsafe { duk_push_int(self.ctx, value) }
108 }
109
110 #[inline]
111 pub fn push_u32(&self, value: u32) {
112 unsafe { duk_push_uint(self.ctx, value) }
113 }
114
115 #[inline]
116 pub fn push_number(&self, value: f64) {
117 unsafe { duk_push_number(self.ctx, value) }
118 }
119
120 #[inline]
121 pub fn push_string(&self, value: &str) {
122 unsafe {
123 duk_push_lstring(self.ctx, value.as_ptr() as *const c_char, value.len());
124 }
125 }
126
127 #[inline]
128 pub fn push_object(&self) -> i32 {
129 unsafe { duk_push_object(self.ctx) }
130 }
131
132 #[inline]
133 pub fn push_ext_buffer(&self, data: &[u8]) {
134 unsafe {
135 duk_push_buffer_raw(self.ctx, 0, (DukBufFlags::DUK_BUF_FLAG_DYNAMIC | DukBufFlags::DUK_BUF_FLAG_EXTERNAL).bits());
136 duk_config_buffer(self.ctx, -1, data.as_ptr() as *mut c_void, data.len());
137 }
138 }
139
140 #[inline]
141 pub fn push_array(&self) -> i32 {
142 unsafe { duk_push_array(self.ctx) }
143 }
144
145 pub fn push_function(&self, func_name: &str, nargs: i32) {
146 unsafe {
147 duk_push_c_function(self.ctx, Some(func_dispatch), nargs);
148 duk_push_lstring(self.ctx, FUNC_NAME_PROP.as_ptr() as *const c_char, FUNC_NAME_PROP.len());
149 duk_push_lstring(self.ctx, func_name.as_ptr() as *const c_char, func_name.len());
150 duk_def_prop(self.ctx, -3, (DukDefpropFlags::DUK_DEFPROP_ENUMERABLE | DukDefpropFlags::DUK_DEFPROP_HAVE_VALUE).bits())
151 }
152 }
153
154 pub fn put_prop_function(&self, obj_index: i32, func_name: &str, nargs: i32) {
155 let obj_index = self.normalize_index(obj_index);
156 self.push_function(func_name, nargs);
157 unsafe {
158 duk_put_prop_lstring(self.ctx, obj_index, func_name.as_ptr() as *const c_char, func_name.len());
159 }
160 }
161
162 pub fn put_global_function(&self, func_name: &str, nargs: i32) {
163 self.push_function(func_name, nargs);
164 self.put_global_string(func_name);
165 }
166
167 #[inline]
168 pub fn get_type(&self, index: i32) -> DukType {
169 DukType::from(unsafe { duk_get_type(self.ctx, index) })
170 }
171
172 #[inline]
173 pub fn is_string(&self, index: i32) -> bool {
174 unsafe { duk_is_string(self.ctx, index) == 1 }
175 }
176
177 #[inline]
178 pub fn is_number(&self, index: i32) -> bool {
179 unsafe { duk_is_number(self.ctx, index) == 1 }
180 }
181
182 #[inline]
183 pub fn is_object(&self, index: i32) -> bool {
184 unsafe { duk_is_object(self.ctx, index) == 1 }
185 }
186
187 #[inline]
188 pub fn is_array(&self, index: i32) -> bool {
189 unsafe { duk_is_array(self.ctx, index) == 1 }
190 }
191
192 #[inline]
193 pub fn is_pure_object(&self, index: i32) -> bool {
194 unsafe {
195 duk_is_object(self.ctx, index) == 1
196 && duk_is_array(self.ctx, index) == 0
197 && duk_is_function(self.ctx, index) == 0
198 && duk_is_thread(self.ctx, index) == 0
199 }
200 }
201
202 #[inline]
203 pub fn get_string(&self, index: i32) -> &str {
204 use std::str;
205 use std::slice;
206 unsafe {
207 let mut len: usize = 0;
208 let ptr = duk_get_lstring(self.ctx, index, Some(&mut len)) as *const u8;
209 str::from_utf8_unchecked(slice::from_raw_parts(ptr, len))
210 }
211 }
212
213 #[inline]
214 pub fn get_buffer(&self, index: i32) -> &[u8] {
215 use std::slice;
216 unsafe {
217 let mut len: usize = 0;
218 let ptr = duk_get_buffer(self.ctx, index, Some(&mut len)) as *const u8;
219 slice::from_raw_parts(ptr, len)
220 }
221 }
222
223 #[inline]
224 pub fn get_number(&self, index: i32) -> f64 {
225 unsafe { duk_get_number(self.ctx, index) }
226 }
227
228 #[inline]
229 pub fn get_boolean(&self, index: i32) -> bool {
230 unsafe { duk_get_boolean(self.ctx, index) != 0 }
231 }
232
233 pub fn get_context(&self, index: i32) -> Result<DukContextGuard, JsError> {
234 let new_ctx = unsafe { duk_get_context(self.ctx, index) };
235 if new_ctx.is_null() {
236 return Err(JsError::from(format!("could not get context from index {}", index)));
237 }
238 Ok(DukContextGuard::new(unsafe { DukContext::from_raw(new_ctx) }))
239 }
240
241 #[inline]
242 pub fn get_prop(&self, obj_index: i32) -> bool {
243 unsafe { duk_get_prop(self.ctx, obj_index) == 1 }
244 }
245
246 #[inline]
247 pub fn put_prop(&self, obj_index: i32) {
248 unsafe { duk_put_prop(self.ctx, obj_index); }
249 }
250
251 #[inline]
252 pub fn get_prop_string(&self, obj_index: i32, key: &str) -> bool {
253 unsafe {
254 duk_get_prop_lstring(self.ctx, obj_index, key.as_ptr() as *const c_char, key.len()) == 1
255 }
256 }
257
258 #[inline]
259 pub fn put_prop_string(&self, obj_index: i32, key: &str) {
260 unsafe {
261 duk_put_prop_lstring(self.ctx,
262 obj_index,
263 key.as_ptr() as *const c_char,
264 key.len());
265 }
266 }
267
268 #[inline]
269 pub fn get_prop_index(&self, obj_index: i32, index: u32) -> bool {
270 unsafe { duk_get_prop_index(self.ctx, obj_index, index) == 1 }
271 }
272
273 #[inline]
274 pub fn put_prop_index(&self, obj_index: i32, index: u32) {
275 unsafe {
276 duk_put_prop_index(self.ctx, obj_index, index);
277 }
278 }
279
280 #[inline]
281 pub fn get_global_string(&self, key: &str) -> bool {
282 unsafe {
283 duk_get_global_lstring(self.ctx, key.as_ptr() as *const c_char, key.len()) == 1
284 }
285 }
286
287 #[inline]
288 pub fn put_global_string(&self, key: &str) {
289 unsafe {
290 duk_put_global_lstring(self.ctx, key.as_ptr() as *const c_char, key.len());
291 }
292 }
293
294 #[inline]
295 pub fn get_length(&self, obj_index: i32) -> usize {
296 unsafe {
297 duk_get_length(self.ctx, obj_index)
298 }
299 }
300
301 #[inline]
302 pub fn enum_indices(&self, obj_index: i32) {
303 unsafe {
304 duk_enum(self.ctx, obj_index, DukEnumFlags::DUK_ENUM_ARRAY_INDICES_ONLY.bits());
305 }
306 }
307
308 #[inline]
309 pub fn enum_keys(&self, obj_index: i32) {
310 unsafe {
311 duk_enum(self.ctx, obj_index, DukEnumFlags::DUK_ENUM_OWN_PROPERTIES_ONLY.bits());
312 }
313 }
314
315 #[inline]
316 pub fn next(&self, obj_index: i32) -> bool {
317 unsafe {
318 duk_next(self.ctx, obj_index, 1) == 1
319 }
320 }
321
322 #[inline]
323 pub fn call_prop(&self, obj_index: i32, nargs: usize) {
324 unsafe {
325 duk_call_prop(self.ctx, obj_index, nargs as i32);
326 }
327 }
328
329 #[inline]
330 pub fn pcall(&self, nargs: usize) -> Result<(), i32> {
331 let res = unsafe {
332 duk_pcall(self.ctx, nargs as i32)
333 };
334 try_exec_success!(res);
335 Ok(())
336 }
337
338 #[inline]
339 pub fn pcall_method(&self, nargs: usize) -> Result<(), i32> {
340 let res = unsafe {
341 duk_pcall_method(self.ctx, nargs as i32)
342 };
343 try_exec_success!(res);
344 Ok(())
345 }
346
347 #[inline]
348 pub fn pcall_prop(&self, obj_index: i32, nargs: usize) -> Result<(), i32> {
349 let res = unsafe {
350 duk_pcall_prop(self.ctx, obj_index, nargs as i32)
351 };
352 try_exec_success!(res);
353 Ok(())
354 }
355
356 #[inline]
357 pub fn safe_to_lstring(&self, obj_index: i32) -> String {
358 unsafe {
359 let mut len: usize = 0;
360 let msg = duk_safe_to_lstring(self.ctx, obj_index, &mut len);
361 String::from(std::str::from_utf8_unchecked(std::slice::from_raw_parts(msg as *const u8, len)))
362 }
363 }
364
365 #[inline]
366 pub fn throw(&self) {
367 unsafe {
368 duk_throw_raw(self.ctx);
369 }
370 }
371
372 #[inline]
373 pub fn push_context_dump(&self) {
374 unsafe {
375 duk_push_context_dump(self.ctx);
376 }
377 }
378
379 pub fn get_stack_dump(&self) -> String {
380 self.push_context_dump();
381 unsafe {
382 let dump = CStr::from_ptr(duk_to_string(self.ctx, -1)).to_string_lossy().to_string();
383 duk_pop(self.ctx);
384 dump
385 }
386 }
387
388 pub fn propagate_js_error<T>(&self, js_res: Result<T, i32>) -> Result<T, JsError> {
393 unsafe {
394 match js_res {
395 Ok(v) => Ok(v),
396 Err(_err) => {
397 let mut len: usize = 0;
398 let msg = duk_safe_to_lstring(self.ctx, -1, &mut len);
399 let s = String::from(std::str::from_utf8_unchecked(std::slice::from_raw_parts(msg as *const u8, len)));
400 duk_pop(self.ctx);
401 Err(JsError::from(s))
402 }
403 }
404 }
405 }
406
407 #[inline]
408 pub fn eval(&self, code: &str) -> Result<(), JsError> {
409 unsafe {
410 if duk_eval_raw(self.ctx,
411 code.as_ptr() as *const c_char,
412 code.len(),
413 0 | (DukCompileFlags::DUK_COMPILE_SAFE | DukCompileFlags::DUK_COMPILE_NOSOURCE | DukCompileFlags::DUK_COMPILE_NOFILENAME).bits()) != 0 {
414 let mut len: usize = 0;
415 let msg = duk_safe_to_lstring(self.ctx, -1, &mut len);
416 let s = String::from(std::str::from_utf8_unchecked(std::slice::from_raw_parts(msg as *const u8, len)));
417 duk_pop(self.ctx);
418 Err(JsError::from(s))
419 } else {
420 Ok(())
421 }
422 }
423 }
424
425 #[inline]
426 pub fn eval_file(&self, filename: &str, code: &str) -> Result<(), JsError> {
427 unsafe {
428 duk_push_lstring(self.ctx, filename.as_ptr() as *const c_char, filename.len());
429 if duk_eval_raw(self.ctx,
430 code.as_ptr() as *const c_char,
431 code.len(),
432 1 | (DukCompileFlags::DUK_COMPILE_SAFE | DukCompileFlags::DUK_COMPILE_NOSOURCE).bits()) != 0 {
433 let s = self.safe_to_lstring(-1);
434 duk_pop(self.ctx);
435 Err(JsError::from(s))
436 } else {
437 Ok(())
438 }
439 }
440 }
441
442 #[inline]
443 pub fn compile(&self, code: &str) -> Result<(), JsError> {
444 unsafe {
445 if duk_compile_raw(self.ctx,
446 code.as_ptr() as *const c_char,
447 code.len(),
448 0 | (DukCompileFlags::DUK_COMPILE_NORESULT | DukCompileFlags::DUK_COMPILE_NOFILENAME).bits()) != 0 {
449 let s = self.safe_to_lstring(-1);
450 duk_pop(self.ctx);
451 Err(JsError::from(s))
452 } else {
453 Ok(())
454 }
455 }
456 }
457
458 #[inline]
459 pub fn compile_file(&self, filename: &str, code: &str) -> Result<(), JsError> {
460 unsafe {
461 duk_push_lstring(self.ctx, filename.as_ptr() as *const c_char, filename.len());
462 if duk_compile_raw(self.ctx,
463 code.as_ptr() as *const c_char,
464 code.len(),
465 1 | DukCompileFlags::DUK_COMPILE_NORESULT.bits()) != 0 {
466 let s = self.safe_to_lstring(-1);
467 duk_pop(self.ctx);
468 Err(JsError::from(s))
469 } else {
470 Ok(())
471 }
472 }
473 }
474
475 #[inline]
476 pub fn write<O: WriteJs>(&self, obj: &O) -> Result<(), JsError> {
477 obj.write_js(self)
478 }
479
480 #[inline]
481 pub fn read<O: ReadJs>(&self, obj_index: i32) -> Result<O, JsError> {
482 let obj_index = self.normalize_index(obj_index);
483 O::read_js(self, obj_index)
484 }
485
486 #[inline]
487 pub fn read_top<O: ReadJs>(&self) -> Result<O, JsError> {
488 self.read( -1)
489 }
490
491 #[inline]
493 pub fn init_console(&self) {
494 unsafe {
495 duk_api_console_init(self.ctx, Some(console_func));
496 }
497 }
498
499 #[inline]
500 pub fn xcopy_top(&self, from: &DukContext, count: i32) {
501 unsafe {
502 duk_xcopymove_raw(self.ctx, from.ctx, count, 1);
503 }
504 }
505
506 #[inline]
507 pub fn xmove_top(&self, from: &mut DukContext, count: i32) {
508 unsafe {
509 duk_xcopymove_raw(self.ctx, from.ctx, count, 0);
510 }
511 }
512
513 #[inline]
514 pub fn check_stack(&self, extra: i32) -> Result<(), JsError> {
515 let res = unsafe {
516 duk_check_stack(self.ctx, extra)
517 };
518
519 if res {
520 Ok(())
521 } else {
522 Err(JsError::from("failed to reserve enough stack space".to_string()))
523 }
524 }
525
526 #[inline]
527 pub fn check_stack_top(&self, top: i32) -> Result<(), JsError> {
528 let res = unsafe {
529 duk_check_stack_top(self.ctx, top)
530 };
531 if res {
532 Ok(())
533 } else {
534 Err(JsError::from("failed to reserve enough stack space".to_string()))
535 }
536 }
537
538 #[inline]
539 pub fn set_global_object(&self) {
540 unsafe {
541 duk_set_global_object(self.ctx);
542 }
543 }
544
545 pub fn gc(&self) {
546 unsafe {
547 duk_gc(self.ctx, DukGcFlags::NONE.bits());
548 }
549 }
550}
551
552pub struct DukContextGuard<'a> {
553 ctx: DukContext,
554 _marker: std::marker::PhantomData<&'a JsEngine>,
555}
556
557impl std::fmt::Debug for DukContextGuard<'_> {
558 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559 f.debug_struct("DukContextGuard").finish()
560 }
561}
562
563impl Deref for DukContextGuard<'_> {
564 type Target = DukContext;
565
566 fn deref(&self) -> &Self::Target {
567 &self.ctx
568 }
569}
570
571impl DerefMut for DukContextGuard<'_> {
572 fn deref_mut(&mut self) -> &mut Self::Target {
573 &mut self.ctx
574 }
575}
576
577impl <'a> DukContextGuard<'a> {
578 pub fn new(ctx: DukContext) -> Self {
579 Self {
580 ctx,
581 _marker: std::marker::PhantomData,
582 }
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use serde::Deserialize;
589 use super::*;
590
591 #[test]
592 fn test_eval() {
593 let engine = JsEngine::new().unwrap();
594 engine.eval(r#" var tmp = {
596 "foo": 1,
597 "bar": "baz"
598 }
599 tmp
600 "#).unwrap();
601
602 #[derive(Deserialize)]
603 struct TestStruct {
604 foo: i32,
605 }
606 assert_eq!(engine.read_top::<TestStruct>().unwrap().foo, 1);
607 engine.pop();
608 }
609
610 #[test]
611 fn test_get_invalid_context() {
612 let engine = JsEngine::new().unwrap();
613 let res = engine.get_context(0);
614 assert!(res.is_err());
615 }
616
617 #[test]
618 fn test_push_thread() {
619 let engine = JsEngine::new().unwrap();
620 let new_idx = engine.push_thread();
621 let new_ctx = engine.get_context(new_idx).unwrap();
622 new_ctx.push_string("test");
623 assert_eq!(new_ctx.get_string(-1), "test");
624 new_ctx.pop();
625 assert!(new_ctx.get_stack_dump().contains("ctx: top=0"));
626
627 drop(new_ctx);
628 engine.pop();
629 assert!(engine.get_stack_dump().contains("ctx: top=0"));
630 }
631
632 #[test]
633 fn test_nested_push_thread() {
634 let engine = JsEngine::new().unwrap();
635 let new_idx = engine.push_thread();
636 let new_ctx = engine.get_context(new_idx).unwrap();
637
638 let nested_id = new_ctx.push_thread();
639 let nested_ctx = new_ctx.get_context(nested_id).unwrap();
640 nested_ctx.push_string("test");
641
642 assert_eq!(nested_ctx.get_string(-1), "test");
643
644 drop(nested_ctx);
645 drop(new_ctx);
646
647 engine.pop();
648
649 assert!(engine.get_stack_dump().contains("ctx: top=0"));
650 }
651
652 #[test]
653 fn test_push_thread_new_globalenv() {
654 let engine = JsEngine::new().unwrap();
655
656 let new_idx = engine.push_thread_new_globalenv();
657 let new_idx2 = engine.push_thread_new_globalenv();
658
659 let new_ctx = engine.get_context(new_idx).unwrap();
660 let new_ctx2 = engine.get_context(new_idx2).unwrap();
661
662 new_ctx.push_string("test");
664 assert_eq!(new_ctx.get_string(-1), "test");
665 new_ctx.pop();
666 assert!(new_ctx.get_stack_dump().contains("ctx: top=0"));
667
668 new_ctx2.push_string("test2");
670 new_ctx2.push_string("test2");
671 assert_eq!(new_ctx2.get_string(-1), "test2");
672 new_ctx2.pop();
674 assert!(new_ctx2.get_stack_dump().contains("ctx: top=1"));
675
676 drop(new_ctx);
677 drop(new_ctx2);
678
679 engine.pop_n(2);
681
682 assert!(engine.get_stack_dump().contains("ctx: top=0"));
683 }
684
685 #[test]
686 fn test_to_lstring_safety() {
687 let engine = JsEngine::new().unwrap();
688 engine.push_string("test");
689 let s = engine.safe_to_lstring(-1);
690 assert_eq!(s, "test");
691 engine.pop();
692 assert_eq!(s, "test");
693 drop(engine);
694 assert_eq!(s, "test");
695 }
696
697 #[test]
698 fn test_check_stack_error() {
699 let engine = JsEngine::new().unwrap();
700 let res = engine.check_stack(i32::MAX);
701 assert!(res.is_err());
702 }
703
704 #[test]
705 fn test_xcopy_top() {
706 let engine = JsEngine::new().unwrap();
707
708 engine.eval("GLOBAL_TEST = { a: 1, b: 2 }").unwrap();
710
711 let ctx1_idx = engine.push_thread_new_globalenv();
712 let ctx1 = engine.get_context(ctx1_idx).unwrap();
713 assert!(engine.get_global_string("GLOBAL_TEST"));
715
716 ctx1.check_stack(1).unwrap();
717 ctx1.xcopy_top(&engine, 1);
719 ctx1.put_global_string("GLOBAL_TEST");
720
721 ctx1.eval("GLOBAL_TEST.b").unwrap();
723 assert_eq!(ctx1.get_number(-1), 2.0);
724
725 ctx1.eval("GLOBAL_TEST.b = 5").unwrap();
727
728 engine.eval("GLOBAL_TEST.b").unwrap();
730 assert_eq!(engine.get_number(-1), 5.0);
731 engine.pop();
732 }
733
734 #[test]
735 fn test_set_global_object() {
736 let engine = JsEngine::new().unwrap();
737
738 engine.eval("Math.abs(-1)").unwrap();
739 assert_eq!(engine.get_number(-1), 1.0);
740
741 engine.pop();
742
743 let _: () = engine.eval("var obj = { a: 1, b: 2 }; obj").unwrap();
745 engine.set_global_object();
746
747 engine.eval("typeof Math").unwrap();
748
749 assert_eq!(engine.get_string(-1), "undefined");
750
751 }
752
753 #[test]
754 fn test_gc() {
755 let engine = JsEngine::new().unwrap();
756
757 engine.eval("Math.abs(-1)").unwrap();
758 assert_eq!(engine.get_number(-1), 1.0);
759 engine.pop();
760 engine.gc();
761 }
762
763 #[test]
764 fn tes_propagate_js_error() {
765 let engine = JsEngine::new().unwrap();
766 engine.eval(r#" var tmp = {
768 "foo": 1,
769 "bar": "baz",
770 "some_fn": function() {
771 throw new Error("test error");
772 }
773 }
774 tmp
775 "#).unwrap();
776
777 engine.push_string("some_fn");
778
779 let call_res = engine.pcall_prop(0, 0);
780 assert!(call_res.is_err());
781 let res = engine.propagate_js_error(call_res);
782
783 assert!(res.is_err());
784 let err = res.unwrap_err();
785 assert!(err.to_string().contains("test error"));
786 }
787}
788