1use std::{
4 ffi::{CStr, CString},
5 marker::PhantomData,
6 mem::MaybeUninit,
7 ptr::{self, NonNull},
8 slice,
9};
10
11use crate::{
12 atom::PredefinedAtom, qjs, Atom, Ctx, Error, FromAtom, FromJs, IntoAtom, IntoJs, Object,
13 Promise, Result, Value,
14};
15
16#[macro_export]
29macro_rules! module_init {
30 ($type:ty) => {
31 $crate::module_init!(js_init_module: $type);
32 };
33
34 ($name:ident: $type:ty) => {
35 #[no_mangle]
36 pub unsafe extern "C" fn $name(
37 ctx: *mut $crate::qjs::JSContext,
38 module_name: *const $crate::qjs::c_char,
39 ) -> *mut $crate::qjs::JSModuleDef {
40 $crate::Module::init_raw::<$type>(ctx, module_name)
41 }
42 };
43}
44
45pub type ModuleLoadFn =
47 unsafe extern "C" fn(*mut qjs::JSContext, *const qjs::c_char) -> *mut qjs::JSModuleDef;
48
49pub trait ModuleDef {
51 fn declare<'js>(decl: &Declarations<'js>) -> Result<()> {
52 let _ = decl;
53 Ok(())
54 }
55
56 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
57 let _ = (exports, ctx);
58 Ok(())
59 }
60}
61
62pub struct Declarations<'js>(Module<'js, Declared>);
64
65impl<'js> Declarations<'js> {
66 pub fn declare<N>(&self, name: N) -> Result<&Self>
68 where
69 N: Into<Vec<u8>>,
70 {
71 let name = CString::new(name)?;
72 self.declare_c_str(name.as_c_str())
73 }
74
75 pub fn declare_c_str(&self, name: &CStr) -> Result<&Self> {
80 unsafe { qjs::JS_AddModuleExport(self.0.ctx.as_ptr(), self.0.as_ptr(), name.as_ptr()) };
81 Ok(self)
82 }
83}
84
85pub struct Exports<'js>(Module<'js, Declared>);
87
88impl<'js> Exports<'js> {
89 pub fn export<N: Into<Vec<u8>>, T: IntoJs<'js>>(&self, name: N, value: T) -> Result<&Self> {
91 let name = CString::new(name.into())?;
92 self.export_c_str(name.as_c_str(), value)
93 }
94
95 pub fn export_c_str<T: IntoJs<'js>>(&self, name: &CStr, value: T) -> Result<&Self> {
99 let value = value.into_js(&self.0.ctx)?;
100 let res = unsafe {
101 qjs::JS_SetModuleExport(
102 self.0.ctx.as_ptr(),
103 self.0.as_ptr(),
104 name.as_ptr(),
105 value.into_js_value(),
106 )
107 };
108 if res < 0 {
109 return Err(Error::InvalidExport);
110 }
111
112 Ok(self)
113 }
114}
115
116#[derive(Clone, Copy, Debug)]
118pub struct Declared;
119#[derive(Clone, Copy, Debug)]
121pub struct Evaluated;
122
123#[derive(Clone, Debug)]
125pub struct Module<'js, T = Declared> {
126 ptr: NonNull<qjs::JSModuleDef>,
127 ctx: Ctx<'js>,
128 _type_marker: PhantomData<T>,
129}
130
131impl<'js, T> Module<'js, T> {
132 pub(crate) fn as_ptr(&self) -> *mut qjs::JSModuleDef {
133 self.ptr.as_ptr()
134 }
135
136 pub(crate) unsafe fn from_ptr(ctx: Ctx<'js>, ptr: NonNull<qjs::JSModuleDef>) -> Module<'js, T> {
137 Module {
138 ptr,
139 ctx,
140 _type_marker: PhantomData,
141 }
142 }
143
144 unsafe extern "C" fn eval_fn<D>(
145 ctx: *mut qjs::JSContext,
146 ptr: *mut qjs::JSModuleDef,
147 ) -> qjs::c_int
148 where
149 D: ModuleDef,
150 {
151 let ctx = Ctx::from_ptr(ctx);
152 let ptr = NonNull::new(ptr).unwrap();
154 let module = unsafe { Module::from_ptr(ctx.clone(), ptr) };
155 let exports = Exports(module);
156 match D::evaluate(&ctx, &exports) {
157 Ok(_) => 0,
158 Err(error) => {
159 error.throw(&ctx);
160 -1
161 }
162 }
163 }
164
165 pub fn name<N>(&self) -> Result<N>
167 where
168 N: FromAtom<'js>,
169 {
170 let name = unsafe {
171 Atom::from_atom_val(
172 self.ctx.clone(),
173 qjs::JS_GetModuleName(self.ctx.as_ptr(), self.as_ptr()),
174 )
175 };
176 N::from_atom(name)
177 }
178}
179
180impl<'js> Module<'js, Declared> {
181 pub fn declare<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js, Declared>>
183 where
184 N: Into<Vec<u8>>,
185 S: Into<Vec<u8>>,
186 {
187 let name = CString::new(name)?;
188 let flag =
189 qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
190
191 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
192 let module_val = unsafe { ctx.handle_exception(module_val)? };
193 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe {
194 qjs::JS_VALUE_GET_TAG(module_val)
195 });
196 let module_ptr = unsafe {
197 NonNull::new(qjs::JS_VALUE_GET_PTR(module_val).cast()).ok_or(Error::Unknown)?
198 };
199 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
200 }
201
202 pub fn declare_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js, Declared>>
204 where
205 N: Into<Vec<u8>>,
206 D: ModuleDef,
207 {
208 let name = CString::new(name)?;
209 let ptr =
210 unsafe { qjs::JS_NewCModule(ctx.as_ptr(), name.as_ptr(), Some(Self::eval_fn::<D>)) };
211 let ptr = NonNull::new(ptr).ok_or(Error::Unknown)?;
212 let m = unsafe { Module::from_ptr(ctx, ptr) };
213
214 let decl = Declarations(m);
215 D::declare(&decl)?;
216
217 Ok(decl.0)
218 }
220
221 pub fn evaluate<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Promise<'js>>
231 where
232 N: Into<Vec<u8>>,
233 S: Into<Vec<u8>>,
234 {
235 let name = CString::new(name)?;
236 let flag = qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT;
237
238 let module_val = unsafe { ctx.eval_raw(source, name.as_c_str(), flag as i32)? };
239 let module_val = unsafe { ctx.handle_exception(module_val)? };
240 let v = unsafe { Value::from_js_value(ctx, module_val) };
241 Ok(v.into_promise().expect("evaluate should return a promise"))
242 }
243
244 pub fn evaluate_def<D, N>(
246 ctx: Ctx<'js>,
247 name: N,
248 ) -> Result<(Module<'js, Evaluated>, Promise<'js>)>
249 where
250 N: Into<Vec<u8>>,
251 D: ModuleDef,
252 {
253 let module = Self::declare_def::<D, N>(ctx, name)?;
254 module.eval()
255 }
256
257 pub unsafe fn load(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Module<'js, Declared>> {
262 let module = unsafe {
263 qjs::JS_ReadObject(
264 ctx.as_ptr(),
265 bytes.as_ptr(),
266 bytes.len() as _,
267 (qjs::JS_READ_OBJ_BYTECODE | qjs::JS_READ_OBJ_ROM_DATA) as i32,
268 )
269 };
270 let module = ctx.handle_exception(module)?;
271 debug_assert_eq!(qjs::JS_TAG_MODULE, unsafe { qjs::JS_VALUE_GET_TAG(module) });
272 let module_ptr =
273 unsafe { NonNull::new(qjs::JS_VALUE_GET_PTR(module).cast()).ok_or(Error::Unknown)? };
274 unsafe { Ok(Module::from_ptr(ctx, module_ptr)) }
275 }
276
277 pub unsafe fn from_load_fn<N>(
283 ctx: Ctx<'js>,
284 name: N,
285 load_fn: ModuleLoadFn,
286 ) -> Result<Module<'js, Declared>>
287 where
288 N: Into<Vec<u8>>,
289 {
290 let name = CString::new(name)?;
291 let ptr = (load_fn)(ctx.as_ptr(), name.as_ptr().cast());
292 let ptr = NonNull::new(ptr).ok_or(Error::Exception)?;
293 unsafe { Ok(Module::from_ptr(ctx, ptr)) }
294 }
295
296 pub fn eval(self) -> Result<(Module<'js, Evaluated>, Promise<'js>)> {
301 let ret = unsafe {
302 let v = qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast());
304 qjs::JS_DupValue(self.ctx.as_ptr(), v);
305 qjs::JS_EvalFunction(self.ctx.as_ptr(), v)
306 };
307 let ret = unsafe { self.ctx.handle_exception(ret)? };
308 let promise = unsafe { Promise::from_js_value(self.ctx.clone(), ret) };
309 Ok((
310 Module {
311 ptr: self.ptr,
312 ctx: self.ctx,
313 _type_marker: PhantomData,
314 },
315 promise,
316 ))
317 }
318
319 pub unsafe extern "C" fn init_raw<D>(
325 ctx: *mut qjs::JSContext,
326 name: *const qjs::c_char,
327 ) -> *mut qjs::JSModuleDef
328 where
329 D: ModuleDef,
330 {
331 let ctx = Ctx::from_ptr(ctx);
332 let name = CStr::from_ptr(name).to_bytes();
333 match Self::declare_def::<D, _>(ctx.clone(), name) {
334 Ok(module) => module.as_ptr(),
335 Err(error) => {
336 error.throw(&ctx);
337 ptr::null_mut()
338 }
339 }
340 }
341
342 pub fn import<S: Into<Vec<u8>>>(ctx: &Ctx<'js>, specifier: S) -> Result<Promise<'js>> {
346 let specifier = CString::new(specifier)?;
347 unsafe {
348 let base_name = ctx
349 .script_or_module_name(1)
350 .unwrap_or_else(|| Atom::from_predefined(ctx.clone(), PredefinedAtom::Empty));
351
352 let base_name_c_str = qjs::JS_AtomToCString(ctx.as_ptr(), base_name.atom);
353
354 let res = qjs::JS_LoadModule(ctx.as_ptr(), base_name_c_str, specifier.as_ptr());
355
356 qjs::JS_FreeCString(ctx.as_ptr(), base_name_c_str);
357
358 let res = ctx.handle_exception(res)?;
359
360 Ok(Promise::from_js_value(ctx.clone(), res))
361 }
362 }
363}
364
365impl<'js, Evaluated> Module<'js, Evaluated> {
366 pub fn write_le(&self) -> Result<Vec<u8>> {
368 let swap = cfg!(target_endian = "big");
369 self.write(swap)
370 }
371
372 pub fn write_be(&self) -> Result<Vec<u8>> {
374 let swap = cfg!(target_endian = "little");
375 self.write(swap)
376 }
377
378 pub fn write(&self, swap_endianess: bool) -> Result<Vec<u8>> {
384 let ctx = &self.ctx;
385 let mut len = MaybeUninit::uninit();
386 let mut flags = qjs::JS_WRITE_OBJ_BYTECODE;
388 if swap_endianess {
389 flags |= qjs::JS_WRITE_OBJ_BSWAP;
390 }
391 let buf = unsafe {
392 qjs::JS_WriteObject(
393 ctx.as_ptr(),
394 len.as_mut_ptr(),
395 qjs::JS_MKPTR(qjs::JS_TAG_MODULE, self.ptr.as_ptr().cast()),
396 flags as i32,
397 )
398 };
399 if buf.is_null() {
400 return Err(ctx.raise_exception());
401 }
402 let len = unsafe { len.assume_init() };
403 let obj = unsafe { slice::from_raw_parts(buf, len as _) };
404 let obj = Vec::from(obj);
405 unsafe { qjs::js_free(ctx.as_ptr(), buf as _) };
406 Ok(obj)
407 }
408
409 pub fn meta(&self) -> Result<Object<'js>> {
411 unsafe {
412 Ok(Object::from_js_value(
413 self.ctx.clone(),
414 self.ctx
415 .handle_exception(qjs::JS_GetImportMeta(self.ctx.as_ptr(), self.as_ptr()))?,
416 ))
417 }
418 }
419
420 pub fn namespace(&self) -> Result<Object<'js>> {
422 unsafe {
423 let v = qjs::JS_GetModuleNamespace(self.ctx.as_ptr(), self.as_ptr());
424 let v = self.ctx.handle_exception(v)?;
425 Ok(Object::from_js_value(self.ctx.clone(), v))
426 }
427 }
428
429 pub fn get<N, T>(&self, name: N) -> Result<T>
431 where
432 N: IntoAtom<'js>,
433 T: FromJs<'js>,
434 {
435 self.namespace()?.get(name)
436 }
437
438 pub fn into_declared(self) -> Module<'js, Declared> {
442 Module {
443 ptr: self.ptr,
444 ctx: self.ctx,
445 _type_marker: PhantomData,
446 }
447 }
448}
449
450#[cfg(test)]
451mod test {
452
453 use super::*;
454 use crate::*;
455
456 pub struct RustModule;
457
458 impl ModuleDef for RustModule {
459 fn declare(define: &Declarations) -> Result<()> {
460 define.declare_c_str(CStr::from_bytes_with_nul(b"hello\0")?)?;
461 Ok(())
462 }
463
464 fn evaluate<'js>(_ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
465 exports.export_c_str(CStr::from_bytes_with_nul(b"hello\0")?, "world")?;
466 Ok(())
467 }
468 }
469
470 pub struct CrashingRustModule;
471
472 impl ModuleDef for CrashingRustModule {
473 fn declare(_: &Declarations) -> Result<()> {
474 Ok(())
475 }
476
477 fn evaluate<'js>(ctx: &Ctx<'js>, _exports: &Exports<'js>) -> Result<()> {
478 ctx.eval::<(), _>(r#"throw new Error("kaboom")"#)?;
479 Ok(())
480 }
481 }
482
483 #[test]
484 fn from_rust_def() {
485 test_with(|ctx| {
486 Module::declare_def::<RustModule, _>(ctx, "rust_mod").unwrap();
487 })
488 }
489
490 #[test]
491 fn from_rust_def_eval() {
492 test_with(|ctx| {
493 let _ = Module::evaluate_def::<RustModule, _>(ctx, "rust_mod").unwrap();
494 })
495 }
496
497 #[test]
498 fn import_native() {
499 test_with(|ctx| {
500 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
501 Module::evaluate(
502 ctx.clone(),
503 "test",
504 r#"
505 import { hello } from "rust_mod";
506
507 globalThis.hello = hello;
508 "#,
509 )
510 .unwrap()
511 .finish::<()>()
512 .unwrap();
513 let text = ctx
514 .globals()
515 .get::<_, String>("hello")
516 .unwrap()
517 .to_string()
518 .unwrap();
519 assert_eq!(text.as_str(), "world");
520 })
521 }
522
523 #[test]
524 fn import_async() {
525 test_with(|ctx| {
526 Module::declare(
527 ctx.clone(),
528 "rust_mod",
529 "
530 async function foo(){
531 return 'world';
532 };
533 export let hello = await foo();
534 ",
535 )
536 .unwrap();
537 Module::evaluate(
538 ctx.clone(),
539 "test",
540 r#"
541 import { hello } from "rust_mod";
542 globalThis.hello = hello;
543 "#,
544 )
545 .unwrap()
546 .finish::<()>()
547 .unwrap();
548 let text = ctx
549 .globals()
550 .get::<_, String>("hello")
551 .unwrap()
552 .to_string()
553 .unwrap();
554 assert_eq!(text.as_str(), "world");
555 })
556 }
557
558 #[test]
559 fn import() {
560 test_with(|ctx| {
561 Module::declare_def::<RustModule, _>(ctx.clone(), "rust_mod").unwrap();
562 let val: Object = Module::import(&ctx, "rust_mod").unwrap().finish().unwrap();
563 let hello: StdString = val.get("hello").unwrap();
564
565 assert_eq!(&hello, "world");
566 })
567 }
568
569 #[test]
570 #[should_panic(expected = "kaboom")]
571 fn import_crashing() {
572 use crate::{CatchResultExt, Context, Runtime};
573
574 let runtime = Runtime::new().unwrap();
575 let ctx = Context::full(&runtime).unwrap();
576 ctx.with(|ctx| {
577 Module::declare_def::<CrashingRustModule, _>(ctx.clone(), "bad_rust_mod").unwrap();
578 let _: Value = Module::import(&ctx, "bad_rust_mod")
579 .catch(&ctx)
580 .unwrap()
581 .finish()
582 .catch(&ctx)
583 .unwrap();
584 });
585 }
586
587 #[test]
588 fn eval_crashing_module_inside_module() {
589 let runtime = Runtime::new().unwrap();
590 let ctx = Context::full(&runtime).unwrap();
591
592 ctx.with(|ctx| {
593 let globals = ctx.globals();
594 let eval_crashing = |ctx: Ctx| {
595 Module::evaluate(ctx, "test2", "throw new Error(1)").map(|x| x.finish::<()>())
596 };
597 let function = Function::new(ctx.clone(), eval_crashing).unwrap();
598 globals.set("eval_crashing", function).unwrap();
599
600 let res = Module::evaluate(ctx, "test", " eval_crashing(); ")
601 .unwrap()
602 .finish::<()>();
603 assert!(res.is_err())
604 });
605 }
606
607 #[test]
608 fn access_before_fully_evaluating_module() {
609 let runtime = Runtime::new().unwrap();
610 let ctx = Context::full(&runtime).unwrap();
611
612 ctx.with(|ctx| {
613 let decl = Module::declare(
614 ctx,
615 "test",
616 r#"
617 async function async_res(){
618 return await (async () => {
619 return "OK"
620 })()
621 };
622
623 export let res = await async_res()
624 "#,
625 )
626 .unwrap();
627
628 let (decl, promise) = decl.eval().unwrap();
629
630 let ns = decl.namespace().unwrap();
631 ns.get::<_, ()>("res").unwrap_err();
632
633 promise.finish::<()>().unwrap();
634
635 assert_eq!(ns.get::<_, std::string::String>("res").unwrap(), "OK");
636 });
637 }
638
639 #[test]
640 fn from_javascript() {
641 test_with(|ctx| {
642 let (module, promise) = Module::declare(
643 ctx.clone(),
644 "Test",
645 r#"
646 export var a = 2;
647 export function foo(){ return "bar"}
648 export class Baz{
649 quel = 3;
650 constructor(){
651 }
652 }
653 "#,
654 )
655 .unwrap()
656 .eval()
657 .unwrap();
658
659 promise.finish::<()>().unwrap();
660
661 assert_eq!(module.name::<StdString>().unwrap(), "Test");
662 let _ = module.meta().unwrap();
663
664 let ns = module.namespace().unwrap();
665
666 assert!(ns.contains_key("a").unwrap());
667 assert!(ns.contains_key("foo").unwrap());
668 assert!(ns.contains_key("Baz").unwrap());
669
670 assert_eq!(ns.get::<_, u32>("a").unwrap(), 2u32);
671 });
672 }
673}