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