1mod compile;
2pub mod convert;
4mod droppable_value;
5pub mod value;
7
8use std::{ffi::CString, os::raw::{c_int, c_void}, sync::Mutex};
9use std::any::Any;
10use std::cell::{Cell};
11use std::ptr::{null_mut};
12use std::rc::Rc;
13use libquickjs_sys as q;
14use libquickjs_sys::{JS_EVAL_TYPE_MODULE, JSClassID};
15
16use crate::{callback::{Arguments, Callback}, console::ConsoleBackend, ContextError, ExecutionError, JsValue, ResourceValue, ValueError};
17
18use value::{JsFunction, OwnedJsObject};
19
20pub use value::{JsCompiledFunction, OwnedJsValue};
21use crate::loader::{quickjs_rs_module_loader, JsModuleLoader};
22
23#[cfg(feature = "bigint")]
26const TAG_BIG_INT: i64 = -10;
27const TAG_STRING: i64 = -7;
28const TAG_FUNCTION_BYTECODE: i64 = -2;
29const TAG_OBJECT: i64 = -1;
30const TAG_INT: i64 = 0;
31const TAG_BOOL: i64 = 1;
32const TAG_NULL: i64 = 2;
33const TAG_UNDEFINED: i64 = 3;
34pub const TAG_EXCEPTION: i64 = 6;
35const TAG_FLOAT64: i64 = 7;
36
37pub fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
39 CString::new(value).map_err(ValueError::StringWithZeroBytes)
40}
41
42pub struct ClassId {
43 id: Cell<JSClassID>,
44}
45
46pub struct ResourceObject {
47 pub data: ResourceValue,
48}
49
50unsafe impl Send for ClassId {}
51unsafe impl Sync for ClassId {}
52
53impl ClassId {
54 pub const fn new() -> Self {
55 ClassId {
56 id: Cell::new(0)
57 }
58 }
59}
60
61trait JsClass {
62 const NAME: &'static str;
63
64 fn class_id() -> Rc<ClassId>;
65
66}
67
68thread_local! {
69 static CLASS_ID: Rc<ClassId> = Rc::new(ClassId::new());
70}
71
72struct Resource;
73
74impl JsClass for Resource {
75 const NAME: &'static str = "Resource";
76
77 fn class_id() -> Rc<ClassId> {
78 CLASS_ID.with(|c| c.clone())
79 }
80}
81
82type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue;
83
84unsafe fn build_closure_trampoline<F>(
92 closure: F,
93) -> ((Box<WrappedCallback>, Box<q::JSValue>), q::JSCFunctionData)
94where
95 F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static,
96{
97 unsafe extern "C" fn trampoline<F>(
98 _ctx: *mut q::JSContext,
99 _this: q::JSValue,
100 argc: c_int,
101 argv: *mut q::JSValue,
102 _magic: c_int,
103 data: *mut q::JSValue,
104 ) -> q::JSValue
105 where
106 F: Fn(c_int, *mut q::JSValue) -> q::JSValue,
107 {
108 let closure_ptr = (*data).u.ptr;
109 let closure: &mut F = &mut *(closure_ptr as *mut F);
110 (*closure)(argc, argv)
111 }
112
113 let boxed_f = Box::new(closure);
114
115 let data = Box::new(q::JSValue {
116 u: q::JSValueUnion {
117 ptr: (&*boxed_f) as *const F as *mut c_void,
118 },
119 tag: TAG_NULL,
120 });
121
122 ((boxed_f, data), Some(trampoline::<F>))
123}
124
125pub struct OwnedValueRef<'a> {
128 context: &'a ContextWrapper,
129 value: q::JSValue,
130}
131
132impl<'a> Drop for OwnedValueRef<'a> {
133 fn drop(&mut self) {
134 unsafe {
135 q::JS_FreeValue(self.context.context, self.value);
136 }
137 }
138}
139
140impl<'a> Clone for OwnedValueRef<'a> {
141 fn clone(&self) -> Self {
142 Self::new_dup(self.context, self.value)
143 }
144}
145
146impl<'a> std::fmt::Debug for OwnedValueRef<'a> {
147 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
148 match self.value.tag {
149 TAG_EXCEPTION => write!(f, "Exception(?)"),
150 TAG_NULL => write!(f, "NULL"),
151 TAG_UNDEFINED => write!(f, "UNDEFINED"),
152 TAG_BOOL => write!(f, "Bool(?)",),
153 TAG_INT => write!(f, "Int(?)"),
154 TAG_FLOAT64 => write!(f, "Float(?)"),
155 TAG_STRING => write!(f, "String(?)"),
156 TAG_OBJECT => write!(f, "Object(?)"),
157 TAG_FUNCTION_BYTECODE => write!(f, "Bytecode(?)"),
158 _ => write!(f, "?"),
159 }
160 }
161}
162
163impl<'a> OwnedValueRef<'a> {
164 pub fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
165 Self { context, value }
166 }
167 pub fn new_dup(context: &'a ContextWrapper, value: q::JSValue) -> Self {
168 let ret = Self::new(context, value);
169 unsafe { q::JS_DupValue(ret.context.context, ret.value) };
170 ret
171 }
172
173 unsafe fn into_inner(self) -> q::JSValue {
177 let v = self.value;
178 std::mem::forget(self);
179 v
180 }
181
182 pub(crate) fn as_inner(&self) -> &q::JSValue {
184 &self.value
185 }
186
187 #[allow(dead_code)]
189 pub(crate) fn as_inner_dup(&self) -> &q::JSValue {
190 unsafe { q::JS_DupValue(self.context.context, self.value) };
191 &self.value
192 }
193
194 pub fn is_null(&self) -> bool {
195 self.value.tag == TAG_NULL
196 }
197
198 pub fn is_bool(&self) -> bool {
199 self.value.tag == TAG_BOOL
200 }
201
202 pub fn is_exception(&self) -> bool {
203 self.value.tag == TAG_EXCEPTION
204 }
205
206 pub fn is_object(&self) -> bool {
207 self.value.tag == TAG_OBJECT
208 }
209
210 pub fn is_string(&self) -> bool {
211 self.value.tag == TAG_STRING
212 }
213
214 pub fn is_compiled_function(&self) -> bool {
215 self.value.tag == TAG_FUNCTION_BYTECODE
216 }
217
218 pub fn to_string(&self) -> Result<String, ExecutionError> {
219 let value = if self.is_string() {
220 self.to_value()?
221 } else {
222 let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
223 let value = OwnedValueRef::new(self.context, raw);
224
225 if value.value.tag != TAG_STRING {
226 return Err(ExecutionError::Exception(
227 "Could not convert value to string".into(),
228 ));
229 }
230 value.to_value()?
231 };
232
233 Ok(value.as_str().unwrap().to_string())
234 }
235
236 pub fn to_value(&self) -> Result<JsValue, ValueError> {
237 self.context.to_value(&self.value)
238 }
239
240 pub fn to_bool(&self) -> Result<bool, ValueError> {
241 match self.to_value()? {
242 JsValue::Bool(b) => Ok(b),
243 _ => Err(ValueError::UnexpectedType),
244 }
245 }
246
247 #[cfg(test)]
248 pub fn get_ref_count(&self) -> i32 {
249 if self.value.tag < 0 {
250 let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader };
253 let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
254 pref.ref_count
255 } else {
256 -1
257 }
258 }
259}
260
261pub struct OwnedObjectRef<'a> {
264 value: OwnedValueRef<'a>,
265}
266
267impl<'a> OwnedObjectRef<'a> {
268 pub fn new(value: OwnedValueRef<'a>) -> Result<Self, ValueError> {
269 if value.value.tag != TAG_OBJECT {
270 Err(ValueError::Internal("Expected an object".into()))
271 } else {
272 Ok(Self { value })
273 }
274 }
275
276 fn into_value(self) -> OwnedValueRef<'a> {
277 self.value
278 }
279
280 fn property_tag(&self, name: &str) -> Result<i64, ValueError> {
282 let cname = make_cstring(name)?;
283 let raw = unsafe {
284 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
285 };
286 let t = raw.tag;
287 unsafe {
288 q::JS_FreeValue(self.value.context.context, raw);
289 }
290 Ok(t)
291 }
292
293 fn is_promise(&self) -> Result<bool, ValueError> {
296 if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT {
297 Ok(true)
298 } else {
299 Ok(false)
300 }
301 }
302
303 pub fn property(&self, name: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
304 let cname = make_cstring(name)?;
305 let raw = unsafe {
306 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
307 };
308
309 if raw.tag == TAG_EXCEPTION {
310 Err(ExecutionError::Internal(format!(
311 "Exception while getting property '{}'",
312 name
313 )))
314 } else if raw.tag == TAG_UNDEFINED {
315 Err(ExecutionError::Internal(format!(
316 "Property '{}' not found",
317 name
318 )))
319 } else {
320 Ok(OwnedValueRef::new(self.value.context, raw))
321 }
322 }
323
324 unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> {
328 let cname = make_cstring(name)?;
329 let ret = q::JS_SetPropertyStr(
330 self.value.context.context,
331 self.value.value,
332 cname.as_ptr(),
333 value,
334 );
335 if ret < 0 {
336 Err(ExecutionError::Exception("Could not set property".into()))
337 } else {
338 Ok(())
339 }
340 }
341
342 pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> {
343 let qval = self.value.context.serialize_value(value)?;
344 unsafe {
345 self.set_property_raw(name, qval.extract())?;
347 }
348 Ok(())
349 }
350}
351
352pub struct ContextWrapper {
375 runtime: *mut q::JSRuntime,
376 pub(crate) context: *mut q::JSContext,
377 callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
382 module_loader: Option<*mut Box<dyn JsModuleLoader>>,
383}
384
385impl Drop for ContextWrapper {
386 fn drop(&mut self) {
387 unsafe {
388 q::JS_FreeContext(self.context);
389 q::JS_FreeRuntime(self.runtime);
390 }
391 }
392}
393
394impl ContextWrapper {
395 pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
397 let runtime = unsafe { q::JS_NewRuntime() };
398 if runtime.is_null() {
399 return Err(ContextError::RuntimeCreationFailed);
400 }
401
402 if let Some(limit) = memory_limit {
404 unsafe {
405 q::JS_SetMemoryLimit(runtime, limit as _);
406 }
407 }
408
409 unsafe {
410 }
413
414 let context = unsafe { q::JS_NewContext(runtime) };
415 if context.is_null() {
416 unsafe {
417 q::JS_FreeRuntime(runtime);
418 }
419 return Err(ContextError::ContextCreationFailed);
420 }
421
422 unsafe {
423 }
426
427 let wrapper = Self {
430 runtime,
431 context,
432 callbacks: Mutex::new(Vec::new()),
433 module_loader: None,
434 };
435
436 Ok(wrapper)
437 }
438
439 pub fn set_module_loader(&mut self, module_loader: Box<dyn JsModuleLoader>) {
440 let module_loader= Box::new(module_loader);
441 unsafe {
442 let module_loader = Box::into_raw(module_loader);
443 self.module_loader = Some(module_loader);
444 q::JS_SetModuleLoaderFunc(self.runtime, None, Some(quickjs_rs_module_loader), module_loader as *mut c_void);
445 }
446 }
447
448 pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
450 use crate::console::Level;
451
452 self.add_callback("__console_write", move |args: Arguments| {
453 let mut args = args.into_vec();
454
455 if args.len() > 1 {
456 let level_raw = args.remove(0);
457
458 let level_opt = level_raw.as_str().and_then(|v| match v {
459 "trace" => Some(Level::Trace),
460 "debug" => Some(Level::Debug),
461 "log" => Some(Level::Log),
462 "info" => Some(Level::Info),
463 "warn" => Some(Level::Warn),
464 "error" => Some(Level::Error),
465 _ => None,
466 });
467
468 if let Some(level) = level_opt {
469 backend.log(level, args);
470 }
471 }
472 })?;
473
474 Ok(())
475 }
476
477 pub fn reset(self) -> Result<Self, ContextError> {
479 unsafe {
480 q::JS_FreeContext(self.context);
481 };
482 self.callbacks.lock().unwrap().clear();
483 let context = unsafe { q::JS_NewContext(self.runtime) };
484 if context.is_null() {
485 return Err(ContextError::ContextCreationFailed);
486 }
487
488 let mut s = self;
489 s.context = context;
490 Ok(s)
491 }
492
493 pub fn serialize_value(&self, value: JsValue) -> Result<OwnedJsValue<'_>, ExecutionError> {
494 let serialized = convert::serialize_value(self.context, value)?;
495 Ok(OwnedJsValue::new(self, serialized))
496 }
497
498 pub(crate) fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
500 convert::deserialize_value(self.context, value)
501 }
502
503 pub fn global(&self) -> Result<OwnedJsObject<'_>, ExecutionError> {
505 let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
506 let global_ref = OwnedJsValue::new(self, global_raw);
507 let global = global_ref.try_into_object()?;
508 Ok(global)
509 }
510
511 pub(crate) fn get_exception(&self) -> Option<ExecutionError> {
513 let value = unsafe {
514 let raw = q::JS_GetException(self.context);
515 OwnedJsValue::new(self, raw)
516 };
517
518 if value.is_null() {
519 None
520 } else if value.is_exception() {
521 Some(ExecutionError::Internal(
522 "Could get exception from runtime".into(),
523 ))
524 } else {
525 match value.js_to_string() {
526 Ok(strval) => {
527 if strval.contains("out of memory") {
528 Some(ExecutionError::OutOfMemory)
529 } else {
530 Some(ExecutionError::Exception(JsValue::String(strval)))
531 }
532 }
533 Err(e) => Some(e),
534 }
535 }
536 }
537
538 pub(crate) fn ensure_no_excpetion(&self) -> Result<(), ExecutionError> {
540 if let Some(e) = self.get_exception() {
541 Err(e)
542 } else {
543 Ok(())
544 }
545 }
546
547 fn resolve_value<'a>(
550 &'a self,
551 value: OwnedJsValue<'a>,
552 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
553 if value.is_exception() {
554 unsafe {
555 }
558 let err = self
559 .get_exception()
560 .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
561 Err(err)
562 } else if value.is_object() {
563 let obj = value.try_into_object()?;
564 Ok(obj.into_value())
565 } else {
566 Ok(value)
567 }
568 }
569
570 pub fn eval<'a>(&'a self, code: &str, eval_type: u32, filename: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
572 let filename_c = make_cstring(filename)?;
573 let code_c = make_cstring(code)?;
574
575 let value_raw = unsafe {
576 q::JS_Eval(
577 self.context,
578 code_c.as_ptr(),
579 code.len() as _,
580 filename_c.as_ptr(),
581 eval_type as i32,
582 )
583 };
584 let value = OwnedJsValue::new(self, value_raw);
585 self.resolve_value(value)
586 }
587
588 pub fn call_function<'a>(
619 &'a self,
620 function: JsFunction<'a>,
621 args: Vec<OwnedJsValue<'a>>,
622 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
623 let ret = function.call(args)?;
624 self.resolve_value(ret)
625 }
626
627 fn exec_callback<F>(
629 context: *mut q::JSContext,
630 argc: c_int,
631 argv: *mut q::JSValue,
632 callback: &impl Callback<F>,
633 ) -> Result<q::JSValue, ExecutionError> {
634 let result = std::panic::catch_unwind(|| {
635 let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
636
637 let args = arg_slice
638 .iter()
639 .map(|raw| convert::deserialize_value(context, raw))
640 .collect::<Result<Vec<_>, _>>()?;
641
642 match callback.call(args) {
643 Ok(Ok(result)) => {
644 let serialized = convert::serialize_value(context, result)?;
645 Ok(serialized)
646 }
647 Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))),
649 Err(e) => Err(e.into()),
650 }
651 });
652
653 match result {
654 Ok(r) => r,
655 Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
656 }
657 }
658
659 pub fn create_callback<'a, F>(
661 &'a self,
662 callback: impl Callback<F> + 'static,
663 ) -> Result<JsFunction<'a>, ExecutionError> {
664 let argcount = callback.argument_count() as i32;
665
666 let context = self.context;
667 let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
668 match Self::exec_callback(context, argc, argv, &callback) {
669 Ok(value) => value,
670 Err(e) => {
672 let js_exception_value = match e {
673 ExecutionError::Exception(e) => e,
674 other => other.to_string().into(),
675 };
676 let js_exception =
677 convert::serialize_value(context, js_exception_value).unwrap();
678 unsafe {
679 q::JS_Throw(context, js_exception);
680 }
681
682 q::JSValue {
683 u: q::JSValueUnion { int32: 0 },
684 tag: TAG_EXCEPTION,
685 }
686 }
687 }
688 };
689
690 let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
691 let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
692 self.callbacks.lock().unwrap().push(pair);
693
694 let obj = unsafe {
695 let f = q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data);
696 OwnedJsValue::new(self, f)
697 };
698
699 let f = obj.try_into_function()?;
700 Ok(f)
701 }
702
703 pub fn add_callback<'a, F>(
704 &'a self,
705 name: &str,
706 callback: impl Callback<F> + 'static,
707 ) -> Result<(), ExecutionError> {
708 let cfunc = self.create_callback(callback)?;
709 let global = self.global()?;
710 global.set_property(name, cfunc.into_value())?;
711 Ok(())
712 }
713
714 pub fn execute_pending_job(&self) -> Result<bool, ExecutionError> {
716 let mut job_ctx = null_mut();
717 let flag = unsafe {
718 q::JS_ExecutePendingJob(self.runtime, &mut job_ctx)
719 };
720 if flag < 0 {
721 let e = self.get_exception().unwrap_or_else(|| {
723 ExecutionError::Exception("Unknown exception".into())
724 });
725 return Err(e);
726 }
727 Ok(flag != 0)
728 }
729
730 pub fn execute_module(&self, module_name: &str) -> Result<(), ExecutionError> {
731 if let Some(ml) = self.module_loader {
732 unsafe {
733 let loader = &mut *ml;
734 let module = loader.load(module_name).map_err(|e| ExecutionError::Internal(format!("Fail to load module:{}", e)))?;
735 self.eval(&module, JS_EVAL_TYPE_MODULE, module_name)?;
736 Ok(())
737 }
738 } else {
739 Err(ExecutionError::Internal("Module loader is not set".to_string()))
740 }
741 }
742
743}