diff --git a/justfile b/justfile index e54c7a4..4e6a84d 100644 --- a/justfile +++ b/justfile @@ -14,7 +14,7 @@ download-cleanup: find "{{embed_dir}}" -type f | grep test | xargs rm generate-bindings: - (cd libquickjs-sys; bindgen wrapper.h -o embed/bindings.rs -- -I ./embed) + (cd libquickjs-sys; bindgen wrapper.h -o src/bindings.rs -- -I ./embed) # Update VERSION in README sed -i "s/**Embedded VERSION: .*/**Embedded VERSION: $(cat ./libquickjs-sys/embed/quickjs/VERSION)**/" ./libquickjs-sys/README.md diff --git a/libquickjs-sys/Cargo.toml b/libquickjs-sys/Cargo.toml index be4905d..6bd99d3 100644 --- a/libquickjs-sys/Cargo.toml +++ b/libquickjs-sys/Cargo.toml @@ -17,11 +17,13 @@ build = "build.rs" bundled = ["copy_dir"] patched = [] bignum = [] -default = ["bundled"] +pic = [] +debug = [] +default = ["bundled","pic","debug"] system = ["bindgen"] [build-dependencies] bindgen = { version = "0.51.0", optional = true } copy_dir = { version = "0.1.2", optional = true } - +regex = "1.3.1" diff --git a/libquickjs-sys/README.md b/libquickjs-sys/README.md index a8d2b35..0389e6f 100644 --- a/libquickjs-sys/README.md +++ b/libquickjs-sys/README.md @@ -25,7 +25,7 @@ QuickJS sources and a pre-generated `bindings.rs` are included in the repo. They are used if the `embedded` feature is enabled. -To updat the bindings, follow these steps: +To update the bindings, follow these steps: * (Install [just](https://github.com/casey/just)) * Update the download URL in ./justfile diff --git a/libquickjs-sys/build.rs b/libquickjs-sys/build.rs index 8b08f6f..127f814 100644 --- a/libquickjs-sys/build.rs +++ b/libquickjs-sys/build.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; -use std::env; +use std::{env, fs}; + +use regex::Regex; fn exists(path: impl AsRef) -> bool { PathBuf::from(path.as_ref()).exists() @@ -70,6 +72,7 @@ fn main() { apply_patches(&code_dir); eprintln!("Compiling quickjs..."); + patch_makefile(code_dir.join("Makefile").as_path()); std::process::Command::new("make") .arg(format!("lib{}.a", LIB_NAME)) .current_dir(&code_dir) @@ -78,9 +81,6 @@ fn main() { .wait() .expect("Could not compile quickjs"); - std::fs::copy("./embed/bindings.rs", out_path.join("bindings.rs")) - .expect("Could not copy bindings.rs"); - // Instruct cargo to statically link quickjs. println!( "cargo:rustc-link-search=native={}", @@ -89,6 +89,33 @@ fn main() { println!("cargo:rustc-link-lib=static={}", LIB_NAME); } +fn patch_makefile(makefile: &Path) { + let content = fs::read_to_string(makefile).unwrap(); + + let content = if cfg!(feature = "debug") { + Regex::new("CFLAGS_OPT=(.*) -O2") + .unwrap() + .replace_all(&content, "CFLAGS_OPT=$1 -O0 -g") + } else { + content.into() + }; + + let content = Regex::new("CROSS_PREFIX=") + .unwrap() + .replace_all(&content, "CROSS_PREFIX?="); + + let content = if cfg!(feature = "pic") { + content + .replace("CFLAGS+=$(DEFINES)\n", "CFLAGS+=$(DEFINES) -fPIC\n") + .into() + } else { + content + }; + + fs::rename(makefile, makefile.with_extension("bak")).unwrap(); + fs::write(makefile, content.as_bytes()).unwrap(); +} + #[cfg(feature = "patched")] fn apply_patches(code_dir: &PathBuf) { use std::fs; diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index 2e4e65c..c402fe2 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -14888,6 +14888,7 @@ static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + b->stack_size); + if (js_check_stack_overflow(ctx, alloca_size)) return JS_ThrowStackOverflow(ctx); diff --git a/libquickjs-sys/embed/bindings.rs b/libquickjs-sys/src/bindings.rs similarity index 64% rename from libquickjs-sys/embed/bindings.rs rename to libquickjs-sys/src/bindings.rs index 8c48dd7..dcba867 100644 --- a/libquickjs-sys/embed/bindings.rs +++ b/libquickjs-sys/src/bindings.rs @@ -161,6 +161,82 @@ pub const JS_DEF_PROP_DOUBLE: u32 = 6; pub const JS_DEF_PROP_UNDEFINED: u32 = 7; pub const JS_DEF_OBJECT: u32 = 8; pub const JS_DEF_ALIAS: u32 = 9; +pub const _STDLIB_H: u32 = 1; +pub const WNOHANG: u32 = 1; +pub const WUNTRACED: u32 = 2; +pub const WSTOPPED: u32 = 2; +pub const WEXITED: u32 = 4; +pub const WCONTINUED: u32 = 8; +pub const WNOWAIT: u32 = 16777216; +pub const __WNOTHREAD: u32 = 536870912; +pub const __WALL: u32 = 1073741824; +pub const __WCLONE: u32 = 2147483648; +pub const __W_CONTINUED: u32 = 65535; +pub const __WCOREFLAG: u32 = 128; +pub const __HAVE_FLOAT128: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT128: u32 = 0; +pub const __HAVE_FLOAT64X: u32 = 1; +pub const __HAVE_FLOAT64X_LONG_DOUBLE: u32 = 1; +pub const __HAVE_FLOAT16: u32 = 0; +pub const __HAVE_FLOAT32: u32 = 1; +pub const __HAVE_FLOAT64: u32 = 1; +pub const __HAVE_FLOAT32X: u32 = 1; +pub const __HAVE_FLOAT128X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT16: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT32: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT64: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT32X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT64X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT128X: u32 = 0; +pub const __HAVE_FLOATN_NOT_TYPEDEF: u32 = 0; +pub const __ldiv_t_defined: u32 = 1; +pub const __lldiv_t_defined: u32 = 1; +pub const RAND_MAX: u32 = 2147483647; +pub const EXIT_FAILURE: u32 = 1; +pub const EXIT_SUCCESS: u32 = 0; +pub const _SYS_TYPES_H: u32 = 1; +pub const __clock_t_defined: u32 = 1; +pub const __clockid_t_defined: u32 = 1; +pub const __time_t_defined: u32 = 1; +pub const __timer_t_defined: u32 = 1; +pub const __BIT_TYPES_DEFINED__: u32 = 1; +pub const _ENDIAN_H: u32 = 1; +pub const __LITTLE_ENDIAN: u32 = 1234; +pub const __BIG_ENDIAN: u32 = 4321; +pub const __PDP_ENDIAN: u32 = 3412; +pub const __BYTE_ORDER: u32 = 1234; +pub const __FLOAT_WORD_ORDER: u32 = 1234; +pub const LITTLE_ENDIAN: u32 = 1234; +pub const BIG_ENDIAN: u32 = 4321; +pub const PDP_ENDIAN: u32 = 3412; +pub const BYTE_ORDER: u32 = 1234; +pub const _BITS_BYTESWAP_H: u32 = 1; +pub const _BITS_UINTN_IDENTITY_H: u32 = 1; +pub const _SYS_SELECT_H: u32 = 1; +pub const __FD_ZERO_STOS: &'static [u8; 6usize] = b"stosq\0"; +pub const __sigset_t_defined: u32 = 1; +pub const __timeval_defined: u32 = 1; +pub const _STRUCT_TIMESPEC: u32 = 1; +pub const FD_SETSIZE: u32 = 1024; +pub const _BITS_PTHREADTYPES_COMMON_H: u32 = 1; +pub const _THREAD_SHARED_TYPES_H: u32 = 1; +pub const _BITS_PTHREADTYPES_ARCH_H: u32 = 1; +pub const __SIZEOF_PTHREAD_MUTEX_T: u32 = 40; +pub const __SIZEOF_PTHREAD_ATTR_T: u32 = 56; +pub const __SIZEOF_PTHREAD_RWLOCK_T: u32 = 56; +pub const __SIZEOF_PTHREAD_BARRIER_T: u32 = 32; +pub const __SIZEOF_PTHREAD_MUTEXATTR_T: u32 = 4; +pub const __SIZEOF_PTHREAD_COND_T: u32 = 48; +pub const __SIZEOF_PTHREAD_CONDATTR_T: u32 = 4; +pub const __SIZEOF_PTHREAD_RWLOCKATTR_T: u32 = 8; +pub const __SIZEOF_PTHREAD_BARRIERATTR_T: u32 = 4; +pub const __PTHREAD_MUTEX_LOCK_ELISION: u32 = 1; +pub const __PTHREAD_MUTEX_NUSERS_AFTER_KIND: u32 = 0; +pub const __PTHREAD_MUTEX_USE_UNION: u32 = 0; +pub const __PTHREAD_RWLOCK_INT_FLAGS_SHARED: u32 = 1; +pub const __PTHREAD_MUTEX_HAVE_PREV: u32 = 1; +pub const __have_pthread_attr_t: u32 = 1; +pub const _ALLOCA_H: u32 = 1; pub type va_list = __builtin_va_list; pub type __gnuc_va_list = __builtin_va_list; pub type __u_char = ::std::os::raw::c_uchar; @@ -3653,6 +3729,2003 @@ extern "C" { len: ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } +pub type wchar_t = ::std::os::raw::c_int; +pub type _Float32 = f32; +pub type _Float64 = f64; +pub type _Float32x = f64; +pub type _Float64x = u128; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct div_t { + pub quot: ::std::os::raw::c_int, + pub rem: ::std::os::raw::c_int, +} +#[test] +fn bindgen_test_layout_div_t() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(div_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(div_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ldiv_t { + pub quot: ::std::os::raw::c_long, + pub rem: ::std::os::raw::c_long, +} +#[test] +fn bindgen_test_layout_ldiv_t() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ldiv_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(ldiv_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct lldiv_t { + pub quot: ::std::os::raw::c_longlong, + pub rem: ::std::os::raw::c_longlong, +} +#[test] +fn bindgen_test_layout_lldiv_t() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(lldiv_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(lldiv_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(rem) + ) + ); +} +extern "C" { + pub fn __ctype_get_mb_cur_max() -> usize; +} +extern "C" { + pub fn atof(__nptr: *const ::std::os::raw::c_char) -> f64; +} +extern "C" { + pub fn atoi(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn atol(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn atoll(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtod( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> f64; +} +extern "C" { + pub fn strtof( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> f32; +} +extern "C" { + pub fn strtold( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> u128; +} +extern "C" { + pub fn strtol( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn strtoul( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strtoq( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtouq( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulonglong; +} +extern "C" { + pub fn strtoll( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtoull( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulonglong; +} +extern "C" { + pub fn l64a(__n: ::std::os::raw::c_long) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn a64l(__s: *const ::std::os::raw::c_char) -> ::std::os::raw::c_long; +} +pub type u_char = __u_char; +pub type u_short = __u_short; +pub type u_int = __u_int; +pub type u_long = __u_long; +pub type quad_t = __quad_t; +pub type u_quad_t = __u_quad_t; +pub type fsid_t = __fsid_t; +pub type loff_t = __loff_t; +pub type ino_t = __ino_t; +pub type dev_t = __dev_t; +pub type gid_t = __gid_t; +pub type mode_t = __mode_t; +pub type nlink_t = __nlink_t; +pub type uid_t = __uid_t; +pub type pid_t = __pid_t; +pub type id_t = __id_t; +pub type daddr_t = __daddr_t; +pub type caddr_t = __caddr_t; +pub type key_t = __key_t; +pub type clock_t = __clock_t; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type timer_t = __timer_t; +pub type ulong = ::std::os::raw::c_ulong; +pub type ushort = ::std::os::raw::c_ushort; +pub type uint = ::std::os::raw::c_uint; +pub type u_int8_t = __uint8_t; +pub type u_int16_t = __uint16_t; +pub type u_int32_t = __uint32_t; +pub type u_int64_t = __uint64_t; +pub type register_t = ::std::os::raw::c_long; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], +} +#[test] +fn bindgen_test_layout___sigset_t() { + assert_eq!( + ::std::mem::size_of::<__sigset_t>(), + 128usize, + concat!("Size of: ", stringify!(__sigset_t)) + ); + assert_eq!( + ::std::mem::align_of::<__sigset_t>(), + 8usize, + concat!("Alignment of ", stringify!(__sigset_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__sigset_t>())).__val as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__sigset_t), + "::", + stringify!(__val) + ) + ); +} +pub type sigset_t = __sigset_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct timeval { + pub tv_sec: __time_t, + pub tv_usec: __suseconds_t, +} +#[test] +fn bindgen_test_layout_timeval() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(timeval)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(timeval)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_sec as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(timeval), + "::", + stringify!(tv_sec) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_usec as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(timeval), + "::", + stringify!(tv_usec) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[test] +fn bindgen_test_layout_timespec() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(timespec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(timespec)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_sec as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(timespec), + "::", + stringify!(tv_sec) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_nsec as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(timespec), + "::", + stringify!(tv_nsec) + ) + ); +} +pub type suseconds_t = __suseconds_t; +pub type __fd_mask = ::std::os::raw::c_long; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct fd_set { + pub __fds_bits: [__fd_mask; 16usize], +} +#[test] +fn bindgen_test_layout_fd_set() { + assert_eq!( + ::std::mem::size_of::(), + 128usize, + concat!("Size of: ", stringify!(fd_set)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(fd_set)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__fds_bits as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(fd_set), + "::", + stringify!(__fds_bits) + ) + ); +} +pub type fd_mask = __fd_mask; +extern "C" { + pub fn select( + __nfds: ::std::os::raw::c_int, + __readfds: *mut fd_set, + __writefds: *mut fd_set, + __exceptfds: *mut fd_set, + __timeout: *mut timeval, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn pselect( + __nfds: ::std::os::raw::c_int, + __readfds: *mut fd_set, + __writefds: *mut fd_set, + __exceptfds: *mut fd_set, + __timeout: *const timespec, + __sigmask: *const __sigset_t, + ) -> ::std::os::raw::c_int; +} +pub type blksize_t = __blksize_t; +pub type blkcnt_t = __blkcnt_t; +pub type fsblkcnt_t = __fsblkcnt_t; +pub type fsfilcnt_t = __fsfilcnt_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __pthread_rwlock_arch_t { + pub __readers: ::std::os::raw::c_uint, + pub __writers: ::std::os::raw::c_uint, + pub __wrphase_futex: ::std::os::raw::c_uint, + pub __writers_futex: ::std::os::raw::c_uint, + pub __pad3: ::std::os::raw::c_uint, + pub __pad4: ::std::os::raw::c_uint, + pub __cur_writer: ::std::os::raw::c_int, + pub __shared: ::std::os::raw::c_int, + pub __rwelision: ::std::os::raw::c_schar, + pub __pad1: [::std::os::raw::c_uchar; 7usize], + pub __pad2: ::std::os::raw::c_ulong, + pub __flags: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_rwlock_arch_t() { + assert_eq!( + ::std::mem::size_of::<__pthread_rwlock_arch_t>(), + 56usize, + concat!("Size of: ", stringify!(__pthread_rwlock_arch_t)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_rwlock_arch_t>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_rwlock_arch_t)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__readers as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__readers) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__writers as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__writers) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__wrphase_futex as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__wrphase_futex) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__writers_futex as *const _ as usize + }, + 12usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__writers_futex) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad3 as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad3) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad4 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad4) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__cur_writer as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__cur_writer) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__shared as *const _ as usize + }, + 28usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__shared) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__rwelision as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__rwelision) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad1 as *const _ as usize }, + 33usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad1) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad2 as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad2) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__flags as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__flags) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +#[test] +fn bindgen_test_layout___pthread_internal_list() { + assert_eq!( + ::std::mem::size_of::<__pthread_internal_list>(), + 16usize, + concat!("Size of: ", stringify!(__pthread_internal_list)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_internal_list>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_internal_list)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_internal_list>())).__prev as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_internal_list), + "::", + stringify!(__prev) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_internal_list>())).__next as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_internal_list), + "::", + stringify!(__next) + ) + ); +} +pub type __pthread_list_t = __pthread_internal_list; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __pthread_mutex_s { + pub __lock: ::std::os::raw::c_int, + pub __count: ::std::os::raw::c_uint, + pub __owner: ::std::os::raw::c_int, + pub __nusers: ::std::os::raw::c_uint, + pub __kind: ::std::os::raw::c_int, + pub __spins: ::std::os::raw::c_short, + pub __elision: ::std::os::raw::c_short, + pub __list: __pthread_list_t, +} +#[test] +fn bindgen_test_layout___pthread_mutex_s() { + assert_eq!( + ::std::mem::size_of::<__pthread_mutex_s>(), + 40usize, + concat!("Size of: ", stringify!(__pthread_mutex_s)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_mutex_s>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_mutex_s)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__lock as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__lock) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__count as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__count) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__owner as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__owner) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__nusers as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__nusers) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__kind as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__kind) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__spins as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__spins) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__elision as *const _ as usize }, + 22usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__elision) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__list as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__list) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __pthread_cond_s { + pub __bindgen_anon_1: __pthread_cond_s__bindgen_ty_1, + pub __bindgen_anon_2: __pthread_cond_s__bindgen_ty_2, + pub __g_refs: [::std::os::raw::c_uint; 2usize], + pub __g_size: [::std::os::raw::c_uint; 2usize], + pub __g1_orig_size: ::std::os::raw::c_uint, + pub __wrefs: ::std::os::raw::c_uint, + pub __g_signals: [::std::os::raw::c_uint; 2usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __pthread_cond_s__bindgen_ty_1 { + pub __wseq: ::std::os::raw::c_ulonglong, + pub __wseq32: __pthread_cond_s__bindgen_ty_1__bindgen_ty_1, + _bindgen_union_align: u64, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __pthread_cond_s__bindgen_ty_1__bindgen_ty_1 { + pub __low: ::std::os::raw::c_uint, + pub __high: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_1__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>(), + 8usize, + concat!( + "Size of: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>(), + 4usize, + concat!( + "Alignment of ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>())).__low + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(__low) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>())).__high + as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(__high) + ) + ); +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_1>(), + 8usize, + concat!("Size of: ", stringify!(__pthread_cond_s__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_1>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1>())).__wseq as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1), + "::", + stringify!(__wseq) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1>())).__wseq32 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1), + "::", + stringify!(__wseq32) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __pthread_cond_s__bindgen_ty_2 { + pub __g1_start: ::std::os::raw::c_ulonglong, + pub __g1_start32: __pthread_cond_s__bindgen_ty_2__bindgen_ty_1, + _bindgen_union_align: u64, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __pthread_cond_s__bindgen_ty_2__bindgen_ty_1 { + pub __low: ::std::os::raw::c_uint, + pub __high: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_2__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>(), + 8usize, + concat!( + "Size of: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>(), + 4usize, + concat!( + "Alignment of ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>())).__low + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1), + "::", + stringify!(__low) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>())).__high + as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1), + "::", + stringify!(__high) + ) + ); +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_2() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_2>(), + 8usize, + concat!("Size of: ", stringify!(__pthread_cond_s__bindgen_ty_2)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_2>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s__bindgen_ty_2)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2>())).__g1_start as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2), + "::", + stringify!(__g1_start) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2>())).__g1_start32 as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2), + "::", + stringify!(__g1_start32) + ) + ); +} +#[test] +fn bindgen_test_layout___pthread_cond_s() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s>(), + 48usize, + concat!("Size of: ", stringify!(__pthread_cond_s)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_refs as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_refs) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_size as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g1_orig_size as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g1_orig_size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__wrefs as *const _ as usize }, + 36usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__wrefs) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_signals as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_signals) + ) + ); +} +pub type pthread_t = ::std::os::raw::c_ulong; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_mutexattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_mutexattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_mutexattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_mutexattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutexattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutexattr_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_condattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_condattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_condattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_condattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_condattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_condattr_t), + "::", + stringify!(__align) + ) + ); +} +pub type pthread_key_t = ::std::os::raw::c_uint; +pub type pthread_once_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_attr_t { + pub __size: [::std::os::raw::c_char; 56usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 7usize], +} +#[test] +fn bindgen_test_layout_pthread_attr_t() { + assert_eq!( + ::std::mem::size_of::(), + 56usize, + concat!("Size of: ", stringify!(pthread_attr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_attr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_attr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_attr_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::std::os::raw::c_char; 40usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 5usize], +} +#[test] +fn bindgen_test_layout_pthread_mutex_t() { + assert_eq!( + ::std::mem::size_of::(), + 40usize, + concat!("Size of: ", stringify!(pthread_mutex_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_mutex_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_cond_t { + pub __data: __pthread_cond_s, + pub __size: [::std::os::raw::c_char; 48usize], + pub __align: ::std::os::raw::c_longlong, + _bindgen_union_align: [u64; 6usize], +} +#[test] +fn bindgen_test_layout_pthread_cond_t() { + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(pthread_cond_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_cond_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_rwlock_t { + pub __data: __pthread_rwlock_arch_t, + pub __size: [::std::os::raw::c_char; 56usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 7usize], +} +#[test] +fn bindgen_test_layout_pthread_rwlock_t() { + assert_eq!( + ::std::mem::size_of::(), + 56usize, + concat!("Size of: ", stringify!(pthread_rwlock_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_rwlock_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_rwlockattr_t { + pub __size: [::std::os::raw::c_char; 8usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: u64, +} +#[test] +fn bindgen_test_layout_pthread_rwlockattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(pthread_rwlockattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_rwlockattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlockattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlockattr_t), + "::", + stringify!(__align) + ) + ); +} +pub type pthread_spinlock_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_barrier_t { + pub __size: [::std::os::raw::c_char; 32usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 4usize], +} +#[test] +fn bindgen_test_layout_pthread_barrier_t() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(pthread_barrier_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_barrier_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrier_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrier_t), + "::", + stringify!(__align) + ) + ); +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_barrierattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_barrierattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_barrierattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_barrierattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrierattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrierattr_t), + "::", + stringify!(__align) + ) + ); +} +extern "C" { + pub fn random() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn srandom(__seed: ::std::os::raw::c_uint); +} +extern "C" { + pub fn initstate( + __seed: ::std::os::raw::c_uint, + __statebuf: *mut ::std::os::raw::c_char, + __statelen: usize, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn setstate(__statebuf: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct random_data { + pub fptr: *mut i32, + pub rptr: *mut i32, + pub state: *mut i32, + pub rand_type: ::std::os::raw::c_int, + pub rand_deg: ::std::os::raw::c_int, + pub rand_sep: ::std::os::raw::c_int, + pub end_ptr: *mut i32, +} +#[test] +fn bindgen_test_layout_random_data() { + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(random_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(random_data)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).fptr as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(fptr) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rptr as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rptr) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).state as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(state) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_type as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_type) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_deg as *const _ as usize }, + 28usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_deg) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_sep as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_sep) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).end_ptr as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(end_ptr) + ) + ); +} +extern "C" { + pub fn random_r(__buf: *mut random_data, __result: *mut i32) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srandom_r( + __seed: ::std::os::raw::c_uint, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn initstate_r( + __seed: ::std::os::raw::c_uint, + __statebuf: *mut ::std::os::raw::c_char, + __statelen: usize, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn setstate_r( + __statebuf: *mut ::std::os::raw::c_char, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn rand() -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srand(__seed: ::std::os::raw::c_uint); +} +extern "C" { + pub fn rand_r(__seed: *mut ::std::os::raw::c_uint) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn drand48() -> f64; +} +extern "C" { + pub fn erand48(__xsubi: *mut ::std::os::raw::c_ushort) -> f64; +} +extern "C" { + pub fn lrand48() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn nrand48(__xsubi: *mut ::std::os::raw::c_ushort) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn mrand48() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn jrand48(__xsubi: *mut ::std::os::raw::c_ushort) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn srand48(__seedval: ::std::os::raw::c_long); +} +extern "C" { + pub fn seed48(__seed16v: *mut ::std::os::raw::c_ushort) -> *mut ::std::os::raw::c_ushort; +} +extern "C" { + pub fn lcong48(__param: *mut ::std::os::raw::c_ushort); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct drand48_data { + pub __x: [::std::os::raw::c_ushort; 3usize], + pub __old_x: [::std::os::raw::c_ushort; 3usize], + pub __c: ::std::os::raw::c_ushort, + pub __init: ::std::os::raw::c_ushort, + pub __a: ::std::os::raw::c_ulonglong, +} +#[test] +fn bindgen_test_layout_drand48_data() { + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(drand48_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(drand48_data)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__x) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__old_x as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__old_x) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__c as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__c) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__init as *const _ as usize }, + 14usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__init) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__a as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__a) + ) + ); +} +extern "C" { + pub fn drand48_r(__buffer: *mut drand48_data, __result: *mut f64) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn erand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut f64, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn lrand48_r( + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn nrand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mrand48_r( + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn jrand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srand48_r( + __seedval: ::std::os::raw::c_long, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn seed48_r( + __seed16v: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn lcong48_r( + __param: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn malloc(__size: ::std::os::raw::c_ulong) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn calloc( + __nmemb: ::std::os::raw::c_ulong, + __size: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn realloc( + __ptr: *mut ::std::os::raw::c_void, + __size: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn reallocarray( + __ptr: *mut ::std::os::raw::c_void, + __nmemb: usize, + __size: usize, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn free(__ptr: *mut ::std::os::raw::c_void); +} +extern "C" { + pub fn alloca(__size: ::std::os::raw::c_ulong) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn valloc(__size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn posix_memalign( + __memptr: *mut *mut ::std::os::raw::c_void, + __alignment: usize, + __size: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn aligned_alloc(__alignment: usize, __size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn abort(); +} +extern "C" { + pub fn atexit(__func: ::std::option::Option) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn at_quick_exit( + __func: ::std::option::Option, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn on_exit( + __func: ::std::option::Option< + unsafe extern "C" fn( + __status: ::std::os::raw::c_int, + __arg: *mut ::std::os::raw::c_void, + ), + >, + __arg: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn quick_exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn _Exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn getenv(__name: *const ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn putenv(__string: *mut ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn setenv( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __replace: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn unsetenv(__name: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn clearenv() -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mktemp(__template: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn mkstemp(__template: *mut ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mkstemps( + __template: *mut ::std::os::raw::c_char, + __suffixlen: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mkdtemp(__template: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn system(__command: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn realpath( + __name: *const ::std::os::raw::c_char, + __resolved: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +pub type __compar_fn_t = ::std::option::Option< + unsafe extern "C" fn( + arg1: *const ::std::os::raw::c_void, + arg2: *const ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, +>; +extern "C" { + pub fn bsearch( + __key: *const ::std::os::raw::c_void, + __base: *const ::std::os::raw::c_void, + __nmemb: usize, + __size: usize, + __compar: __compar_fn_t, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn qsort( + __base: *mut ::std::os::raw::c_void, + __nmemb: usize, + __size: usize, + __compar: __compar_fn_t, + ); +} +extern "C" { + pub fn abs(__x: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn labs(__x: ::std::os::raw::c_long) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn llabs(__x: ::std::os::raw::c_longlong) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn div(__numer: ::std::os::raw::c_int, __denom: ::std::os::raw::c_int) -> div_t; +} +extern "C" { + pub fn ldiv(__numer: ::std::os::raw::c_long, __denom: ::std::os::raw::c_long) -> ldiv_t; +} +extern "C" { + pub fn lldiv( + __numer: ::std::os::raw::c_longlong, + __denom: ::std::os::raw::c_longlong, + ) -> lldiv_t; +} +extern "C" { + pub fn ecvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn fcvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn gcvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qecvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qfcvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qgcvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn ecvt_r( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn fcvt_r( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn qecvt_r( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn qfcvt_r( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mblen(__s: *const ::std::os::raw::c_char, __n: usize) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbtowc( + __pwc: *mut wchar_t, + __s: *const ::std::os::raw::c_char, + __n: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wctomb(__s: *mut ::std::os::raw::c_char, __wchar: wchar_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbstowcs(__pwcs: *mut wchar_t, __s: *const ::std::os::raw::c_char, __n: usize) -> usize; +} +extern "C" { + pub fn wcstombs(__s: *mut ::std::os::raw::c_char, __pwcs: *const wchar_t, __n: usize) -> usize; +} +extern "C" { + pub fn rpmatch(__response: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn getsubopt( + __optionp: *mut *mut ::std::os::raw::c_char, + __tokens: *const *mut ::std::os::raw::c_char, + __valuep: *mut *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn getloadavg(__loadavg: *mut f64, __nelem: ::std::os::raw::c_int) + -> ::std::os::raw::c_int; +} +extern "C" { + pub fn js_init_module_std( + ctx: *mut JSContext, + module_name: *const ::std::os::raw::c_char, + ) -> *mut JSModuleDef; +} +extern "C" { + pub fn js_init_module_os( + ctx: *mut JSContext, + module_name: *const ::std::os::raw::c_char, + ) -> *mut JSModuleDef; +} +extern "C" { + pub fn js_std_add_helpers( + ctx: *mut JSContext, + argc: ::std::os::raw::c_int, + argv: *mut *mut ::std::os::raw::c_char, + ); +} +extern "C" { + pub fn js_std_loop(ctx: *mut JSContext); +} +extern "C" { + pub fn js_std_free_handlers(rt: *mut JSRuntime); +} +extern "C" { + pub fn js_std_dump_error(ctx: *mut JSContext); +} +extern "C" { + pub fn js_load_file( + ctx: *mut JSContext, + pbuf_len: *mut usize, + filename: *const ::std::os::raw::c_char, + ) -> *mut u8; +} +extern "C" { + pub fn js_module_set_import_meta( + ctx: *mut JSContext, + func_val: JSValue, + use_realpath: ::std::os::raw::c_int, + is_main: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn js_module_loader( + ctx: *mut JSContext, + module_name: *const ::std::os::raw::c_char, + opaque: *mut ::std::os::raw::c_void, + ) -> *mut JSModuleDef; +} +extern "C" { + pub fn js_std_eval_binary( + ctx: *mut JSContext, + buf: *const u8, + buf_len: usize, + flags: ::std::os::raw::c_int, + ); +} pub type __builtin_va_list = [__va_list_tag; 1usize]; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/libquickjs-sys/src/lib.rs b/libquickjs-sys/src/lib.rs index 8180c20..c418884 100644 --- a/libquickjs-sys/src/lib.rs +++ b/libquickjs-sys/src/lib.rs @@ -6,8 +6,10 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(improper_ctypes)] -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +mod bindings; +pub use bindings::*; #[cfg(test)] mod tests { diff --git a/libquickjs-sys/wrapper.h b/libquickjs-sys/wrapper.h index e37268c..848e625 100644 --- a/libquickjs-sys/wrapper.h +++ b/libquickjs-sys/wrapper.h @@ -1,2 +1,3 @@ #include +#include diff --git a/src/bindings.rs b/src/bindings.rs index fd406d9..7ea2837 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -1,798 +1,61 @@ +use std::path::{PathBuf}; +use std::ptr::NonNull; +use std::sync::Arc; use std::{ - collections::HashMap, - ffi::CString, - os::raw::{c_char, c_int, c_void}, + slice, sync::Mutex, }; use libquickjs_sys as q; +use crate::callback::WrappedCallback; +use crate::utils::{js_null_value, make_cstring}; #[cfg(feature = "bigint")] use crate::value::{bigint::BigIntOrI64, BigInt}; use crate::{ - callback::{Arguments, Callback}, + callback::{Arguments}, console::ConsoleBackend, - droppable_value::DroppableValue, - ContextError, ExecutionError, JsValue, ValueError, + owned_value_ref::{OwnedObjectRef, OwnedValueRef}, + marshal::{serialize_value}, + timers::JsTimerRef, + ContextError, ExecutionError, JsValue, }; // JS_TAG_* constants from quickjs. // For some reason bindgen does not pick them up. #[cfg(feature = "bigint")] -const TAG_BIG_INT: i64 = -10; -const TAG_STRING: i64 = -7; -const TAG_OBJECT: i64 = -1; -const TAG_INT: i64 = 0; -const TAG_BOOL: i64 = 1; -const TAG_NULL: i64 = 2; -const TAG_UNDEFINED: i64 = 3; -const TAG_EXCEPTION: i64 = 6; -const TAG_FLOAT64: i64 = 7; - -/// Free a JSValue. -/// This function is the equivalent of JS_FreeValue from quickjs, which can not -/// be used due to being `static inline`. -unsafe fn free_value(context: *mut q::JSContext, value: q::JSValue) { - // All tags < 0 are garbage collected and need to be freed. - if value.tag < 0 { - // This transmute is OK since if tag < 0, the union will be a refcount - // pointer. - let ptr = value.u.ptr as *mut q::JSRefCountHeader; - let pref: &mut q::JSRefCountHeader = &mut *ptr; - pref.ref_count -= 1; - if pref.ref_count <= 0 { - q::__JS_FreeValue(context, value); - } - } -} - -#[cfg(feature = "chrono")] -fn js_date_constructor(context: *mut q::JSContext) -> q::JSValue { - let global = unsafe { q::JS_GetGlobalObject(context) }; - assert_eq!(global.tag, TAG_OBJECT); - - let date_constructor = unsafe { - q::JS_GetPropertyStr( - context, - global, - std::ffi::CStr::from_bytes_with_nul(b"Date\0") - .unwrap() - .as_ptr(), - ) - }; - assert_eq!(date_constructor.tag, TAG_OBJECT); - unsafe { free_value(context, global) }; - date_constructor -} - -#[cfg(feature = "bigint")] -fn js_create_bigint_function(context: *mut q::JSContext) -> q::JSValue { - let global = unsafe { q::JS_GetGlobalObject(context) }; - assert_eq!(global.tag, TAG_OBJECT); - - let bigint_function = unsafe { - q::JS_GetPropertyStr( - context, - global, - std::ffi::CStr::from_bytes_with_nul(b"BigInt\0") - .unwrap() - .as_ptr(), - ) - }; - assert_eq!(bigint_function.tag, TAG_OBJECT); - unsafe { free_value(context, global) }; - bigint_function -} - -/// Serialize a Rust value into a quickjs runtime value. -fn serialize_value(context: *mut q::JSContext, value: JsValue) -> Result { - let v = match value { - JsValue::Null => q::JSValue { - u: q::JSValueUnion { int32: 0 }, - tag: TAG_NULL, - }, - JsValue::Bool(flag) => q::JSValue { - u: q::JSValueUnion { - int32: if flag { 1 } else { 0 }, - }, - tag: TAG_BOOL, - }, - JsValue::Int(val) => q::JSValue { - u: q::JSValueUnion { int32: val }, - tag: TAG_INT, - }, - JsValue::Float(val) => q::JSValue { - u: q::JSValueUnion { float64: val }, - tag: TAG_FLOAT64, - }, - JsValue::String(val) => { - let qval = - unsafe { q::JS_NewStringLen(context, val.as_ptr() as *const c_char, val.len()) }; - - if qval.tag == TAG_EXCEPTION { - return Err(ValueError::Internal( - "Could not create string in runtime".into(), - )); - } - - qval - } - JsValue::Array(values) => { - // Allocate a new array in the runtime. - let arr = unsafe { q::JS_NewArray(context) }; - if arr.tag == TAG_EXCEPTION { - return Err(ValueError::Internal( - "Could not create array in runtime".into(), - )); - } - - for (index, value) in values.into_iter().enumerate() { - let qvalue = match serialize_value(context, value) { - Ok(qval) => qval, - Err(e) => { - // Make sure to free the array if a individual element - // fails. - unsafe { - free_value(context, arr); - } - return Err(e); - } - }; - - let ret = unsafe { - q::JS_DefinePropertyValueUint32( - context, - arr, - index as u32, - qvalue, - q::JS_PROP_C_W_E as i32, - ) - }; - if ret < 0 { - // Make sure to free the array if a individual - // element fails. - unsafe { - free_value(context, arr); - } - return Err(ValueError::Internal( - "Could not append element to array".into(), - )); - } - } - arr - } - JsValue::Object(map) => { - let obj = unsafe { q::JS_NewObject(context) }; - if obj.tag == TAG_EXCEPTION { - return Err(ValueError::Internal("Could not create object".into())); - } - - for (key, value) in map { - let ckey = make_cstring(key)?; - - let qvalue = serialize_value(context, value).map_err(|e| { - // Free the object if a property failed. - unsafe { - free_value(context, obj); - } - e - })?; - - let ret = unsafe { - q::JS_DefinePropertyValueStr( - context, - obj, - ckey.as_ptr(), - qvalue, - q::JS_PROP_C_W_E as i32, - ) - }; - if ret < 0 { - // Free the object if a property failed. - unsafe { - free_value(context, obj); - } - return Err(ValueError::Internal( - "Could not add add property to object".into(), - )); - } - } - - obj - } - #[cfg(feature = "chrono")] - JsValue::Date(datetime) => { - let date_constructor = js_date_constructor(context); - - let f = datetime.timestamp_millis() as f64; - - let timestamp = q::JSValue { - u: q::JSValueUnion { float64: f }, - tag: TAG_FLOAT64, - }; - - let mut args = vec![timestamp]; - - let value = unsafe { - q::JS_CallConstructor( - context, - date_constructor, - args.len() as i32, - args.as_mut_ptr(), - ) - }; - unsafe { - free_value(context, date_constructor); - } - - if value.tag != TAG_OBJECT { - return Err(ValueError::Internal( - "Could not construct Date object".into(), - )); - } - value - } - #[cfg(feature = "bigint")] - JsValue::BigInt(int) => match int.inner { - BigIntOrI64::Int(int) => unsafe { q::JS_NewBigInt64(context, int) }, - BigIntOrI64::BigInt(bigint) => { - let bigint_string = bigint.to_str_radix(10); - let s = unsafe { - q::JS_NewStringLen( - context, - bigint_string.as_ptr() as *const c_char, - bigint_string.len(), - ) - }; - let s = DroppableValue::new(s, |&mut s| unsafe { - free_value(context, s); - }); - if (*s).tag != TAG_STRING { - return Err(ValueError::Internal( - "Could not construct String object needed to create BigInt object".into(), - )); - } - - let mut args = vec![*s]; - - let bigint_function = js_create_bigint_function(context); - let bigint_function = - DroppableValue::new(bigint_function, |&mut bigint_function| unsafe { - free_value(context, bigint_function); - }); - let js_bigint = unsafe { - q::JS_Call( - context, - *bigint_function, - js_null_value(), - 1, - args.as_mut_ptr(), - ) - }; - - if js_bigint.tag != TAG_BIG_INT { - return Err(ValueError::Internal( - "Could not construct BigInt object".into(), - )); - } - - js_bigint - } - }, - JsValue::__NonExhaustive => { - unreachable!() - } - }; - Ok(v) -} - -fn deserialize_array( - context: *mut q::JSContext, - raw_value: &q::JSValue, -) -> Result { - assert_eq!(raw_value.tag, TAG_OBJECT); - - let length_name = make_cstring("length")?; - - let len_raw = unsafe { q::JS_GetPropertyStr(context, *raw_value, length_name.as_ptr()) }; - - let len_res = deserialize_value(context, &len_raw); - unsafe { free_value(context, len_raw) }; - let len = match len_res? { - JsValue::Int(x) => x, - _ => { - return Err(ValueError::Internal( - "Could not determine array length".into(), - )); - } - }; - - let mut values = Vec::new(); - for index in 0..(len as usize) { - let value_raw = unsafe { q::JS_GetPropertyUint32(context, *raw_value, index as u32) }; - if value_raw.tag == TAG_EXCEPTION { - return Err(ValueError::Internal("Could not build array".into())); - } - let value_res = deserialize_value(context, &value_raw); - unsafe { free_value(context, value_raw) }; - - let value = value_res?; - values.push(value); - } - - Ok(JsValue::Array(values)) -} - -fn deserialize_object(context: *mut q::JSContext, obj: &q::JSValue) -> Result { - assert_eq!(obj.tag, TAG_OBJECT); - - let mut properties: *mut q::JSPropertyEnum = std::ptr::null_mut(); - let mut count: u32 = 0; - - let flags = (q::JS_GPN_STRING_MASK | q::JS_GPN_SYMBOL_MASK | q::JS_GPN_ENUM_ONLY) as i32; - let ret = - unsafe { q::JS_GetOwnPropertyNames(context, &mut properties, &mut count, *obj, flags) }; - if ret != 0 { - return Err(ValueError::Internal( - "Could not get object properties".into(), - )); - } - - // TODO: refactor into a more Rust-idiomatic iterator wrapper. - let properties = DroppableValue::new(properties, |&mut properties| { - for index in 0..count { - let prop = unsafe { properties.offset(index as isize) }; - unsafe { - q::JS_FreeAtom(context, (*prop).atom); - } - } - unsafe { - q::js_free(context, properties as *mut std::ffi::c_void); - } - }); - - let mut map = HashMap::new(); - for index in 0..count { - let prop = unsafe { (*properties).offset(index as isize) }; - let raw_value = unsafe { q::JS_GetPropertyInternal(context, *obj, (*prop).atom, *obj, 0) }; - if raw_value.tag == TAG_EXCEPTION { - return Err(ValueError::Internal("Could not get object property".into())); - } - - let value_res = deserialize_value(context, &raw_value); - unsafe { - free_value(context, raw_value); - } - let value = value_res?; - - let key_value = unsafe { q::JS_AtomToString(context, (*prop).atom) }; - if key_value.tag == TAG_EXCEPTION { - return Err(ValueError::Internal( - "Could not get object property name".into(), - )); - } - - let key_res = deserialize_value(context, &key_value); - unsafe { - free_value(context, key_value); - } - let key = match key_res? { - JsValue::String(s) => s, - _ => { - return Err(ValueError::Internal("Could not get property name".into())); - } - }; - map.insert(key, value); - } - - Ok(JsValue::Object(map)) -} - -fn deserialize_value( - context: *mut q::JSContext, - value: &q::JSValue, -) -> Result { - let r = value; - - match r.tag { - // Int. - TAG_INT => { - let val = unsafe { r.u.int32 }; - Ok(JsValue::Int(val)) - } - // Bool. - TAG_BOOL => { - let raw = unsafe { r.u.int32 }; - let val = raw > 0; - Ok(JsValue::Bool(val)) - } - // Null. - TAG_NULL => Ok(JsValue::Null), - // Undefined. - TAG_UNDEFINED => Ok(JsValue::Null), - // Float. - TAG_FLOAT64 => { - let val = unsafe { r.u.float64 }; - Ok(JsValue::Float(val)) - } - // String. - TAG_STRING => { - let ptr = unsafe { - q::JS_ToCStringLen2(context, std::ptr::null::() as *mut usize, *r, 0) - }; - - if ptr.is_null() { - return Err(ValueError::Internal( - "Could not convert string: got a null pointer".into(), - )); - } - - let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) }; - - let s = cstr - .to_str() - .map_err(ValueError::InvalidString)? - .to_string(); - - // Free the c string. - unsafe { q::JS_FreeCString(context, ptr) }; - - Ok(JsValue::String(s)) - } - // Object. - TAG_OBJECT => { - let is_array = unsafe { q::JS_IsArray(context, *r) } > 0; - if is_array { - deserialize_array(context, r) - } else { - #[cfg(feature = "chrono")] - { - use chrono::offset::TimeZone; - - let date_constructor = js_date_constructor(context); - let is_date = unsafe { q::JS_IsInstanceOf(context, *r, date_constructor) > 0 }; - - if is_date { - let getter = unsafe { - q::JS_GetPropertyStr( - context, - *r, - std::ffi::CStr::from_bytes_with_nul(b"getTime\0") - .unwrap() - .as_ptr(), - ) - }; - assert_eq!(getter.tag, TAG_OBJECT); - - let timestamp_raw = - unsafe { q::JS_Call(context, getter, *r, 0, std::ptr::null_mut()) }; - unsafe { - free_value(context, getter); - free_value(context, date_constructor); - }; - - let res = if timestamp_raw.tag != TAG_FLOAT64 { - Err(ValueError::Internal( - "Could not convert 'Date' instance to timestamp".into(), - )) - } else { - let f = unsafe { timestamp_raw.u.float64 } as i64; - let datetime = chrono::Utc.timestamp_millis(f); - Ok(JsValue::Date(datetime)) - }; - return res; - } else { - unsafe { free_value(context, date_constructor) }; - } - } - - deserialize_object(context, r) - } - } - // BigInt - #[cfg(feature = "bigint")] - TAG_BIG_INT => { - let mut int: i64 = 0; - let ret = unsafe { q::JS_ToBigInt64(context, &mut int, *r) }; - if ret == 0 { - Ok(JsValue::BigInt(BigInt { - inner: BigIntOrI64::Int(int), - })) - } else { - let ptr = unsafe { q::JS_ToCStringLen2(context, std::ptr::null_mut(), *r, 0) }; - - if ptr.is_null() { - return Err(ValueError::Internal( - "Could not convert BigInt to string: got a null pointer".into(), - )); - } - - let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) }; - let bigint = num_bigint::BigInt::parse_bytes(cstr.to_bytes(), 10).unwrap(); - - // Free the c string. - unsafe { q::JS_FreeCString(context, ptr) }; - - Ok(JsValue::BigInt(BigInt { - inner: BigIntOrI64::BigInt(bigint), - })) - } - } - x => Err(ValueError::Internal(format!( - "Unhandled JS_TAG value: {}", - x - ))), - } -} - -/// Helper for creating CStrings. -fn make_cstring(value: impl Into>) -> Result { - CString::new(value).map_err(ValueError::StringWithZeroBytes) -} - -/// Helper to construct null JsValue -fn js_null_value() -> q::JSValue { - q::JSValue { - u: q::JSValueUnion { int32: 0 }, - tag: TAG_NULL, - } -} - -type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue; - -/// Taken from: https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html -/// -/// Create a C wrapper function for a Rust closure to enable using it as a -/// callback function in the Quickjs runtime. -/// -/// Both the boxed closure and the boxed data are returned and must be stored -/// by the caller to guarantee they stay alive. -unsafe fn build_closure_trampoline( - closure: F, -) -> ((Box, Box), q::JSCFunctionData) -where - F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static, -{ - unsafe extern "C" fn trampoline( - _ctx: *mut q::JSContext, - _this: q::JSValue, - argc: c_int, - argv: *mut q::JSValue, - _magic: c_int, - data: *mut q::JSValue, - ) -> q::JSValue - where - F: Fn(c_int, *mut q::JSValue) -> q::JSValue, - { - let closure_ptr = (*data).u.ptr; - let closure: &mut F = &mut *(closure_ptr as *mut F); - (*closure)(argc, argv) - } - - let boxed_f = Box::new(closure); - - let data = Box::new(q::JSValue { - u: q::JSValueUnion { - ptr: (&*boxed_f) as *const F as *mut c_void, - }, - tag: TAG_NULL, - }); - - ((boxed_f, data), Some(trampoline::)) -} - -/// OwnedValueRef wraps a Javascript value from the quickjs runtime. -/// It prevents leaks by ensuring that the inner value is deallocated on drop. -pub struct OwnedValueRef<'a> { - context: &'a ContextWrapper, - value: q::JSValue, -} - -impl<'a> Drop for OwnedValueRef<'a> { - fn drop(&mut self) { - unsafe { - free_value(self.context.context, self.value); - } - } -} - -impl<'a> std::fmt::Debug for OwnedValueRef<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self.value.tag { - TAG_EXCEPTION => write!(f, "Exception(?)"), - TAG_NULL => write!(f, "NULL"), - TAG_UNDEFINED => write!(f, "UNDEFINED"), - TAG_BOOL => write!(f, "Bool(?)",), - TAG_INT => write!(f, "Int(?)"), - TAG_FLOAT64 => write!(f, "Float(?)"), - TAG_STRING => write!(f, "String(?)"), - TAG_OBJECT => write!(f, "Object(?)"), - _ => write!(f, "?"), - } - } -} - -impl<'a> OwnedValueRef<'a> { - pub fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self { - Self { context, value } - } - - /// Get the inner JSValue without freeing in drop. - /// - /// Unsafe because the caller is responsible for freeing the value. - //unsafe fn into_inner(mut self) -> q::JSValue { - //let v = self.value; - //self.value = q::JSValue { - //u: q::JSValueUnion { int32: 0 }, - //tag: TAG_NULL, - //}; - //v - //} - - pub fn is_null(&self) -> bool { - self.value.tag == TAG_NULL - } - - pub fn is_bool(&self) -> bool { - self.value.tag == TAG_BOOL - } - - pub fn is_exception(&self) -> bool { - self.value.tag == TAG_EXCEPTION - } - - pub fn is_object(&self) -> bool { - self.value.tag == TAG_OBJECT - } - - pub fn is_string(&self) -> bool { - self.value.tag == TAG_STRING - } - - pub fn to_string(&self) -> Result { - let value = if self.is_string() { - self.to_value()? - } else { - let raw = unsafe { q::JS_ToString(self.context.context, self.value) }; - let value = OwnedValueRef::new(self.context, raw); - - if value.value.tag != TAG_STRING { - return Err(ExecutionError::Exception( - "Could not convert value to string".into(), - )); - } - value.to_value()? - }; - - Ok(value.as_str().unwrap().to_string()) - } - - pub fn to_value(&self) -> Result { - self.context.to_value(&self.value) - } - - pub fn to_bool(&self) -> Result { - match self.to_value()? { - JsValue::Bool(b) => Ok(b), - _ => Err(ValueError::UnexpectedType), - } - } -} - -/// Wraps an object from the quickjs runtime. -/// Provides convenience property accessors. -pub struct OwnedObjectRef<'a> { - value: OwnedValueRef<'a>, -} - -impl<'a> OwnedObjectRef<'a> { - pub fn new(value: OwnedValueRef<'a>) -> Result { - if value.value.tag != TAG_OBJECT { - Err(ValueError::Internal("Expected an object".into())) - } else { - Ok(Self { value }) - } - } - - fn into_value(self) -> OwnedValueRef<'a> { - self.value - } - - /// Get the tag of a property. - fn property_tag(&self, name: &str) -> Result { - let cname = make_cstring(name)?; - let raw = unsafe { - q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr()) - }; - let t = raw.tag; - unsafe { - free_value(self.value.context.context, raw); - } - Ok(t) - } - - /// Determine if the object is a promise by checking the presence of - /// a 'then' and a 'catch' property. - fn is_promise(&self) -> Result { - if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT { - Ok(true) - } else { - Ok(false) - } - } - - pub fn property(&self, name: &str) -> Result, ExecutionError> { - let cname = make_cstring(name)?; - let raw = unsafe { - q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr()) - }; - - if raw.tag == TAG_EXCEPTION { - Err(ExecutionError::Internal(format!( - "Exception while getting property '{}'", - name - ))) - } else if raw.tag == TAG_UNDEFINED { - Err(ExecutionError::Internal(format!( - "Property '{}' not found", - name - ))) - } else { - Ok(OwnedValueRef::new(self.value.context, raw)) - } - } - - unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> { - let cname = make_cstring(name)?; - let ret = q::JS_SetPropertyStr( - self.value.context.context, - self.value.value, - cname.as_ptr(), - value, - ); - if ret < 0 { - Err(ExecutionError::Exception("Could not set property".into())) - } else { - Ok(()) - } - } - - // pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> { - // let qval = self.value.context.serialize_value(value)?; - // unsafe { self.set_property_raw(name, qval.value) } - // } -} - -/* -type ModuleInit = dyn Fn(*mut q::JSContext, *mut q::JSModuleDef); - -thread_local! { - static NATIVE_MODULE_INIT: RefCell>> = RefCell::new(None); -} - -unsafe extern "C" fn native_module_init( - ctx: *mut q::JSContext, - m: *mut q::JSModuleDef, -) -> ::std::os::raw::c_int { - NATIVE_MODULE_INIT.with(|init| { - let init = init.replace(None).unwrap(); - init(ctx, m); - }); - 0 -} -*/ +pub const TAG_BIG_INT: i64 = -10; +pub const TAG_STRING: i64 = -7; +pub const TAG_MODULE: i64 = -3; +pub const TAG_OBJECT: i64 = -1; +pub const TAG_INT: i64 = 0; +pub const TAG_BOOL: i64 = 1; +pub const TAG_NULL: i64 = 2; +pub const TAG_UNDEFINED: i64 = 3; +pub const TAG_EXCEPTION: i64 = 6; +pub const TAG_FLOAT64: i64 = 7; + +pub const TAG_INVALID: i64 = 100000; /// Wraps a quickjs context. /// /// Cleanup of the context happens in drop. pub struct ContextWrapper { - runtime: *mut q::JSRuntime, - context: *mut q::JSContext, + pub(crate) runtime: *mut q::JSRuntime, + pub(crate) context: *mut q::JSContext, + /// Used for QuickJS error reporting + filename_hint: String, + /// The directory path where modules are looked for. + pub(crate) module_load_path: PathBuf, /// Stores callback closures and quickjs data pointers. /// This array is write-only and only exists to ensure the lifetime of /// the closure. - // A Mutex is used over a RefCell because it needs to be unwind-safe. - callbacks: Mutex, Box)>>, + /// + /// A Mutex is used over a RefCell because it needs to be unwind-safe. + pub(crate) callbacks: Mutex, Box)>>, + /// Timer references. Structured in a linked list + /// A Mutex is used over a RefCell because it needs to be unwind-safe. + pub(crate) timers: Arc>, } impl Drop for ContextWrapper { @@ -833,12 +96,15 @@ impl ContextWrapper { runtime, context, callbacks: Mutex::new(Vec::new()), + filename_hint: "script.js".to_owned(), + module_load_path: PathBuf::new(), + timers: Arc::new(Mutex::new(None)), }; Ok(wrapper) } - // See console standard: https://console.spec.whatwg.org + /// See console standard: https://console.spec.whatwg.org pub fn set_console(&self, backend: Box) -> Result<(), ExecutionError> { use crate::console::Level; @@ -887,6 +153,8 @@ impl ContextWrapper { }, }; "#, + false, + false, )?; Ok(()) @@ -894,6 +162,7 @@ impl ContextWrapper { /// Reset the wrapper by creating a new context. pub fn reset(self) -> Result { + self.cancel_all_timers(); unsafe { q::JS_FreeContext(self.context); }; @@ -908,20 +177,21 @@ impl ContextWrapper { Ok(s) } - pub fn serialize_value(&self, value: JsValue) -> Result, ExecutionError> { + pub fn serialize_value(&self, value: JsValue) -> Result { let serialized = serialize_value(self.context, value)?; - Ok(OwnedValueRef::new(self, serialized)) + Ok(OwnedValueRef::wrap(self.context, serialized)) } - // Deserialize a quickjs runtime value into a Rust value. - fn to_value(&self, value: &q::JSValue) -> Result { - deserialize_value(self.context, value) + /// QuickJS error reporting includes a script filename. + /// Set the file name to be reported here. + pub fn set_filename_hint(&mut self, filename: String) { + self.filename_hint = filename; } /// Get the global object. - pub fn global(&self) -> Result, ExecutionError> { + pub fn global(&self) -> Result { let global_raw = unsafe { q::JS_GetGlobalObject(self.context) }; - let global_ref = OwnedValueRef::new(self, global_raw); + let global_ref = OwnedValueRef::wrap(self.context, global_raw); let global = OwnedObjectRef::new(global_ref)?; Ok(global) } @@ -929,7 +199,7 @@ impl ContextWrapper { /// Get the last exception from the runtime, and if present, convert it to a ExceptionError. fn get_exception(&self) -> Option { let raw = unsafe { q::JS_GetException(self.context) }; - let value = OwnedValueRef::new(self, raw); + let value = OwnedValueRef::wrap(self.context, raw); if value.is_null() { None @@ -952,101 +222,214 @@ impl ContextWrapper { } } + fn resolve_promise_prepare( + &self, + value: OwnedValueRef, + ) -> Result<(bool, OwnedValueRef), ExecutionError> { + if !value.is_object() { + return Ok((false, value)); + } + + let obj = OwnedObjectRef::new(value)?; + if !obj.is_promise()? { + return Ok((false, obj.into_value())); + } + // Values: + // - undefined: promise not finished + // - false: error ocurred, __promiseError is set. + // - true: finished, __promiseSuccess is set. + self.eval( + r#" + var __promiseResult = 0; + var __promiseValue = 0; + + var __resolvePromise = function(p) { + p + .then(value => { + __promiseResult = true; + __promiseValue = value; + }) + .catch(e => { + __promiseResult = false; + __promiseValue = e; + }); + } + "#, + false, + false, + )?; + + let global = self.global()?; + let resolver = global.property("__resolvePromise")?; + + // Call the resolver code that sets the result values once + // the promise resolves. + Ok(( + true, + self.call_function(resolver, vec![obj.into_value()], true)?, + )) + } + /// If the given value is a promise, run the event loop until it is /// resolved, and return the final value. - fn resolve_value<'a>( - &'a self, - value: OwnedValueRef<'a>, - ) -> Result, ExecutionError> { + fn resolve_value( + &self, + value: OwnedValueRef, + ) -> Result { if value.is_exception() { let err = self .get_exception() .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into())); - Err(err) - } else if value.is_object() { - let obj = OwnedObjectRef::new(value)?; - if obj.is_promise()? { - self.eval( - r#" - // Values: - // - undefined: promise not finished - // - false: error ocurred, __promiseError is set. - // - true: finished, __promiseSuccess is set. - var __promiseResult = 0; - var __promiseValue = 0; - - var __resolvePromise = function(p) { - p - .then(value => { - __promiseResult = true; - __promiseValue = value; - }) - .catch(e => { - __promiseResult = false; - __promiseValue = e; - }); - } - "#, - )?; - - let global = self.global()?; - let resolver = global.property("__resolvePromise")?; + return Err(err); + } - // Call the resolver code that sets the result values once - // the promise resolves. - self.call_function(resolver, vec![obj.into_value()])?; + let (is_promise, value) = self.resolve_promise_prepare(value)?; + let mut running = true; - loop { - let flag = unsafe { - let wrapper_mut = self as *const Self as *mut Self; - let ctx_mut = &mut (*wrapper_mut).context; - q::JS_ExecutePendingJob(self.runtime, ctx_mut) - }; - if flag < 0 { - let e = self.get_exception().unwrap_or_else(|| { - ExecutionError::Exception("Unknown exception".into()) - }); - return Err(e); - } + while running { + running = self.await_timers(); + let flag = unsafe { + let ctx_mut = NonNull::new_unchecked(*&self.context); + let ctx_mut = &mut ctx_mut.as_ptr(); + q::JS_ExecutePendingJob(self.runtime, ctx_mut) + }; + if flag < 0 { + let e = self + .get_exception() + .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into())); + return Err(e); + } else if flag > 0 { + running = true; + continue; + } - // Check if promise is finished. - let res_val = global.property("__promiseResult")?; - if res_val.is_bool() { - let ok = res_val.to_bool()?; - let value = global.property("__promiseValue")?; + // flag==0 means no progress. Check timers and if the return value promise has resolved - if ok { - return self.resolve_value(value); - } else { - let err_msg = value.to_string()?; - return Err(ExecutionError::Exception(JsValue::String(err_msg))); - } + if is_promise { + let global = self.global()?; + // Check if promise is finished. + let res_val = global.property("__promiseResult")?; + if res_val.is_bool() { + let ok = res_val.to_bool()?; + let value = global.property("__promiseValue")?; + + if ok { + return self.resolve_value(value); + } else { + let err_msg = value.to_string()?; + return Err(ExecutionError::Exception(JsValue::String(err_msg))); } } - } else { - Ok(obj.into_value()) } - } else { - Ok(value) } + + Ok(value) } /// Evaluate javascript code. - pub fn eval<'a>(&'a self, code: &str) -> Result, ExecutionError> { - let filename = "script.js"; - let filename_c = make_cstring(filename)?; + pub fn eval( + &self, + code: &str, + compile_only: bool, + module: bool, + ) -> Result { + let filename_c = make_cstring(&self.filename_hint[..])?; let code_c = make_cstring(code)?; - let value_raw = unsafe { - q::JS_Eval( + let mut flags = 0; + if module { + flags |= q::JS_EVAL_TYPE_MODULE as i32 + } + if compile_only { + flags |= q::JS_EVAL_FLAG_COMPILE_ONLY as i32 + } + let ctx = self.context; + if ctx.is_null() { + return Err(ExecutionError::InputWithZeroBytes); + } + let value_raw = + unsafe { q::JS_Eval(ctx, code_c.as_ptr(), code.len(), filename_c.as_ptr(), flags) }; + let value = OwnedValueRef::wrap(self.context, value_raw); + if compile_only { + if value.is_exception() { + let err = self + .get_exception() + .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into())); + return Err(err); + } + if (module || compile_only) && value.value.tag != TAG_MODULE { + return Err(ExecutionError::Internal(format!( + "Expected module value. Got {:?}", + value + ))); + } + Ok(value) + } else { + self.resolve_value(value) + } + } + + /// Converts a QuickJS value into bytecode. Called by compile(). + pub(crate) fn value_to_bytecode( + &self, + value: OwnedValueRef, + ) -> Result, ExecutionError> { + let raw_value = unsafe { + let mut len = 0; + let buf = q::JS_WriteObject( self.context, - code_c.as_ptr(), + &mut len, + value.value.clone(), + q::JS_WRITE_OBJ_BYTECODE as i32, + ); + let data = slice::from_raw_parts::(buf, len).to_vec(); + q::js_free(self.context, buf as *mut std::ffi::c_void); + data + }; + Ok(raw_value) + } + + /// This method is similar to [`eval()`] but executes byte code, produced by [`compile()`] instead. + pub fn run_bytecode(&self, code: &[u8]) -> Result { + /// allow function/module + const BYTECODE: u32 = q::JS_READ_OBJ_BYTECODE; + /// avoid duplicating 'buf' data + const ROM_DATA: u32 = q::JS_READ_OBJ_ROM_DATA; + + let raw_value = unsafe { + q::JS_ReadObject( + self.context, + code.as_ptr(), code.len(), - filename_c.as_ptr(), - q::JS_EVAL_TYPE_GLOBAL as i32, + (BYTECODE | ROM_DATA) as i32, ) }; - let value = OwnedValueRef::new(self, value_raw); + + if raw_value.tag != TAG_MODULE { + let value = OwnedValueRef::wrap(self.context, raw_value); + return Err(ExecutionError::Internal(format!( + "Expected module value. Got {:?}", + value + ))); + } + + let raw_value = unsafe { + if q::JS_ResolveModule(self.context, raw_value) != 0 { + let err = self + .get_exception() + .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into())); + return Err(err); + } + if q::js_module_set_import_meta(self.context, raw_value, 0, 1) != 0 { + let err = self + .get_exception() + .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into())); + return Err(err); + } + q::JS_EvalFunction(self.context, raw_value) + }; + + let value = OwnedValueRef::wrap(self.context, raw_value); self.resolve_value(value) } @@ -1079,12 +462,18 @@ impl ContextWrapper { } */ - /// Call a JS function with the given arguments. - pub fn call_function<'a>( - &'a self, - function: OwnedValueRef<'a>, - args: Vec>, - ) -> Result, ExecutionError> { + /// Call a JS function with the given arguments: + /// * `function` The function to call + /// * `args` The function arguments + /// * `resolve_value` Set to true if you want to resolve the returned value. + /// Never set this if called from within [`resolve_value`], it will deadlock. + /// Setting this is only really necessary if the returned value is a promise that should be awaited for. + pub fn call_function( + &self, + function: OwnedValueRef, + args: Vec, + resolve_value: bool, + ) -> Result { let mut qargs = args.iter().map(|arg| arg.value).collect::>(); let qres_raw = unsafe { @@ -1096,95 +485,11 @@ impl ContextWrapper { qargs.as_mut_ptr(), ) }; - let qres = OwnedValueRef::new(self, qres_raw); - self.resolve_value(qres) - } - - /// Helper for executing a callback closure. - fn exec_callback( - context: *mut q::JSContext, - argc: c_int, - argv: *mut q::JSValue, - callback: &impl Callback, - ) -> Result { - let result = std::panic::catch_unwind(|| { - let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) }; - - let args = arg_slice - .iter() - .map(|raw| deserialize_value(context, raw)) - .collect::, _>>()?; - - match callback.call(args) { - Ok(Ok(result)) => { - let serialized = serialize_value(context, result)?; - Ok(serialized) - } - // TODO: better error reporting. - Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))), - Err(e) => Err(e.into()), - } - }); - - match result { - Ok(r) => r, - Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())), - } - } - - /// Add a global JS function that is backed by a Rust function or closure. - pub fn create_callback<'a, F>( - &'a self, - callback: impl Callback + 'static, - ) -> Result { - let argcount = callback.argument_count() as i32; - - let context = self.context; - let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue { - match Self::exec_callback(context, argc, argv, &callback) { - Ok(value) => value, - // TODO: better error reporting. - Err(e) => { - let js_exception_value = match e { - ExecutionError::Exception(e) => e, - other => other.to_string().into(), - }; - let js_exception = serialize_value(context, js_exception_value).unwrap(); - unsafe { - q::JS_Throw(context, js_exception); - } - - q::JSValue { - u: q::JSValueUnion { int32: 0 }, - tag: TAG_EXCEPTION, - } - } - } - }; - - let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) }; - let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue; - self.callbacks.lock().unwrap().push(pair); - - let cfunc = - unsafe { q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data) }; - if cfunc.tag != TAG_OBJECT { - return Err(ExecutionError::Internal("Could not create callback".into())); - } - - Ok(cfunc) - } - - pub fn add_callback<'a, F>( - &'a self, - name: &str, - callback: impl Callback + 'static, - ) -> Result<(), ExecutionError> { - let cfunc = self.create_callback(callback)?; - let global = self.global()?; - unsafe { - global.set_property_raw(name, cfunc)?; + let qres = OwnedValueRef::wrap(self.context, qres_raw); + if resolve_value { + self.resolve_value(qres) + } else { + Ok(qres) } - Ok(()) } } diff --git a/src/callback.rs b/src/callback.rs index 765ad27..ec5c4f2 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,6 +1,13 @@ use std::{convert::TryFrom, marker::PhantomData, panic::RefUnwindSafe}; +use std::os::raw::c_int; +use std::ffi::c_void; +use crate::bindings::{ContextWrapper, TAG_EXCEPTION, TAG_NULL, TAG_OBJECT}; use crate::value::{JsValue, ValueError}; +use crate::ExecutionError; +use crate::marshal::{deserialize_value, serialize_value}; + +use libquickjs_sys as q; pub trait IntoCallbackResult { fn into_callback_res(self) -> Result; @@ -30,7 +37,7 @@ pub trait Callback: RefUnwindSafe { /// /// Should return: /// - Err(_) if the JS values could not be converted - /// - Ok(Err(_)) if an error ocurred while processing. + /// - Ok(Err(_)) if an error occurred while processing. /// The given error will be raised as a JS exception. /// - Ok(Ok(result)) when execution succeeded. fn call(&self, args: Vec) -> Result, ValueError>; @@ -141,157 +148,135 @@ where } } -// Implement Callback for Fn() -> R functions. -//impl Callback> for F -//where -//R: Into, -//F: Fn() -> R + Sized + RefUnwindSafe, -//{ -//fn argument_count(&self) -> usize { -//0 -//} - -//fn call(&self, args: Vec) -> Result, ValueError> { -//if !args.is_empty() { -//return Ok(Err(format!( -//"Invalid argument count: Expected 0, got {}", -//args.len() -//))); -//} - -//let res = self().into(); -//Ok(Ok(res)) -//} -//} - -// Implement Callback for Fn(A) -> R functions. -//impl Callback> for F -//where -//A1: TryFrom, -//R: Into, -//F: Fn(A1) -> R + Sized + RefUnwindSafe, -//{ -//fn argument_count(&self) -> usize { -//1 -//} -//fn call(&self, args: Vec) -> Result, ValueError> { -//if args.len() != 1 { -//return Ok(Err(format!( -//"Invalid argument count: Expected 1, got {}", -//args.len() -//))); -//} - -//let arg_raw = args.into_iter().next().expect("Invalid argument count"); -//let arg = A1::try_from(arg_raw)?; -//let res = self(arg).into(); -//Ok(Ok(res)) -//} -//} - -//// Implement Callback for Fn(A1, A2) -> R functions. -//impl Callback> for F -//where -//A1: TryFrom, -//A2: TryFrom, -//R: Into, -//F: Fn(A1, A2) -> R + Sized + RefUnwindSafe, -//{ -//fn argument_count(&self) -> usize { -//2 -//} - -//fn call(&self, args: Vec) -> Result, ValueError> { -//if args.len() != 2 { -//return Ok(Err(format!( -//"Invalid argument count: Expected 2, got {}", -//args.len() -//))); -//} - -//let mut iter = args.into_iter(); -//let arg1_raw = iter.next().expect("Invalid argument count"); -//let arg1 = A1::try_from(arg1_raw)?; - -//let arg2_raw = iter.next().expect("Invalid argument count"); -//let arg2 = A2::try_from(arg2_raw)?; - -//let res = self(arg1, arg2).into(); -//Ok(Ok(res)) -//} -//} - -// Implement Callback for Fn(A1, A2, A3) -> R functions. -//impl Callback> for F -//where -//A1: TryFrom, -//A2: TryFrom, -//A3: TryFrom, -//R: Into, -//F: Fn(A1, A2, A3) -> R + Sized + RefUnwindSafe, -//{ -//fn argument_count(&self) -> usize { -//3 -//} - -//fn call(&self, args: Vec) -> Result, ValueError> { -//if args.len() != self.argument_count() { -//return Ok(Err(format!( -//"Invalid argument count: Expected 3, got {}", -//args.len() -//))); -//} - -//let mut iter = args.into_iter(); -//let arg1_raw = iter.next().expect("Invalid argument count"); -//let arg1 = A1::try_from(arg1_raw)?; - -//let arg2_raw = iter.next().expect("Invalid argument count"); -//let arg2 = A2::try_from(arg2_raw)?; - -//let arg3_raw = iter.next().expect("Invalid argument count"); -//let arg3 = A3::try_from(arg3_raw)?; - -//let res = self(arg1, arg2, arg3).into(); -//Ok(Ok(res)) -//} -//} - -//// Implement Callback for Fn(A1, A2, A3, A4) -> R functions. -//impl Callback> for F -//where -//A1: TryFrom, -//A2: TryFrom, -//A3: TryFrom, -//A4: TryFrom, -//R: Into, -//F: Fn(A1, A2, A3) -> R + Sized + RefUnwindSafe, -//{ -//fn argument_count(&self) -> usize { -//4 -//} - -//fn call(&self, args: Vec) -> Result, ValueError> { -//if args.len() != self.argument_count() { -//return Ok(Err(format!( -//"Invalid argument count: Expected 3, got {}", -//args.len() -//))); -//} - -//let mut iter = args.into_iter(); -//let arg1_raw = iter.next().expect("Invalid argument count"); -//let arg1 = A1::try_from(arg1_raw)?; - -//let arg2_raw = iter.next().expect("Invalid argument count"); -//let arg2 = A2::try_from(arg2_raw)?; - -//let arg3_raw = iter.next().expect("Invalid argument count"); -//let arg3 = A3::try_from(arg3_raw)?; - -//let res = self(arg1, arg2, arg3).into(); -//Ok(Ok(res)) -//} -//} - -// RESULT variants. +impl ContextWrapper { + /// Helper for executing a callback closure. + fn exec_callback( + context: *mut q::JSContext, + argc: c_int, + argv: *mut q::JSValue, + callback: &impl Callback, + ) -> Result { + let result = std::panic::catch_unwind(|| { + let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) }; + + let args = arg_slice + .iter() + .map(|raw| deserialize_value(context, raw)) + .collect::, _>>()?; + + match callback.call(args) { + Ok(Ok(result)) => { + let serialized = serialize_value(context, result)?; + Ok(serialized) + } + // TODO: better error reporting. + Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))), + Err(e) => Err(e.into()), + } + }); + + match result { + Ok(r) => r, + Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())), + } + } + + /// Add a global JS function that is backed by a Rust function or closure. + pub fn create_callback( + &self, + callback: impl Callback + 'static, + ) -> Result { + let argcount = callback.argument_count() as i32; + + let context = self.context; + let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue { + match Self::exec_callback(context, argc, argv, &callback) { + Ok(value) => value, + // TODO: better error reporting. + Err(e) => { + let js_exception_value = match e { + ExecutionError::Exception(e) => e, + other => other.to_string().into(), + }; + let js_exception = serialize_value(context, js_exception_value).unwrap(); + unsafe { + q::JS_Throw(context, js_exception); + } + + q::JSValue { + u: q::JSValueUnion { int32: 0 }, + tag: TAG_EXCEPTION, + } + } + } + }; + + let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) }; + let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue; + self.callbacks.lock().unwrap().push(pair); + + let cfunc = + unsafe { q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data) }; + if cfunc.tag != TAG_OBJECT { + return Err(ExecutionError::Internal("Could not create callback".into())); + } + + Ok(cfunc) + } + + pub fn add_callback<'a, F>( + &'a self, + name: &str, + callback: impl Callback + 'static, + ) -> Result<(), ExecutionError> { + let cfunc = self.create_callback(callback)?; + let global = self.global()?; + unsafe { + global.set_property_raw(name, cfunc)?; + } + Ok(()) + } +} + +pub type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue; + +/// Taken from: https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html +/// +/// Create a C wrapper function for a Rust closure to enable using it as a +/// callback function in the Quickjs runtime. +/// +/// Both the boxed closure and the boxed data are returned and must be stored +/// by the caller to guarantee they stay alive. +unsafe fn build_closure_trampoline( + closure: F, +) -> ((Box, Box), q::JSCFunctionData) +where + F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static, +{ + unsafe extern "C" fn trampoline( + _ctx: *mut q::JSContext, + _this: q::JSValue, + argc: c_int, + argv: *mut q::JSValue, + _magic: c_int, + data: *mut q::JSValue, + ) -> q::JSValue + where + F: Fn(c_int, *mut q::JSValue) -> q::JSValue, + { + let closure_ptr = (*data).u.ptr; + let closure: &mut F = &mut *(closure_ptr as *mut F); + (*closure)(argc, argv) + } + + let boxed_f = Box::new(closure); + + let data = Box::new(q::JSValue { + u: q::JSValueUnion { + ptr: (&*boxed_f) as *const F as *mut c_void, + }, + tag: TAG_NULL, + }); + + ((boxed_f, data), Some(trampoline::)) +} diff --git a/src/console.rs b/src/console.rs index c8eecd2..98e611c 100644 --- a/src/console.rs +++ b/src/console.rs @@ -104,6 +104,7 @@ mod log { JsValue::Date(v) => v.to_string(), #[cfg(feature = "bigint")] JsValue::BigInt(v) => v.to_string(), + _ => String::new(), } } diff --git a/src/droppable_value.rs b/src/droppable_value.rs deleted file mode 100644 index 31cd198..0000000 --- a/src/droppable_value.rs +++ /dev/null @@ -1,47 +0,0 @@ -/// A small wrapper that frees resources that have to be freed -/// automatically when they go out of scope. -pub struct DroppableValue -where - F: FnMut(&mut T), -{ - value: T, - drop_fn: F, -} - -impl DroppableValue -where - F: FnMut(&mut T), -{ - pub fn new(value: T, drop_fn: F) -> Self { - Self { value, drop_fn } - } -} - -impl Drop for DroppableValue -where - F: FnMut(&mut T), -{ - fn drop(&mut self) { - (self.drop_fn)(&mut self.value); - } -} - -impl std::ops::Deref for DroppableValue -where - F: FnMut(&mut T), -{ - type Target = T; - - fn deref(&self) -> &T { - &self.value - } -} - -impl std::ops::DerefMut for DroppableValue -where - F: FnMut(&mut T), -{ - fn deref_mut(&mut self) -> &mut T { - &mut self.value - } -} diff --git a/src/lib.rs b/src/lib.rs index a30287b..464838d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,13 +36,17 @@ mod bindings; mod callback; +mod modules; pub mod console; -mod droppable_value; +mod owned_value_ref; +mod timers; +mod utils; mod value; use std::{convert::TryFrom, error, fmt}; pub use callback::{Arguments, Callback}; +use std::path::Path; pub use value::*; /// Error on Javascript execution. @@ -115,6 +119,7 @@ impl error::Error for ContextError {} /// /// Create with [Context::builder](Context::builder). pub struct ContextBuilder { + enable_timer_api: bool, memory_limit: Option, console_backend: Option>, } @@ -122,11 +127,19 @@ pub struct ContextBuilder { impl ContextBuilder { fn new() -> Self { Self { + enable_timer_api: false, memory_limit: None, console_backend: None, } } + /// Enables the Timer API: setTimeout, clearTimeout + pub fn enable_timer_api(self) -> Self { + let mut s = self; + s.enable_timer_api = true; + s + } + /// Sets the memory limit of the Javascript runtime (in bytes). /// /// If the limit is exceeded, methods like `eval` will return @@ -158,6 +171,11 @@ impl ContextBuilder { if let Some(be) = self.console_backend { wrapper.set_console(be).map_err(ContextError::Execution)?; } + if self.enable_timer_api { + wrapper + .enable_timer_api() + .map_err(ContextError::Execution)?; + } Ok(Context::from_wrapper(wrapper)) } } @@ -236,11 +254,35 @@ impl Context { /// ); /// ``` pub fn eval(&self, code: &str) -> Result { - let value_raw = self.wrapper.eval(code)?; + let value_raw = self.wrapper.eval(code, false, false)?; let value = value_raw.to_value()?; Ok(value) } + /// QuickJS error reporting includes a script filename. + /// Set the file name to be reported here. + pub fn set_filename_hint(&mut self, filename: String) { + self.wrapper.set_filename_hint(filename); + } + + /// Compile + pub fn compile(&self, code: &str, is_module: bool) -> Result, ExecutionError> { + let value_raw = self.wrapper.eval(code, true, is_module)?; + self.wrapper.value_to_bytecode(value_raw) + } + + /// Run compiled byte code + pub fn run_bytecode(&self, code: &[u8]) -> Result { + let value_raw = self.wrapper.run_bytecode(code)?; + let value = value_raw.to_value()?; + Ok(value) + } + + /// Set the module loader to use the given path for loading modules. + pub fn set_module_loader_with_path(&mut self, path: &Path) { + self.wrapper.set_module_loader_with_path(path) + } + /// Evaluates Javascript code and returns the value of the final expression /// as a Rust type. /// @@ -251,7 +293,7 @@ impl Context { /// promise failed. /// /// ```rust - /// use quick_js::{Context}; + /// use quick_js::Context; /// let context = Context::new().unwrap(); /// /// let res = context.eval_as::(" 100 > 10 "); @@ -271,7 +313,7 @@ impl Context { R: TryFrom, R::Error: Into, { - let value_raw = self.wrapper.eval(code)?; + let value_raw = self.wrapper.eval(code, false, false)?; let value = value_raw.to_value()?; let ret = R::try_from(value).map_err(|e| e.into())?; Ok(ret) @@ -315,7 +357,10 @@ impl Context { ))); } - let value = self.wrapper.call_function(func_obj, qargs)?.to_value()?; + let value = self + .wrapper + .call_function(func_obj, qargs, true)? + .to_value()?; Ok(value) } @@ -482,7 +527,7 @@ mod tests { f(); "# ), - Err(ExecutionError::Exception("Error: My Error".into(),)) + Err(ExecutionError::Exception("Error: My Error".into())) ); } diff --git a/src/modules.rs b/src/modules.rs new file mode 100644 index 0000000..c1857bd --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,107 @@ +//! # Support for js modules. +//! Javascript files (.js, .jsm) are supported as modules. +//! Files must be referenced as relative paths to the path set with [`set_module_loader_with_path`]. +//! +//! The QuickJS documentation clearly states that the bytecode that could be used for +//! binary module files is not stable. +//! +//! Implementation note: This is implemented via QuickJS-libc. +use crate::bindings::ContextWrapper; +use std::path::Path; +use std::ffi::c_void; +use std::os::raw::c_char; +use std::ptr::NonNull; +use std::io::ErrorKind; + +use libquickjs_sys as q; + +impl ContextWrapper{ + /// Sets a custom module loader. + /// + /// The loader will use the given path as root path to resolve files. + /// In your scripts you must reference other files via relative paths. + /// + /// # Example + /// `import {a_string} from './test.js';` + pub fn set_module_loader_with_path(&mut self, path: &Path) { + self.module_load_path = path.to_path_buf(); + + let runtime_ptr = self.runtime.clone(); + let context_ptr = NonNull::from(self); + unsafe { + q::JS_SetModuleLoaderFunc( + runtime_ptr, + None, + Some(jsc_module_loader), + context_ptr.as_ptr() as *mut c_void, + ) + } + } +} + +unsafe extern "C" fn jsc_module_loader( + _ctx: *mut q::JSContext, + module_name: *const c_char, + opaque: *mut c_void, +) -> *mut q::JSModuleDef { + use std::ffi::{CStr, OsStr}; + use std::os::unix::ffi::OsStrExt; + // Safe: We can expect the C-API to hand us a valid module name c-string. + let module_name = Path::new(OsStr::from_bytes(CStr::from_ptr(module_name).to_bytes())); + + let context_wrapper = opaque as *mut ContextWrapper; + let context_wrapper = match NonNull::new(context_wrapper) { + Some(v) => v, + None => { + eprintln!("load module failed: {:?}", module_name); + return std::ptr::null_mut(); + } + }; + let context_wrapper = context_wrapper.as_ref(); + let path = context_wrapper.module_load_path.join(module_name); + println!("load module: {:?} from {:?}", module_name, path); + + let module_code = std::fs::read_to_string(path).and_then(|code| { + context_wrapper + .eval(&code, true, true) + .map_err(|e| std::io::Error::new(ErrorKind::Other, e.to_string())) + }); + match module_code { + Err(e) => { + eprintln!("{}", e); + return std::ptr::null_mut(); + } + Ok(code) => { + let p = code.value; + q::js_module_set_import_meta(context_wrapper.context, p, 0, 0); + + // Note: code will go out of scope and the ref-count decreases. + // Safe: const to mut cast. The engine runs in a single thread, as long as that is the case, the cast + // is safe. We reuse a module if it is referenced multiple times in different script files and the internal + // module "state" will be shared, which is absolutely fine. + let p = p.u.ptr as *mut q::JSModuleDef; + return p; + } + } +} + +/* +// To avoid QuickJS-libc, module book keeping could be done manually + +type ModuleInit = dyn Fn(*mut q::JSContext, *mut q::JSModuleDef); + +thread_local! { + static NATIVE_MODULE_INIT: RefCell>> = RefCell::new(None); +} + +unsafe extern "C" fn native_module_init( + ctx: *mut q::JSContext, + m: *mut q::JSModuleDef, +) -> ::std::os::raw::c_int { + NATIVE_MODULE_INIT.with(|init| { + let init = init.replace(None).unwrap(); + init(ctx, m); + }); + 0 +} +*/ diff --git a/src/owned_value_ref.rs b/src/owned_value_ref.rs new file mode 100644 index 0000000..7dbd6fe --- /dev/null +++ b/src/owned_value_ref.rs @@ -0,0 +1,271 @@ +use crate::bindings::{TAG_INVALID, TAG_NULL, TAG_BOOL, TAG_INT, TAG_FLOAT64, TAG_EXCEPTION, TAG_OBJECT, TAG_STRING, TAG_UNDEFINED}; +use crate::utils::{free_value, make_cstring}; +use crate::{ExecutionError, JsValue, ValueError}; +use libquickjs_sys as q; +use crate::marshal::deserialize_value; + +/// OwnedValueRef wraps a Javascript value from the quickjs runtime. +/// It prevents leaks by ensuring that the inner value is deallocated on drop. +pub struct OwnedValueRef { + pub(crate) context: *mut q::JSContext, + pub(crate) value: q::JSValue, +} + +// OwnedValueRef is NOT thread-safe, because q::JSValue is not. +// Send+Sync impl is required because quick_js::ContextError must be thread-safe and is allowed to carry a JsValue. +// A JsValue may carry an OwnedValueRef for wrapping a js-function. +// quick_js::ContextError will never print or access the OpaqueFunction enum option of JsValue though, +// therefore this is safe. +unsafe impl Send for OwnedValueRef {} + +unsafe impl Sync for OwnedValueRef {} + +impl Drop for OwnedValueRef { + fn drop(&mut self) { + unsafe { + if self.value.tag != TAG_INVALID { + free_value(self.context, self.value); + } + } + } +} + +impl Clone for OwnedValueRef { + fn clone(&self) -> Self { + // All tags < 0 are garbage collected and the reference count need to be adapted + if self.value.tag < 0 { + // This transmute is OK since if tag < 0, the union will be a refcount pointer. + let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader }; + let pref: &mut q::JSRefCountHeader = unsafe { &mut *ptr }; + pref.ref_count += 1; + } + return OwnedValueRef { + context: self.context, + value: self.value, + }; + } +} + +impl std::fmt::Debug for OwnedValueRef { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self.value.tag { + TAG_EXCEPTION => write!(f, "Exception(?)"), + TAG_NULL => write!(f, "NULL"), + TAG_UNDEFINED => write!(f, "UNDEFINED"), + TAG_BOOL => write!(f, "Bool(?)", ), + TAG_INT => write!(f, "Int(?)"), + TAG_FLOAT64 => write!(f, "Float(?)"), + TAG_STRING => write!(f, "String(?)"), + TAG_OBJECT => write!(f, "Object(?)"), + _ => write!(f, "?"), + } + } +} + +impl PartialEq for OwnedValueRef { + fn eq(&self, other: &Self) -> bool { + self.value.tag == other.value.tag && unsafe { self.value.u.int32 == other.value.u.int32 } + } +} + +impl OwnedValueRef { + /// Wrap a QuickJS JSValue in an owned reference. + /// + /// For reference counted JSValues: + /// The reference count is not changed while wrapping. + /// It will decrease however when dropping this wrapper (freeing the value if necessary). + pub fn wrap(context: *mut q::JSContext, value: q::JSValue) -> Self { + OwnedValueRef { + context, + value, + } + } + /// Increases the reference count of the given value (if any) and therefore fully + /// own the value. + pub fn owned(context: *mut q::JSContext, value: q::JSValue) -> Self { + // All tags < 0 are garbage collected and need to be freed. + if value.tag < 0 { + // This transmute is OK since if tag < 0, the union will be a refcount + // pointer. + let ptr = unsafe { value.u.ptr as *mut q::JSRefCountHeader }; + let pref: &mut q::JSRefCountHeader = unsafe { &mut *ptr }; + pref.ref_count += 1; + } + OwnedValueRef { + context, + value, + } + } + + /// Get the inner JSValue without freeing in drop. + /// + /// Unsafe because the caller is responsible for freeing the value. + #[allow(dead_code)] + unsafe fn into_inner(mut self) -> q::JSValue { + let v = self.value; + self.value = q::JSValue { + u: q::JSValueUnion { int32: 0 }, + tag: TAG_INVALID, + }; + v + } + + /// Return the reference count value. Useful for debugging + #[allow(dead_code)] + pub fn ref_count(&self) -> i32 { + if self.value.tag < 0 { + // This transmute is OK since if tag < 0, the union will be a refcount + // pointer. + let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader }; + let pref: &mut q::JSRefCountHeader = unsafe { &mut *ptr }; + pref.ref_count + } else { + 0 + } + } + + /// Return true if this is a null value + pub fn is_null(&self) -> bool { + self.value.tag == TAG_NULL + } + + /// Return true if this is a boolean value + pub fn is_bool(&self) -> bool { + self.value.tag == TAG_BOOL + } + + /// Return true if this is an exception + pub fn is_exception(&self) -> bool { + self.value.tag == TAG_EXCEPTION + } + + /// Return true if this is an object + pub fn is_object(&self) -> bool { + self.value.tag == TAG_OBJECT + } + + /// Return true if this is a string value + pub fn is_string(&self) -> bool { + self.value.tag == TAG_STRING + } + + /// Return the string value or convert the value to a string and return it + pub fn to_string(&self) -> Result { + let value = if self.is_string() { + self.to_value()? + } else { + let raw = unsafe { q::JS_ToString(self.context, self.value) }; + let value = OwnedValueRef::wrap(self.context, raw); + + if value.value.tag != TAG_STRING { + return Err(ExecutionError::Exception( + "Could not convert value to string".into(), + )); + } + value.to_value()? + }; + + Ok(value.as_str().unwrap().to_string()) + } + + /// Deserialize the inner QuickJS JSValue into a Rust type JsValue + pub fn to_value(&self) -> Result { + deserialize_value(self.context, &self.value) + } + + /// Return the boolean value or an error + pub fn to_bool(&self) -> Result { + match self.to_value()? { + JsValue::Bool(b) => Ok(b), + _ => Err(ValueError::UnexpectedType), + } + } +} + +/// Wraps an object from the quickjs runtime. +/// Provides convenience property accessors. +pub struct OwnedObjectRef { + value: OwnedValueRef, +} + +impl OwnedObjectRef { + pub fn new(value: OwnedValueRef) -> Result { + if value.value.tag != TAG_OBJECT { + Err(ValueError::Internal("Expected an object".into())) + } else { + Ok(Self { value }) + } + } + + pub(crate) fn into_value(self) -> OwnedValueRef { + self.value + } + + /// Get the tag of a property. + fn property_tag(&self, name: &str) -> Result { + let cname = make_cstring(name)?; + let raw = unsafe { + q::JS_GetPropertyStr(self.value.context, self.value.value, cname.as_ptr()) + }; + let t = raw.tag; + unsafe { + free_value(self.value.context, raw); + } + Ok(t) + } + + /// Determine if the object is a promise by checking the presence of + /// a 'then' and a 'catch' property. + pub(crate) fn is_promise(&self) -> Result { + if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT { + Ok(true) + } else { + Ok(false) + } + } + + pub fn property(&self, name: &str) -> Result { + let cname = make_cstring(name)?; + let raw = unsafe { + q::JS_GetPropertyStr(self.value.context, self.value.value, cname.as_ptr()) + }; + + if raw.tag == TAG_EXCEPTION { + Err(ExecutionError::Internal(format!( + "Exception while getting property '{}'", + name + ))) + } else if raw.tag == TAG_UNDEFINED { + Err(ExecutionError::Internal(format!( + "Property '{}' not found", + name + ))) + } else { + Ok(OwnedValueRef::wrap(self.value.context, raw)) + } + } + + pub(crate) unsafe fn set_property_raw( + &self, + name: &str, + value: q::JSValue, + ) -> Result<(), ExecutionError> { + let cname = make_cstring(name)?; + let ret = q::JS_SetPropertyStr( + self.value.context, + self.value.value, + cname.as_ptr(), + value, + ); + if ret < 0 { + Err(ExecutionError::Exception("Could not set property".into())) + } else { + Ok(()) + } + } + + // pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> { + // let qval = self.value.context.serialize_value(value)?; + // unsafe { self.set_property_raw(name, qval.value) } + // } +} diff --git a/src/timers.rs b/src/timers.rs new file mode 100644 index 0000000..ca1e78e --- /dev/null +++ b/src/timers.rs @@ -0,0 +1,143 @@ +//! # JS Timer API support (setTimeout, clearTimeout) +use crate::bindings::ContextWrapper; +use crate::{Arguments, ExecutionError, JsValue}; +use std::ops::DerefMut; +use crate::owned_value_ref::OwnedValueRef; + +/// A js timer has an expire time, a callback function and a reference to the next timer (linked list). +pub struct JsTimer { + /// Unix time in milliseconds + expire: i64, + /// A function will be taken out of this option as soon as it is about to be executed + value: Option, + /// Pointer to the next timer + next: JsTimerRef, +} + +pub type JsTimerRef = Option>; + +impl JsTimer { + pub(crate) fn new(func: OwnedValueRef, timeout_ms: i32) -> Self { + JsTimer { + expire: chrono::Utc::now().timestamp_millis() + timeout_ms as i64, + value: Some(func), + next: None, + } + } + pub(crate) fn get_next(&mut self) -> &mut JsTimerRef { + &mut self.next + } +} + +impl ContextWrapper { + /// Enables the timer API setTimeout and clearTimeout. + /// Execution of [`eval`] and [`run_bytecode`] will only finish after all timers have finished. + /// You may forcefully quit all timers with [`cancel_all_timers`] + pub fn enable_timer_api(&self) -> Result<(), ExecutionError> { + let timers = self.timers.clone(); + self.add_callback("clearTimeout", move |args: Arguments| { + let mut args = args.into_vec(); + if args.len() != 1 { + return Err("Expect 1 arguments!".to_owned()); + } + if let JsValue::Int(timer_id) = args.remove(0) { + let mut timer_root = timers.lock().unwrap(); + timer_iter( + timer_root.deref_mut(), + // For each matching timer + |v| v.expire == timer_id as i64, + // Do nothing. The entry will be removed by "timer_iter" + |_| {}, + ); + return Ok(JsValue::Null); + } + return Err("First argument must be a number!".to_owned()); + })?; + let timers = self.timers.clone(); + self.add_callback("setTimeout", move |args: Arguments| { + let mut args = args.into_vec(); + if args.len() != 2 { + return Err("Expect 2 arguments!".to_owned()); + } + match (args.remove(1), args.remove(0)) { + (JsValue::Int(timeout_ms), JsValue::OpaqueFunction(func)) => { + // Go to the very last item and add a new TimerRef + let mut timer_root = timers.lock().unwrap(); + let mut timer = timer_root.deref_mut(); + let timer_next = loop { + timer = match timer { + Some(v) => &mut v.next, + None => break timer, + }; + }; + let next_timer = JsTimer::new(func, timeout_ms); + // The expire time will be the id + let id = next_timer.expire; + timer_next.replace(Box::new(next_timer)); + + Ok(JsValue::Int(id as i32)) + } + _ => Err("Arguments invalid".to_owned()), + } + })?; + Ok(()) + } + + /// Stop all timers. + pub fn cancel_all_timers(&self) { + let mut timer = self.timers.lock().unwrap(); + // This will recursively drop() all timer values + let _ = timer.take(); + } + + pub(crate) fn await_timers(&self) -> bool { + let now = chrono::Utc::now().timestamp_millis(); + let mut timer_root = self.timers.lock().unwrap(); + timer_iter( + timer_root.deref_mut(), + // For each expired timer + |v| v.expire < now, + // Call the respective callback function + |value| { + let result = self.call_function(value, Vec::new(), false); + if let Err(e) = result { + eprintln!("{:?}", e); + } + }, + ); + + timer_root.is_some() + } +} + +/// Timers are basically structured in a linked list. +/// Looping through a linked list in Rust requires some thoughts to satisfy the borrow checker. +/// If mutability (removing) is required, the next timer link, which is an Option> is taken +/// and then reassigned again for each loop iteration (next_timer_option.take() + *next_timer_option = v). +fn timer_iter(mut next_timer_option: &mut JsTimerRef, condition: C, exec: E) +where + C: Fn(&Box) -> bool, + E: Fn(OwnedValueRef) -> (), +{ + loop { + match next_timer_option.take() { + Some(mut v) if condition(&v) => { + if let Some(value) = v.value.take() { + exec(value) + } + let v = v.next; + *next_timer_option = v; + next_timer_option = match next_timer_option.as_mut() { + Some(v) => v.get_next(), + None => break, + }; + } + Some(v) => { + *next_timer_option = Some(v); + // Unwrap is safe, see line above + next_timer_option = next_timer_option.as_mut().unwrap().get_next(); + } + None => break, + }; + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..54da0a5 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,74 @@ +use std::ffi::CString; + +use crate::bindings::{TAG_NULL, TAG_OBJECT}; +use crate::ValueError; + +use libquickjs_sys as q; + +/// Free a JSValue. +/// This function is the equivalent of JS_FreeValue from quickjs, which can not +/// be used due to being `static inline`. +pub unsafe fn free_value(context: *mut q::JSContext, value: q::JSValue) { + // All tags < 0 are garbage collected and need to be freed. + if value.tag < 0 { + // This transmute is OK since if tag < 0, the union will be a refcount + // pointer. + let ptr = value.u.ptr as *mut q::JSRefCountHeader; + let pref: &mut q::JSRefCountHeader = &mut *ptr; + pref.ref_count -= 1; + if pref.ref_count <= 0 { + q::__JS_FreeValue(context, value); + } + } +} + +#[cfg(feature = "chrono")] +pub fn js_date_constructor(context: *mut q::JSContext) -> q::JSValue { + let global = unsafe { q::JS_GetGlobalObject(context) }; + assert_eq!(global.tag, TAG_OBJECT); + + let date_constructor = unsafe { + q::JS_GetPropertyStr( + context, + global, + std::ffi::CStr::from_bytes_with_nul(b"Date\0") + .unwrap() + .as_ptr(), + ) + }; + assert_eq!(date_constructor.tag, TAG_OBJECT); + unsafe { free_value(context, global) }; + date_constructor +} + +#[cfg(feature = "bigint")] +pub fn js_create_bigint_function(context: *mut q::JSContext) -> q::JSValue { + let global = unsafe { q::JS_GetGlobalObject(context) }; + assert_eq!(global.tag, TAG_OBJECT); + + let bigint_function = unsafe { + q::JS_GetPropertyStr( + context, + global, + std::ffi::CStr::from_bytes_with_nul(b"BigInt\0") + .unwrap() + .as_ptr(), + ) + }; + assert_eq!(bigint_function.tag, TAG_OBJECT); + unsafe { free_value(context, global) }; + bigint_function +} + +/// Helper for creating CStrings. +pub fn make_cstring(value: impl Into>) -> Result { + CString::new(value).map_err(ValueError::StringWithZeroBytes) +} + +/// Helper to construct null JsValue +pub fn js_null_value() -> q::JSValue { + q::JSValue { + u: q::JSValueUnion { int32: 0 }, + tag: TAG_NULL, + } +} diff --git a/src/value/marshal.rs b/src/value/marshal.rs new file mode 100644 index 0000000..b0b4c17 --- /dev/null +++ b/src/value/marshal.rs @@ -0,0 +1,499 @@ +//! # Serialize / Deserialize from/to a C-QuickJS JSValue and a Rust type JsValue +#[cfg(feature = "bigint")] +use crate::bigint::{BigInt, BigIntOrI64}; +#[cfg(feature = "bigint")] +use crate::bindings::TAG_BIG_INT; +use crate::bindings::{ + TAG_BOOL, TAG_EXCEPTION, TAG_FLOAT64, TAG_INT, TAG_NULL, TAG_OBJECT, TAG_STRING, TAG_UNDEFINED, +}; +use crate::owned_value_ref::OwnedValueRef; +use crate::utils::{free_value, make_cstring}; +#[cfg(feature = "chrono")] +use crate::utils::js_date_constructor; +#[cfg(feature = "bigint")] +use crate::utils::js_create_bigint_function; + +use crate::{JsValue, ValueError}; + +use libquickjs_sys as q; + +use std::collections::HashMap; +use std::os::raw::c_char; + +/// Serialize a Rust value into a quickjs runtime value. +pub fn serialize_value( + context: *mut q::JSContext, + value: JsValue, +) -> Result { + let v = match value { + JsValue::Null => q::JSValue { + u: q::JSValueUnion { int32: 0 }, + tag: TAG_NULL, + }, + JsValue::Bool(flag) => q::JSValue { + u: q::JSValueUnion { + int32: if flag { 1 } else { 0 }, + }, + tag: TAG_BOOL, + }, + JsValue::Int(val) => q::JSValue { + u: q::JSValueUnion { int32: val }, + tag: TAG_INT, + }, + JsValue::Float(val) => q::JSValue { + u: q::JSValueUnion { float64: val }, + tag: TAG_FLOAT64, + }, + JsValue::String(val) => { + let qval = + unsafe { q::JS_NewStringLen(context, val.as_ptr() as *const c_char, val.len()) }; + + if qval.tag == TAG_EXCEPTION { + return Err(ValueError::Internal( + "Could not create string in runtime".into(), + )); + } + + qval + } + JsValue::Array(values) => { + // Allocate a new array in the runtime. + let arr = unsafe { q::JS_NewArray(context) }; + if arr.tag == TAG_EXCEPTION { + return Err(ValueError::Internal( + "Could not create array in runtime".into(), + )); + } + + for (index, value) in values.into_iter().enumerate() { + let qvalue = match serialize_value(context, value) { + Ok(qval) => qval, + Err(e) => { + // Make sure to free the array if a individual element + // fails. + unsafe { + free_value(context, arr); + } + return Err(e); + } + }; + + let ret = unsafe { + q::JS_DefinePropertyValueUint32( + context, + arr, + index as u32, + qvalue, + q::JS_PROP_C_W_E as i32, + ) + }; + if ret < 0 { + // Make sure to free the array if a individual + // element fails. + unsafe { + free_value(context, arr); + } + return Err(ValueError::Internal( + "Could not append element to array".into(), + )); + } + } + arr + } + JsValue::Object(map) => { + let obj = unsafe { q::JS_NewObject(context) }; + if obj.tag == TAG_EXCEPTION { + return Err(ValueError::Internal("Could not create object".into())); + } + + for (key, value) in map { + let ckey = make_cstring(key)?; + + let qvalue = serialize_value(context, value).map_err(|e| { + // Free the object if a property failed. + unsafe { + free_value(context, obj); + } + e + })?; + + let ret = unsafe { + q::JS_DefinePropertyValueStr( + context, + obj, + ckey.as_ptr(), + qvalue, + q::JS_PROP_C_W_E as i32, + ) + }; + if ret < 0 { + // Free the object if a property failed. + unsafe { + free_value(context, obj); + } + return Err(ValueError::Internal( + "Could not add add property to object".into(), + )); + } + } + + obj + } + #[cfg(feature = "chrono")] + JsValue::Date(datetime) => { + let date_constructor = js_date_constructor(context); + + let f = datetime.timestamp_millis() as f64; + + let timestamp = q::JSValue { + u: q::JSValueUnion { float64: f }, + tag: TAG_FLOAT64, + }; + + let mut args = vec![timestamp]; + + let value = unsafe { + q::JS_CallConstructor( + context, + date_constructor, + args.len() as i32, + args.as_mut_ptr(), + ) + }; + unsafe { + free_value(context, date_constructor); + } + + if value.tag != TAG_OBJECT { + return Err(ValueError::Internal( + "Could not construct Date object".into(), + )); + } + value + } + #[cfg(feature = "bigint")] + JsValue::BigInt(int) => match int.inner { + BigIntOrI64::Int(int) => unsafe { q::JS_NewBigInt64(context, int) }, + BigIntOrI64::BigInt(bigint) => { + let bigint_string = bigint.to_str_radix(10); + let s = unsafe { + q::JS_NewStringLen( + context, + bigint_string.as_ptr() as *const c_char, + bigint_string.len(), + ) + }; + let s = OwnedValueRef::wrap(context, s); + if (*s).tag != TAG_STRING { + return Err(ValueError::Internal( + "Could not construct String object needed to create BigInt object".into(), + )); + } + + let mut args = vec![*s]; + + use crate::utils::js_null_value; + + let bigint_function = js_create_bigint_function(context); + let bigint_function = OwnedValueRef::wrap(context, bigint_function); + let js_bigint = unsafe { + q::JS_Call( + context, + *bigint_function, + js_null_value(), + 1, + args.as_mut_ptr(), + ) + }; + + if js_bigint.tag != TAG_BIG_INT { + return Err(ValueError::Internal( + "Could not construct BigInt object".into(), + )); + } + + js_bigint + } + }, + _ => unreachable!(), + }; + Ok(v) +} + +fn deserialize_array( + context: *mut q::JSContext, + raw_value: &q::JSValue, +) -> Result { + assert_eq!(raw_value.tag, TAG_OBJECT); + + let length_name = make_cstring("length")?; + + let len_raw = unsafe { q::JS_GetPropertyStr(context, *raw_value, length_name.as_ptr()) }; + + let len_res = deserialize_value(context, &len_raw); + unsafe { free_value(context, len_raw) }; + let len = match len_res? { + JsValue::Int(x) => x, + _ => { + return Err(ValueError::Internal( + "Could not determine array length".into(), + )); + } + }; + + let mut values = Vec::new(); + for index in 0..(len as usize) { + let value_raw = unsafe { q::JS_GetPropertyUint32(context, *raw_value, index as u32) }; + if value_raw.tag == TAG_EXCEPTION { + return Err(ValueError::Internal("Could not build array".into())); + } + let value_res = deserialize_value(context, &value_raw); + unsafe { free_value(context, value_raw) }; + + let value = value_res?; + values.push(value); + } + + Ok(JsValue::Array(values)) +} + + +/// A small wrapper around JSPropertyEnum, that frees resources that have to be freed +/// automatically when this goes out of scope. +pub struct OwnedPropertiesRef { + value: *mut q::JSPropertyEnum, + context: *mut q::JSContext, + count: u32, +} + +impl OwnedPropertiesRef { + pub fn new(obj: &q::JSValue, context: *mut q::JSContext) -> Result { + let mut value: *mut q::JSPropertyEnum = std::ptr::null_mut(); + let mut count: u32 = 0; + + let flags = (q::JS_GPN_STRING_MASK | q::JS_GPN_SYMBOL_MASK | q::JS_GPN_ENUM_ONLY) as i32; + let ret = + unsafe { q::JS_GetOwnPropertyNames(context, &mut value, &mut count, *obj, flags) }; + if ret != 0 { + return Err(ValueError::Internal( + "Could not get object properties".into(), + )); + } + + Ok(Self { value, context, count }) + } +} + +impl Drop for OwnedPropertiesRef { + fn drop(&mut self) { + let properties = &mut self.value; + for index in 0..self.count { + let prop = unsafe { properties.offset(index as isize) }; + unsafe { + q::JS_FreeAtom(self.context, (*prop).atom); + } + } + unsafe { + q::js_free(self.context, self.value as *mut std::ffi::c_void); + } + } +} + +impl std::ops::Deref for OwnedPropertiesRef { + type Target = *mut q::JSPropertyEnum; + + fn deref(&self) -> &*mut q::JSPropertyEnum { + &self.value + } +} + +impl std::ops::DerefMut for OwnedPropertiesRef { + fn deref_mut(&mut self) -> &mut *mut q::JSPropertyEnum { + &mut self.value + } +} + +fn deserialize_object(context: *mut q::JSContext, obj: &q::JSValue) -> Result { + assert_eq!(obj.tag, TAG_OBJECT); + + if unsafe { q::JS_IsFunction(context, *obj) } > 0 { + return Ok(JsValue::OpaqueFunction(OwnedValueRef::owned( + context, *obj, + ))); + } + + let properties = OwnedPropertiesRef::new(obj, context)?; + + let mut map = HashMap::new(); + for index in 0..properties.count { + let prop = unsafe { (*properties).offset(index as isize) }; + let raw_value = unsafe { q::JS_GetPropertyInternal(context, *obj, (*prop).atom, *obj, 0) }; + if raw_value.tag == TAG_EXCEPTION { + return Err(ValueError::Internal("Could not get object property".into())); + } + + let value_res = deserialize_value(context, &raw_value); + unsafe { + free_value(context, raw_value); + } + let value = value_res?; + + let key_value = unsafe { q::JS_AtomToString(context, (*prop).atom) }; + if key_value.tag == TAG_EXCEPTION { + return Err(ValueError::Internal( + "Could not get object property name".into(), + )); + } + + let key_res = deserialize_value(context, &key_value); + unsafe { + free_value(context, key_value); + } + let key = match key_res? { + JsValue::String(s) => s, + _ => { + return Err(ValueError::Internal("Could not get property name".into())); + } + }; + map.insert(key, value); + } + + Ok(JsValue::Object(map)) +} + +pub fn deserialize_value( + context: *mut q::JSContext, + value: &q::JSValue, +) -> Result { + let r = value; + + match r.tag { + // Int. + TAG_INT => { + let val = unsafe { r.u.int32 }; + Ok(JsValue::Int(val)) + } + // Bool. + TAG_BOOL => { + let raw = unsafe { r.u.int32 }; + let val = raw > 0; + Ok(JsValue::Bool(val)) + } + // Null. + TAG_NULL => Ok(JsValue::Null), + // Undefined. + TAG_UNDEFINED => Ok(JsValue::Null), + // Float. + TAG_FLOAT64 => { + let val = unsafe { r.u.float64 }; + Ok(JsValue::Float(val)) + } + // String. + TAG_STRING => { + let ptr = unsafe { + q::JS_ToCStringLen2(context, std::ptr::null::() as *mut usize, *r, 0) + }; + + if ptr.is_null() { + return Err(ValueError::Internal( + "Could not convert string: got a null pointer".into(), + )); + } + + let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) }; + + let s = cstr + .to_str() + .map_err(ValueError::InvalidString)? + .to_string(); + + // Free the c string. + unsafe { q::JS_FreeCString(context, ptr) }; + + Ok(JsValue::String(s)) + } + // Object. + TAG_OBJECT => { + let is_array = unsafe { q::JS_IsArray(context, *r) } > 0; + if is_array { + deserialize_array(context, r) + } else { + #[cfg(feature = "chrono")] + { + use chrono::offset::TimeZone; + + let date_constructor = js_date_constructor(context); + let is_date = unsafe { q::JS_IsInstanceOf(context, *r, date_constructor) > 0 }; + + if is_date { + let getter = unsafe { + q::JS_GetPropertyStr( + context, + *r, + std::ffi::CStr::from_bytes_with_nul(b"getTime\0") + .unwrap() + .as_ptr(), + ) + }; + assert_eq!(getter.tag, TAG_OBJECT); + + let timestamp_raw = + unsafe { q::JS_Call(context, getter, *r, 0, std::ptr::null_mut()) }; + unsafe { + free_value(context, getter); + free_value(context, date_constructor); + }; + + let res = if timestamp_raw.tag != TAG_FLOAT64 { + Err(ValueError::Internal( + "Could not convert 'Date' instance to timestamp".into(), + )) + } else { + let f = unsafe { timestamp_raw.u.float64 } as i64; + let datetime = chrono::Utc.timestamp_millis(f); + Ok(JsValue::Date(datetime)) + }; + return res; + } else { + unsafe { free_value(context, date_constructor) }; + } + } + + deserialize_object(context, r) + } + } + // BigInt + #[cfg(feature = "bigint")] + TAG_BIG_INT => { + let mut int: i64 = 0; + let ret = unsafe { q::JS_ToBigInt64(context, &mut int, *r) }; + if ret == 0 { + Ok(JsValue::BigInt(BigInt { + inner: BigIntOrI64::Int(int), + })) + } else { + let ptr = unsafe { q::JS_ToCStringLen2(context, std::ptr::null_mut(), *r, 0) }; + + if ptr.is_null() { + return Err(ValueError::Internal( + "Could not convert BigInt to string: got a null pointer".into(), + )); + } + + let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) }; + let bigint = num_bigint::BigInt::parse_bytes(cstr.to_bytes(), 10).unwrap(); + + // Free the c string. + unsafe { q::JS_FreeCString(context, ptr) }; + + Ok(JsValue::BigInt(BigInt { + inner: BigIntOrI64::BigInt(bigint), + })) + } + } + x => Err(ValueError::Internal(format!( + "Unhandled JS_TAG value: {}", + x + ))), + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs index 97f0292..52f2c0b 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,12 +1,18 @@ #[cfg(feature = "bigint")] pub(crate) mod bigint; +pub(crate) mod marshal; use std::{collections::HashMap, error, fmt}; +use crate::owned_value_ref::OwnedValueRef; #[cfg(feature = "bigint")] pub use bigint::BigInt; +use crate::ExecutionError; +use crate::utils::js_null_value; +use crate::marshal::{serialize_value, deserialize_value}; /// A value that can be (de)serialized to/from the quickjs runtime. +/// See the marshal module. #[derive(PartialEq, Clone, Debug)] #[allow(missing_docs)] pub enum JsValue { @@ -25,6 +31,7 @@ pub enum JsValue { /// Only available with the optional `bigint` feature #[cfg(feature = "bigint")] BigInt(crate::BigInt), + OpaqueFunction(OwnedValueRef), #[doc(hidden)] __NonExhaustive, } @@ -179,8 +186,8 @@ impl std::convert::TryFrom for num_bigint::BigInt { } impl From> for JsValue -where - T: Into, + where + T: Into, { fn from(values: Vec) -> Self { let items = values.into_iter().map(|x| x.into()).collect(); @@ -195,8 +202,8 @@ impl<'a> From<&'a str> for JsValue { } impl From> for JsValue -where - T: Into, + where + T: Into, { fn from(opt: Option) -> Self { if let Some(value) = opt { @@ -208,9 +215,9 @@ where } impl From> for JsValue -where - K: Into, - V: Into, + where + K: Into, + V: Into, { fn from(map: HashMap) -> Self { let new_map = map.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); @@ -218,6 +225,102 @@ where } } +/// Represents a JS function. Can be used for calling back into JS code. +/// +/// # Example +/// ```rust +/// use quick_js::OpaqueJsFunction; +/// fn register_state_listener(id: String, callback_function: OpaqueJsFunction) -> String { +/// callback_function.invoke(vec![id]); +/// id +///} +/// +/// fn main() -> Result<(), dyn std::error::Error> { +/// use quick_js::Context; +/// let mut context = Context::builder().build()?; +/// context.add_callback("notifyOnThingStatesChange", register_state_listener)?; +/// Ok(()) +/// } +/// ``` +pub struct OpaqueJsFunction(OwnedValueRef); + +impl OpaqueJsFunction { + /// Calls the js function + pub fn invoke( + &self, + args: impl IntoIterator>, + ) -> Result { + let mut qargs :Vec= args + .into_iter() + .map(|arg| serialize_value(self.0.context,arg.into()).unwrap_or(js_null_value())) + .collect(); + + use libquickjs_sys as q; + + let qres_raw = unsafe { + q::JS_Call( + self.0.context, + self.0.value, + js_null_value(), + qargs.len() as i32, + qargs.as_mut_ptr(), + ) + }; + let qres = OwnedValueRef::wrap(self.0.context, qres_raw); + Ok(deserialize_value(self.0.context, &qres.value)?) + } +} + +impl std::convert::TryFrom for OpaqueJsFunction { + type Error = ValueError; + + fn try_from(value: JsValue) -> Result { + match value { + JsValue::OpaqueFunction(value) => Ok(OpaqueJsFunction(value)), + _ => Err(ValueError::UnexpectedType), + } + } +} + +/// Can be used as generic argument in a callback function. +/// Can only contain basic types, no functions, no objects, no nested ones. +#[derive(Debug)] +#[allow(missing_docs)] +pub enum JsSimpleArgumentValue { + Null, + Bool(bool), + Int(i32), + Float(f64), + String(String), + /// chrono::Datetime / JS Date integration. + /// Only available with the optional `chrono` feature. + #[cfg(feature = "chrono")] + Date(chrono::DateTime), + /// num_bigint::BigInt / JS BigInt integration + /// Only available with the optional `bigint` feature + #[cfg(feature = "bigint")] + BigInt(BigInt), +} + +impl std::convert::TryFrom for JsSimpleArgumentValue { + type Error = ValueError; + + fn try_from(value: JsValue) -> Result { + match value { + JsValue::Null => Ok(JsSimpleArgumentValue::Null), + JsValue::Bool(value) => Ok(JsSimpleArgumentValue::Bool(value)), + JsValue::Int(value) => Ok(JsSimpleArgumentValue::Int(value)), + JsValue::Float(value) => Ok(JsSimpleArgumentValue::Float(value)), + JsValue::String(value) => Ok(JsSimpleArgumentValue::String(value)), + #[cfg(feature = "chrono")] + JsValue::Date(value) => Ok(JsSimpleArgumentValue::Date(value)), + #[cfg(feature = "bigint")] + JsValue::BigInt(value) => Ok(JsSimpleArgumentValue::BigInt(value)), + _ => Err(ValueError::UnexpectedType), + } + } +} + /// Error during value conversion. #[derive(PartialEq, Eq, Debug)] pub enum ValueError { @@ -250,7 +353,7 @@ impl fmt::Display for ValueError { "Value conversion failed - invalid non-utf8 string: {}", e ), - StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",), + StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes", ), Internal(e) => write!(f, "Value conversion failed - internal error: {}", e), UnexpectedType => write!(f, "Could not convert - received unexpected type"), __NonExhaustive => unreachable!(),