quickjs_runtime/quickjs_utils/
modules.rs1use crate::jsutils::{JsError, Script};
4use crate::quickjs_utils::atoms;
5use crate::quickjs_utils::atoms::JSAtomRef;
6use crate::quickjsrealmadapter::QuickJsRealmAdapter;
7use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
8use crate::quickjsvalueadapter::QuickJsValueAdapter;
9use core::ptr;
10
11use libquickjs_sys as q;
12use std::ffi::{CStr, CString};
13
14pub unsafe fn compile_module(
18 context: *mut q::JSContext,
19 script: Script,
20) -> Result<QuickJsValueAdapter, JsError> {
21 let code_str = script.get_runnable_code();
22
23 let code_c = CString::new(code_str).ok().unwrap();
24 let filename_c = CString::new(script.get_path()).ok().unwrap();
25
26 let value_raw = q::JS_Eval(
27 context,
28 code_c.as_ptr(),
29 code_str.len() as _,
30 filename_c.as_ptr(),
31 (q::JS_EVAL_TYPE_MODULE | q::JS_EVAL_FLAG_COMPILE_ONLY) as i32,
32 );
33
34 let ret = QuickJsValueAdapter::new(
36 context,
37 value_raw,
38 false,
39 true,
40 format!("compile_module result of {}", script.get_path()).as_str(),
41 );
42
43 log::trace!("compile module yielded a {}", ret.borrow_value().tag);
44
45 if ret.is_exception() {
46 let ex_opt = QuickJsRealmAdapter::get_exception(context);
47 if let Some(ex) = ex_opt {
48 Err(ex)
49 } else {
50 Err(JsError::new_str(
51 "compile_module failed and could not get exception",
52 ))
53 }
54 } else {
55 Ok(ret)
56 }
57}
58
59pub fn get_module_def(value: &QuickJsValueAdapter) -> *mut q::JSModuleDef {
61 log::trace!("get_module_def");
62 assert!(value.is_module());
63 log::trace!("get_module_def / 2");
64 unsafe { value.borrow_value().u.ptr as *mut q::JSModuleDef }
65}
66
67#[allow(dead_code)]
68pub fn set_module_loader(q_js_rt: &QuickJsRuntimeAdapter) {
69 log::trace!("setting up module loader");
70
71 let module_normalize: q::JSModuleNormalizeFunc = Some(js_module_normalize);
72 let module_loader: q::JSModuleLoaderFunc = Some(js_module_loader);
73
74 let opaque = std::ptr::null_mut();
75
76 unsafe { q::JS_SetModuleLoaderFunc(q_js_rt.runtime, module_normalize, module_loader, opaque) }
77}
78
79pub fn detect_module(source: &str) -> bool {
81 #[cfg(feature = "quickjs-ng")]
87 {
88 for line in source.lines() {
89 let trimmed = line.trim();
90 if trimmed.starts_with("import ") && !trimmed.contains("(") {
91 return true;
92 }
93 if trimmed.starts_with("export ") {
94 return true;
95 }
96 }
97 false
98 }
99
100 #[cfg(feature = "bellard")]
101 {
102 let cstr =
103 CString::new(source).expect("could not create CString due to null term in source");
104 let res = unsafe { q::JS_DetectModule(cstr.as_ptr(), source.len() as _) };
105 res != 0
107 }
108}
109
110pub unsafe fn new_module(
114 ctx: *mut q::JSContext,
115 name: &str,
116 init_func: q::JSModuleInitFunc,
117) -> Result<*mut q::JSModuleDef, JsError> {
118 let name_cstr = CString::new(name).map_err(|_e| JsError::new_str("CString failed"))?;
119 Ok(q::JS_NewCModule(ctx, name_cstr.as_ptr(), init_func))
120}
121
122pub unsafe fn set_module_export(
127 ctx: *mut q::JSContext,
128 module: *mut q::JSModuleDef,
129 export_name: &str,
130 js_val: QuickJsValueAdapter,
131) -> Result<(), JsError> {
132 let name_cstr = CString::new(export_name).map_err(|_e| JsError::new_str("CString failed"))?;
133 let res = q::JS_SetModuleExport(
134 ctx,
135 module,
136 name_cstr.as_ptr(),
137 js_val.clone_value_incr_rc(),
138 );
139 if res == 0 {
140 Ok(())
141 } else {
142 Err(JsError::new_str("JS_SetModuleExport failed"))
143 }
144}
145
146pub unsafe fn add_module_export(
150 ctx: *mut q::JSContext,
151 module: *mut q::JSModuleDef,
152 export_name: &str,
153) -> Result<(), JsError> {
154 let name_cstr = CString::new(export_name).map_err(|_e| JsError::new_str("CString failed"))?;
155 let res = q::JS_AddModuleExport(ctx, module, name_cstr.as_ptr());
156 if res == 0 {
157 Ok(())
158 } else {
159 Err(JsError::new_str("JS_SetModuleExport failed"))
160 }
161}
162
163pub unsafe fn get_module_name(
167 ctx: *mut q::JSContext,
168 module: *mut q::JSModuleDef,
169) -> Result<String, JsError> {
170 let atom_raw = q::JS_GetModuleName(ctx, module);
171 let atom_ref = JSAtomRef::new(ctx, atom_raw);
172 atoms::to_string(ctx, &atom_ref)
173}
174
175unsafe extern "C" fn js_module_normalize(
176 ctx: *mut q::JSContext,
177 module_base_name: *const ::std::os::raw::c_char,
178 module_name: *const ::std::os::raw::c_char,
179 _opaque: *mut ::std::os::raw::c_void,
180) -> *mut ::std::os::raw::c_char {
181 log::trace!("js_module_normalize called.");
182
183 let base_c = CStr::from_ptr(module_base_name);
184 let base_str = base_c
185 .to_str()
186 .expect("could not convert module_base_name to str");
187 let name_c = CStr::from_ptr(module_name);
188 let name_str = name_c
189 .to_str()
190 .expect("could not convert module_name to str");
191
192 log::trace!(
193 "js_module_normalize called. base: {}. name: {}",
194 base_str,
195 name_str
196 );
197
198 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
199 let q_ctx = q_js_rt.get_quickjs_context(ctx);
200
201 if let Some(res) = q_js_rt.with_all_module_loaders(|loader| {
202 if let Some(normalized_path) = loader.normalize_path(q_ctx, base_str, name_str) {
203 let c_absolute_path = CString::new(normalized_path.as_str()).expect("fail");
204 Some(c_absolute_path.into_raw())
205 } else {
206 None
207 }
208 }) {
209 res
210 } else {
211 q_ctx.report_ex(format!("Module {name_str} was not found").as_str());
212 ptr::null_mut()
213 }
214 })
215}
216
217unsafe extern "C" fn js_module_loader(
218 ctx: *mut q::JSContext,
219 module_name_raw: *const ::std::os::raw::c_char,
220 _opaque: *mut ::std::os::raw::c_void,
221) -> *mut q::JSModuleDef {
222 log::trace!("js_module_loader called.");
223
224 let module_name_c = CStr::from_ptr(module_name_raw);
225 let module_name = module_name_c.to_str().expect("could not get module name");
226
227 log::trace!("js_module_loader called: {}", module_name);
228
229 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
230 QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
231 if let Some(res) = q_js_rt.with_all_module_loaders(|module_loader| {
232 if module_loader.has_module(q_ctx, module_name) {
233 let mod_val_res = module_loader.load_module(q_ctx, module_name);
234 return match mod_val_res {
235 Ok(mod_val) => Some(mod_val),
236 Err(e) => {
237 let err =
238 format!("Module load failed for {module_name} because of: {e}");
239 log::error!("{}", err);
240 q_ctx.report_ex(err.as_str());
241 Some(std::ptr::null_mut())
242 }
243 };
244 }
245 None
246 }) {
247 res
248 } else {
249 std::ptr::null_mut()
250 }
251 })
252 })
253}
254
255#[cfg(test)]
256pub mod tests {
257 use crate::facades::tests::init_test_rt;
258 use crate::jsutils::Script;
259 use crate::quickjs_utils::modules::detect_module;
260 use crate::values::JsValueFacade;
261 use std::time::Duration;
262
263 #[test]
264 fn test_native_modules() {
265 let rt = init_test_rt();
266 let mres = rt.eval_module_sync(None, Script::new(
267 "test.mes",
268 "import {a, b, c} from 'greco://testmodule1';\nconsole.log('testmodule1.a = %s, testmodule1.b = %s, testmodule1.c = %s', a, b, c);",
269 ));
270 match mres {
271 Ok(_module_res) => {}
272 Err(e) => panic!("test_native_modules failed: {}", e),
273 }
274
275 let res_prom = rt.eval_sync(None, Script::new("test_mod_nat_async.es", "(import('greco://someMod').then((module) => {return {a: module.a, b: module.b, c: module.c};}));")).ok().unwrap();
276 assert!(res_prom.is_js_promise());
277
278 match res_prom {
279 JsValueFacade::JsPromise { cached_promise } => {
280 let res = cached_promise
281 .get_promise_result_sync()
282 .expect("prom timed out");
283 let obj = res.expect("prom failed");
284 assert!(obj.is_js_object());
285 match obj {
286 JsValueFacade::JsObject { cached_object } => {
287 let map = cached_object.get_object_sync().expect("esvf to map failed");
288 let a = map.get("a").expect("obj did not have a");
289 assert_eq!(a.get_i32(), 1234);
290 let b = map.get("b").expect("obj did not have b");
291 assert_eq!(b.get_i32(), 64834);
292 }
293 _ => {}
294 }
295 }
296 _ => {}
297 }
298 }
299
300 #[test]
301 fn test_detect() {
302 assert!(detect_module("import {} from 'foo.js';"));
303 assert!(detect_module("export function a(){};"));
304 assert!(!detect_module("//hi"));
305 assert!(!detect_module("let a = 1;"));
306 assert!(!detect_module("import('foo.js').then((a) = {});"));
307 }
308
309 #[test]
310 fn test_module_sandbox() {
311 log::info!("> test_module_sandbox");
312
313 let rt = init_test_rt();
314 rt.exe_rt_task_in_event_loop(|q_js_rt| {
315 let q_ctx = q_js_rt.get_main_realm();
316 let res = q_ctx.eval_module(Script::new(
317 "test1.mes",
318 "export const name = 'foobar';\nconsole.log('evalling module');",
319 ));
320
321 if res.is_err() {
322 panic!("parse module failed: {}", res.err().unwrap())
323 }
324 res.ok().expect("parse module failed");
325 });
326
327 rt.exe_rt_task_in_event_loop(|q_js_rt| {
328 let q_ctx = q_js_rt.get_main_realm();
329 let res = q_ctx.eval_module(Script::new(
330 "test2.mes",
331 "import {name} from 'test1.mes';\n\nconsole.log('imported name: ' + name);",
332 ));
333
334 if res.is_err() {
335 panic!("parse module2 failed: {}", res.err().unwrap())
336 }
337
338 res.ok().expect("parse module2 failed");
339 });
340
341 rt.exe_rt_task_in_event_loop(|q_js_rt| {
342 let q_ctx = q_js_rt.get_main_realm();
343 let res = q_ctx.eval_module(Script::new(
344 "test3.mes",
345 "import {name} from 'notfound.mes';\n\nconsole.log('imported name: ' + name);",
346 ));
347
348 assert!(res.is_err());
349 assert!(res
350 .err()
351 .unwrap()
352 .get_message()
353 .contains("Module notfound.mes was not found"));
354 });
355
356 rt.exe_rt_task_in_event_loop(|q_js_rt| {
357 let q_ctx = q_js_rt.get_main_realm();
358 let res = q_ctx.eval_module(Script::new(
359 "test4.mes",
360 "import {name} from 'invalid.mes';\n\nconsole.log('imported name: ' + name);",
361 ));
362
363 assert!(res.is_err());
364 assert!(res
365 .err()
366 .unwrap()
367 .get_message()
368 .contains("Module load failed for invalid.mes"));
369 });
370
371 rt.exe_rt_task_in_event_loop(|q_js_rt| {
372 let q_ctx = q_js_rt.get_main_realm();
373 let res = q_ctx.eval_module(Script::new(
374 "test2.mes",
375 "import {name} from 'test1.mes';\n\nconsole.log('imported name: ' + name);",
376 ));
377
378 if res.is_err() {
379 panic!("parse module2 failed: {}", res.err().unwrap())
380 }
381
382 res.ok().expect("parse module2 failed");
383 });
384
385 std::thread::sleep(Duration::from_secs(1));
386
387 log::info!("< test_module_sandbox");
388 }
389}