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 anyhow::Context;
14use libquickjs_sys as q;
15use libquickjs_sys::{JS_EVAL_TYPE_MODULE, JSClassID, JSContext, JSValue};
16
17use crate::{callback::{Arguments, Callback}, console::ConsoleBackend, ContextError, ExecutionError, JsValue, ResourceValue, ValueError};
18
19use value::{JsFunction, OwnedJsObject};
20
21pub use value::{JsCompiledFunction, OwnedJsValue};
22use crate::bindings::convert::deserialize_value;
23use crate::exception::{HostPromiseRejectionTracker, HostPromiseRejectionTrackerWrapper};
24use crate::loader::{quickjs_rs_module_loader, JsModuleLoader};
25
26#[cfg(feature = "bigint")]
29const TAG_BIG_INT: i64 = -10;
30const TAG_STRING: i64 = -7;
31const TAG_FUNCTION_BYTECODE: i64 = -2;
32const TAG_OBJECT: i64 = -1;
33const TAG_INT: i64 = 0;
34const TAG_BOOL: i64 = 1;
35const TAG_NULL: i64 = 2;
36const TAG_UNDEFINED: i64 = 3;
37pub const TAG_EXCEPTION: i64 = 6;
38const TAG_FLOAT64: i64 = 7;
39
40extern "C" fn host_promise_rejection_tracker(
41 ctx: *mut JSContext,
42 promise: JSValue,
43 reason: JSValue,
44 is_handled: ::std::os::raw::c_int,
45 opaque: *mut ::std::os::raw::c_void,
46) {
47 let promise = deserialize_value(ctx, &promise).unwrap();
48 let reason = deserialize_value(ctx, &reason).unwrap();
49 let is_handled = is_handled != 0;
50 let mut opaque = opaque as *mut HostPromiseRejectionTrackerWrapper;
51 unsafe {
52 (*opaque).tracker.track_promise_rejection(promise, reason, is_handled);
53 }
54}
55
56pub fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
58 CString::new(value).map_err(ValueError::StringWithZeroBytes)
59}
60
61pub struct ClassId {
62 id: Cell<JSClassID>,
63}
64
65pub struct ResourceObject {
66 pub data: ResourceValue,
67}
68
69unsafe impl Send for ClassId {}
70unsafe impl Sync for ClassId {}
71
72impl ClassId {
73 pub const fn new() -> Self {
74 ClassId {
75 id: Cell::new(0)
76 }
77 }
78}
79
80trait JsClass {
81 const NAME: &'static str;
82
83 fn class_id() -> Rc<ClassId>;
84
85}
86
87thread_local! {
88 static CLASS_ID: Rc<ClassId> = Rc::new(ClassId::new());
89}
90
91struct Resource;
92
93impl JsClass for Resource {
94 const NAME: &'static str = "Resource";
95
96 fn class_id() -> Rc<ClassId> {
97 CLASS_ID.with(|c| c.clone())
98 }
99}
100
101type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue;
102
103unsafe fn build_closure_trampoline<F>(
111 closure: F,
112) -> ((Box<WrappedCallback>, Box<q::JSValue>), q::JSCFunctionData)
113where
114 F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static,
115{
116 unsafe extern "C" fn trampoline<F>(
117 _ctx: *mut q::JSContext,
118 _this: q::JSValue,
119 argc: c_int,
120 argv: *mut q::JSValue,
121 _magic: c_int,
122 data: *mut q::JSValue,
123 ) -> q::JSValue
124 where
125 F: Fn(c_int, *mut q::JSValue) -> q::JSValue,
126 {
127 let closure_ptr = (*data).u.ptr;
128 let closure: &mut F = &mut *(closure_ptr as *mut F);
129 (*closure)(argc, argv)
130 }
131
132 let boxed_f = Box::new(closure);
133
134 let data = Box::new(q::JSValue {
135 u: q::JSValueUnion {
136 ptr: (&*boxed_f) as *const F as *mut c_void,
137 },
138 tag: TAG_NULL,
139 });
140
141 ((boxed_f, data), Some(trampoline::<F>))
142}
143
144pub struct OwnedValueRef<'a> {
147 context: &'a ContextWrapper,
148 value: q::JSValue,
149}
150
151impl<'a> Drop for OwnedValueRef<'a> {
152 fn drop(&mut self) {
153 unsafe {
154 q::JS_FreeValue(self.context.context, self.value);
155 }
156 }
157}
158
159impl<'a> Clone for OwnedValueRef<'a> {
160 fn clone(&self) -> Self {
161 Self::new_dup(self.context, self.value)
162 }
163}
164
165impl<'a> std::fmt::Debug for OwnedValueRef<'a> {
166 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
167 match self.value.tag {
168 TAG_EXCEPTION => write!(f, "Exception(?)"),
169 TAG_NULL => write!(f, "NULL"),
170 TAG_UNDEFINED => write!(f, "UNDEFINED"),
171 TAG_BOOL => write!(f, "Bool(?)",),
172 TAG_INT => write!(f, "Int(?)"),
173 TAG_FLOAT64 => write!(f, "Float(?)"),
174 TAG_STRING => write!(f, "String(?)"),
175 TAG_OBJECT => write!(f, "Object(?)"),
176 TAG_FUNCTION_BYTECODE => write!(f, "Bytecode(?)"),
177 _ => write!(f, "?"),
178 }
179 }
180}
181
182impl<'a> OwnedValueRef<'a> {
183 pub fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
184 Self { context, value }
185 }
186 pub fn new_dup(context: &'a ContextWrapper, value: q::JSValue) -> Self {
187 let ret = Self::new(context, value);
188 unsafe { q::JS_DupValue(ret.context.context, ret.value) };
189 ret
190 }
191
192 unsafe fn into_inner(self) -> q::JSValue {
196 let v = self.value;
197 std::mem::forget(self);
198 v
199 }
200
201 pub(crate) fn as_inner(&self) -> &q::JSValue {
203 &self.value
204 }
205
206 #[allow(dead_code)]
208 pub(crate) fn as_inner_dup(&self) -> &q::JSValue {
209 unsafe { q::JS_DupValue(self.context.context, self.value) };
210 &self.value
211 }
212
213 pub fn is_null(&self) -> bool {
214 self.value.tag == TAG_NULL
215 }
216
217 pub fn is_bool(&self) -> bool {
218 self.value.tag == TAG_BOOL
219 }
220
221 pub fn is_exception(&self) -> bool {
222 self.value.tag == TAG_EXCEPTION
223 }
224
225 pub fn is_object(&self) -> bool {
226 self.value.tag == TAG_OBJECT
227 }
228
229 pub fn is_string(&self) -> bool {
230 self.value.tag == TAG_STRING
231 }
232
233 pub fn is_compiled_function(&self) -> bool {
234 self.value.tag == TAG_FUNCTION_BYTECODE
235 }
236
237 pub fn to_string(&self) -> Result<String, ExecutionError> {
238 let value = if self.is_string() {
239 self.to_value()?
240 } else {
241 let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
242 let value = OwnedValueRef::new(self.context, raw);
243
244 if value.value.tag != TAG_STRING {
245 return Err(ExecutionError::Exception(
246 "Could not convert value to string".into(),
247 ));
248 }
249 value.to_value()?
250 };
251
252 Ok(value.as_str().unwrap().to_string())
253 }
254
255 pub fn to_value(&self) -> Result<JsValue, ValueError> {
256 self.context.to_value(&self.value)
257 }
258
259 pub fn to_bool(&self) -> Result<bool, ValueError> {
260 match self.to_value()? {
261 JsValue::Bool(b) => Ok(b),
262 _ => Err(ValueError::UnexpectedType),
263 }
264 }
265
266 #[cfg(test)]
267 pub fn get_ref_count(&self) -> i32 {
268 if self.value.tag < 0 {
269 let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader };
272 let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
273 pref.ref_count
274 } else {
275 -1
276 }
277 }
278}
279
280pub struct OwnedObjectRef<'a> {
283 value: OwnedValueRef<'a>,
284}
285
286impl<'a> OwnedObjectRef<'a> {
287 pub fn new(value: OwnedValueRef<'a>) -> Result<Self, ValueError> {
288 if value.value.tag != TAG_OBJECT {
289 Err(ValueError::Internal("Expected an object".into()))
290 } else {
291 Ok(Self { value })
292 }
293 }
294
295 fn into_value(self) -> OwnedValueRef<'a> {
296 self.value
297 }
298
299 fn property_tag(&self, name: &str) -> Result<i64, ValueError> {
301 let cname = make_cstring(name)?;
302 let raw = unsafe {
303 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
304 };
305 let t = raw.tag;
306 unsafe {
307 q::JS_FreeValue(self.value.context.context, raw);
308 }
309 Ok(t)
310 }
311
312 fn is_promise(&self) -> Result<bool, ValueError> {
315 if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT {
316 Ok(true)
317 } else {
318 Ok(false)
319 }
320 }
321
322 pub fn property(&self, name: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
323 let cname = make_cstring(name)?;
324 let raw = unsafe {
325 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
326 };
327
328 if raw.tag == TAG_EXCEPTION {
329 Err(ExecutionError::Internal(format!(
330 "Exception while getting property '{}'",
331 name
332 )))
333 } else if raw.tag == TAG_UNDEFINED {
334 Err(ExecutionError::Internal(format!(
335 "Property '{}' not found",
336 name
337 )))
338 } else {
339 Ok(OwnedValueRef::new(self.value.context, raw))
340 }
341 }
342
343 unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> {
347 let cname = make_cstring(name)?;
348 let ret = q::JS_SetPropertyStr(
349 self.value.context.context,
350 self.value.value,
351 cname.as_ptr(),
352 value,
353 );
354 if ret < 0 {
355 Err(ExecutionError::Exception("Could not set property".into()))
356 } else {
357 Ok(())
358 }
359 }
360
361 pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> {
362 let qval = self.value.context.serialize_value(value)?;
363 unsafe {
364 self.set_property_raw(name, qval.extract())?;
366 }
367 Ok(())
368 }
369}
370
371pub struct ContextWrapper {
394 runtime: *mut q::JSRuntime,
395 pub(crate) context: *mut q::JSContext,
396 callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
401 module_loader: Option<*mut Box<dyn JsModuleLoader>>,
402 host_promise_rejection_tracker_wrapper: Option<*mut HostPromiseRejectionTrackerWrapper>,
403}
404
405impl Drop for ContextWrapper {
406 fn drop(&mut self) {
407 unsafe {
408 {
409 if let Some(p) = self.host_promise_rejection_tracker_wrapper {
410 let _ = Box::from_raw(p);
411 }
412 }
413 q::JS_FreeContext(self.context);
414 q::JS_FreeRuntime(self.runtime);
415 }
416 }
417}
418
419impl ContextWrapper {
420 pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
422 let runtime = unsafe { q::JS_NewRuntime() };
423 if runtime.is_null() {
424 return Err(ContextError::RuntimeCreationFailed);
425 }
426
427 if let Some(limit) = memory_limit {
429 unsafe {
430 q::JS_SetMemoryLimit(runtime, limit as _);
431 }
432 }
433
434 unsafe {
435 }
438
439 let context = unsafe { q::JS_NewContext(runtime) };
440 if context.is_null() {
441 unsafe {
442 q::JS_FreeRuntime(runtime);
443 }
444 return Err(ContextError::ContextCreationFailed);
445 }
446
447 let wrapper = Self {
450 runtime,
451 context,
452 callbacks: Mutex::new(Vec::new()),
453 module_loader: None,
454 host_promise_rejection_tracker_wrapper: None,
455 };
456
457 Ok(wrapper)
458 }
459
460 pub fn set_host_promise_rejection_tracker<F: HostPromiseRejectionTracker + 'static>(&mut self, tracker: F) {
461 let tracker = HostPromiseRejectionTrackerWrapper::new(Box::new(tracker));
462 let ptr = Box::into_raw(Box::new(tracker));
463 self.host_promise_rejection_tracker_wrapper = Some(ptr);
464 unsafe {
465 q::JS_SetHostPromiseRejectionTracker(self.runtime, Some(host_promise_rejection_tracker), ptr as _);
466 }
467 }
468
469 pub fn set_module_loader(&mut self, module_loader: Box<dyn JsModuleLoader>) {
470 let module_loader= Box::new(module_loader);
471 unsafe {
472 let module_loader = Box::into_raw(module_loader);
473 self.module_loader = Some(module_loader);
474 q::JS_SetModuleLoaderFunc(self.runtime, None, Some(quickjs_rs_module_loader), module_loader as *mut c_void);
475 }
476 }
477
478 pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
480 use crate::console::Level;
481
482 self.add_callback("__console_write", move |args: Arguments| {
483 let mut args = args.into_vec();
484
485 if args.len() > 1 {
486 let level_raw = args.remove(0);
487
488 let level_opt = level_raw.as_str().and_then(|v| match v {
489 "trace" => Some(Level::Trace),
490 "debug" => Some(Level::Debug),
491 "log" => Some(Level::Log),
492 "info" => Some(Level::Info),
493 "warn" => Some(Level::Warn),
494 "error" => Some(Level::Error),
495 _ => None,
496 });
497
498 if let Some(level) = level_opt {
499 backend.log(level, args);
500 }
501 }
502 })?;
503
504 Ok(())
505 }
506
507 pub fn reset(self) -> Result<Self, ContextError> {
509 unsafe {
510 q::JS_FreeContext(self.context);
511 };
512 self.callbacks.lock().unwrap().clear();
513 let context = unsafe { q::JS_NewContext(self.runtime) };
514 if context.is_null() {
515 return Err(ContextError::ContextCreationFailed);
516 }
517
518 let mut s = self;
519 s.context = context;
520 Ok(s)
521 }
522
523 pub fn serialize_value(&self, value: JsValue) -> Result<OwnedJsValue<'_>, ExecutionError> {
524 let serialized = convert::serialize_value(self.context, value)?;
525 Ok(OwnedJsValue::new(self, serialized))
526 }
527
528 pub(crate) fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
530 convert::deserialize_value(self.context, value)
531 }
532
533 pub fn global(&self) -> Result<OwnedJsObject<'_>, ExecutionError> {
535 let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
536 let global_ref = OwnedJsValue::new(self, global_raw);
537 let global = global_ref.try_into_object()?;
538 Ok(global)
539 }
540
541 pub(crate) fn get_exception(&self) -> Option<ExecutionError> {
543 let value = unsafe {
544 let raw = q::JS_GetException(self.context);
545 OwnedJsValue::new(self, raw)
546 };
547
548 if value.is_null() {
549 None
550 } else if value.is_exception() {
551 Some(ExecutionError::Internal(
552 "Could get exception from runtime".into(),
553 ))
554 } else {
555 match value.js_to_string() {
556 Ok(strval) => {
557 if strval.contains("out of memory") {
558 Some(ExecutionError::OutOfMemory)
559 } else {
560 Some(ExecutionError::Exception(JsValue::String(strval)))
561 }
562 }
563 Err(e) => Some(e),
564 }
565 }
566 }
567
568 pub(crate) fn ensure_no_excpetion(&self) -> Result<(), ExecutionError> {
570 if let Some(e) = self.get_exception() {
571 Err(e)
572 } else {
573 Ok(())
574 }
575 }
576
577 fn resolve_value<'a>(
580 &'a self,
581 value: OwnedJsValue<'a>,
582 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
583 if value.is_exception() {
584 unsafe {
585 }
588 let err = self
589 .get_exception()
590 .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
591 Err(err)
592 } else if value.is_object() {
593 let obj = value.try_into_object()?;
594 Ok(obj.into_value())
595 } else {
596 Ok(value)
597 }
598 }
599
600 pub fn eval<'a>(&'a self, code: &str, eval_type: u32, filename: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
602 let filename_c = make_cstring(filename)?;
603 let code_c = make_cstring(code)?;
604
605 let value_raw = unsafe {
606 q::JS_Eval(
607 self.context,
608 code_c.as_ptr(),
609 code.len() as _,
610 filename_c.as_ptr(),
611 eval_type as i32,
612 )
613 };
614 let value = OwnedJsValue::new(self, value_raw);
615 self.resolve_value(value)
616 }
617
618 pub fn call_function<'a>(
649 &'a self,
650 function: JsFunction<'a>,
651 args: Vec<OwnedJsValue<'a>>,
652 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
653 let ret = function.call(args)?;
654 self.resolve_value(ret)
655 }
656
657 fn exec_callback<F>(
659 context: *mut q::JSContext,
660 argc: c_int,
661 argv: *mut q::JSValue,
662 callback: &impl Callback<F>,
663 ) -> Result<q::JSValue, ExecutionError> {
664 let result = std::panic::catch_unwind(|| {
665 let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
666
667 let mut args = Vec::with_capacity(arg_slice.len());
668 for a in arg_slice {
669 let a = deserialize_value(context, a)
670 .map_err(|e| {
671 ExecutionError::Internal(
672 format!("failed to deserialize arguments {} (zero-based) to JS value, {}", args.len(), e)
673 )
674 })?;
675 args.push(a);
676 }
677
678 match callback.call(args) {
679 Ok(Ok(result)) => {
680 let serialized = convert::serialize_value(context, result)
681 .map_err(|e| {
682 ExecutionError::Internal(format!("failed to serialize rust value to js value, {}", e))
683 })?;
684 Ok(serialized)
685 }
686 Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))),
688 Err(e) => Err(e.into()),
689 }
690 });
691
692 match result {
693 Ok(r) => r,
694 Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
695 }
696 }
697
698 pub fn create_callback<'a, F>(
700 &'a self,
701 name: &str,
702 callback: impl Callback<F> + 'static,
703 ) -> Result<JsFunction<'a>, ExecutionError> {
704 let argcount = callback.argument_count() as i32;
705
706 let context = self.context;
707 let name = name.to_string();
708 let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
709 match Self::exec_callback(context, argc, argv, &callback) {
710 Ok(value) => value,
711 Err(e) => {
713 let js_exception_value = match e {
714 ExecutionError::Exception(e) => e,
715 other => format!("Failed to call [{}], {}", &name, other.to_string()).into(),
716 };
717 let js_exception =
718 convert::serialize_value(context, js_exception_value).unwrap();
719 unsafe {
720 q::JS_Throw(context, js_exception);
721 }
722
723 q::JSValue {
724 u: q::JSValueUnion { int32: 0 },
725 tag: TAG_EXCEPTION,
726 }
727 }
728 }
729 };
730
731 let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
732 let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
733 self.callbacks.lock().unwrap().push(pair);
734
735 let obj = unsafe {
736 let f = q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data);
737 OwnedJsValue::new(self, f)
738 };
739
740 let f = obj.try_into_function()?;
741 Ok(f)
742 }
743
744 pub fn add_callback<'a, F>(
745 &'a self,
746 name: &str,
747 callback: impl Callback<F> + 'static,
748 ) -> Result<(), ExecutionError> {
749 let cfunc = self.create_callback(name, callback)?;
750 let global = self.global()?;
751 global.set_property(name, cfunc.into_value())?;
752 Ok(())
753 }
754
755 pub fn execute_pending_job(&self) -> Result<bool, ExecutionError> {
757 let mut job_ctx = null_mut();
758 let flag = unsafe {
759 q::JS_ExecutePendingJob(self.runtime, &mut job_ctx)
760 };
761 if flag < 0 {
762 let e = self.get_exception().unwrap_or_else(|| {
764 ExecutionError::Exception("Unknown exception".into())
765 });
766 return Err(e);
767 }
768 Ok(flag != 0)
769 }
770
771 pub fn execute_module(&self, module_name: &str) -> Result<(), ExecutionError> {
772 if let Some(ml) = self.module_loader {
773 unsafe {
774 let loader = &mut *ml;
775 let module = loader.load(module_name).map_err(|e| ExecutionError::Internal(format!("Fail to load module:{}", e)))?;
776 self.eval(&module, JS_EVAL_TYPE_MODULE, module_name)?;
777 Ok(())
778 }
779 } else {
780 Err(ExecutionError::Internal("Module loader is not set".to_string()))
781 }
782 }
783
784}