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 eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
174 self.eval_with_options(
175 source,
176 EvalOptions {
177 promise: true,
178 ..Default::default()
179 },
180 )
181 }
182
183 pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
185 &self,
186 source: S,
187 options: EvalOptions,
188 ) -> Result<V> {
189 #[cfg(feature = "std")]
190 let file_name = {
191 if let Some(filename) = &options.filename {
192 &CString::new(filename.clone())?
193 } else {
194 c"eval_script"
195 }
196 };
197
198 #[cfg(not(feature = "std"))]
199 let file_name = c"eval_script";
200
201 V::from_js(self, unsafe {
202 let val = self.eval_raw(source, file_name, options.to_flag())?;
203 Value::from_js_value(self.clone(), val)
204 })
205 }
206
207 #[cfg(feature = "std")]
208 pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
210 self.eval_file_with_options(path, Default::default())
211 }
212
213 #[cfg(feature = "std")]
214 pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
215 &self,
216 path: P,
217 options: EvalOptions,
218 ) -> Result<V> {
219 let buffer = fs::read(path.as_ref())?;
220 let file_name = CString::new(
221 path.as_ref()
222 .file_name()
223 .unwrap()
224 .to_string_lossy()
225 .into_owned(),
226 )?;
227
228 V::from_js(self, unsafe {
229 let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
230 Value::from_js_value(self.clone(), val)
231 })
232 }
233
234 pub fn globals(&self) -> Object<'js> {
236 unsafe {
237 let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
238 Object::from_js_value(self.clone(), v)
239 }
240 }
241
242 pub fn catch(&self) -> Value<'js> {
258 unsafe {
259 let v = qjs::JS_GetException(self.ctx.as_ptr());
260 Value::from_js_value(self.clone(), v)
261 }
262 }
263
264 pub fn has_exception(&self) -> bool {
266 unsafe { qjs::JS_HasException(self.ctx.as_ptr()) }
267 }
268
269 pub fn throw(&self, value: Value<'js>) -> Error {
272 unsafe {
273 let v = value.into_js_value();
274 qjs::JS_Throw(self.ctx.as_ptr(), v);
275 }
276 Error::Exception
277 }
278
279 pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
281 where
282 S: Into<Vec<u8>>,
283 {
284 let src = json.into();
285 let len = src.len();
286 let src = CString::new(src)?;
287 unsafe {
288 let name = b"<input>\0";
289 let v = qjs::JS_ParseJSON(
290 self.as_ptr(),
291 src.as_ptr().cast(),
292 len.try_into().expect(qjs::SIZE_T_ERROR),
293 name.as_ptr().cast(),
294 );
295 self.handle_exception(v)?;
296 Ok(Value::from_js_value(self.clone(), v))
297 }
298 }
299
300 pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
302 where
303 V: IntoJs<'js>,
304 {
305 self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
306 }
307
308 pub fn json_stringify_replacer<V, R>(
313 &self,
314 value: V,
315 replacer: R,
316 ) -> Result<Option<String<'js>>>
317 where
318 V: IntoJs<'js>,
319 R: IntoJs<'js>,
320 {
321 let replacer = replacer.into_js(self)?;
322
323 self.json_stringify_inner(
324 &value.into_js(self)?,
325 replacer.as_js_value(),
326 qjs::JS_UNDEFINED,
327 )
328 }
329
330 pub fn json_stringify_replacer_space<V, R, S>(
340 &self,
341 value: V,
342 replacer: R,
343 space: S,
344 ) -> Result<Option<String<'js>>>
345 where
346 V: IntoJs<'js>,
347 R: IntoJs<'js>,
348 S: IntoJs<'js>,
349 {
350 let replacer = replacer.into_js(self)?;
351 let space = space.into_js(self)?;
352
353 self.json_stringify_inner(
354 &value.into_js(self)?,
355 replacer.as_js_value(),
356 space.as_js_value(),
357 )
358 }
359
360 fn json_stringify_inner(
362 &self,
363 value: &Value<'js>,
364 replacer: qjs::JSValueConst,
365 space: qjs::JSValueConst,
366 ) -> Result<Option<String<'js>>> {
367 unsafe {
368 let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
369 self.handle_exception(res)?;
370 let v = Value::from_js_value(self.clone(), res);
371 if v.is_undefined() {
372 Ok(None)
373 } else {
374 let v = v.into_string().expect(
375 "JS_JSONStringify did not return either an exception, undefined, or a string",
376 );
377 Ok(Some(v))
378 }
379 }
380 }
381
382 pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
384 let mut funcs = mem::MaybeUninit::<[qjs::JSValue; 2]>::uninit();
385
386 Ok(unsafe {
387 let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
388 self.ctx.as_ptr(),
389 funcs.as_mut_ptr() as _,
390 ))?;
391 let [resolve, reject] = funcs.assume_init();
392 (
393 Promise::from_js_value(self.clone(), promise),
394 Function::from_js_value(self.clone(), resolve),
395 Function::from_js_value(self.clone(), reject),
396 )
397 })
398 }
399
400 pub fn execute_pending_job(&self) -> bool {
405 let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
406 let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
407 let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
408 res != 0
409 }
410
411 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
412 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
413 }
414
415 #[cfg(feature = "futures")]
417 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
418 pub fn spawn<F>(&self, future: F)
419 where
420 F: Future<Output = ()> + 'js,
421 {
422 unsafe { self.get_opaque().push(future) }
423 }
424
425 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
432 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
433 Ctx { ctx, _marker: inv }
434 }
435
436 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
443 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
444 Ctx {
445 ctx,
446 _marker: Invariant::new(),
447 }
448 }
449
450 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
456 let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
457 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
458 #[allow(clippy::useless_conversion)] if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
460 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
461 return None;
462 }
463 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
464 }
465
466 pub fn run_gc(&self) {
472 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
473 }
474
475 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
481 where
482 U: JsLifetime<'js>,
483 U::Changed<'static>: Any,
484 {
485 unsafe { self.get_opaque().insert_userdata(data) }
486 }
487
488 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
493 where
494 U: JsLifetime<'js>,
495 U::Changed<'static>: Any,
496 {
497 unsafe { self.get_opaque().remove_userdata() }
498 }
499
500 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
504 where
505 U: JsLifetime<'js>,
506 U::Changed<'static>: Any,
507 {
508 unsafe { self.get_opaque().get_userdata() }
509 }
510
511 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
513 self.ctx
514 }
515}
516
517#[cfg(test)]
518mod test {
519 use crate::{CatchResultExt, JsLifetime};
520
521 #[test]
522 fn exports() {
523 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
524
525 let runtime = Runtime::new().unwrap();
526 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
527 ctx.with(|ctx| {
528 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
529 .unwrap()
530 .eval()
531 .unwrap();
532 promise.finish::<()>().unwrap();
533 let func: Function = module.get("default").unwrap();
534 func.call::<(), Promise>(()).unwrap();
535 });
536 }
537
538 #[test]
539 fn eval() {
540 use crate::{Context, Runtime};
541
542 let runtime = Runtime::new().unwrap();
543 let ctx = Context::full(&runtime).unwrap();
544 ctx.with(|ctx| {
545 let res: String = ctx
546 .eval(
547 r#"
548 function test() {
549 var foo = "bar";
550 return foo;
551 }
552
553 test()
554 "#,
555 )
556 .unwrap();
557
558 assert_eq!("bar".to_string(), res);
559 })
560 }
561
562 #[test]
563 fn eval_minimal_test() {
564 use crate::{Context, Runtime};
565
566 let runtime = Runtime::new().unwrap();
567 let ctx = Context::full(&runtime).unwrap();
568 ctx.with(|ctx| {
569 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
570 assert_eq!(2, res);
571 })
572 }
573
574 #[test]
575 #[should_panic(expected = "foo is not defined")]
576 fn eval_with_sloppy_code() {
577 use crate::{CatchResultExt, Context, Runtime};
578
579 let runtime = Runtime::new().unwrap();
580 let ctx = Context::full(&runtime).unwrap();
581 ctx.with(|ctx| {
582 let _: String = ctx
583 .eval(
584 r#"
585 function test() {
586 foo = "bar";
587 return foo;
588 }
589
590 test()
591 "#,
592 )
593 .catch(&ctx)
594 .unwrap();
595 })
596 }
597
598 #[test]
599 fn eval_with_options_no_strict_sloppy_code() {
600 use crate::{context::EvalOptions, Context, Runtime};
601
602 let runtime = Runtime::new().unwrap();
603 let ctx = Context::full(&runtime).unwrap();
604 ctx.with(|ctx| {
605 let res: String = ctx
606 .eval_with_options(
607 r#"
608 function test() {
609 foo = "bar";
610 return foo;
611 }
612
613 test()
614 "#,
615 EvalOptions {
616 strict: false,
617 ..Default::default()
618 },
619 )
620 .unwrap();
621
622 assert_eq!("bar".to_string(), res);
623 })
624 }
625
626 #[test]
627 #[should_panic(expected = "foo is not defined")]
628 fn eval_with_options_strict_sloppy_code() {
629 use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
630
631 let runtime = Runtime::new().unwrap();
632 let ctx = Context::full(&runtime).unwrap();
633 ctx.with(|ctx| {
634 let _: String = ctx
635 .eval_with_options(
636 r#"
637 function test() {
638 foo = "bar";
639 return foo;
640 }
641
642 test()
643 "#,
644 EvalOptions {
645 strict: true,
646 ..Default::default()
647 },
648 )
649 .catch(&ctx)
650 .unwrap();
651 })
652 }
653
654 #[test]
655 fn json_parse() {
656 use crate::{Array, Context, Object, Runtime};
657
658 let runtime = Runtime::new().unwrap();
659 let ctx = Context::full(&runtime).unwrap();
660 ctx.with(|ctx| {
661 let v = ctx
662 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
663 .unwrap();
664 let obj = v.into_object().unwrap();
665 let inner_obj: Object = obj.get("a").unwrap();
666 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
667 assert!(inner_obj.get::<_, bool>("c").unwrap());
668 let inner_array: Array = obj.get("d").unwrap();
669 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
670 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
671 })
672 }
673
674 #[test]
675 fn json_stringify() {
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 obj_inner = Object::new(ctx.clone()).unwrap();
682 obj_inner.set("b", 1).unwrap();
683 obj_inner.set("c", true).unwrap();
684
685 let array_inner = Array::new(ctx.clone()).unwrap();
686 array_inner.set(0, 0).unwrap();
687 array_inner.set(1, "foo").unwrap();
688
689 let obj = Object::new(ctx.clone()).unwrap();
690 obj.set("a", obj_inner).unwrap();
691 obj.set("d", array_inner).unwrap();
692
693 let str = ctx
694 .json_stringify(obj)
695 .unwrap()
696 .unwrap()
697 .to_string()
698 .unwrap();
699
700 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
701 })
702 }
703
704 #[test]
705 fn userdata() {
706 use crate::{Context, Function, Runtime};
707
708 pub struct MyUserData<'js> {
709 base: Function<'js>,
710 }
711
712 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
713 type Changed<'to> = MyUserData<'to>;
714 }
715
716 let rt = Runtime::new().unwrap();
717 let ctx = Context::full(&rt).unwrap();
718
719 ctx.with(|ctx| {
720 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
721 ctx.store_userdata(MyUserData { base: func }).unwrap();
722 });
723
724 ctx.with(|ctx| {
725 let userdata = ctx.userdata::<MyUserData>().unwrap();
726
727 assert!(ctx.remove_userdata::<MyUserData>().is_err());
728
729 let r: usize = userdata.base.call(()).unwrap();
730 assert_eq!(r, 42)
731 });
732
733 ctx.with(|ctx| {
734 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
735 })
736 }
737}