Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update rquickjs to the newest version of QuickJS #293

Merged
merged 22 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ jobs:
os: ubuntu-latest
rust: stable
target: i686-unknown-linux-gnu
features: exports
features: full-async
optimization: false
- task: bindings
os: ubuntu-latest
rust: stable
target: x86_64-unknown-linux-gnu
features: exports
features: full-async
optimization: false
- task: bindings
os: macos-latest
Expand Down Expand Up @@ -233,12 +233,6 @@ jobs:
features: full-async
optimization: false
# Test features
- task: features
os: ubuntu-latest
rust: stable
target: x86_64-unknown-linux-gnu
features: exports
optimization: false
- task: features
os: ubuntu-latest
rust: stable
Expand Down Expand Up @@ -401,7 +395,7 @@ jobs:
RUST_LOG: bindgen=warn,bindgen::ir=error,bindgen::codegen=error
run: |
cargo clean
cargo build ${{ matrix.optimization && '--release' || '' }} --manifest-path sys/Cargo.toml --target ${{ matrix.target }} --features exports,bindgen,update-bindings,logging
cargo build ${{ matrix.optimization && '--release' || '' }} --manifest-path sys/Cargo.toml --target ${{ matrix.target }} --features bindgen,update-bindings,logging
- name: Upload bindings
if: matrix.task == 'bindings'
uses: actions/upload-artifact@v3
Expand Down
7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ members = [
]

[features]
default = ["exports", "classes", "properties"]
default = ["classes", "properties"]

# Almost all features excluding "parallel" and support for async runtimes
full = ["chrono", "exports", "loader", "allocator", "dyn-load", "either", "indexmap", "classes", "properties", "array-buffer", "macro", "phf"]
full = ["chrono", "loader", "allocator", "dyn-load", "either", "indexmap", "classes", "properties", "array-buffer", "macro", "phf"]

# Almost all features excluding "parallel"
full-async = ["full", "futures"]
Expand All @@ -68,9 +68,6 @@ bindgen = ["rquickjs-core/bindgen", "rquickjs-macro?/bindgen"]
# Enable support of parallel execution
parallel = ["rquickjs-core/parallel"]

# Enable support of reading module exports
exports = ["rquickjs-core/exports"]

# Enable user-defined module loader support
loader = ["rquickjs-core/loader"]

Expand Down
5 changes: 1 addition & 4 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ optional = true
default = []

# Almost all features excluding "parallel" and support for async runtimes
full = ["chrono", "exports", "loader", "allocator", "dyn-load", "either", "indexmap", "classes", "properties", "array-buffer"]
full = ["chrono", "loader", "allocator", "dyn-load", "either", "indexmap", "classes", "properties", "array-buffer"]

# Almost all features excluding "parallel"
full-async = ["full", "futures"]
Expand All @@ -59,9 +59,6 @@ bindgen = ["rquickjs-sys/bindgen"]
# Enable support of parallel execution
parallel = []

# Enable support of reading module exports
exports = ["rquickjs-sys/exports"]

# Enable user-defined module loader support
loader = ["relative-path"]

Expand Down
7 changes: 5 additions & 2 deletions core/src/class/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::marker::PhantomData;
#[cfg(feature = "either")]
use either::{Either, Left, Right};

use crate::{markers::Invariant, qjs, Class, Ctx, Value};
use crate::{markers::Invariant, qjs, Class, Ctx, Module, Value};

use super::JsClass;

Expand Down Expand Up @@ -80,6 +80,10 @@ where
}
}

impl<'js, T> Trace<'js> for Module<'js, T> {
fn trace<'a>(&self, _tracer: Tracer<'a, 'js>) {}
}

impl<'js, T> Trace<'js> for Option<T>
where
T: Trace<'js>,
Expand Down Expand Up @@ -200,7 +204,6 @@ trace_impls! {
bool,char,
String,
crate::Atom<'js>,
crate::Module<'js>,
}

