quickjs_rs/bindings/
mod.rs

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// JS_TAG_* constants from quickjs.
25// For some reason bindgen does not pick them up.
26#[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
38/// Helper for creating CStrings.
39fn 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
45/// Taken from: https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html
46///
47/// Create a C wrapper function for a Rust closure to enable using it as a
48/// callback function in the Quickjs runtime.
49///
50/// Both the boxed closure and the boxed data are returned and must be stored
51/// by the caller to guarantee they stay alive.
52unsafe 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
86/// OwnedValueRef wraps a Javascript value from the quickjs runtime.
87/// It prevents leaks by ensuring that the inner value is deallocated on drop.
88pub 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    /// Get the inner JSValue without freeing in drop.
135    ///
136    /// Unsafe because the caller is responsible for freeing the returned value.
137    unsafe fn into_inner(self) -> q::JSValue {
138        let v = self.value;
139        std::mem::forget(self);
140        v
141    }
142
143    /// Get the inner JSValue without increasing ref count
144    pub(crate) fn as_inner(&self) -> &q::JSValue {
145        &self.value
146    }
147
148    /// Get the inner JSValue while increasing ref count, this is handy when you pass a JSValue to a new owner like e.g. setProperty
149    #[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            // This transmute is OK since if tag < 0, the union will be a refcount
212            // pointer.
213            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
222/// Wraps an object from the quickjs runtime.
223/// Provides convenience property accessors.
224pub 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    /// Get the tag of a property.
242    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    /// Determine if the object is a promise by checking the presence of
255    /// a 'then' and a 'catch' property.
256    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    // Set a property on an object.
286    // NOTE: this method takes ownership of the `JSValue`, so it must not be
287    // freed later.
288    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            // set_property_raw takes ownership, so we must prevent a free.
307            self.set_property_raw(name, qval.extract())?;
308        }
309        Ok(())
310    }
311}
312
313/*
314type ModuleInit = dyn Fn(*mut q::JSContext, *mut q::JSModuleDef);
315
316thread_local! {
317    static NATIVE_MODULE_INIT: RefCell<Option<Box<ModuleInit>>> = RefCell::new(None);
318}
319
320unsafe extern "C" fn native_module_init(
321    ctx: *mut q::JSContext,
322    m: *mut q::JSModuleDef,
323) -> ::std::os::raw::c_int {
324    NATIVE_MODULE_INIT.with(|init| {
325        let init = init.replace(None).unwrap();
326        init(ctx, m);
327    });
328    0
329}
330*/
331
332/// Wraps a quickjs context.
333///
334/// Cleanup of the context happens in drop.
335pub struct ContextWrapper {
336    runtime: *mut q::JSRuntime,
337    pub(crate) context: *mut q::JSContext,
338    /// Stores callback closures and quickjs data pointers.
339    /// This array is write-only and only exists to ensure the lifetime of
340    /// the closure.
341    // A Mutex is used over a RefCell because it needs to be unwind-safe.
342    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    /// Initialize a wrapper by creating a JSRuntime and JSContext.
356    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        // Configure memory limit if specified.
363        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        // Initialize the promise resolver helper code.
378        // This code is needed by Self::resolve_value
379        let wrapper = Self {
380            runtime,
381            context,
382            callbacks: Mutex::new(Vec::new()),
383        };
384
385        Ok(wrapper)
386    }
387
388    // See console standard: https://console.spec.whatwg.org
389    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    /// Reset the wrapper by creating a new context.
443    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    // Deserialize a quickjs runtime value into a Rust value.
464    pub(crate) fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
465        convert::deserialize_value(self.context, value)
466    }
467
468    /// Get the global object.
469    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    /// Get the last exception from the runtime, and if present, convert it to a ExceptionError.
477    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    /// Returns `Result::Err` when an error ocurred.
504    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    /// If the given value is a promise, run the event loop until it is
513    /// resolved, and return the final value.
514    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                // Call the resolver code that sets the result values once
555                // the promise resolves.
556                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                    // Check if promise is finished.
572                    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    /// Evaluate javascript code.
594    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    /*
613    /// Call a constructor function.
614    fn call_constructor<'a>(
615        &'a self,
616        function: OwnedJsValue<'a>,
617        args: Vec<OwnedJsValue<'a>>,
618    ) -> Result<OwnedJsValue<'a>, ExecutionError> {
619        let mut qargs = args.iter().map(|arg| arg.value).collect::<Vec<_>>();
620
621        let value_raw = unsafe {
622            q::JS_CallConstructor(
623                self.context,
624                function.value,
625                qargs.len() as i32,
626                qargs.as_mut_ptr(),
627            )
628        };
629        let value = OwnedJsValue::new(self, value_raw);
630        if value.is_exception() {
631            let err = self
632                .get_exception()
633                .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
634            Err(err)
635        } else {
636            Ok(value)
637        }
638    }
639    */
640
641    /// Call a JS function with the given arguments.
642    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    /// Helper for executing a callback closure.
652    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                // TODO: better error reporting.
672                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    /// Add a global JS function that is backed by a Rust function or closure.
684    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                // TODO: better error reporting.
695                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}