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, JS_VALUE_GET_PTR};
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 = JS_VALUE_GET_PTR(*data);
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(
134 q::JS_MKPTR(q::JS_TAG_NULL, (&*boxed_f) as *const F as *mut c_void)
135 );
136
137 ((boxed_f, data), Some(trampoline::<F>))
138}
139
140pub struct OwnedValueRef<'a> {
143 context: &'a ContextWrapper,
144 value: q::JSValue,
145}
146
147impl<'a> Drop for OwnedValueRef<'a> {
148 fn drop(&mut self) {
149 unsafe {
150 q::JS_FreeValue(self.context.context, self.value);
151 }
152 }
153}
154
155impl<'a> Clone for OwnedValueRef<'a> {
156 fn clone(&self) -> Self {
157 Self::new_dup(self.context, self.value)
158 }
159}
160
161impl<'a> std::fmt::Debug for OwnedValueRef<'a> {
162 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163 unsafe {
164 match q::JS_VALUE_GET_TAG(self.value) {
165 q::JS_TAG_EXCEPTION => write!(f, "Exception(?)"),
166 q::JS_TAG_NULL => write!(f, "NULL"),
167 q::JS_TAG_UNDEFINED => write!(f, "UNDEFINED"),
168 q::JS_TAG_BOOL => write!(f, "Bool(?)",),
169 q::JS_TAG_INT => write!(f, "Int(?)"),
170 q::JS_TAG_FLOAT64 => write!(f, "Float(?)"),
171 q::JS_TAG_STRING => write!(f, "String(?)"),
172 q::JS_TAG_OBJECT => write!(f, "Object(?)"),
173 q::JS_TAG_FUNCTION_BYTECODE => write!(f, "Bytecode(?)"),
174 _ => write!(f, "?"),
175 }
176 }
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 q::JS_IsNull(self.value)
214 }
215
216 pub fn is_bool(&self) -> bool {
217 q::JS_IsBool(self.value)
218 }
219
220 pub fn is_exception(&self) -> bool {
221 q::JS_IsException(self.value)
222 }
223
224 pub fn is_object(&self) -> bool {
225 q::JS_IsObject(self.value)
226 }
227
228 pub fn is_string(&self) -> bool {
229 q::JS_IsString(self.value)
230 }
231
232 pub fn is_compiled_function(&self) -> bool {
233 q::JS_VALUE_GET_TAG(self.value) == q::JS_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.is_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 q::JS_VALUE_GET_TAG(self.value) < 0 {
268 let ptr = unsafe { q::JS_VALUE_GET_PTR(self.value) 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.is_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 = unsafe {
305 q::JS_VALUE_GET_TAG(raw)
306 };
307 unsafe {
308 q::JS_FreeValue(self.value.context.context, raw);
309 }
310 Ok(t as i64)
311 }
312
313 fn is_promise(&self) -> Result<bool, ValueError> {
316 if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT {
317 Ok(true)
318 } else {
319 Ok(false)
320 }
321 }
322
323 pub fn property(&self, name: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
324 let cname = make_cstring(name)?;
325 let raw = unsafe {
326 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
327 };
328
329 if q::JS_IsException(raw) {
330 Err(ExecutionError::Internal(format!(
331 "Exception while getting property '{}'",
332 name
333 )))
334 } else if q::JS_IsUndefined(raw) {
335 Err(ExecutionError::Internal(format!(
336 "Property '{}' not found",
337 name
338 )))
339 } else {
340 Ok(OwnedValueRef::new(self.value.context, raw))
341 }
342 }
343
344 unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> {
348 let cname = make_cstring(name)?;
349 let ret = q::JS_SetPropertyStr(
350 self.value.context.context,
351 self.value.value,
352 cname.as_ptr(),
353 value,
354 );
355 if ret < 0 {
356 Err(ExecutionError::Exception("Could not set property".into()))
357 } else {
358 Ok(())
359 }
360 }
361
362 pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> {
363 let qval = self.value.context.serialize_value(value)?;
364 unsafe {
365 self.set_property_raw(name, qval.extract())?;
367 }
368 Ok(())
369 }
370}
371
372pub struct ContextWrapper {
395 runtime: *mut q::JSRuntime,
396 pub(crate) context: *mut q::JSContext,
397 callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
402 module_loader: Option<*mut Box<dyn JsModuleLoader>>,
403 host_promise_rejection_tracker_wrapper: Option<*mut HostPromiseRejectionTrackerWrapper>,
404}
405
406impl Drop for ContextWrapper {
407 fn drop(&mut self) {
408 unsafe {
409 {
410 if let Some(p) = self.host_promise_rejection_tracker_wrapper {
411 let _ = Box::from_raw(p);
412 }
413 }
414 q::JS_FreeContext(self.context);
415 q::JS_FreeRuntime(self.runtime);
416 }
417 }
418}
419
420impl ContextWrapper {
421 pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
423 let runtime = unsafe { q::JS_NewRuntime() };
424 if runtime.is_null() {
425 return Err(ContextError::RuntimeCreationFailed);
426 }
427
428 if let Some(limit) = memory_limit {
430 unsafe {
431 q::JS_SetMemoryLimit(runtime, limit as _);
432 }
433 }
434
435 unsafe {
436 }
439
440 let context = unsafe { q::JS_NewContext(runtime) };
441 if context.is_null() {
442 unsafe {
443 q::JS_FreeRuntime(runtime);
444 }
445 return Err(ContextError::ContextCreationFailed);
446 }
447
448 let wrapper = Self {
451 runtime,
452 context,
453 callbacks: Mutex::new(Vec::new()),
454 module_loader: None,
455 host_promise_rejection_tracker_wrapper: None,
456 };
457
458 Ok(wrapper)
459 }
460
461 pub fn set_host_promise_rejection_tracker<F: HostPromiseRejectionTracker + 'static>(&mut self, tracker: F) {
462 let tracker = HostPromiseRejectionTrackerWrapper::new(Box::new(tracker));
463 let ptr = Box::into_raw(Box::new(tracker));
464 self.host_promise_rejection_tracker_wrapper = Some(ptr);
465 unsafe {
466 q::JS_SetHostPromiseRejectionTracker(self.runtime, Some(host_promise_rejection_tracker), ptr as _);
467 }
468 }
469
470 pub fn set_module_loader(&mut self, module_loader: Box<dyn JsModuleLoader>) {
471 let module_loader= Box::new(module_loader);
472 unsafe {
473 let module_loader = Box::into_raw(module_loader);
474 self.module_loader = Some(module_loader);
475 q::JS_SetModuleLoaderFunc(self.runtime, None, Some(quickjs_rs_module_loader), module_loader as *mut c_void);
476 }
477 }
478
479 pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
481 use crate::console::Level;
482
483 self.add_callback("__console_write", move |args: Arguments| {
484 let mut args = args.into_vec();
485
486 if args.len() > 1 {
487 let level_raw = args.remove(0);
488
489 let level_opt = level_raw.as_str().and_then(|v| match v {
490 "trace" => Some(Level::Trace),
491 "debug" => Some(Level::Debug),
492 "log" => Some(Level::Log),
493 "info" => Some(Level::Info),
494 "warn" => Some(Level::Warn),
495 "error" => Some(Level::Error),
496 _ => None,
497 });
498
499 if let Some(level) = level_opt {
500 backend.log(level, args);
501 }
502 }
503 })?;
504
505 Ok(())
506 }
507
508 pub fn reset(self) -> Result<Self, ContextError> {
510 unsafe {
511 q::JS_FreeContext(self.context);
512 };
513 self.callbacks.lock().unwrap().clear();
514 let context = unsafe { q::JS_NewContext(self.runtime) };
515 if context.is_null() {
516 return Err(ContextError::ContextCreationFailed);
517 }
518
519 let mut s = self;
520 s.context = context;
521 Ok(s)
522 }
523
524 pub fn serialize_value(&self, value: JsValue) -> Result<OwnedJsValue<'_>, ExecutionError> {
525 let serialized = convert::serialize_value(self.context, value)?;
526 Ok(OwnedJsValue::new(self, serialized))
527 }
528
529 pub(crate) fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
531 convert::deserialize_value(self.context, value)
532 }
533
534 pub fn global(&self) -> Result<OwnedJsObject<'_>, ExecutionError> {
536 let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
537 let global_ref = OwnedJsValue::new(self, global_raw);
538 let global = global_ref.try_into_object()?;
539 Ok(global)
540 }
541
542 pub(crate) fn get_exception(&self) -> Option<ExecutionError> {
544 let value = unsafe {
545 let raw = q::JS_GetException(self.context);
546 OwnedJsValue::new(self, raw)
547 };
548
549 if value.is_null() {
550 None
551 } else if value.is_exception() {
552 Some(ExecutionError::Internal(
553 "Could get exception from runtime".into(),
554 ))
555 } else {
556 match value.js_to_string() {
557 Ok(strval) => {
558 if strval.contains("out of memory") {
559 Some(ExecutionError::OutOfMemory)
560 } else {
561 Some(ExecutionError::Exception(JsValue::String(strval)))
562 }
563 }
564 Err(e) => Some(e),
565 }
566 }
567 }
568
569 pub(crate) fn ensure_no_excpetion(&self) -> Result<(), ExecutionError> {
571 if let Some(e) = self.get_exception() {
572 Err(e)
573 } else {
574 Ok(())
575 }
576 }
577
578 fn resolve_value<'a>(
581 &'a self,
582 value: OwnedJsValue<'a>,
583 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
584 if value.is_exception() {
585 unsafe {
586 }
589 let err = self
590 .get_exception()
591 .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
592 Err(err)
593 } else if value.is_object() {
594 let obj = value.try_into_object()?;
595 Ok(obj.into_value())
596 } else {
597 Ok(value)
598 }
599 }
600
601 pub fn eval<'a>(&'a self, code: &str, eval_type: u32, filename: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
603 let filename_c = make_cstring(filename)?;
604 let code_c = make_cstring(code)?;
605
606 let value_raw = unsafe {
607 q::JS_Eval(
608 self.context,
609 code_c.as_ptr(),
610 code.len() as _,
611 filename_c.as_ptr(),
612 eval_type as i32,
613 )
614 };
615 let value = OwnedJsValue::new(self, value_raw);
616 self.resolve_value(value)
617 }
618
619 pub fn call_function<'a>(
650 &'a self,
651 function: JsFunction<'a>,
652 args: Vec<OwnedJsValue<'a>>,
653 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
654 let ret = function.call(args)?;
655 self.resolve_value(ret)
656 }
657
658 fn exec_callback<F>(
660 context: *mut q::JSContext,
661 argc: c_int,
662 argv: *mut q::JSValue,
663 callback: &impl Callback<F>,
664 ) -> Result<q::JSValue, ExecutionError> {
665 let result = std::panic::catch_unwind(|| {
666 let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
667
668 let mut args = Vec::with_capacity(arg_slice.len());
669 for a in arg_slice {
670 let a = deserialize_value(context, a)
671 .map_err(|e| {
672 ExecutionError::Internal(
673 format!("failed to deserialize arguments {} (zero-based) to JS value, {}", args.len(), e)
674 )
675 })?;
676 args.push(a);
677 }
678
679 match callback.call(args) {
680 Ok(Ok(result)) => {
681 let serialized = convert::serialize_value(context, result)
682 .map_err(|e| {
683 ExecutionError::Internal(format!("failed to serialize rust value to js value, {}", e))
684 })?;
685 Ok(serialized)
686 }
687 Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))),
689 Err(e) => Err(e.into()),
690 }
691 });
692
693 match result {
694 Ok(r) => r,
695 Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
696 }
697 }
698
699 pub fn create_callback<'a, F>(
701 &'a self,
702 name: &str,
703 callback: impl Callback<F> + 'static,
704 ) -> Result<JsFunction<'a>, ExecutionError> {
705 let argcount = callback.argument_count() as i32;
706
707 let context = self.context;
708 let name = name.to_string();
709 let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
710 match Self::exec_callback(context, argc, argv, &callback) {
711 Ok(value) => value,
712 Err(e) => {
714 let js_exception_value = match e {
715 ExecutionError::Exception(e) => e,
716 other => format!("Failed to call [{}], {}", &name, other.to_string()).into(),
717 };
718 let js_exception =
719 convert::serialize_value(context, js_exception_value).unwrap();
720 unsafe {
721 q::JS_Throw(context, js_exception);
722 }
723
724 q::JS_MKVAL(q::JS_TAG_EXCEPTION, 0)
725 }
726 }
727 };
728
729 let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
730 let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
731 self.callbacks.lock().unwrap().push(pair);
732
733 let obj = unsafe {
734 let f = q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data);
735 OwnedJsValue::new(self, f)
736 };
737
738 let f = obj.try_into_function()?;
739 Ok(f)
740 }
741
742 pub fn add_callback<'a, F>(
743 &'a self,
744 name: &str,
745 callback: impl Callback<F> + 'static,
746 ) -> Result<(), ExecutionError> {
747 let cfunc = self.create_callback(name, callback)?;
748 let global = self.global()?;
749 global.set_property(name, cfunc.into_value())?;
750 Ok(())
751 }
752
753 pub fn execute_pending_job(&self) -> Result<bool, ExecutionError> {
755 let mut job_ctx = null_mut();
756 let flag = unsafe {
757 q::JS_ExecutePendingJob(self.runtime, &mut job_ctx)
758 };
759 if flag < 0 {
760 let e = self.get_exception().unwrap_or_else(|| {
762 ExecutionError::Exception("Unknown exception".into())
763 });
764 return Err(e);
765 }
766 Ok(flag != 0)
767 }
768
769 pub fn execute_module(&self, module_name: &str) -> Result<(), ExecutionError> {
770 if let Some(ml) = self.module_loader {
771 unsafe {
772 let loader = &mut *ml;
773 let module = loader.load(module_name).map_err(|e| ExecutionError::Internal(format!("Fail to load module:{}", e)))?;
774 self.eval(&module, JS_EVAL_TYPE_MODULE, module_name)?;
775 Ok(())
776 }
777 } else {
778 Err(ExecutionError::Internal("Module loader is not set".to_string()))
779 }
780 }
781
782}