1use alloc::{ffi::CString, vec::Vec};
4use core::{
5 ffi::CStr,
6 marker::PhantomData,
7 mem::MaybeUninit,
8 ptr::{self, null_mut, NonNull},
9 slice,
10};
11
12use crate::{
13 atom::PredefinedAtom, qjs, Atom, Ctx, Error, FromAtom, FromJs, IntoAtom, IntoJs, Object,
14 Promise, Result, Value,
15};
16
17#[derive(Default)]
18pub enum WriteOptionsEndianness {
19 #[default]
21 Native,
22 Little,
24 Big,
26 Swap,
28}
29
30#[derive(Default)]
32pub struct WriteOptions {
33 pub endianness: WriteOptionsEndianness,
35 pub allow_shared_array_buffer: bool,
37 pub object_reference: bool,
39 pub strip_source: bool,
41 pub strip_debug: bool,
43}
44
45impl WriteOptions {
46 pub fn to_flag(&self) -> i32 {
47 let mut flag = qjs::JS_WRITE_OBJ_BYTECODE;
48
49 let should_swap = match &self.endianness {
50 WriteOptionsEndianness::Native => false,
51 WriteOptionsEndianness::Little => cfg!(target_endian = "big"),
52 WriteOptionsEndianness::Big => cfg!(target_endian = "little"),
53 WriteOptionsEndianness::Swap => true,
54 };
55 if should_swap {
56 flag |= qjs::JS_WRITE_OBJ_BSWAP;
57 }
58
59 if self.allow_shared_array_buffer {
60 flag |= qjs::JS_WRITE_OBJ_SAB;
61 }
62
63 if self.object_reference {
64 flag |= qjs::JS_WRITE_OBJ_REFERENCE;
65 }
66
67 if self.strip_source {
68 flag |= qjs::JS_WRITE_OBJ_STRIP_SOURCE;
69 }
70
71 if self.strip_debug {
72 flag |= qjs::JS_WRITE_OBJ_STRIP_DEBUG;
73 }
74
75 flag as i32
76 }
77}
78
79#[macro_export]
92macro_rules! module_init {
93 ($type:ty) => {
94 $crate::module_init!(js_init_module: $type);
95 };
96
97 ($name:ident: $type:ty) => {
98 #[no_mangle]
99 pub unsafe extern "C" fn $name(
100 ctx: *mut $crate::qjs::JSContext,
101 module_name: *const $crate::qjs::c_char,
102 ) -> *mut $crate::qjs::JSModuleDef {
103 $crate::Module::init_raw::<$type>(ctx, module_name)
104 }
105 };
106}
107
108pub type ModuleLoadFn =
110 unsafe extern "C" fn(*mut qjs::JSContext, *const qjs::c_char) -> *mut qjs::JSModuleDef;
111
112pub trait ModuleDef {
114 fn declare<'js>(decl: &Declarations<'js>) -> Result<()> {
115 let _ = decl;
116 Ok(())
117 }
118
119 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
120 let _ = (exports, ctx);
121 Ok(())
122 }
123}
124
125pub struct Declarations<'js>(Module<'js, Declared>);
127
128impl<'js> Declarations<'js> {
129 pub fn declare<N>(&self, name: N) -> Result<&Self>
131 where
132 N: Into<Vec<u8>>,
133 {
134 let name = CString::new(name)?;
135 self.declare_c_str(name.as_c_str())
136 }
137
138 pub fn declare_c_str(&self, name: &CStr) -> Result<&Self> {
143 unsafe { qjs::JS_AddModuleExport(self.0.ctx.as_ptr(), self.0.as_ptr(), name.as_ptr()) };
144 Ok(self)
145 }
146
147 pub fn module(&self) -> &Module<'js, Declared> {
149 &self.0
150 }
151}
152
153pub struct Exports<'js>(Module<'js, Declared>);
155
156impl<'js> Exports<'js> {
157 pub fn export<N: Into<Vec<u8>>, T: IntoJs<'js>>(&self, name: N, value: T) -> Result<&Self> {
159 let name = CString::new(name.into())?;
160 self.export_c_str(name.as_c_str(), value)
161 }
162
163 pub fn export_c_str<T: IntoJs<'js>>(&self, name: &CStr, value: T) -> Result<&Self> {
167 let value = value.into_js(&self.0.ctx)?;
168 let res = unsafe {
169 qjs::JS_SetModuleExport(
170 self.0.ctx.as_ptr(),
171 self.0.as_ptr(),
172 name.as_ptr(),
173 value.into_js_value(),
174 )
175 };
176 if res < 0 {
177 return Err(Error::InvalidExport);
178 }
179
180 Ok(self)
181 }
182
183 pub fn module(&self) -> &Module<'js, Declared> {
185 &self.0
186 }
187}
188
189#[derive(Clone, Copy, Debug)]
191pub struct Declared;
192#[derive(Clone, Copy, Debug)]
194pub struct Evaluated;
195
196#[derive(Clone, Debug)]
198pub struct Module<'js, T = Declared> {
199 ptr: NonNull<qjs::JSModuleDef>,
200 ctx: Ctx<'js>,
201 _type_marker: PhantomData<T>,
202}
203
204impl<'js, T> Module<'js, T> {
205 pub(crate) fn as_ptr(&self) -> *mut qjs::JSModuleDef {
206 self.ptr.as_ptr()
207 }
208
209 pub(crate) unsafe fn from_ptr(ctx: Ctx<'js>, ptr: NonNull<qjs::JSModuleDef>) -> Module<'js, T> {
210 Module {
211 ptr,
212 ctx,
213 _type_marker: PhantomData,
214 }
215 }
216
217 unsafe extern "C" fn eval_fn<D>(
218 ctx: *mut qjs::JSContext,
219 ptr: *mut qjs::JSModuleDef,
220 ) -> qjs::c_int
221 where
222 D: ModuleDef,
223 {
224 let ctx = Ctx::from_ptr(ctx);
225 let ptr = NonNull::new(ptr).unwrap();
227 let module = unsafe { Module::from_ptr(ctx.clone(), ptr) };
228 let exports = Exports(module);
229 match D::evaluate(&ctx, &exports) {
230 Ok(_) => 0,
231 Err(error) => {
232 error.throw(&ctx);
233 -1
234 }
235 }
236 }
237
238 pub fn name<N>(&self) -> Result<N>
240 where
241 N: FromAtom<'js>,
242 {
243 let name = unsafe {
244 Atom::from_atom_val(
245 self.ctx.clone(),
246 qjs::JS_GetModuleName(self.ctx.as_ptr(), self.as_ptr()),
247 )
248 };
249 N::from_atom(name)
250 }
251
252 pub fn ctx(&self) -> &Ctx<'js> {
254 &self.ctx
255 }
256}
257
258impl<'js> Module<'js, Declared> {
259 pub fn declare<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js, Declared>>
261 where
262 N: Into<Vec<u8>>,
263 S: Into<Vec<u8>>,
264 {
265 let name = CString::new(name)?;
266 let flag =
267 qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
268
269 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
270 let module_val = unsafe { ctx.handle_exception(module_val)? };
271 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe {
272 qjs::JS_VALUE_GET_TAG(module_val)
273 });
274 let module_ptr = unsafe {
275 NonNull::new(qjs::JS_VALUE_GET_PTR(module_val).cast()).ok_or(Error::Unknown)?
276 };
277 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
278 }
279
280 pub fn declare_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js, Declared>>
282 where
283 N: Into<Vec<u8>>,
284 D: ModuleDef,
285 {
286 let name = CString::new(name)?;
287 let ptr =
288 unsafe { qjs::JS_NewCModule(ctx.as_ptr(), name.as_ptr(), Some(Self::eval_fn::<D>)) };
289 let ptr = NonNull::new(ptr).ok_or(Error::Unknown)?;
290 let m = unsafe { Module::from_ptr(ctx, ptr) };
291
292 let decl = Declarations(m);
293 D::declare(&decl)?;
294
295 Ok(decl.0)
296 }
298
299 pub fn evaluate<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Promise<'js>>
309 where
310 N: Into<Vec<u8>>,
311 S: Into<Vec<u8>>,
312 {
313 let name = CString::new(name)?;
314 let flag = qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT;
315
316 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
317 let module_val = unsafe { ctx.handle_exception(module_val)? };
318 let v = unsafe { Value::from_js_value(ctx, module_val) };
319 Ok(v.into_promise().expect("evaluate should return a promise"))
320 }
321
322 pub fn evaluate_def<D, N>(
324 ctx: Ctx<'js>,
325 name: N,
326 ) -> Result<(Module<'js, Evaluated>, Promise<'js>)>
327 where
328 N: Into<Vec<u8>>,
329 D: ModuleDef,
330 {
331 let module = Self::declare_def::<D, N>(ctx, name)?;
332 module.eval()
333 }
334
335 pub unsafe fn load(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Module<'js, Declared>> {
340 let module = unsafe {
341 qjs::JS_ReadObject(
342 ctx.as_ptr(),
343 bytes.as_ptr(),
344 bytes.len() as _,
345 (qjs::JS_READ_OBJ_BYTECODE | qjs::JS_READ_OBJ_ROM_DATA) as i32,
346 )
347 };
348 let module = ctx.handle_exception(module)?;
349 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe { qjs::JS_VALUE_GET_TAG(module) });
350 let module_ptr =
351 unsafe { NonNull::new(qjs::JS_VALUE_GET_PTR(module).cast()).ok_or(Error::Unknown)? };
352 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
353 }
354
355 pub unsafe fn from_load_fn<N>(
361 ctx: Ctx<'js>,
362 name: N,
363 load_fn: ModuleLoadFn,
364 ) -> Result<Module<'js, Declared>>
365 where
366 N: Into<Vec<u8>>,
367 {
368 let name = CString::new(name)?;
369 let ptr = (load_fn)(ctx.as_ptr(), name.as_ptr().cast());
370 let ptr = NonNull::new(ptr).ok_or(Error::Exception)?;
371 unsafe { Ok(Module::from_ptr(ctx, ptr)) }
372 }
373
374 pub fn eval(self) -> Result<(Module<'js, Evaluated>, Promise<'js>)> {
379 let ret = unsafe {
380 #[cfg(feature = "parallel")]
381 qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
382
383 let v = qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast());
385 qjs::JS_DupValue(self.ctx.as_ptr(), v);
386 qjs::JS_EvalFunction(self.ctx.as_ptr(), v)
387 };
388 let ret = unsafe { self.ctx.handle_exception(ret)? };
389 let promise = unsafe { Promise::from_js_value(self.ctx.clone(), ret) };
390 Ok((
391 Module {
392 ptr: self.ptr,
393 ctx: self.ctx,
394 _type_marker: PhantomData,
395 },
396 promise,
397 ))
398 }
399
400 pub unsafe extern "C" fn init_raw<D>(
406 ctx: *mut qjs::JSContext,
407 name: *const qjs::c_char,
408 ) -> *mut qjs::JSModuleDef
409 where
410 D: ModuleDef,
411 {
412 let ctx = Ctx::from_ptr(ctx);
413 let name = CStr::from_ptr(name).to_bytes();
414 match Self::declare_def::<D, _>(ctx.clone(), name) {
415 Ok(module) => module.as_ptr(),
416 Err(error) => {
417 error.throw(&ctx);
418 ptr::null_mut()
419 }
420 }
421 }
422
423 pub fn import<S: Into<Vec<u8>>>(ctx: &Ctx<'js>, specifier: S) -> Result<Promise<'js>> {
427 let specifier = CString::new(specifier)?;
428 unsafe {
429 let base_name = ctx
430 .script_or_module_name(1)
431 .unwrap_or_else(|| Atom::from_predefined(ctx.clone(), PredefinedAtom::Empty));
432
433 let base_name_c_str =
434 qjs::JS_AtomToCStringLen(ctx.as_ptr(), null_mut(), base_name.atom);
435
436 let res = qjs::JS_LoadModule(ctx.as_ptr(), base_name_c_str, specifier.as_ptr());
437
438 qjs::JS_FreeCString(ctx.as_ptr(), base_name_c_str);
439
440 let res = ctx.handle_exception(res)?;
441
442 Ok(Promise::from_js_value(ctx.clone(), res))
443 }
444 }
445}
446
447impl<'js, Evaluated> Module<'js, Evaluated> {
448 pub fn write(&self, options: WriteOptions) -> Result<Vec<u8>> {
470 let ctx = &self.ctx;
471 let mut len = MaybeUninit::uninit();
472 let buf = unsafe {
473 qjs::JS_WriteObject(
474 ctx.as_ptr(),
475 len.as_mut_ptr(),
476 qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast()),
477 options.to_flag(),
478 )
479 };
480 if buf.is_null() {
481 return Err(ctx.raise_exception());
482 }
483 let len = unsafe { len.assume_init() };
484 let obj = unsafe { slice::from_raw_parts(buf, len as _) };
485 let obj = Vec::from(obj);
486 unsafe { qjs::js_free(ctx.as_ptr(), buf as _) };
487 Ok(obj)
488 }
489
490 pub fn meta(&self) -> Result<Object<'js>> {
492 unsafe {
493 Ok(Object::from_js_value(
494 self.ctx.clone(),
495 self.ctx
496 .handle_exception(qjs::JS_GetImportMeta(self.ctx.as_ptr(), self.as_ptr()))?,
497 ))
498 }
499 }
500
501 pub fn namespace(&self) -> Result<Object<'js>> {
503 unsafe {
504 let v = qjs::JS_GetModuleNamespace(self.ctx.as_ptr(), self.as_ptr());
505 let v = self.ctx.handle_exception(v)?;
506 Ok(Object::from_js_value(self.ctx.clone(), v))
507 }
508 }
509
510 pub fn get<N, T>(&self, name: N) -> Result<T>
512 where
513 N: IntoAtom<'js>,
514 T: FromJs<'js>,
515 {
516 self.namespace()?.get(name)
517 }
518
519 pub fn into_declared(self) -> Module<'js, Declared> {
523 Module {
524 ptr: self.ptr,
525 ctx: self.ctx,
526 _type_marker: PhantomData,
527 }
528 }
529}
530
531#[cfg(test)]
532mod test {
533
534 use super::*;
535 use crate::*;
536
537 pub struct RustModule;
538
539 impl ModuleDef for RustModule {
540 fn declare(define: &Declarations) -> Result<()> {
541 define.declare_c_str(c"hello")?;
542 Ok(())
543 }
544
545 fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
546 exports.export_c_str(c"hello", "world")?;
547 Ok(())
548 }
549 }
550
551 pub struct CrashingRustModule;
552
553 impl ModuleDef for CrashingRustModule {
554 fn declare(_: &Declarations) -> Result<()> {
555 Ok(())
556 }
557
558 fn evaluate<'js>(ctx: &Ctx<'js>, _exports: &Exports<'js>) -> Result<()> {
559 ctx.eval::<(), _>(r#"throw new Error("kaboom")"#)?;
560 Ok(())
561 }
562 }
563
564 #[test]
565 fn from_rust_def() {
566 test_with(|ctx| {
567 Module::declare_def::<RustModule, _>(ctx, "rust_mod").unwrap();
568 })
569 }
570
571 #[test]
572 fn from_rust_def_eval() {
573 test_with(|ctx| {
574 let _ = Module::evaluate_def::<RustModule, _>(ctx, "rust_mod").unwrap();
575 })
576 }
577
578 #[test]
579 fn import_native() {
580 test_with(|ctx| {
581 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
582 Module::evaluate(
583 ctx.clone(),
584 "test",
585 r#"
586 import { hello } from "rust_mod";
587
588 globalThis.hello = hello;
589 "#,
590 )
591 .unwrap()
592 .finish::<()>()
593 .unwrap();
594 let text = ctx
595 .globals()
596 .get::<_, String>("hello")
597 .unwrap()
598 .to_string()
599 .unwrap();
600 assert_eq!(text.as_str(), "world");
601 })
602 }
603
604 #[test]
605 fn import_async() {
606 test_with(|ctx| {
607 Module::declare(
608 ctx.clone(),
609 "rust_mod",
610 "
611 async function foo(){
612 return 'world';
613 };
614 export let hello = await foo();
615 ",
616 )
617 .unwrap();
618 Module::evaluate(
619 ctx.clone(),
620 "test",
621 r#"
622 import { hello } from "rust_mod";
623 globalThis.hello = hello;
624 "#,
625 )
626 .unwrap()
627 .finish::<()>()
628 .unwrap();
629 let text = ctx
630 .globals()
631 .get::<_, String>("hello")
632 .unwrap()
633 .to_string()
634 .unwrap();
635 assert_eq!(text.as_str(), "world");
636 })
637 }
638
639 #[test]
640 fn import() {
641 test_with(|ctx| {
642 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
643 let val: Object = Module::import(&ctx, "rust_mod").unwrap().finish().unwrap();
644 let hello: StdString = val.get("hello").unwrap();
645
646 assert_eq!(&hello, "world");
647 })
648 }
649
650 #[test]
651 #[should_panic(expected = "kaboom")]
652 fn import_crashing() {
653 use crate::{CatchResultExt, Context, Runtime};
654
655 let runtime = Runtime::new().unwrap();
656 let ctx = Context::full(&runtime).unwrap();
657 ctx.with(|ctx| {
658 Module::declare_def::<CrashingRustModule, _>(ctx.clone(), "bad_rust_mod").unwrap();
659 let _: Value = Module::import(&ctx, "bad_rust_mod")
660 .catch(&ctx)
661 .unwrap()
662 .finish()
663 .catch(&ctx)
664 .unwrap();
665 });
666 }
667
668 #[test]
669 fn eval_crashing_module_inside_module() {
670 let runtime = Runtime::new().unwrap();
671 let ctx = Context::full(&runtime).unwrap();
672
673 ctx.with(|ctx| {
674 let globals = ctx.globals();
675 let eval_crashing = |ctx: Ctx| {
676 Module::evaluate(ctx, "test2", "throw new Error(1)").map(|x| x.finish::<()>())
677 };
678 let function = Function::new(ctx.clone(), eval_crashing).unwrap();
679 globals.set("eval_crashing", function).unwrap();
680
681 let res = Module::evaluate(ctx, "test", " eval_crashing(); ")
682 .unwrap()
683 .finish::<()>();
684 assert!(res.is_err())
685 });
686 }
687
688 #[test]
689 fn access_before_fully_evaluating_module() {
690 let runtime = Runtime::new().unwrap();
691 let ctx = Context::full(&runtime).unwrap();
692
693 ctx.with(|ctx| {
694 let decl = Module::declare(
695 ctx,
696 "test",
697 r#"
698 async function async_res(){
699 return await (async () => {
700 return "OK"
701 })()
702 };
703
704 export let res = await async_res()
705 "#,
706 )
707 .unwrap();
708
709 let (decl, promise) = decl.eval().unwrap();
710
711 let ns = decl.namespace().unwrap();
712 ns.get::<_, ()>("res").unwrap_err();
713
714 promise.finish::<()>().unwrap();
715
716 assert_eq!(ns.get::<_, std::string::String>("res").unwrap(), "OK");
717 });
718 }
719
720 #[test]
721 fn from_javascript() {
722 test_with(|ctx| {
723 let (module, promise) = Module::declare(
724 ctx.clone(),
725 "Test",
726 r#"
727 export var a = 2;
728 export function foo(){ return "bar"}
729 export class Baz{
730 quel = 3;
731 constructor(){
732 }
733 }
734 "#,
735 )
736 .unwrap()
737 .eval()
738 .unwrap();
739
740 promise.finish::<()>().unwrap();
741
742 assert_eq!(module.name::<StdString>().unwrap(), "Test");
743 let _ = module.meta().unwrap();
744
745 let ns = module.namespace().unwrap();
746
747 assert!(ns.contains_key("a").unwrap());
748 assert!(ns.contains_key("foo").unwrap());
749 assert!(ns.contains_key("Baz").unwrap());
750
751 assert_eq!(ns.get::<_, u32>("a").unwrap(), 2u32);
752 });
753 }
754}