1mod compile;
2mod convert;
3mod droppable_value;
4mod value;
5
6use std::{
7 ffi::CString,
8 os::raw::{c_int, c_void},
9 sync::Mutex,
10};
11
12use libquickjs_sys_latest as q;
13
14use crate::{
15 callback::{Arguments, Callback},
16 console::ConsoleBackend,
17 ContextError, ExecutionError, JsValue, ValueError,
18};
19
20use value::{JsFunction, OwnedJsObject};
21
22pub use value::{JsCompiledFunction, OwnedJsValue};
23
24#[cfg(feature = "bigint")]
27const TAG_BIG_INT: i64 = -10;
28const TAG_STRING: i64 = -7;
29const TAG_FUNCTION_BYTECODE: i64 = -2;
30const TAG_OBJECT: i64 = -1;
31const TAG_INT: i64 = 0;
32const TAG_BOOL: i64 = 1;
33const TAG_NULL: i64 = 2;
34const TAG_UNDEFINED: i64 = 3;
35const TAG_EXCEPTION: i64 = 6;
36const TAG_FLOAT64: i64 = 7;
37
38fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
40 CString::new(value).map_err(ValueError::StringWithZeroBytes)
41}
42
43type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue;
44
45unsafe fn build_closure_trampoline<F>(
53 closure: F,
54) -> ((Box<WrappedCallback>, Box<q::JSValue>), q::JSCFunctionData)
55where
56 F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static,
57{
58 unsafe extern "C" fn trampoline<F>(
59 _ctx: *mut q::JSContext,
60 _this: q::JSValue,
61 argc: c_int,
62 argv: *mut q::JSValue,
63 _magic: c_int,
64 data: *mut q::JSValue,
65 ) -> q::JSValue
66 where
67 F: Fn(c_int, *mut q::JSValue) -> q::JSValue,
68 {
69 let closure_ptr = (*data).u.ptr;
70 let closure: &mut F = &mut *(closure_ptr as *mut F);
71 (*closure)(argc, argv)
72 }
73
74 let boxed_f = Box::new(closure);
75
76 let data = Box::new(q::JSValue {
77 u: q::JSValueUnion {
78 ptr: (&*boxed_f) as *const F as *mut c_void,
79 },
80 tag: TAG_NULL,
81 });
82
83 ((boxed_f, data), Some(trampoline::<F>))
84}
85
86pub struct OwnedValueRef<'a> {
89 context: &'a ContextWrapper,
90 value: q::JSValue,
91}
92
93impl<'a> Drop for OwnedValueRef<'a> {
94 fn drop(&mut self) {
95 unsafe {
96 q::JS_FreeValue(self.context.context, self.value);
97 }
98 }
99}
100
101impl<'a> Clone for OwnedValueRef<'a> {
102 fn clone(&self) -> Self {
103 Self::new_dup(self.context, self.value)
104 }
105}
106
107impl<'a> std::fmt::Debug for OwnedValueRef<'a> {
108 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
109 match self.value.tag {
110 TAG_EXCEPTION => write!(f, "Exception(?)"),
111 TAG_NULL => write!(f, "NULL"),
112 TAG_UNDEFINED => write!(f, "UNDEFINED"),
113 TAG_BOOL => write!(f, "Bool(?)",),
114 TAG_INT => write!(f, "Int(?)"),
115 TAG_FLOAT64 => write!(f, "Float(?)"),
116 TAG_STRING => write!(f, "String(?)"),
117 TAG_OBJECT => write!(f, "Object(?)"),
118 TAG_FUNCTION_BYTECODE => write!(f, "Bytecode(?)"),
119 _ => write!(f, "?"),
120 }
121 }
122}
123
124impl<'a> OwnedValueRef<'a> {
125 pub fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
126 Self { context, value }
127 }
128 pub fn new_dup(context: &'a ContextWrapper, value: q::JSValue) -> Self {
129 let ret = Self::new(context, value);
130 unsafe { q::JS_DupValue(ret.context.context, ret.value) };
131 ret
132 }
133
134 unsafe fn into_inner(self) -> q::JSValue {
138 let v = self.value;
139 std::mem::forget(self);
140 v
141 }
142
143 pub(crate) fn as_inner(&self) -> &q::JSValue {
145 &self.value
146 }
147
148 #[allow(dead_code)]
150 pub(crate) fn as_inner_dup(&self) -> &q::JSValue {
151 unsafe { q::JS_DupValue(self.context.context, self.value) };
152 &self.value
153 }
154
155 pub fn is_null(&self) -> bool {
156 self.value.tag == TAG_NULL
157 }
158
159 pub fn is_bool(&self) -> bool {
160 self.value.tag == TAG_BOOL
161 }
162
163 pub fn is_exception(&self) -> bool {
164 self.value.tag == TAG_EXCEPTION
165 }
166
167 pub fn is_object(&self) -> bool {
168 self.value.tag == TAG_OBJECT
169 }
170
171 pub fn is_string(&self) -> bool {
172 self.value.tag == TAG_STRING
173 }
174
175 pub fn is_compiled_function(&self) -> bool {
176 self.value.tag == TAG_FUNCTION_BYTECODE
177 }
178
179 pub fn to_string(&self) -> Result<String, ExecutionError> {
180 let value = if self.is_string() {
181 self.to_value()?
182 } else {
183 let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
184 let value = OwnedValueRef::new(self.context, raw);
185
186 if value.value.tag != TAG_STRING {
187 return Err(ExecutionError::Exception(
188 "Could not convert value to string".into(),
189 ));
190 }
191 value.to_value()?
192 };
193
194 Ok(value.as_str().unwrap().to_string())
195 }
196
197 pub fn to_value(&self) -> Result<JsValue, ValueError> {
198 self.context.to_value(&self.value)
199 }
200
201 pub fn to_bool(&self) -> Result<bool, ValueError> {
202 match self.to_value()? {
203 JsValue::Bool(b) => Ok(b),
204 _ => Err(ValueError::UnexpectedType),
205 }
206 }
207
208 #[cfg(test)]
209 pub fn get_ref_count(&self) -> i32 {
210 if self.value.tag < 0 {
211 let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader };
214 let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
215 pref.ref_count
216 } else {
217 -1
218 }
219 }
220}
221
222pub struct OwnedObjectRef<'a> {
225 value: OwnedValueRef<'a>,
226}
227
228impl<'a> OwnedObjectRef<'a> {
229 pub fn new(value: OwnedValueRef<'a>) -> Result<Self, ValueError> {
230 if value.value.tag != TAG_OBJECT {
231 Err(ValueError::Internal("Expected an object".into()))
232 } else {
233 Ok(Self { value })
234 }
235 }
236
237 fn into_value(self) -> OwnedValueRef<'a> {
238 self.value
239 }
240
241 fn property_tag(&self, name: &str) -> Result<i64, ValueError> {
243 let cname = make_cstring(name)?;
244 let raw = unsafe {
245 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
246 };
247 let t = raw.tag;
248 unsafe {
249 q::JS_FreeValue(self.value.context.context, raw);
250 }
251 Ok(t)
252 }
253
254 fn is_promise(&self) -> Result<bool, ValueError> {
257 if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT {
258 Ok(true)
259 } else {
260 Ok(false)
261 }
262 }
263
264 pub fn property(&self, name: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
265 let cname = make_cstring(name)?;
266 let raw = unsafe {
267 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
268 };
269
270 if raw.tag == TAG_EXCEPTION {
271 Err(ExecutionError::Internal(format!(
272 "Exception while getting property '{}'",
273 name
274 )))
275 } else if raw.tag == TAG_UNDEFINED {
276 Err(ExecutionError::Internal(format!(
277 "Property '{}' not found",
278 name
279 )))
280 } else {
281 Ok(OwnedValueRef::new(self.value.context, raw))
282 }
283 }
284
285 unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> {
289 let cname = make_cstring(name)?;
290 let ret = q::JS_SetPropertyStr(
291 self.value.context.context,
292 self.value.value,
293 cname.as_ptr(),
294 value,
295 );
296 if ret < 0 {
297 Err(ExecutionError::Exception("Could not set property".into()))
298 } else {
299 Ok(())
300 }
301 }
302
303 pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> {
304 let qval = self.value.context.serialize_value(value)?;
305 unsafe {
306 self.set_property_raw(name, qval.extract())?;
308 }
309 Ok(())
310 }
311}
312
313pub struct ContextWrapper {
336 runtime: *mut q::JSRuntime,
337 pub(crate) context: *mut q::JSContext,
338 callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
343}
344
345impl Drop for ContextWrapper {
346 fn drop(&mut self) {
347 unsafe {
348 q::JS_FreeContext(self.context);
349 q::JS_FreeRuntime(self.runtime);
350 }
351 }
352}
353
354impl ContextWrapper {
355 pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
357 let runtime = unsafe { q::JS_NewRuntime() };
358 if runtime.is_null() {
359 return Err(ContextError::RuntimeCreationFailed);
360 }
361
362 if let Some(limit) = memory_limit {
364 unsafe {
365 q::JS_SetMemoryLimit(runtime, limit as _);
366 }
367 }
368
369 let context = unsafe { q::JS_NewContext(runtime) };
370 if context.is_null() {
371 unsafe {
372 q::JS_FreeRuntime(runtime);
373 }
374 return Err(ContextError::ContextCreationFailed);
375 }
376
377 let wrapper = Self {
380 runtime,
381 context,
382 callbacks: Mutex::new(Vec::new()),
383 };
384
385 Ok(wrapper)
386 }
387
388 pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
390 use crate::console::Level;
391
392 self.add_callback("__console_write", move |args: Arguments| {
393 let mut args = args.into_vec();
394
395 if args.len() > 1 {
396 let level_raw = args.remove(0);
397
398 let level_opt = level_raw.as_str().and_then(|v| match v {
399 "trace" => Some(Level::Trace),
400 "debug" => Some(Level::Debug),
401 "log" => Some(Level::Log),
402 "info" => Some(Level::Info),
403 "warn" => Some(Level::Warn),
404 "error" => Some(Level::Error),
405 _ => None,
406 });
407
408 if let Some(level) = level_opt {
409 backend.log(level, args);
410 }
411 }
412 })?;
413
414 self.eval(
415 r#"
416 globalThis.console = {
417 trace: (...args) => {
418 globalThis.__console_write("trace", ...args);
419 },
420 debug: (...args) => {
421 globalThis.__console_write("debug", ...args);
422 },
423 log: (...args) => {
424 globalThis.__console_write("log", ...args);
425 },
426 info: (...args) => {
427 globalThis.__console_write("info", ...args);
428 },
429 warn: (...args) => {
430 globalThis.__console_write("warn", ...args);
431 },
432 error: (...args) => {
433 globalThis.__console_write("error", ...args);
434 },
435 };
436 "#,
437 )?;
438
439 Ok(())
440 }
441
442 pub fn reset(self) -> Result<Self, ContextError> {
444 unsafe {
445 q::JS_FreeContext(self.context);
446 };
447 self.callbacks.lock().unwrap().clear();
448 let context = unsafe { q::JS_NewContext(self.runtime) };
449 if context.is_null() {
450 return Err(ContextError::ContextCreationFailed);
451 }
452
453 let mut s = self;
454 s.context = context;
455 Ok(s)
456 }
457
458 pub fn serialize_value(&self, value: JsValue) -> Result<OwnedJsValue<'_>, ExecutionError> {
459 let serialized = convert::serialize_value(self.context, value)?;
460 Ok(OwnedJsValue::new(self, serialized))
461 }
462
463 pub(crate) fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
465 convert::deserialize_value(self.context, value)
466 }
467
468 pub fn global(&self) -> Result<OwnedJsObject<'_>, ExecutionError> {
470 let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
471 let global_ref = OwnedJsValue::new(self, global_raw);
472 let global = global_ref.try_into_object()?;
473 Ok(global)
474 }
475
476 pub(crate) fn get_exception(&self) -> Option<ExecutionError> {
478 let value = unsafe {
479 let raw = q::JS_GetException(self.context);
480 OwnedJsValue::new(self, raw)
481 };
482
483 if value.is_null() {
484 None
485 } else if value.is_exception() {
486 Some(ExecutionError::Internal(
487 "Could get exception from runtime".into(),
488 ))
489 } else {
490 match value.js_to_string() {
491 Ok(strval) => {
492 if strval.contains("out of memory") {
493 Some(ExecutionError::OutOfMemory)
494 } else {
495 Some(ExecutionError::Exception(JsValue::String(strval)))
496 }
497 }
498 Err(e) => Some(e),
499 }
500 }
501 }
502
503 pub(crate) fn ensure_no_excpetion(&self) -> Result<(), ExecutionError> {
505 if let Some(e) = self.get_exception() {
506 Err(e)
507 } else {
508 Ok(())
509 }
510 }
511
512 fn resolve_value<'a>(
515 &'a self,
516 value: OwnedJsValue<'a>,
517 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
518 if value.is_exception() {
519 let err = self
520 .get_exception()
521 .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
522 Err(err)
523 } else if value.is_object() {
524 let obj = value.try_into_object()?;
525 if obj.is_promise()? {
526 self.eval(
527 r#"
528 // Values:
529 // - undefined: promise not finished
530 // - false: error ocurred, __promiseError is set.
531 // - true: finished, __promiseSuccess is set.
532 var __promiseResult = 0;
533 var __promiseValue = 0;
534
535 var __resolvePromise = function(p) {
536 p
537 .then(value => {
538 __promiseResult = true;
539 __promiseValue = value;
540 })
541 .catch(e => {
542 __promiseResult = false;
543 __promiseValue = e;
544 });
545 }
546 "#,
547 )?;
548
549 let global = self.global()?;
550 let resolver = global
551 .property_require("__resolvePromise")?
552 .try_into_function()?;
553
554 resolver.call(vec![obj.into_value()])?;
557
558 loop {
559 let flag = unsafe {
560 let wrapper_mut = self as *const Self as *mut Self;
561 let ctx_mut = &mut (*wrapper_mut).context;
562 q::JS_ExecutePendingJob(self.runtime, ctx_mut)
563 };
564 if flag < 0 {
565 let e = self.get_exception().unwrap_or_else(|| {
566 ExecutionError::Exception("Unknown exception".into())
567 });
568 return Err(e);
569 }
570
571 let res_val = global.property_require("__promiseResult")?;
573 if res_val.is_bool() {
574 let ok = res_val.to_bool()?;
575 let value = global.property_require("__promiseValue")?;
576
577 if ok {
578 return self.resolve_value(value);
579 } else {
580 let err_msg = value.js_to_string()?;
581 return Err(ExecutionError::Exception(JsValue::String(err_msg)));
582 }
583 }
584 }
585 } else {
586 Ok(obj.into_value())
587 }
588 } else {
589 Ok(value)
590 }
591 }
592
593 pub fn eval<'a>(&'a self, code: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
595 let filename = "script.js";
596 let filename_c = make_cstring(filename)?;
597 let code_c = make_cstring(code)?;
598
599 let value_raw = unsafe {
600 q::JS_Eval(
601 self.context,
602 code_c.as_ptr(),
603 code.len() as _,
604 filename_c.as_ptr(),
605 q::JS_EVAL_TYPE_GLOBAL as i32,
606 )
607 };
608 let value = OwnedJsValue::new(self, value_raw);
609 self.resolve_value(value)
610 }
611
612 pub fn call_function<'a>(
643 &'a self,
644 function: JsFunction<'a>,
645 args: Vec<OwnedJsValue<'a>>,
646 ) -> Result<OwnedJsValue<'a>, ExecutionError> {
647 let ret = function.call(args)?;
648 self.resolve_value(ret)
649 }
650
651 fn exec_callback<F>(
653 context: *mut q::JSContext,
654 argc: c_int,
655 argv: *mut q::JSValue,
656 callback: &impl Callback<F>,
657 ) -> Result<q::JSValue, ExecutionError> {
658 let result = std::panic::catch_unwind(|| {
659 let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
660
661 let args = arg_slice
662 .iter()
663 .map(|raw| convert::deserialize_value(context, raw))
664 .collect::<Result<Vec<_>, _>>()?;
665
666 match callback.call(args) {
667 Ok(Ok(result)) => {
668 let serialized = convert::serialize_value(context, result)?;
669 Ok(serialized)
670 }
671 Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))),
673 Err(e) => Err(e.into()),
674 }
675 });
676
677 match result {
678 Ok(r) => r,
679 Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
680 }
681 }
682
683 pub fn create_callback<F>(
685 &self,
686 callback: impl Callback<F> + 'static,
687 ) -> Result<JsFunction<'_>, ExecutionError> {
688 let argcount = callback.argument_count() as i32;
689
690 let context = self.context;
691 let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
692 match Self::exec_callback(context, argc, argv, &callback) {
693 Ok(value) => value,
694 Err(e) => {
696 let js_exception_value = match e {
697 ExecutionError::Exception(e) => e,
698 other => other.to_string().into(),
699 };
700 let js_exception =
701 convert::serialize_value(context, js_exception_value).unwrap();
702 unsafe {
703 q::JS_Throw(context, js_exception);
704 }
705
706 q::JSValue {
707 u: q::JSValueUnion { int32: 0 },
708 tag: TAG_EXCEPTION,
709 }
710 }
711 }
712 };
713
714 let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
715 let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
716 self.callbacks.lock().unwrap().push(pair);
717
718 let obj = unsafe {
719 let f = q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data);
720 OwnedJsValue::new(self, f)
721 };
722
723 let f = obj.try_into_function()?;
724 Ok(f)
725 }
726
727 pub fn add_callback<F>(
728 &self,
729 name: &str,
730 callback: impl Callback<F> + 'static,
731 ) -> Result<(), ExecutionError> {
732 let cfunc = self.create_callback(callback)?;
733 let global = self.global()?;
734 global.set_property(name, cfunc.into_value())?;
735 Ok(())
736 }
737}