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