trace_impls! {
Expand Down
17 changes: 10 additions & 7 deletions core/src/context/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ impl Context {
let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.rt.as_ptr()) })
.ok_or_else(|| Error::Allocation)?;
unsafe { I::add_intrinsic(ctx) };
// rquickjs assumes the base objects exist, so we allways need to add this.
unsafe { intrinsic::Base::add_intrinsic(ctx) };
unsafe { Self::init_raw(ctx.as_ptr()) }
let res = Inner {
ctx,
Expand Down Expand Up @@ -194,20 +196,21 @@ mod test {
});
}

#[cfg(feature = "exports")]
#[test]
fn module() {
test_with(|ctx| {
let _value: Module = ctx
.compile(
"test_mod",
r#"
Module::evaluate(
ctx,
"test_mod",
r#"
let t = "3";
let b = (a) => a + 3;
export { b, t}
"#,
)
.unwrap();
)
.unwrap()
.finish::<()>()
.unwrap();
});
}

Expand Down
114 changes: 82 additions & 32 deletions core/src/context/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
ffi::{CStr, CString},
fs, mem,
fs,
mem::{self, MaybeUninit},
path::Path,
ptr::NonNull,
};
Expand All @@ -11,18 +12,21 @@ use std::future::Future;
#[cfg(feature = "futures")]
use crate::AsyncContext;
use crate::{
markers::Invariant, qjs, runtime::raw::Opaque, Context, Error, FromJs, Function, IntoJs,
Module, Object, Result, String, Value,
atom::PredefinedAtom, markers::Invariant, qjs, runtime::raw::Opaque, Atom, Context, Error,
FromJs, Function, IntoJs, Object, Promise, Result, String, Value,
};

/// Eval options.
#[non_exhaustive]
pub struct EvalOptions {
/// Global code.
pub global: bool,
/// Force 'strict' mode.
pub strict: bool,
/// Don't include the stack frames before this eval in the Error() backtraces.
pub backtrace_barrier: bool,
/// Support top-level-await.
pub promise: bool,
}

impl EvalOptions {
Expand All @@ -41,6 +45,10 @@ impl EvalOptions {
flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
}

if self.promise {
flag |= qjs::JS_EVAL_FLAG_ASYNC;
}

flag as i32
}
}
Expand All @@ -51,6 +59,7 @@ impl Default for EvalOptions {
global: true,
strict: true,
backtrace_barrier: false,
promise: false,
}
}
}
Expand Down Expand Up @@ -135,6 +144,20 @@ impl<'js> Ctx<'js> {
self.eval_with_options(source, Default::default())
}

/// Evaluate a script in global context with top level await support.
///
/// This function always returns a promise which resolves to the result of the evaluated
/// expression.
pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
self.eval_with_options(
source,
EvalOptions {
promise: true,
..Default::default()
},
)
}

/// Evaluate a script with the given options.
pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
&self,
Expand Down Expand Up @@ -174,15 +197,6 @@ impl<'js> Ctx<'js> {
})
}

/// Compile a module for later use.
pub fn compile<N, S>(self, name: N, source: S) -> Result<Module<'js>>
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
Module::evaluate(self, name, source)
}

/// Returns the global object of this context.
pub fn globals(&self) -> Object<'js> {
unsafe {
Expand Down Expand Up @@ -344,24 +358,35 @@ impl<'js> Ctx<'js> {
}
}

// Creates promise and resolving functions.
pub fn promise(&self) -> Result<(Object<'js>, Function<'js>, Function<'js>)> {
/// Creates javascipt promise along with its reject and resolve functions.
pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
let mut funcs = mem::MaybeUninit::<(qjs::JSValue, qjs::JSValue)>::uninit();

Ok(unsafe {
let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
self.ctx.as_ptr(),
funcs.as_mut_ptr() as _,
))?;
let (then, catch) = funcs.assume_init();
let (resolve, reject) = funcs.assume_init();
(
Object::from_js_value(self.clone(), promise),
Function::from_js_value(self.clone(), then),
Function::from_js_value(self.clone(), catch),
Promise::from_js_value(self.clone(), promise),
Function::from_js_value(self.clone(), resolve),
Function::from_js_value(self.clone(), reject),
)
})
}

