1#[cfg(feature = "futures")]
2use core::future::Future;
3
4use alloc::{boxed::Box, ffi::CString, vec::Vec};
5use core::{
6 any::Any,
7 ffi::CStr,
8 mem::{self, MaybeUninit},
9 ptr::NonNull,
10 result::Result as StdResult,
11};
12
13#[cfg(feature = "std")]
14use std::{fs, path::Path, string::String as StdString};
15
16#[cfg(feature = "futures")]
17use crate::AsyncContext;
18use crate::{
19 markers::Invariant,
20 qjs,
21 runtime::{opaque::Opaque, UserDataError, UserDataGuard},
22 Atom, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, String, Value,
23};
24
25use super::Context;
26
27#[non_exhaustive]
29pub struct EvalOptions {
30 pub global: bool,
32 pub strict: bool,
34 pub backtrace_barrier: bool,
36 pub promise: bool,
38 #[cfg(feature = "std")]
40 pub filename: Option<StdString>,
41}
42
43impl EvalOptions {
44 fn to_flag(&self) -> i32 {
45 let mut flag = if self.global {
46 qjs::JS_EVAL_TYPE_GLOBAL
47 } else {
48 qjs::JS_EVAL_TYPE_MODULE
49 };
50
51 if self.strict {
52 flag |= qjs::JS_EVAL_FLAG_STRICT;
53 }
54
55 if self.backtrace_barrier {
56 flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
57 }
58
59 if self.promise {
60 flag |= qjs::JS_EVAL_FLAG_ASYNC;
61 }
62
63 flag as i32
64 }
65}
66
67impl Default for EvalOptions {
68 fn default() -> Self {
69 EvalOptions {
70 global: true,
71 strict: true,
72 backtrace_barrier: false,
73 promise: false,
74 #[cfg(feature = "std")]
75 filename: None,
76 }
77 }
78}
79
80#[derive(Debug)]
82pub struct Ctx<'js> {
83 ctx: NonNull<qjs::JSContext>,
84 _marker: Invariant<'js>,
85}
86
87impl<'js> Clone for Ctx<'js> {
88 fn clone(&self) -> Self {
89 unsafe { qjs::JS_DupContext(self.ctx.as_ptr()) };
90 Ctx {
91 ctx: self.ctx,
92 _marker: self._marker,
93 }
94 }
95}
96
97impl<'js> Drop for Ctx<'js> {
98 fn drop(&mut self) {
99 unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) };
100 }
101}
102
103unsafe impl Send for Ctx<'_> {}
104
105#[repr(C)] pub(crate) struct RefCountHeader {
107 pub ref_count: i32, }
109
110impl<'js> Ctx<'js> {
111 pub(crate) fn as_ptr(&self) -> *mut qjs::JSContext {
112 self.ctx.as_ptr()
113 }
114
115 pub(crate) unsafe fn from_ptr(ctx: *mut qjs::JSContext) -> Self {
116 unsafe { qjs::JS_DupContext(ctx) };
117 let ctx = NonNull::new_unchecked(ctx);
118 Ctx {
119 ctx,
120 _marker: Invariant::new(),
121 }
122 }
123
124 pub(crate) unsafe fn new(ctx: &'js Context) -> Self {
125 unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
126 Ctx {
127 ctx: ctx.0.ctx(),
128 _marker: Invariant::new(),
129 }
130 }
131
132 #[cfg(feature = "futures")]
133 pub(crate) unsafe fn new_async(ctx: &'js AsyncContext) -> Self {
134 unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
135 Ctx {
136 ctx: ctx.0.ctx(),
137 _marker: Invariant::new(),
138 }
139 }
140
141 pub(crate) unsafe fn eval_raw<S: Into<Vec<u8>>>(
142 &self,
143 source: S,
144 file_name: &CStr,
145 flag: i32,
146 ) -> Result<qjs::JSValue> {
147 let src = source.into();
148 let len = src.len();
149 let src = CString::new(src)?;
150
151 #[cfg(feature = "parallel")]
152 qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
153
154 let val = qjs::JS_Eval(
155 self.ctx.as_ptr(),
156 src.as_ptr(),
157 len as _,
158 file_name.as_ptr(),
159 flag,
160 );
161 self.handle_exception(val)
162 }
163
164 pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
166 self.eval_with_options(source, Default::default())
167 }
168
169 pub fn compile<S: Into<Vec<u8>>, N: Into<Vec<u8>>>(
174 &self,
175 source: S,
176 filename: N,
177 strict: bool,
178 ) -> Result<()> {
179 let filename = CString::new(filename)?;
180 let mut flags = qjs::JS_EVAL_TYPE_GLOBAL | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
181 if strict {
182 flags |= qjs::JS_EVAL_FLAG_STRICT;
183 }
184 let value = unsafe { self.eval_raw(source, filename.as_c_str(), flags as i32)? };
185 unsafe { qjs::JS_FreeValue(self.as_ptr(), value) };
186 Ok(())
187 }
188
189 pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
194 self.eval_with_options(
195 source,
196 EvalOptions {
197 promise: true,
198 ..Default::default()
199 },
200 )
201 }
202
203 pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
205 &self,
206 source: S,
207 options: EvalOptions,
208 ) -> Result<V> {
209 #[cfg(feature = "std")]
210 let file_name = {
211 if let Some(filename) = &options.filename {
212 &CString::new(filename.clone())?
213 } else {
214 c"eval_script"
215 }
216 };
217
218 #[cfg(not(feature = "std"))]
219 let file_name = c"eval_script";
220
221 V::from_js(self, unsafe {
222 let val = self.eval_raw(source, file_name, options.to_flag())?;
223 Value::from_js_value(self.clone(), val)
224 })
225 }
226
227 #[cfg(feature = "std")]
228 pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
230 self.eval_file_with_options(path, Default::default())
231 }
232
233 #[cfg(feature = "std")]
234 pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
235 &self,
236 path: P,
237 options: EvalOptions,
238 ) -> Result<V> {
239 let buffer = fs::read(path.as_ref())?;
240 let file_name = CString::new(
241 path.as_ref()
242 .file_name()
243 .unwrap()
244 .to_string_lossy()
245 .into_owned(),
246 )?;
247
248 V::from_js(self, unsafe {
249 let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
250 Value::from_js_value(self.clone(), val)
251 })
252 }
253
254 pub fn globals(&self) -> Object<'js> {
256 unsafe {
257 let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
258 Object::from_js_value(self.clone(), v)
259 }
260 }
261
262 pub fn catch(&self) -> Value<'js> {
278 unsafe {
279 let v = qjs::JS_GetException(self.ctx.as_ptr());
280 Value::from_js_value(self.clone(), v)
281 }
282 }
283
284 pub fn has_exception(&self) -> bool {
286 unsafe { qjs::JS_HasException(self.ctx.as_ptr()) }
287 }
288
289 pub fn throw(&self, value: Value<'js>) -> Error {
292 unsafe {
293 let v = value.into_js_value();
294 qjs::JS_Throw(self.ctx.as_ptr(), v);
295 }
296 Error::Exception
297 }
298
299 pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
301 where
302 S: Into<Vec<u8>>,
303 {
304 let src = json.into();
305 let len = src.len();
306 let src = CString::new(src)?;
307 unsafe {
308 let name = b"<input>\0";
309 let v = qjs::JS_ParseJSON(
310 self.as_ptr(),
311 src.as_ptr().cast(),
312 len.try_into().expect(qjs::SIZE_T_ERROR),
313 name.as_ptr().cast(),
314 );
315 self.handle_exception(v)?;
316 Ok(Value::from_js_value(self.clone(), v))
317 }
318 }
319
320 pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
322 where
323 V: IntoJs<'js>,
324 {
325 self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
326 }
327
328 pub fn json_stringify_replacer<V, R>(
333 &self,
334 value: V,
335 replacer: R,
336 ) -> Result<Option<String<'js>>>
337 where
338 V: IntoJs<'js>,
339 R: IntoJs<'js>,
340 {
341 let replacer = replacer.into_js(self)?;
342
343 self.json_stringify_inner(
344 &value.into_js(self)?,
345 replacer.as_js_value(),
346 qjs::JS_UNDEFINED,
347 )
348 }
349
350 pub fn json_stringify_replacer_space<V, R, S>(
360 &self,
361 value: V,
362 replacer: R,
363 space: S,
364 ) -> Result<Option<String<'js>>>
365 where
366 V: IntoJs<'js>,
367 R: IntoJs<'js>,
368 S: IntoJs<'js>,
369 {
370 let replacer = replacer.into_js(self)?;
371 let space = space.into_js(self)?;
372
373 self.json_stringify_inner(
374 &value.into_js(self)?,
375 replacer.as_js_value(),
376 space.as_js_value(),
377 )
378 }
379
380 fn json_stringify_inner(
382 &self,
383 value: &Value<'js>,
384 replacer: qjs::JSValueConst,
385 space: qjs::JSValueConst,
386 ) -> Result<Option<String<'js>>> {
387 unsafe {
388 let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
389 self.handle_exception(res)?;
390 let v = Value::from_js_value(self.clone(), res);
391 if v.is_undefined() {
392 Ok(None)
393 } else {
394 let v = v.into_string().expect(
395 "JS_JSONStringify did not return either an exception, undefined, or a string",
396 );
397 Ok(Some(v))
398 }
399 }
400 }
401
402 pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
404 let mut funcs = mem::MaybeUninit::<[qjs::JSValue; 2]>::uninit();
405
406 Ok(unsafe {
407 let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
408 self.ctx.as_ptr(),
409 funcs.as_mut_ptr() as _,
410 ))?;
411 let [resolve, reject] = funcs.assume_init();
412 (
413 Promise::from_js_value(self.clone(), promise),
414 Function::from_js_value(self.clone(), resolve),
415 Function::from_js_value(self.clone(), reject),
416 )
417 })
418 }
419
420 pub fn execute_pending_job(&self) -> bool {
425 let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
426 let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
427 let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
428 res != 0
429 }
430
431 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
432 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
433 }
434
435 #[cfg(feature = "futures")]
437 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
438 pub fn spawn<F>(&self, future: F)
439 where
440 F: Future<Output = ()> + 'js,
441 {
442 unsafe { self.get_opaque().push(future) }
443 }
444
445 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
452 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
453 Ctx { ctx, _marker: inv }
454 }
455
456 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
463 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
464 Ctx {
465 ctx,
466 _marker: Invariant::new(),
467 }
468 }
469
470 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
476 let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
477 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
478 #[allow(clippy::useless_conversion)] if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
480 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
481 return None;
482 }
483 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
484 }
485
486 pub fn run_gc(&self) {
492 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
493 }
494
495 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
501 where
502 U: JsLifetime<'js>,
503 U::Changed<'static>: Any,
504 {
505 unsafe { self.get_opaque().insert_userdata(data) }
506 }
507
508 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
513 where
514 U: JsLifetime<'js>,
515 U::Changed<'static>: Any,
516 {
517 unsafe { self.get_opaque().remove_userdata() }
518 }
519
520 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
524 where
525 U: JsLifetime<'js>,
526 U::Changed<'static>: Any,
527 {
528 unsafe { self.get_opaque().get_userdata() }
529 }
530
531 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
533 self.ctx
534 }
535}
536
537#[cfg(test)]
538mod test {
539 use crate::{CatchResultExt, JsLifetime};
540
541 #[test]
542 fn exports() {
543 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
544
545 let runtime = Runtime::new().unwrap();
546 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
547 ctx.with(|ctx| {
548 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
549 .unwrap()
550 .eval()
551 .unwrap();
552 promise.finish::<()>().unwrap();
553 let func: Function = module.get("default").unwrap();
554 func.call::<(), Promise>(()).unwrap();
555 });
556 }
557
558 #[test]
559 fn eval() {
560 use crate::{Context, Runtime};
561
562 let runtime = Runtime::new().unwrap();
563 let ctx = Context::full(&runtime).unwrap();
564 ctx.with(|ctx| {
565 let res: String = ctx
566 .eval(
567 r#"
568 function test() {
569 var foo = "bar";
570 return foo;
571 }
572
573 test()
574 "#,
575 )
576 .unwrap();
577
578 assert_eq!("bar".to_string(), res);
579 })
580 }
581
582 #[test]
583 fn eval_minimal_test() {
584 use crate::{Context, Runtime};
585
586 let runtime = Runtime::new().unwrap();
587 let ctx = Context::full(&runtime).unwrap();
588 ctx.with(|ctx| {
589 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
590 assert_eq!(2, res);
591 })
592 }
593
594 #[test]
595 #[should_panic(expected = "foo is not defined")]
596 fn eval_with_sloppy_code() {
597 use crate::{CatchResultExt, Context, Runtime};
598
599 let runtime = Runtime::new().unwrap();
600 let ctx = Context::full(&runtime).unwrap();
601 ctx.with(|ctx| {
602 let _: String = ctx
603 .eval(
604 r#"
605 function test() {
606 foo = "bar";
607 return foo;
608 }
609
610 test()
611 "#,
612 )
613 .catch(&ctx)
614 .unwrap();
615 })
616 }
617
618 #[test]
619 fn eval_with_options_no_strict_sloppy_code() {
620 use crate::{context::EvalOptions, Context, Runtime};
621
622 let runtime = Runtime::new().unwrap();
623 let ctx = Context::full(&runtime).unwrap();
624 ctx.with(|ctx| {
625 let res: String = ctx
626 .eval_with_options(
627 r#"
628 function test() {
629 foo = "bar";
630 return foo;
631 }
632
633 test()
634 "#,
635 EvalOptions {
636 strict: false,
637 ..Default::default()
638 },
639 )
640 .unwrap();
641
642 assert_eq!("bar".to_string(), res);
643 })
644 }
645
646 #[test]
647 #[should_panic(expected = "foo is not defined")]
648 fn eval_with_options_strict_sloppy_code() {
649 use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
650
651 let runtime = Runtime::new().unwrap();
652 let ctx = Context::full(&runtime).unwrap();
653 ctx.with(|ctx| {
654 let _: String = ctx
655 .eval_with_options(
656 r#"
657 function test() {
658 foo = "bar";
659 return foo;
660 }
661
662 test()
663 "#,
664 EvalOptions {
665 strict: true,
666 ..Default::default()
667 },
668 )
669 .catch(&ctx)
670 .unwrap();
671 })
672 }
673
674 #[test]
675 fn json_parse() {
676 use crate::{Array, Context, Object, Runtime};
677
678 let runtime = Runtime::new().unwrap();
679 let ctx = Context::full(&runtime).unwrap();
680 ctx.with(|ctx| {
681 let v = ctx
682 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
683 .unwrap();
684 let obj = v.into_object().unwrap();
685 let inner_obj: Object = obj.get("a").unwrap();
686 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
687 assert!(inner_obj.get::<_, bool>("c").unwrap());
688 let inner_array: Array = obj.get("d").unwrap();
689 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
690 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
691 })
692 }
693
694 #[test]
695 fn json_stringify() {
696 use crate::{Array, Context, Object, Runtime};
697
698 let runtime = Runtime::new().unwrap();
699 let ctx = Context::full(&runtime).unwrap();
700 ctx.with(|ctx| {
701 let obj_inner = Object::new(ctx.clone()).unwrap();
702 obj_inner.set("b", 1).unwrap();
703 obj_inner.set("c", true).unwrap();
704
705 let array_inner = Array::new(ctx.clone()).unwrap();
706 array_inner.set(0, 0).unwrap();
707 array_inner.set(1, "foo").unwrap();
708
709 let obj = Object::new(ctx.clone()).unwrap();
710 obj.set("a", obj_inner).unwrap();
711 obj.set("d", array_inner).unwrap();
712
713 let str = ctx
714 .json_stringify(obj)
715 .unwrap()
716 .unwrap()
717 .to_string()
718 .unwrap();
719
720 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
721 })
722 }
723
724 #[test]
725 fn userdata() {
726 use crate::{Context, Function, Runtime};
727
728 pub struct MyUserData<'js> {
729 base: Function<'js>,
730 }
731
732 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
733 type Changed<'to> = MyUserData<'to>;
734 }
735
736 let rt = Runtime::new().unwrap();
737 let ctx = Context::full(&rt).unwrap();
738
739 ctx.with(|ctx| {
740 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
741 ctx.store_userdata(MyUserData { base: func }).unwrap();
742 });
743
744 ctx.with(|ctx| {
745 let userdata = ctx.userdata::<MyUserData>().unwrap();
746
747 assert!(ctx.remove_userdata::<MyUserData>().is_err());
748
749 let r: usize = userdata.base.call(()).unwrap();
750 assert_eq!(r, 42)
751 });
752
753 ctx.with(|ctx| {
754 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
755 })
756 }
757}