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
148pub struct Exports<'js>(Module<'js, Declared>);
150
151impl<'js> Exports<'js> {
152 pub fn export<N: Into<Vec<u8>>, T: IntoJs<'js>>(&self, name: N, value: T) -> Result<&Self> {
154 let name = CString::new(name.into())?;
155 self.export_c_str(name.as_c_str(), value)
156 }
157
158 pub fn export_c_str<T: IntoJs<'js>>(&self, name: &CStr, value: T) -> Result<&Self> {
162 let value = value.into_js(&self.0.ctx)?;
163 let res = unsafe {
164 qjs::JS_SetModuleExport(
165 self.0.ctx.as_ptr(),
166 self.0.as_ptr(),
167 name.as_ptr(),
168 value.into_js_value(),
169 )
170 };
171 if res < 0 {
172 return Err(Error::InvalidExport);
173 }
174
175 Ok(self)
176 }
177}
178
179#[derive(Clone, Copy, Debug)]
181pub struct Declared;
182#[derive(Clone, Copy, Debug)]
184pub struct Evaluated;
185
186#[derive(Clone, Debug)]
188pub struct Module<'js, T = Declared> {
189 ptr: NonNull<qjs::JSModuleDef>,
190 ctx: Ctx<'js>,
191 _type_marker: PhantomData<T>,
192}
193
194impl<'js, T> Module<'js, T> {
195 pub(crate) fn as_ptr(&self) -> *mut qjs::JSModuleDef {
196 self.ptr.as_ptr()
197 }
198
199 pub(crate) unsafe fn from_ptr(ctx: Ctx<'js>, ptr: NonNull<qjs::JSModuleDef>) -> Module<'js, T> {
200 Module {
201 ptr,
202 ctx,
203 _type_marker: PhantomData,
204 }
205 }
206
207 unsafe extern "C" fn eval_fn<D>(
208 ctx: *mut qjs::JSContext,
209 ptr: *mut qjs::JSModuleDef,
210 ) -> qjs::c_int
211 where
212 D: ModuleDef,
213 {
214 let ctx = Ctx::from_ptr(ctx);
215 let ptr = NonNull::new(ptr).unwrap();
217 let module = unsafe { Module::from_ptr(ctx.clone(), ptr) };
218 let exports = Exports(module);
219 match D::evaluate(&ctx, &exports) {
220 Ok(_) => 0,
221 Err(error) => {
222 error.throw(&ctx);
223 -1
224 }
225 }
226 }
227
228 pub fn name<N>(&self) -> Result<N>
230 where
231 N: FromAtom<'js>,
232 {
233 let name = unsafe {
234 Atom::from_atom_val(
235 self.ctx.clone(),
236 qjs::JS_GetModuleName(self.ctx.as_ptr(), self.as_ptr()),
237 )
238 };
239 N::from_atom(name)
240 }
241}
242
243impl<'js> Module<'js, Declared> {
244 pub fn declare<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js, Declared>>
246 where
247 N: Into<Vec<u8>>,
248 S: Into<Vec<u8>>,
249 {
250 let name = CString::new(name)?;
251 let flag =
252 qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
253
254 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
255 let module_val = unsafe { ctx.handle_exception(module_val)? };
256 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe {
257 qjs::JS_VALUE_GET_TAG(module_val)
258 });
259 let module_ptr = unsafe {
260 NonNull::new(qjs::JS_VALUE_GET_PTR(module_val).cast()).ok_or(Error::Unknown)?
261 };
262 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
263 }
264
265 pub fn declare_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js, Declared>>
267 where
268 N: Into<Vec<u8>>,
269 D: ModuleDef,
270 {
271 let name = CString::new(name)?;
272 let ptr =
273 unsafe { qjs::JS_NewCModule(ctx.as_ptr(), name.as_ptr(), Some(Self::eval_fn::<D>)) };
274 let ptr = NonNull::new(ptr).ok_or(Error::Unknown)?;
275 let m = unsafe { Module::from_ptr(ctx, ptr) };
276
277 let decl = Declarations(m);
278 D::declare(&decl)?;
279
280 Ok(decl.0)
281 }
283
284 pub fn evaluate<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Promise<'js>>
294 where
295 N: Into<Vec<u8>>,
296 S: Into<Vec<u8>>,
297 {
298 let name = CString::new(name)?;
299 let flag = qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT;
300
301 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
302 let module_val = unsafe { ctx.handle_exception(module_val)? };
303 let v = unsafe { Value::from_js_value(ctx, module_val) };
304 Ok(v.into_promise().expect("evaluate should return a promise"))
305 }
306
307 pub fn evaluate_def<D, N>(
309 ctx: Ctx<'js>,
310 name: N,
311 ) -> Result<(Module<'js, Evaluated>, Promise<'js>)>
312 where
313 N: Into<Vec<u8>>,
314 D: ModuleDef,
315 {
316 let module = Self::declare_def::<D, N>(ctx, name)?;
317 module.eval()
318 }
319
320 pub unsafe fn load(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Module<'js, Declared>> {
325 let module = unsafe {
326 qjs::JS_ReadObject(
327 ctx.as_ptr(),
328 bytes.as_ptr(),
329 bytes.len() as _,
330 (qjs::JS_READ_OBJ_BYTECODE | qjs::JS_READ_OBJ_ROM_DATA) as i32,
331 )
332 };
333 let module = ctx.handle_exception(module)?;
334 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe { qjs::JS_VALUE_GET_TAG(module) });
335 let module_ptr =
336 unsafe { NonNull::new(qjs::JS_VALUE_GET_PTR(module).cast()).ok_or(Error::Unknown)? };
337 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
338 }
339
340 pub unsafe fn from_load_fn<N>(
346 ctx: Ctx<'js>,
347 name: N,
348 load_fn: ModuleLoadFn,
349 ) -> Result<Module<'js, Declared>>
350 where
351 N: Into<Vec<u8>>,
352 {
353 let name = CString::new(name)?;
354 let ptr = (load_fn)(ctx.as_ptr(), name.as_ptr().cast());
355 let ptr = NonNull::new(ptr).ok_or(Error::Exception)?;
356 unsafe { Ok(Module::from_ptr(ctx, ptr)) }
357 }
358
359 pub fn eval(self) -> Result<(Module<'js, Evaluated>, Promise<'js>)> {
364 let ret = unsafe {
365 let v = qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast());
367 qjs::JS_DupValue(self.ctx.as_ptr(), v);
368 qjs::JS_EvalFunction(self.ctx.as_ptr(), v)
369 };
370 let ret = unsafe { self.ctx.handle_exception(ret)? };
371 let promise = unsafe { Promise::from_js_value(self.ctx.clone(), ret) };
372 Ok((
373 Module {
374 ptr: self.ptr,
375 ctx: self.ctx,
376 _type_marker: PhantomData,
377 },
378 promise,
379 ))
380 }
381
382 pub unsafe extern "C" fn init_raw<D>(
388 ctx: *mut qjs::JSContext,
389 name: *const qjs::c_char,
390 ) -> *mut qjs::JSModuleDef
391 where
392 D: ModuleDef,
393 {
394 let ctx = Ctx::from_ptr(ctx);
395 let name = CStr::from_ptr(name).to_bytes();
396 match Self::declare_def::<D, _>(ctx.clone(), name) {
397 Ok(module) => module.as_ptr(),
398 Err(error) => {
399 error.throw(&ctx);
400 ptr::null_mut()
401 }
402 }
403 }
404
405 pub fn import<S: Into<Vec<u8>>>(ctx: &Ctx<'js>, specifier: S) -> Result<Promise<'js>> {
409 let specifier = CString::new(specifier)?;
410 unsafe {
411 let base_name = ctx
412 .script_or_module_name(1)
413 .unwrap_or_else(|| Atom::from_predefined(ctx.clone(), PredefinedAtom::Empty));
414
415 let base_name_c_str =
416 qjs::JS_AtomToCStringLen(ctx.as_ptr(), null_mut(), base_name.atom);
417
418 let res = qjs::JS_LoadModule(ctx.as_ptr(), base_name_c_str, specifier.as_ptr());
419
420 qjs::JS_FreeCString(ctx.as_ptr(), base_name_c_str);
421
422 let res = ctx.handle_exception(res)?;
423
424 Ok(Promise::from_js_value(ctx.clone(), res))
425 }
426 }
427}
428
429impl<'js, Evaluated> Module<'js, Evaluated> {
430 pub fn write(&self, options: WriteOptions) -> Result<Vec<u8>> {
452 let ctx = &self.ctx;
453 let mut len = MaybeUninit::uninit();
454 let buf = unsafe {
455 qjs::JS_WriteObject(
456 ctx.as_ptr(),
457 len.as_mut_ptr(),
458 qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast()),
459 options.to_flag(),
460 )
461 };
462 if buf.is_null() {
463 return Err(ctx.raise_exception());
464 }
465 let len = unsafe { len.assume_init() };
466 let obj = unsafe { slice::from_raw_parts(buf, len as _) };
467 let obj = Vec::from(obj);
468 unsafe { qjs::js_free(ctx.as_ptr(), buf as _) };
469 Ok(obj)
470 }
471
472 pub fn meta(&self) -> Result<Object<'js>> {
474 unsafe {
475 Ok(Object::from_js_value(
476 self.ctx.clone(),
477 self.ctx
478 .handle_exception(qjs::JS_GetImportMeta(self.ctx.as_ptr(), self.as_ptr()))?,
479 ))
480 }
481 }
482
483 pub fn namespace(&self) -> Result<Object<'js>> {
485 unsafe {
486 let v = qjs::JS_GetModuleNamespace(self.ctx.as_ptr(), self.as_ptr());
487 let v = self.ctx.handle_exception(v)?;
488 Ok(Object::from_js_value(self.ctx.clone(), v))
489 }
490 }
491
492 pub fn get<N, T>(&self, name: N) -> Result<T>
494 where
495 N: IntoAtom<'js>,
496 T: FromJs<'js>,
497 {
498 self.namespace()?.get(name)
499 }
500
501 pub fn into_declared(self) -> Module<'js, Declared> {
505 Module {
506 ptr: self.ptr,
507 ctx: self.ctx,
508 _type_marker: PhantomData,
509 }
510 }
511}
512
513#[cfg(test)]
514mod test {
515
516 use super::*;
517 use crate::*;
518
519 pub struct RustModule;
520
521 impl ModuleDef for RustModule {
522 fn declare(define: &Declarations) -> Result<()> {
523 define.declare_c_str(c"hello")?;
524 Ok(())
525 }
526
527 fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
528 exports.export_c_str(c"hello", "world")?;
529 Ok(())
530 }
531 }
532
533 pub struct CrashingRustModule;
534
535 impl ModuleDef for CrashingRustModule {
536 fn declare(_: &Declarations) -> Result<()> {
537 Ok(())
538 }
539
540 fn evaluate<'js>(ctx: &Ctx<'js>, _exports: &Exports<'js>) -> Result<()> {
541 ctx.eval::<(), _>(r#"throw new Error("kaboom")"#)?;
542 Ok(())
543 }
544 }
545
546 #[test]
547 fn from_rust_def() {
548 test_with(|ctx| {
549 Module::declare_def::<RustModule, _>(ctx, "rust_mod").unwrap();
550 })
551 }
552
553 #[test]
554 fn from_rust_def_eval() {
555 test_with(|ctx| {
556 let _ = Module::evaluate_def::<RustModule, _>(ctx, "rust_mod").unwrap();
557 })
558 }
559
560 #[test]
561 fn import_native() {
562 test_with(|ctx| {
563 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
564 Module::evaluate(
565 ctx.clone(),
566 "test",
567 r#"
568 import { hello } from "rust_mod";
569
570 globalThis.hello = hello;
571 "#,
572 )
573 .unwrap()
574 .finish::<()>()
575 .unwrap();
576 let text = ctx
577 .globals()
578 .get::<_, String>("hello")
579 .unwrap()
580 .to_string()
581 .unwrap();
582 assert_eq!(text.as_str(), "world");
583 })
584 }
585
586 #[test]
587 fn import_async() {
588 test_with(|ctx| {
589 Module::declare(
590 ctx.clone(),
591 "rust_mod",
592 "
593 async function foo(){
594 return 'world';
595 };
596 export let hello = await foo();
597 ",
598 )
599 .unwrap();
600 Module::evaluate(
601 ctx.clone(),
602 "test",
603 r#"
604 import { hello } from "rust_mod";
605 globalThis.hello = hello;
606 "#,
607 )
608 .unwrap()
609 .finish::<()>()
610 .unwrap();
611 let text = ctx
612 .globals()
613 .get::<_, String>("hello")
614 .unwrap()
615 .to_string()
616 .unwrap();
617 assert_eq!(text.as_str(), "world");
618 })
619 }
620
621 #[test]
622 fn import() {
623 test_with(|ctx| {
624 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
625 let val: Object = Module::import(&ctx, "rust_mod").unwrap().finish().unwrap();
626 let hello: StdString = val.get("hello").unwrap();
627
628 assert_eq!(&hello, "world");
629 })
630 }
631
632 #[test]
633 #[should_panic(expected = "kaboom")]
634 fn import_crashing() {
635 use crate::{CatchResultExt, Context, Runtime};
636
637 let runtime = Runtime::new().unwrap();
638 let ctx = Context::full(&runtime).unwrap();
639 ctx.with(|ctx| {
640 Module::declare_def::<CrashingRustModule, _>(ctx.clone(), "bad_rust_mod").unwrap();
641 let _: Value = Module::import(&ctx, "bad_rust_mod")
642 .catch(&ctx)
643 .unwrap()
644 .finish()
645 .catch(&ctx)
646 .unwrap();
647 });
648 }
649
650 #[test]
651 fn eval_crashing_module_inside_module() {
652 let runtime = Runtime::new().unwrap();
653 let ctx = Context::full(&runtime).unwrap();
654
655 ctx.with(|ctx| {
656 let globals = ctx.globals();
657 let eval_crashing = |ctx: Ctx| {
658 Module::evaluate(ctx, "test2", "throw new Error(1)").map(|x| x.finish::<()>())
659 };
660 let function = Function::new(ctx.clone(), eval_crashing).unwrap();
661 globals.set("eval_crashing", function).unwrap();
662
663 let res = Module::evaluate(ctx, "test", " eval_crashing(); ")
664 .unwrap()
665 .finish::<()>();
666 assert!(res.is_err())
667 });
668 }
669
670 #[test]
671 fn access_before_fully_evaluating_module() {
672 let runtime = Runtime::new().unwrap();
673 let ctx = Context::full(&runtime).unwrap();
674
675 ctx.with(|ctx| {
676 let decl = Module::declare(
677 ctx,
678 "test",
679 r#"
680 async function async_res(){
681 return await (async () => {
682 return "OK"
683 })()
684 };
685
686 export let res = await async_res()
687 "#,
688 )
689 .unwrap();
690
691 let (decl, promise) = decl.eval().unwrap();
692
693 let ns = decl.namespace().unwrap();
694 ns.get::<_, ()>("res").unwrap_err();
695
696 promise.finish::<()>().unwrap();
697
698 assert_eq!(ns.get::<_, std::string::String>("res").unwrap(), "OK");
699 });
700 }
701
702 #[test]
703 fn from_javascript() {
704 test_with(|ctx| {
705 let (module, promise) = Module::declare(
706 ctx.clone(),
707 "Test",
708 r#"
709 export var a = 2;
710 export function foo(){ return "bar"}
711 export class Baz{
712 quel = 3;
713 constructor(){
714 }
715 }
716 "#,
717 )
718 .unwrap()
719 .eval()
720 .unwrap();
721
722 promise.finish::<()>().unwrap();
723
724 assert_eq!(module.name::<StdString>().unwrap(), "Test");
725 let _ = module.meta().unwrap();
726
727 let ns = module.namespace().unwrap();
728
729 assert!(ns.contains_key("a").unwrap());
730 assert!(ns.contains_key("foo").unwrap());
731 assert!(ns.contains_key("Baz").unwrap());
732
733 assert_eq!(ns.get::<_, u32>("a").unwrap(), 2u32);
734 });
735 }
736}