/// Executes a quickjs job.
///
/// Returns wether a job was actually executed.
/// If this function returned false, no job was pending.
pub fn execute_pending_job(&self) -> bool {
let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
res != 0
}

pub(crate) unsafe fn get_opaque(&self) -> *mut Opaque<'js> {
let rt = qjs::JS_GetRuntime(self.ctx.as_ptr());
qjs::JS_GetRuntimeOpaque(rt).cast::<Opaque>()
Expand Down Expand Up @@ -402,41 +427,54 @@ impl<'js> Ctx<'js> {
}
}

pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
let stack_level = std::os::raw::c_int::try_from(stack_level).unwrap();
let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
if PredefinedAtom::Null as u32 == atom {
unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
return None;
}
unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
}

/// Returns the pointer to the C library context.
pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
self.ctx
}

/// Frees modules which aren't evaluated.
///
/// When a module is compiled and the compilation results in an error the module can already
/// have resolved several modules. Originally QuickJS freed all these module when compiling
/// (but not when a it was dynamically imported), this library patched that behavior out
/// because it proved to be hard to make safe. This function will free those modules.
///
/// # Safety
/// Caller must ensure that this method is not called from a module being evaluated.
/*
// Frees modules which aren't evaluated.
//
// When a module is compiled and the compilation results in an error the module can already
// have resolved several modules. Originally QuickJS freed all these module when compiling
// (but not when a it was dynamically imported), this library patched that behavior out
// because it proved to be hard to make safe. This function will free those modules.
//
// # Safety
// Caller must ensure that this method is not called from a module being evaluated.
pub unsafe fn free_unevaluated_modules(&self) {
qjs::JS_FreeUnevaluatedModules(self.ctx.as_ptr())
}
*/
}

#[cfg(test)]
mod test {

#[cfg(feature = "exports")]
#[test]
fn exports() {
use crate::{context::intrinsic, Context, Function, Runtime};
use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};

let runtime = Runtime::new().unwrap();
let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
ctx.with(|ctx| {
let module = ctx
.compile("test", "export default async () => 1;")
let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
.unwrap()
.eval()
.unwrap();
promise.finish::<()>().unwrap();
let func: Function = module.get("default").unwrap();
func.call::<(), ()>(()).unwrap();
func.call::<(), Promise>(()).unwrap();
});
}

Expand Down Expand Up @@ -464,6 +502,18 @@ mod test {
})
}

#[test]
fn eval_minimal_test() {
use crate::{Context, Runtime};

let runtime = Runtime::new().unwrap();
let ctx = Context::full(&runtime).unwrap();
ctx.with(|ctx| {
let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
assert_eq!(2, res);
})
}

#[test]
#[should_panic(expected = "'foo' is not defined")]
fn eval_with_sloppy_code() {
Expand Down
10 changes: 3 additions & 7 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ mod persistent;
mod value;
pub use persistent::{Outlive, Persistent};
pub use value::{
array, atom, convert, function, module, object, Array, Atom, BigInt, Coerced, Exception,
Filter, FromAtom, FromIteratorJs, FromJs, Function, IntoAtom, IntoJs, IteratorJs, Module, Null,
Object, String, Symbol, Type, Undefined, Value,
array, atom, convert, function, module, object, promise, Array, Atom, BigInt, Coerced,
Exception, Filter, FromAtom, FromIteratorJs, FromJs, Function, IntoAtom, IntoJs, IteratorJs,
Module, Null, Object, Promise, String, Symbol, Type, Undefined, Value,
};

pub mod class;
Expand All @@ -73,10 +73,6 @@ pub use value::{ArrayBuffer, TypedArray};

pub(crate) use std::{result::Result as StdResult, string::String as StdString};

#[cfg(feature = "futures")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
pub mod promise;

#[cfg(feature = "allocator")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "allocator")))]
pub mod allocator;
Expand Down
Loading
Loading