From 7b08602e49e5a6d1ffa64bd6b4aa9b78fb7722ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 5 Mar 2024 21:08:16 -0500 Subject: [PATCH 01/22] chore: Initial migration to rquickjs --- crates/apis/src/console/mod.rs | 38 +++++++++++++++++++++++----------- crates/javy/Cargo.toml | 3 ++- crates/javy/src/lib.rs | 3 ++- crates/javy/src/runtime.rs | 16 +++++++------- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/apis/src/console/mod.rs b/crates/apis/src/console/mod.rs index eb75d342..b24b8b91 100644 --- a/crates/apis/src/console/mod.rs +++ b/crates/apis/src/console/mod.rs @@ -1,8 +1,8 @@ use std::io::Write; -use anyhow::Result; +use anyhow::{Error, Result}; use javy::{ - quickjs::{JSContextRef, JSValue, JSValueRef}, + quickjs::{prelude::Rest, Context, Ctx, Function, Object, Rest, Value}, Runtime, }; @@ -31,29 +31,43 @@ impl JSApiSet for Console { } } -fn register_console(context: &JSContextRef, log_stream: T, error_stream: U) -> Result<()> +fn register_console<'js, T, U>(context: &Context, log_stream: T, error_stream: U) -> Result<()> where T: Write + 'static, U: Write + 'static, { - let console_log_callback = context.wrap_callback(console_log_to(log_stream))?; - let console_error_callback = context.wrap_callback(console_log_to(error_stream))?; - let console_object = context.object_value()?; - console_object.set_property("log", console_log_callback)?; - console_object.set_property("error", console_error_callback)?; - context - .global_object()? - .set_property("console", console_object)?; + context.with(|cx| { + let globals = cx.globals(); + let console = Object::new(cx)?; + console.set( + "log", + Function::new(cx, |cx: Ctx<'js>, args: Rest>| { + Value::new_undefined(cx) + }), + )?; + + console.set( + "error", + Function::new(cx, |cx: Ctx<'js>, args: Rest>| { + Value::new_undefined(cx) + }), + )?; + + globals.set("console", console)?; + Ok::<_, Error>(()) + }); Ok(()) } +fn log<'js, T>(mut stream: T, cx: Ctx<'js>, args: &[Value<'js>]) -> Result> {} + fn console_log_to( mut stream: T, ) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> Result where T: Write + 'static, { - move |_ctx: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]| { + move |_ctx: Cx<'_>, _this: JSValueRef, args: &[JSValueRef]| { // Write full string to in-memory destination before writing to stream since each write call to the stream // will invoke a hostcall. let mut log_line = String::new(); diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index 524f91b1..25b90681 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -11,7 +11,8 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } -quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } +# quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } +rquickjs = "0.5.1" serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } rmp-serde = { version = "^1.1", optional = true } diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index ad0ffa09..a36ceeaa 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -40,7 +40,8 @@ //! and MessagePack byte slices pub use config::Config; -pub use quickjs_wasm_rs as quickjs; +// pub use quickjs_wasm_rs as quickjs; +pub use rquickjs as quickjs; pub use runtime::Runtime; pub mod alloc; diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 353e26e2..5c6f095a 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -1,5 +1,6 @@ -use crate::quickjs::JSContextRef; +// use crate::quickjs::JSContextRef; use anyhow::Result; +use rquickjs::{Context, Runtime as QRuntime}; use crate::Config; @@ -33,20 +34,21 @@ use crate::Config; /// .unwrap(); /// context.eval_global("hello.js", "print('hello!');").unwrap(); /// ``` -#[derive(Debug)] pub struct Runtime { - context: JSContextRef, + /// The QuickJS context. + context: Context, } impl Runtime { - /// Creates a new [`Runtime`]. + /// Creates a new [Runtime]. pub fn new(_config: Config) -> Result { - let context = JSContextRef::default(); + let rt = QRuntime::new()?; + let context = Context::base(&rt)?; Ok(Self { context }) } - /// A reference to a [`JSContextRef`]. - pub fn context(&self) -> &JSContextRef { + /// A reference to the inner [Context]. + pub fn context(&self) -> &Context { &self.context } } From 300c3ca6508748a86bf7983a924aa1c94d53eca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 19 Mar 2024 08:53:58 -0400 Subject: [PATCH 02/22] More progress on migration: Migrated the `Console` and `Random` APIs; got to the `StreamIO` API. --- Cargo.lock | 28 +++++++ crates/apis/src/console/mod.rs | 59 +++++++------ crates/apis/src/lib.rs | 39 +++++++++ crates/apis/src/random/mod.rs | 21 +++-- crates/apis/src/stream_io/mod.rs | 138 +++++++++++++++++++------------ crates/javy/Cargo.toml | 2 +- crates/javy/src/lib.rs | 1 - 7 files changed, 195 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33b9dccd..775f8925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,6 +1412,7 @@ dependencies = [ "anyhow", "quickjs-wasm-rs", "rmp-serde", + "rquickjs", "serde-transcode", "serde_json", ] @@ -2149,6 +2150,33 @@ dependencies = [ "serde", ] +[[package]] +name = "rquickjs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7f63201fa6f2ff8173e4758ea552549d687d8f63003361a8b5c50f7c446ded" +dependencies = [ + "rquickjs-core", +] + +[[package]] +name = "rquickjs-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad00eeddc0f88af54ee202c8385fb214fe0423897c056a7df8369fb482e3695" +dependencies = [ + "rquickjs-sys", +] + +[[package]] +name = "rquickjs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120dbbc3296de9b96de8890091635d46f3506cd38b4e8f21800c386c035d64fa" +dependencies = [ + "cc", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/crates/apis/src/console/mod.rs b/crates/apis/src/console/mod.rs index b24b8b91..68422519 100644 --- a/crates/apis/src/console/mod.rs +++ b/crates/apis/src/console/mod.rs @@ -2,11 +2,14 @@ use std::io::Write; use anyhow::{Error, Result}; use javy::{ - quickjs::{prelude::Rest, Context, Ctx, Function, Object, Rest, Value}, + quickjs::{ + prelude::{MutFn, Rest}, + Context, Ctx, Function, Object, Value, + }, Runtime, }; -use crate::{APIConfig, JSApiSet}; +use crate::{print, APIConfig, JSApiSet}; pub(super) use config::ConsoleConfig; pub use config::LogStream; @@ -33,24 +36,30 @@ impl JSApiSet for Console { fn register_console<'js, T, U>(context: &Context, log_stream: T, error_stream: U) -> Result<()> where - T: Write + 'static, - U: Write + 'static, + T: Write, + U: Write, { context.with(|cx| { let globals = cx.globals(); let console = Object::new(cx)?; console.set( "log", - Function::new(cx, |cx: Ctx<'js>, args: Rest>| { - Value::new_undefined(cx) - }), + Function::new( + cx, + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + log(cx, &args, &mut log_stream).unwrap() + }), + )?, )?; console.set( "error", - Function::new(cx, |cx: Ctx<'js>, args: Rest>| { - Value::new_undefined(cx) - }), + Function::new( + cx, + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + log(cx, &args, &mut error_stream).unwrap() + }), + )?, )?; globals.set("console", console)?; @@ -59,30 +68,18 @@ where Ok(()) } -fn log<'js, T>(mut stream: T, cx: Ctx<'js>, args: &[Value<'js>]) -> Result> {} - -fn console_log_to( - mut stream: T, -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> Result -where - T: Write + 'static, -{ - move |_ctx: Cx<'_>, _this: JSValueRef, args: &[JSValueRef]| { - // Write full string to in-memory destination before writing to stream since each write call to the stream - // will invoke a hostcall. - let mut log_line = String::new(); - for (i, arg) in args.iter().enumerate() { - if i != 0 { - log_line.push(' '); - } - let line = arg.to_string(); - log_line.push_str(&line); +fn log<'js, T: Write>(ctx: Ctx<'js>, args: &[Value<'js>], mut stream: T) -> Result> { + let mut buf = String::new(); + for (i, arg) in args.iter().enumerate() { + if i != 0 { + buf.push(' '); } + print(arg, &mut buf)?; + } - writeln!(stream, "{log_line}")?; + writeln!(stream, "{buf}")?; - Ok(JSValue::Undefined) - } + Ok(Value::new_undefined(ctx)) } #[cfg(test)] diff --git a/crates/apis/src/lib.rs b/crates/apis/src/lib.rs index e433f6bf..851a9a68 100644 --- a/crates/apis/src/lib.rs +++ b/crates/apis/src/lib.rs @@ -45,6 +45,9 @@ use anyhow::Result; use javy::Runtime; +use javy::quickjs::{Type, Value}; +use std::fmt::Write; + pub use api_config::APIConfig; #[cfg(feature = "console")] pub use console::LogStream; @@ -87,3 +90,39 @@ pub fn add_to_runtime(runtime: &Runtime, config: APIConfig) -> Result<()> { text_encoding::TextEncoding.register(runtime, &config)?; Ok(()) } + +/// Print the given JS value. +/// +/// The implementation matches the default JavaScript display format for each value. +pub(crate) fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { + match val.type_of() { + Type::Undefined => write!(sink, "undefined").map_err(Into::into), + Type::Null => write!(sink, "null").map_err(Into::into), + Type::Bool => { + let b = val.as_bool().unwrap(); + write!(sink, "{}", b).map_err(Into::into) + } + Type::Int => { + let i = val.as_int().unwrap(); + write!(sink, "{}", i).map_err(Into::into) + } + Type::Float => { + let f = val.as_float().unwrap(); + write!(sink, "{}", f).map_err(Into::into) + } + Type::String => { + let s = val.as_string().unwrap(); + write!(sink, "{}", s.to_string()?).map_err(Into::into) + } + Type::Array => { + let inner = val.as_array().unwrap(); + for e in inner.iter() { + print(&e?, sink)? + } + Ok(()) + } + Type::Object => write!(sink, "[object Object]").map_err(Into::into), + // TODO: Implement the rest. + _ => unimplemented!(), + } +} diff --git a/crates/apis/src/random/mod.rs b/crates/apis/src/random/mod.rs index e7bbb570..fb5fc31a 100644 --- a/crates/apis/src/random/mod.rs +++ b/crates/apis/src/random/mod.rs @@ -1,5 +1,8 @@ -use anyhow::Result; -use javy::{quickjs::JSValue, Runtime}; +use anyhow::{Error, Result}; +use javy::{ + quickjs::{prelude::Func, Object}, + Runtime, +}; use crate::{APIConfig, JSApiSet}; @@ -7,12 +10,14 @@ pub struct Random; impl JSApiSet for Random { fn register(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { - let ctx = runtime.context(); - ctx.global_object()?.get_property("Math")?.set_property( - "random", - // TODO figure out if we can lazily initialize the PRNG - ctx.wrap_callback(|_ctx, _this, _args| Ok(JSValue::Float(fastrand::f64())))?, - )?; + runtime.context().with(|cx| { + let globals = cx.globals(); + let math: Object<'_> = globals.get("Math").expect("Math global to be defined"); + math.set("random", Func::from(fastrand::f64))?; + + Ok::<_, Error>(()) + }); + Ok(()) } } diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index abdf1217..13d980cc 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -1,68 +1,102 @@ -use anyhow::Result; +use anyhow::{bail, Error, Result}; use std::io::{Read, Write}; -use javy::Runtime; +use javy::{ + quickjs::{prelude::MutFn, Ctx, Function, Object, Value}, + Runtime, +}; use crate::{APIConfig, JSApiSet}; pub(super) struct StreamIO; +fn write<'js>(cx: Ctx<'js>, args: &[Value<'js>]) -> Result { + let [fd, data, offset, length, ..] = args else { + bail!("Expected 4 parameters to write to stdout") + }; + + let mut fd: Box = match fd.try_into()? { + 1 => Box::new(std::io::stdout()), + 2 => Box::new(std::io::stderr()), + _ => anyhow::bail!("Only stdout and stderr are supported"), + }; + let data: Vec = data.try_into()?; + let offset: usize = offset.try_into()?; + let length: usize = length.try_into()?; + let data = &data[offset..(offset + length)]; + let n = fd.write(data)?; + fd.flush()?; + Ok(n.into()) +} + impl JSApiSet for StreamIO { fn register(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { - let context = runtime.context(); - let global = context.global_object()?; + runtime.context().with(|cx| { + let globals = cx.globals(); + if globals.get::<_, Object>("Javy").is_err() { + globals.set("Javy", Object::new(cx)?)? + } + + globals.set("__javy_io_writeSync", Function::new(cx, MutFn::new()))?; - let mut javy_object = global.get_property("Javy")?; - if javy_object.is_undefined() { - javy_object = context.object_value()?; - global.set_property("Javy", javy_object)?; - } + Ok::<_, Error>(()) + }); - global.set_property( - "__javy_io_writeSync", - context.wrap_callback(|_, _this_arg, args| { - let [fd, data, offset, length, ..] = args else { - anyhow::bail!("Invalid number of parameters"); - }; + Ok(()) + // let context = runtime.context(); + // let global = context.global_object()?; - let mut fd: Box = match fd.try_into()? { - 1 => Box::new(std::io::stdout()), - 2 => Box::new(std::io::stderr()), - _ => anyhow::bail!("Only stdout and stderr are supported"), - }; - let data: Vec = data.try_into()?; - let offset: usize = offset.try_into()?; - let length: usize = length.try_into()?; - let data = &data[offset..(offset + length)]; - let n = fd.write(data)?; - fd.flush()?; - Ok(n.into()) - })?, - )?; + // let mut javy_object = global.get_property("Javy")?; + // if javy_object.is_undefined() { + // javy_object = context.object_value()?; + // global.set_property("Javy", javy_object)?; + // } - global.set_property( - "__javy_io_readSync", - context.wrap_callback(|_, _this_arg, args| { - let [fd, data, offset, length, ..] = args else { - anyhow::bail!("Invalid number of parameters"); - }; - let mut fd: Box = match fd.try_into()? { - 0 => Box::new(std::io::stdin()), - _ => anyhow::bail!("Only stdin is supported"), - }; - let offset: usize = offset.try_into()?; - let length: usize = length.try_into()?; - if !data.is_array_buffer() { - anyhow::bail!("Data needs to be an ArrayBuffer"); - } - let data = data.as_bytes_mut()?; - let data = &mut data[offset..(offset + length)]; - let n = fd.read(data)?; - Ok(n.into()) - })?, - )?; + // global.set_property( + // "__javy_io_writeSync", + // context.wrap_callback(|_, _this_arg, args| { + // let [fd, data, offset, length, ..] = args else { + // anyhow::bail!("Invalid number of parameters"); + // }; - context.eval_global("io.js", include_str!("io.js"))?; - Ok(()) + // let mut fd: Box = match fd.try_into()? { + // 1 => Box::new(std::io::stdout()), + // 2 => Box::new(std::io::stderr()), + // _ => anyhow::bail!("Only stdout and stderr are supported"), + // }; + // let data: Vec = data.try_into()?; + // let offset: usize = offset.try_into()?; + // let length: usize = length.try_into()?; + // let data = &data[offset..(offset + length)]; + // let n = fd.write(data)?; + // fd.flush()?; + // Ok(n.into()) + // })?, + // )?; + + // global.set_property( + // "__javy_io_readSync", + // context.wrap_callback(|_, _this_arg, args| { + // let [fd, data, offset, length, ..] = args else { + // anyhow::bail!("Invalid number of parameters"); + // }; + // let mut fd: Box = match fd.try_into()? { + // 0 => Box::new(std::io::stdin()), + // _ => anyhow::bail!("Only stdin is supported"), + // }; + // let offset: usize = offset.try_into()?; + // let length: usize = length.try_into()?; + // if !data.is_array_buffer() { + // anyhow::bail!("Data needs to be an ArrayBuffer"); + // } + // let data = data.as_bytes_mut()?; + // let data = &mut data[offset..(offset + length)]; + // let n = fd.read(data)?; + // Ok(n.into()) + // })?, + // )?; + + // context.eval_global("io.js", include_str!("io.js"))?; + // Ok(()) } } diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index 25b90681..f8cba6f1 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -11,7 +11,7 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } -# quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } +quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } rquickjs = "0.5.1" serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index a36ceeaa..9f8b75b9 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -40,7 +40,6 @@ //! and MessagePack byte slices pub use config::Config; -// pub use quickjs_wasm_rs as quickjs; pub use rquickjs as quickjs; pub use runtime::Runtime; From 878ddd5f11b3a247f58410b403e6f5babab10cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Fri, 5 Apr 2024 15:11:47 -0400 Subject: [PATCH 03/22] Migrate all of the APIs to use rquickjs This commit migrates the `console`, `stream_io`, `random` and `text_encoding` APIs to use rquickjs. One notable change in this commit is the introduction of the `Args` struct to tie the lifetime of `Ctx<'js>` and `Rest>` arguments, given that explicit lifetime binding is not possible in Rust closures at the moment. --- crates/apis/src/console/mod.rs | 121 +++++++++-------- crates/apis/src/lib.rs | 22 +++- crates/apis/src/random/mod.rs | 21 ++- crates/apis/src/stream_io/mod.rs | 187 ++++++++++++++++----------- crates/apis/src/text_encoding/mod.rs | 170 ++++++++++++++---------- crates/core/src/execution.rs | 51 ++++---- crates/javy/Cargo.toml | 2 +- 7 files changed, 347 insertions(+), 227 deletions(-) diff --git a/crates/apis/src/console/mod.rs b/crates/apis/src/console/mod.rs index 68422519..6abdffb1 100644 --- a/crates/apis/src/console/mod.rs +++ b/crates/apis/src/console/mod.rs @@ -2,20 +2,18 @@ use std::io::Write; use anyhow::{Error, Result}; use javy::{ - quickjs::{ - prelude::{MutFn, Rest}, - Context, Ctx, Function, Object, Value, - }, + quickjs::{prelude::MutFn, Context, Function, Object, Value}, Runtime, }; -use crate::{print, APIConfig, JSApiSet}; +use crate::{print, APIConfig, Args, JSApiSet}; pub(super) use config::ConsoleConfig; pub use config::LogStream; mod config; +// TODO: #[derive(Default)] pub(super) struct Console {} impl Console { @@ -34,20 +32,27 @@ impl JSApiSet for Console { } } -fn register_console<'js, T, U>(context: &Context, log_stream: T, error_stream: U) -> Result<()> +fn register_console<'js, T, U>( + context: &Context, + mut log_stream: T, + mut error_stream: U, +) -> Result<()> where - T: Write, - U: Write, + T: Write + 'static, + U: Write + 'static, { - context.with(|cx| { - let globals = cx.globals(); - let console = Object::new(cx)?; + // TODO: Revisit the callback signatures, there's a possibility that we can + // actually convert from anyhow::Error to quickjs::Error. + context.with(|this| { + let globals = this.globals(); + let console = Object::new(this.clone())?; + console.set( "log", Function::new( - cx, - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - log(cx, &args, &mut log_stream).unwrap() + this.clone(), + MutFn::new(move |cx, args| { + log(Args::hold(cx, args), &mut log_stream).expect("console.log to succeed") }), )?, )?; @@ -55,20 +60,21 @@ where console.set( "error", Function::new( - cx, - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - log(cx, &args, &mut error_stream).unwrap() + this, + MutFn::new(move |cx, args| { + log(Args::hold(cx, args), &mut error_stream).expect("console.error to succeed") }), )?, )?; globals.set("console", console)?; Ok::<_, Error>(()) - }); + })?; Ok(()) } -fn log<'js, T: Write>(ctx: Ctx<'js>, args: &[Value<'js>], mut stream: T) -> Result> { +fn log<'js, T: Write>(args: Args<'js>, stream: &mut T) -> Result> { + let (ctx, args) = args.release(); let mut buf = String::new(); for (i, arg) in args.iter().enumerate() { if i != 0 { @@ -79,13 +85,16 @@ fn log<'js, T: Write>(ctx: Ctx<'js>, args: &[Value<'js>], mut stream: T) -> Resu writeln!(stream, "{buf}")?; - Ok(Value::new_undefined(ctx)) + Ok(Value::new_undefined(ctx.clone())) } #[cfg(test)] mod tests { - use anyhow::Result; - use javy::Runtime; + use anyhow::{Error, Result}; + use javy::{ + quickjs::{Object, Value}, + Runtime, + }; use std::cell::RefCell; use std::rc::Rc; use std::{cmp, io}; @@ -99,9 +108,13 @@ mod tests { fn test_register() -> Result<()> { let runtime = Runtime::default(); Console::new().register(&runtime, &APIConfig::default())?; - let console = runtime.context().global_object()?.get_property("console")?; - assert!(console.get_property("log").is_ok()); - assert!(console.get_property("error").is_ok()); + runtime.context().with(|cx| { + let console: Object<'_> = cx.globals().get("console")?; + assert!(console.get::<&str, Value<'_>>("log").is_ok()); + assert!(console.get::<&str, Value<'_>>("error").is_ok()); + + Ok::<_, Error>(()) + })?; Ok(()) } @@ -113,24 +126,25 @@ mod tests { let ctx = runtime.context(); register_console(ctx, stream.clone(), stream.clone())?; - ctx.eval_global("main", "console.log(\"hello world\");")?; - assert_eq!(b"hello world\n", stream.buffer.borrow().as_slice()); + ctx.with(|this| { + this.eval("console.log(\"hello world\");")?; + assert_eq!(b"hello world\n", stream.buffer.borrow().as_slice()); + stream.clear(); - stream.clear(); + this.eval("console.log(\"bonjour\", \"le\", \"monde\")")?; + assert_eq!(b"bonjour le monde\n", stream.buffer.borrow().as_slice()); - ctx.eval_global("main", "console.log(\"bonjour\", \"le\", \"monde\")")?; - assert_eq!(b"bonjour le monde\n", stream.buffer.borrow().as_slice()); + stream.clear(); - stream.clear(); + this.eval("console.log(2.3, true, { foo: 'bar' }, null, undefined)")?; + assert_eq!( + b"2.3 true [object Object] null undefined\n", + stream.buffer.borrow().as_slice() + ); + + Ok::<_, Error>(()) + })?; - ctx.eval_global( - "main", - "console.log(2.3, true, { foo: 'bar' }, null, undefined)", - )?; - assert_eq!( - b"2.3 true [object Object] null undefined\n", - stream.buffer.borrow().as_slice() - ); Ok(()) } @@ -142,24 +156,25 @@ mod tests { let ctx = runtime.context(); register_console(ctx, stream.clone(), stream.clone())?; - ctx.eval_global("main", "console.error(\"hello world\");")?; - assert_eq!(b"hello world\n", stream.buffer.borrow().as_slice()); + ctx.with(|this| { + this.eval("console.error(\"hello world\");")?; + assert_eq!(b"hello world\n", stream.buffer.borrow().as_slice()); - stream.clear(); + stream.clear(); - ctx.eval_global("main", "console.error(\"bonjour\", \"le\", \"monde\")")?; - assert_eq!(b"bonjour le monde\n", stream.buffer.borrow().as_slice()); + this.eval("console.error(\"bonjour\", \"le\", \"monde\")")?; + assert_eq!(b"bonjour le monde\n", stream.buffer.borrow().as_slice()); - stream.clear(); + stream.clear(); + + this.eval("console.error(2.3, true, { foo: 'bar' }, null, undefined)")?; + assert_eq!( + b"2.3 true [object Object] null undefined\n", + stream.buffer.borrow().as_slice() + ); + Ok::<_, Error>(()) + })?; - ctx.eval_global( - "main", - "console.error(2.3, true, { foo: 'bar' }, null, undefined)", - )?; - assert_eq!( - b"2.3 true [object Object] null undefined\n", - stream.buffer.borrow().as_slice() - ); Ok(()) } diff --git a/crates/apis/src/lib.rs b/crates/apis/src/lib.rs index 851a9a68..123e321f 100644 --- a/crates/apis/src/lib.rs +++ b/crates/apis/src/lib.rs @@ -45,7 +45,7 @@ use anyhow::Result; use javy::Runtime; -use javy::quickjs::{Type, Value}; +use javy::quickjs::{prelude::Rest, Ctx, Type, Value}; use std::fmt::Write; pub use api_config::APIConfig; @@ -126,3 +126,23 @@ pub(crate) fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { _ => unimplemented!(), } } + +/// A struct to hold the current [Ctx] and [Value]s passed as arguments to Rust +/// functions. +/// A struct here is used to explicitly tie these values with a particular +/// lifetime. +// +// See: https://github.com/rust-lang/rfcs/pull/3216 +pub(crate) struct Args<'js>(Ctx<'js>, Rest>); + +impl<'js> Args<'js> { + /// Tie the [Ctx] and [Rest]. + fn hold(cx: Ctx<'js>, args: Rest>) -> Self { + Self(cx, args) + } + + /// Get the [Ctx] and [Rest]. + fn release(self) -> (Ctx<'js>, Rest>) { + (self.0, self.1) + } +} diff --git a/crates/apis/src/random/mod.rs b/crates/apis/src/random/mod.rs index fb5fc31a..7522ab05 100644 --- a/crates/apis/src/random/mod.rs +++ b/crates/apis/src/random/mod.rs @@ -25,18 +25,25 @@ impl JSApiSet for Random { #[cfg(test)] mod tests { use crate::{random::Random, APIConfig, JSApiSet}; - use anyhow::Result; - use javy::Runtime; + use anyhow::{Error, Result}; + use javy::{quickjs::Value, Runtime}; #[test] fn test_random() -> Result<()> { let runtime = Runtime::default(); Random.register(&runtime, &APIConfig::default())?; - let ctx = runtime.context(); - ctx.eval_global("test.js", "result = Math.random()")?; - let result = ctx.global_object()?.get_property("result")?.as_f64()?; - assert!(result >= 0.0); - assert!(result < 1.0); + runtime.context().with(|this| { + this.eval("result = Math.random()")?; + let result: f64 = this + .globals() + .get::<&str, Value<'_>>("result")? + .as_float() + .unwrap(); + assert!(result >= 0.0); + assert!(result < 1.0); + Ok::<_, Error>(()) + }); + Ok(()) } } diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index 13d980cc..77094e11 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -1,102 +1,145 @@ -use anyhow::{bail, Error, Result}; +use anyhow::{anyhow, bail, Error, Result}; use std::io::{Read, Write}; use javy::{ - quickjs::{prelude::MutFn, Ctx, Function, Object, Value}, + quickjs::{Function, Object, Value}, Runtime, }; -use crate::{APIConfig, JSApiSet}; +use crate::{APIConfig, Args, JSApiSet}; pub(super) struct StreamIO; -fn write<'js>(cx: Ctx<'js>, args: &[Value<'js>]) -> Result { +fn ensure_io_args<'a, 'js: 'a>( + args: &'a [Value<'js>], + for_func: &str, +) -> Result<( + &'a Value<'js>, + &'a Value<'js>, + &'a Value<'js>, + &'a Value<'js>, +)> { let [fd, data, offset, length, ..] = args else { - bail!("Expected 4 parameters to write to stdout") + bail!( + r#" + {} expects 4 parameters: the file descriptor, the + TypedArray buffer, the TypedArray byteOffset and the TypedArray + byteLength. + + Got: {} parameters. + "#, + for_func, + args.len() + ); }; - let mut fd: Box = match fd.try_into()? { + Ok((fd, data, offset, length)) +} + +fn write<'js>(args: Args<'js>) -> Result> { + let (cx, args) = args.release(); + let (fd, data, offset, length) = ensure_io_args(&args, "Javy.IO.writeSync")?; + let mut fd: Box = match fd + .as_int() + .ok_or_else(|| anyhow!("File descriptor must be a number"))? + { + // TODO: Drop the `Box` to avoid a heap allocation? 1 => Box::new(std::io::stdout()), 2 => Box::new(std::io::stderr()), - _ => anyhow::bail!("Only stdout and stderr are supported"), + x => anyhow::bail!( + "Wrong file descriptor: {}. Only stdin(1) and stderr(2) are supported", + x + ), }; - let data: Vec = data.try_into()?; - let offset: usize = offset.try_into()?; - let length: usize = length.try_into()?; + let data = data + .as_array() + .ok_or_else(|| anyhow!("Data must be an Array object"))? + .as_typed_array::() + .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? + .as_bytes() + .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; + + // TODO: Revisit the f64 to usize conversions. + let offset = offset + .as_number() + .ok_or_else(|| anyhow!("offset must be a number"))? as usize; + let length = length + .as_number() + .ok_or_else(|| anyhow!("offset must be a number"))? as usize; let data = &data[offset..(offset + length)]; let n = fd.write(data)?; fd.flush()?; - Ok(n.into()) + + Ok(Value::new_number(cx, n as f64)) +} + +fn read<'js>(args: Args<'js>) -> Result> { + let (cx, args) = args.release(); + let (fd, data, offset, length) = ensure_io_args(&args, "Javy.IO.readSync")?; + + let mut fd: Box = match fd + .as_int() + .ok_or_else(|| anyhow!("File descriptor must be a number"))? + { + // TODO: Drop the `Box` to avoid a heap allocation? + 0 => Box::new(std::io::stdin()), + x => anyhow::bail!("Wrong file descriptor: {}. Only stdin(1) is supported", x), + }; + + let offset = offset + .as_number() + .ok_or_else(|| anyhow!("offset must be a number"))? as usize; + let length = length + .as_number() + .ok_or_else(|| anyhow!("length must be a number"))? as usize; + + let data = data + .as_array() + .ok_or_else(|| anyhow!("Data must be an Array object"))? + .as_typed_array::() + .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? + .as_bytes() + .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; + + let len = data.len(); + let mut_ptr = data.as_ptr() as *mut u8; + // TODO: Can we avoid doing this? Is there a way to expose a safe way to mutate + // the underlying array buffer with rquickjs? + let mut_data = unsafe { &mut *std::ptr::slice_from_raw_parts_mut(mut_ptr, len) }; + + let data = &mut mut_data[offset..(offset + length)]; + let n = fd.read(data)?; + + Ok(Value::new_number(cx, n as f64)) } impl JSApiSet for StreamIO { - fn register(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { - runtime.context().with(|cx| { - let globals = cx.globals(); + fn register<'js>(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { + runtime.context().with(|this| { + let globals = this.globals(); + // TODO: Do we need this? if globals.get::<_, Object>("Javy").is_err() { - globals.set("Javy", Object::new(cx)?)? + globals.set("Javy", Object::new(this.clone())?)? } - globals.set("__javy_io_writeSync", Function::new(cx, MutFn::new()))?; + globals.set( + "__javy_io_writeSync", + Function::new(this.clone(), |cx, args| { + write(Args::hold(cx, args)).expect("write to succeed") + }), + )?; + + globals.set( + "__javy_io_readSync", + Function::new(this.clone(), |cx, args| { + read(Args::hold(cx, args)).expect("read to succeed") + }), + )?; + this.eval(include_str!("io.js"))?; Ok::<_, Error>(()) - }); + })?; Ok(()) - // let context = runtime.context(); - // let global = context.global_object()?; - - // let mut javy_object = global.get_property("Javy")?; - // if javy_object.is_undefined() { - // javy_object = context.object_value()?; - // global.set_property("Javy", javy_object)?; - // } - - // global.set_property( - // "__javy_io_writeSync", - // context.wrap_callback(|_, _this_arg, args| { - // let [fd, data, offset, length, ..] = args else { - // anyhow::bail!("Invalid number of parameters"); - // }; - - // let mut fd: Box = match fd.try_into()? { - // 1 => Box::new(std::io::stdout()), - // 2 => Box::new(std::io::stderr()), - // _ => anyhow::bail!("Only stdout and stderr are supported"), - // }; - // let data: Vec = data.try_into()?; - // let offset: usize = offset.try_into()?; - // let length: usize = length.try_into()?; - // let data = &data[offset..(offset + length)]; - // let n = fd.write(data)?; - // fd.flush()?; - // Ok(n.into()) - // })?, - // )?; - - // global.set_property( - // "__javy_io_readSync", - // context.wrap_callback(|_, _this_arg, args| { - // let [fd, data, offset, length, ..] = args else { - // anyhow::bail!("Invalid number of parameters"); - // }; - // let mut fd: Box = match fd.try_into()? { - // 0 => Box::new(std::io::stdin()), - // _ => anyhow::bail!("Only stdin is supported"), - // }; - // let offset: usize = offset.try_into()?; - // let length: usize = length.try_into()?; - // if !data.is_array_buffer() { - // anyhow::bail!("Data needs to be an ArrayBuffer"); - // } - // let data = data.as_bytes_mut()?; - // let data = &mut data[offset..(offset + length)]; - // let n = fd.read(data)?; - // Ok(n.into()) - // })?, - // )?; - - // context.eval_global("io.js", include_str!("io.js"))?; - // Ok(()) } } diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index 7d800764..df608d10 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -1,102 +1,130 @@ -use std::{borrow::Cow, str}; +use std::str; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Error, Result}; use javy::{ - quickjs::{JSContextRef, JSError, JSValue, JSValueRef}, + quickjs::{Error as JSError, Function, String as JSString, TypedArray, Value}, Runtime, }; -use crate::{APIConfig, JSApiSet}; +use crate::{APIConfig, Args, JSApiSet}; pub(super) struct TextEncoding; impl JSApiSet for TextEncoding { - fn register(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { - let context = runtime.context(); - let global = context.global_object()?; - global.set_property( - "__javy_decodeUtf8BufferToString", - context.wrap_callback(decode_utf8_buffer_to_js_string())?, - )?; - global.set_property( - "__javy_encodeStringToUtf8Buffer", - context.wrap_callback(encode_js_string_to_utf8_buffer())?, - )?; - - context.eval_global("text-encoding.js", include_str!("./text-encoding.js"))?; + fn register<'js>(&self, runtime: &Runtime, _: &APIConfig) -> Result<()> { + runtime.context().with(|this| { + let globals = this.globals(); + globals.set( + "__javy_decodeUtf8BufferToString", + Function::new(this.clone(), |cx, args| { + decode(Args::hold(cx, args)).expect("decode to succeed") + }), + )?; + globals.set( + "__javy_encodeStringToUtf8Buffer", + Function::new(this.clone(), |cx, args| { + encode(Args::hold(cx, args)).expect("encode to succeed") + }), + )?; + this.eval(include_str!("./text-encoding.js"))?; + Ok::<_, Error>(()) + })?; + Ok(()) } } -fn decode_utf8_buffer_to_js_string( -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { - move |_ctx: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]| { - if args.len() != 5 { - return Err(anyhow!("Expecting 5 arguments, received {}", args.len())); - } - - let buffer: Vec = args[0].try_into()?; - let byte_offset: usize = args[1].try_into()?; - let byte_length: usize = args[2].try_into()?; - let fatal: bool = args[3].try_into()?; - let ignore_bom: bool = args[4].try_into()?; - - let mut view = buffer - .get(byte_offset..(byte_offset + byte_length)) - .ok_or_else(|| { - anyhow!("Provided offset and length is not valid for provided buffer") - })?; - - if !ignore_bom { - view = match view { - // [0xEF, 0xBB, 0xBF] is the UTF-8 BOM which we want to strip - [0xEF, 0xBB, 0xBF, rest @ ..] => rest, - _ => view, - }; - } - - let str = - if fatal { - Cow::from(str::from_utf8(view).map_err(|_| { - JSError::Type("The encoded data was not valid utf-8".to_string()) - })?) - } else { - String::from_utf8_lossy(view) - }; - Ok(str.to_string().into()) +/// Decode a UTF-8 byte buffer as a JavaScript String. +fn decode<'js>(args: Args<'js>) -> Result> { + let (cx, args) = args.release(); + if args.len() != 5 { + bail!( + "Wrong number of arguments. Expected 5 arguments. Got: {}", + args.len() + ); } -} -fn encode_js_string_to_utf8_buffer( -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { - move |_ctx: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]| { - if args.len() != 1 { - return Err(anyhow!("Expecting 1 argument, got {}", args.len())); - } + let buffer = args[0] + .as_array() + .ok_or_else(|| anyhow!("buffer must be an array"))? + .as_typed_array::() + .ok_or_else(|| anyhow!("buffer must be a UInt8Array"))? + .as_bytes() + .ok_or_else(|| anyhow!("Couldn't retrive &[u8] from buffer"))?; + + let byte_offset = args[1] + .as_number() + .ok_or_else(|| anyhow!("offset must be a number"))? as usize; + let byte_length = args[2] + .as_number() + .ok_or_else(|| anyhow!("byte_length must be a number"))? as usize; + let fatal = args[3] + .as_bool() + .ok_or_else(|| anyhow!("fatal must be a boolean"))?; + let ignore_bom = args[4] + .as_bool() + .ok_or_else(|| anyhow!("ignore_bom must be a boolean"))?; + + let mut view = buffer + .get(byte_offset..(byte_offset + byte_length)) + .ok_or_else(|| anyhow!("Provided offset and length is not valid for provided buffer"))?; - let js_string: String = args[0].try_into()?; - Ok(js_string.into_bytes().into()) + if !ignore_bom { + view = match view { + // [0xEF, 0xBB, 0xBF] is the UTF-8 BOM which we want to strip + [0xEF, 0xBB, 0xBF, rest @ ..] => rest, + _ => view, + }; } + + let js_string = if fatal { + JSString::from_str(cx, str::from_utf8(view).map_err(JSError::Utf8)?) + } else { + let str = String::from_utf8_lossy(view); + JSString::from_str(cx, &str) + }; + + Ok(Value::from_string(js_string?)) +} + +/// Encode a JavaScript String into a JavaScript UInt8Array. +fn encode<'js>(args: Args<'js>) -> Result> { + let (cx, args) = args.release(); + if args.len() != 1 { + bail!("Wrong number of arguments. Expected 1. Got {}", args.len()); + } + + let js_string = args[0] + .as_string() + .ok_or_else(|| anyhow!("Argument must be a String"))? + .to_string()?; + + Ok(TypedArray::new(cx, js_string.into_bytes())? + .as_value() + .to_owned()) } #[cfg(test)] mod tests { use crate::{APIConfig, JSApiSet}; - use anyhow::Result; - use javy::Runtime; + use anyhow::{Error, Result}; + use javy::{quickjs::Value, Runtime}; use super::TextEncoding; #[test] fn test_text_encoder_decoder() -> Result<()> { let runtime = Runtime::default(); - let context = runtime.context(); - TextEncoding.register(&runtime, &APIConfig::default())?; - let result = context.eval_global( - "main", - "let encoder = new TextEncoder(); let buffer = encoder.encode('hello'); let decoder = new TextDecoder(); decoder.decode(buffer) == 'hello';" - )?; - assert!(result.as_bool()?); + + runtime.context().with(|this| { + + TextEncoding.register(&runtime, &APIConfig::default())?; + let result: Value<'_> = this.eval( + "let encoder = new TextEncoder(); let buffer = encoder.encode('hello'); let decoder = new TextDecoder(); decoder.decode(buffer) == 'hello';" + )?; + assert!(result.as_bool().unwrap()); + Ok::<_, Error>(()) + })?; Ok(()) } } diff --git a/crates/core/src/execution.rs b/crates/core/src/execution.rs index 992aacd9..941ef300 100644 --- a/crates/core/src/execution.rs +++ b/crates/core/src/execution.rs @@ -1,35 +1,42 @@ use std::process; use anyhow::{bail, Error, Result}; -use javy::{quickjs::JSContextRef, Runtime}; +use javy::{ + quickjs::{Ctx, Module}, + Runtime, +}; pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { - let context = runtime.context(); - context - .eval_binary(bytecode) - .and_then(|_| process_event_loop(context)) - .unwrap_or_else(handle_error); + runtime.context().with(|this| { + // Module::instantiate_read_object(this.clone(), bytecode) + // .and_then(|_| process_event_loop(this)) + // .unwrap_or_else(handle_error); + }); + // context + // .eval_binary(bytecode) + // .and_then(|_| process_event_loop(context)) + // .unwrap_or_else(handle_error); } pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { - let context = runtime.context(); - let js = if fn_name == "default" { - format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") - } else { - format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") - }; - context - .eval_module("runtime.mjs", &js) - .and_then(|_| process_event_loop(context)) - .unwrap_or_else(handle_error); + // let context = runtime.context(); + // let js = if fn_name == "default" { + // format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") + // } else { + // format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") + // }; + // context + // .eval_module("runtime.mjs", &js) + // .and_then(|_| process_event_loop(context)) + // .unwrap_or_else(handle_error); } -fn process_event_loop(context: &JSContextRef) -> Result<()> { - if cfg!(feature = "experimental_event_loop") { - context.execute_pending()?; - } else if context.is_pending() { - bail!("Adding tasks to the event queue is not supported"); - } +fn process_event_loop<'js>(cx: Ctx<'js>) -> Result<()> { + // if cfg!(feature = "experimental_event_loop") { + // context.execute_pending()?; + // } else if context.is_pending() { + // bail!("Adding tasks to the event queue is not supported"); + // } Ok(()) } diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index f8cba6f1..3cbbc913 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -12,7 +12,7 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } -rquickjs = "0.5.1" +rquickjs = {version = "0.5.1", features = ["array-buffer"] } serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } rmp-serde = { version = "^1.1", optional = true } From 2c4b52b928b834753dc3125f937b9c30c1707ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Wed, 10 Apr 2024 21:37:38 -0400 Subject: [PATCH 04/22] Finalize migration of the toolchain as a whole --- Cargo.lock | 4 +- Makefile | 5 +- crates/apis/src/console/mod.rs | 13 +-- crates/apis/src/lib.rs | 59 ------------ crates/apis/src/random/mod.rs | 13 ++- crates/apis/src/runtime_ext.rs | 1 + crates/apis/src/stream_io/io.js | 4 +- crates/apis/src/stream_io/mod.rs | 72 +++++++++------ crates/apis/src/text_encoding/mod.rs | 43 ++++++--- crates/cli/src/wasm_generator/static.rs | 3 + crates/cli/src/wasm_generator/transform.rs | 2 + crates/cli/tests/dylib_test.rs | 2 +- crates/cli/tests/integration_test.rs | 8 +- crates/core/src/execution.rs | 92 +++++++++++++------ crates/core/src/lib.rs | 28 ++++-- crates/core/src/main.rs | 16 +++- crates/javy/Cargo.toml | 2 +- crates/javy/src/lib.rs | 100 +++++++++++++++++++++ crates/javy/src/runtime.rs | 46 ++++++++-- 19 files changed, 344 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 775f8925..1e7f9883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2408,9 +2408,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smartstring" diff --git a/Makefile b/Makefile index b3223760..9b0b5c2e 100644 --- a/Makefile +++ b/Makefile @@ -21,9 +21,6 @@ docs: cargo doc --package=javy-cli --open cargo doc --package=javy-core --open --target=wasm32-wasi -test-quickjs-wasm-rs: - cargo wasi test --package=quickjs-wasm-rs -- --nocapture - test-javy: cargo wasi test --package=javy --features json,messagepack -- --nocapture @@ -47,7 +44,7 @@ test-wpt: npm install --prefix wpt npm test --prefix wpt -tests: test-quickjs-wasm-rs test-javy test-apis test-core test-cli test-wpt +tests: test-javy test-apis test-core test-cli test-wpt fmt: fmt-quickjs-wasm-sys fmt-quickjs-wasm-rs fmt-javy fmt-apis fmt-core fmt-cli diff --git a/crates/apis/src/console/mod.rs b/crates/apis/src/console/mod.rs index 6abdffb1..31560be6 100644 --- a/crates/apis/src/console/mod.rs +++ b/crates/apis/src/console/mod.rs @@ -2,11 +2,12 @@ use std::io::Write; use anyhow::{Error, Result}; use javy::{ + hold, hold_and_release, print, quickjs::{prelude::MutFn, Context, Function, Object, Value}, - Runtime, + to_js_error, Args, Runtime, }; -use crate::{print, APIConfig, Args, JSApiSet}; +use crate::{APIConfig, JSApiSet}; pub(super) use config::ConsoleConfig; pub use config::LogStream; @@ -52,7 +53,8 @@ where Function::new( this.clone(), MutFn::new(move |cx, args| { - log(Args::hold(cx, args), &mut log_stream).expect("console.log to succeed") + let (cx, args) = hold_and_release!(cx, args); + log(hold!(cx.clone(), args), &mut log_stream).map_err(|e| to_js_error(cx, e)) }), )?, )?; @@ -60,9 +62,10 @@ where console.set( "error", Function::new( - this, + this.clone(), MutFn::new(move |cx, args| { - log(Args::hold(cx, args), &mut error_stream).expect("console.error to succeed") + let (cx, args) = hold_and_release!(cx, args); + log(hold!(cx.clone(), args), &mut error_stream).map_err(|e| to_js_error(cx, e)) }), )?, )?; diff --git a/crates/apis/src/lib.rs b/crates/apis/src/lib.rs index 123e321f..e433f6bf 100644 --- a/crates/apis/src/lib.rs +++ b/crates/apis/src/lib.rs @@ -45,9 +45,6 @@ use anyhow::Result; use javy::Runtime; -use javy::quickjs::{prelude::Rest, Ctx, Type, Value}; -use std::fmt::Write; - pub use api_config::APIConfig; #[cfg(feature = "console")] pub use console::LogStream; @@ -90,59 +87,3 @@ pub fn add_to_runtime(runtime: &Runtime, config: APIConfig) -> Result<()> { text_encoding::TextEncoding.register(runtime, &config)?; Ok(()) } - -/// Print the given JS value. -/// -/// The implementation matches the default JavaScript display format for each value. -pub(crate) fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { - match val.type_of() { - Type::Undefined => write!(sink, "undefined").map_err(Into::into), - Type::Null => write!(sink, "null").map_err(Into::into), - Type::Bool => { - let b = val.as_bool().unwrap(); - write!(sink, "{}", b).map_err(Into::into) - } - Type::Int => { - let i = val.as_int().unwrap(); - write!(sink, "{}", i).map_err(Into::into) - } - Type::Float => { - let f = val.as_float().unwrap(); - write!(sink, "{}", f).map_err(Into::into) - } - Type::String => { - let s = val.as_string().unwrap(); - write!(sink, "{}", s.to_string()?).map_err(Into::into) - } - Type::Array => { - let inner = val.as_array().unwrap(); - for e in inner.iter() { - print(&e?, sink)? - } - Ok(()) - } - Type::Object => write!(sink, "[object Object]").map_err(Into::into), - // TODO: Implement the rest. - _ => unimplemented!(), - } -} - -/// A struct to hold the current [Ctx] and [Value]s passed as arguments to Rust -/// functions. -/// A struct here is used to explicitly tie these values with a particular -/// lifetime. -// -// See: https://github.com/rust-lang/rfcs/pull/3216 -pub(crate) struct Args<'js>(Ctx<'js>, Rest>); - -impl<'js> Args<'js> { - /// Tie the [Ctx] and [Rest]. - fn hold(cx: Ctx<'js>, args: Rest>) -> Self { - Self(cx, args) - } - - /// Get the [Ctx] and [Rest]. - fn release(self) -> (Ctx<'js>, Rest>) { - (self.0, self.1) - } -} diff --git a/crates/apis/src/random/mod.rs b/crates/apis/src/random/mod.rs index 7522ab05..89699cea 100644 --- a/crates/apis/src/random/mod.rs +++ b/crates/apis/src/random/mod.rs @@ -16,7 +16,7 @@ impl JSApiSet for Random { math.set("random", Func::from(fastrand::f64))?; Ok::<_, Error>(()) - }); + })?; Ok(()) } @@ -26,14 +26,19 @@ impl JSApiSet for Random { mod tests { use crate::{random::Random, APIConfig, JSApiSet}; use anyhow::{Error, Result}; - use javy::{quickjs::Value, Runtime}; + use javy::{ + quickjs::{context::EvalOptions, Value}, + Runtime, + }; #[test] fn test_random() -> Result<()> { let runtime = Runtime::default(); Random.register(&runtime, &APIConfig::default())?; runtime.context().with(|this| { - this.eval("result = Math.random()")?; + let mut eval_opts = EvalOptions::default(); + eval_opts.strict = false; + this.eval_with_options("result = Math.random()", eval_opts)?; let result: f64 = this .globals() .get::<&str, Value<'_>>("result")? @@ -42,7 +47,7 @@ mod tests { assert!(result >= 0.0); assert!(result < 1.0); Ok::<_, Error>(()) - }); + })?; Ok(()) } diff --git a/crates/apis/src/runtime_ext.rs b/crates/apis/src/runtime_ext.rs index d6491574..c7382f9b 100644 --- a/crates/apis/src/runtime_ext.rs +++ b/crates/apis/src/runtime_ext.rs @@ -26,6 +26,7 @@ impl RuntimeExt for Runtime { fn new_with_apis(config: Config, api_config: APIConfig) -> Result { let runtime = Runtime::new(config)?; crate::add_to_runtime(&runtime, api_config)?; + Ok(runtime) } diff --git a/crates/apis/src/stream_io/io.js b/crates/apis/src/stream_io/io.js index 8777c80a..2bc96a63 100644 --- a/crates/apis/src/stream_io/io.js +++ b/crates/apis/src/stream_io/io.js @@ -8,7 +8,7 @@ } return __javy_io_readSync( fd, - data.buffer, + data, data.byteOffset, data.byteLength ); @@ -19,7 +19,7 @@ } return __javy_io_writeSync( fd, - data.buffer, + data, data.byteOffset, data.byteLength ); diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index 77094e11..4e64bb18 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -1,16 +1,17 @@ use anyhow::{anyhow, bail, Error, Result}; -use std::io::{Read, Write}; +use std::io::{Read, Stdin, Write}; use javy::{ + hold, hold_and_release, quickjs::{Function, Object, Value}, - Runtime, + to_js_error, Args, Runtime, }; -use crate::{APIConfig, Args, JSApiSet}; +use crate::{APIConfig, JSApiSet}; pub(super) struct StreamIO; -fn ensure_io_args<'a, 'js: 'a>( +fn extract_args<'a, 'js: 'a>( args: &'a [Value<'js>], for_func: &str, ) -> Result<( @@ -37,23 +38,27 @@ fn ensure_io_args<'a, 'js: 'a>( } fn write<'js>(args: Args<'js>) -> Result> { + enum Fd { + Stdout, + Stderr, + } + let (cx, args) = args.release(); - let (fd, data, offset, length) = ensure_io_args(&args, "Javy.IO.writeSync")?; - let mut fd: Box = match fd + let (fd, data, offset, length) = extract_args(&args, "Javy.IO.writeSync")?; + let fd = match fd .as_int() .ok_or_else(|| anyhow!("File descriptor must be a number"))? { - // TODO: Drop the `Box` to avoid a heap allocation? - 1 => Box::new(std::io::stdout()), - 2 => Box::new(std::io::stderr()), + 1 => Fd::Stdout, + 2 => Fd::Stderr, x => anyhow::bail!( "Wrong file descriptor: {}. Only stdin(1) and stderr(2) are supported", x ), }; let data = data - .as_array() - .ok_or_else(|| anyhow!("Data must be an Array object"))? + .as_object() + .ok_or_else(|| anyhow!("Data must be an Object"))? .as_typed_array::() .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? .as_bytes() @@ -67,22 +72,33 @@ fn write<'js>(args: Args<'js>) -> Result> { .as_number() .ok_or_else(|| anyhow!("offset must be a number"))? as usize; let data = &data[offset..(offset + length)]; - let n = fd.write(data)?; - fd.flush()?; + let n = match fd { + Fd::Stdout => { + let mut fd = std::io::stdout(); + let n = fd.write(data)?; + fd.flush()?; + n + } + Fd::Stderr => { + let mut fd = std::io::stderr(); + let n = fd.write(data)?; + fd.flush()?; + n + } + }; Ok(Value::new_number(cx, n as f64)) } fn read<'js>(args: Args<'js>) -> Result> { let (cx, args) = args.release(); - let (fd, data, offset, length) = ensure_io_args(&args, "Javy.IO.readSync")?; + let (fd, data, offset, length) = extract_args(&args, "Javy.IO.readSync")?; - let mut fd: Box = match fd + let mut fd: Stdin = match fd .as_int() .ok_or_else(|| anyhow!("File descriptor must be a number"))? { - // TODO: Drop the `Box` to avoid a heap allocation? - 0 => Box::new(std::io::stdin()), + 0 => std::io::stdin(), x => anyhow::bail!("Wrong file descriptor: {}. Only stdin(1) is supported", x), }; @@ -94,21 +110,17 @@ fn read<'js>(args: Args<'js>) -> Result> { .ok_or_else(|| anyhow!("length must be a number"))? as usize; let data = data - .as_array() - .ok_or_else(|| anyhow!("Data must be an Array object"))? + .as_object() + .ok_or_else(|| anyhow!("Data must be an Object"))? .as_typed_array::() .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? .as_bytes() .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; - let len = data.len(); - let mut_ptr = data.as_ptr() as *mut u8; - // TODO: Can we avoid doing this? Is there a way to expose a safe way to mutate - // the underlying array buffer with rquickjs? - let mut_data = unsafe { &mut *std::ptr::slice_from_raw_parts_mut(mut_ptr, len) }; - - let data = &mut mut_data[offset..(offset + length)]; - let n = fd.read(data)?; + // TODO: Comment on safety. + let dst = data.as_ptr() as *mut _; + let dst: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(dst, length) }; + let n = fd.read(&mut dst[offset..(offset + length)])?; Ok(Value::new_number(cx, n as f64)) } @@ -125,14 +137,16 @@ impl JSApiSet for StreamIO { globals.set( "__javy_io_writeSync", Function::new(this.clone(), |cx, args| { - write(Args::hold(cx, args)).expect("write to succeed") + let (cx, args) = hold_and_release!(cx, args); + write(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; globals.set( "__javy_io_readSync", Function::new(this.clone(), |cx, args| { - read(Args::hold(cx, args)).expect("read to succeed") + let (cx, args) = hold_and_release!(cx, args); + read(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index df608d10..12c8388d 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -2,11 +2,12 @@ use std::str; use anyhow::{anyhow, bail, Error, Result}; use javy::{ - quickjs::{Error as JSError, Function, String as JSString, TypedArray, Value}, - Runtime, + hold, hold_and_release, + quickjs::{context::EvalOptions, Function, String as JSString, TypedArray, Value}, + to_js_error, Args, Runtime, }; -use crate::{APIConfig, Args, JSApiSet}; +use crate::{APIConfig, JSApiSet}; pub(super) struct TextEncoding; @@ -17,16 +18,21 @@ impl JSApiSet for TextEncoding { globals.set( "__javy_decodeUtf8BufferToString", Function::new(this.clone(), |cx, args| { - decode(Args::hold(cx, args)).expect("decode to succeed") + let (cx, args) = hold_and_release!(cx, args); + decode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; globals.set( "__javy_encodeStringToUtf8Buffer", Function::new(this.clone(), |cx, args| { - encode(Args::hold(cx, args)).expect("encode to succeed") + let (cx, args) = hold_and_release!(cx, args); + encode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; - this.eval(include_str!("./text-encoding.js"))?; + let mut opts = EvalOptions::default(); + opts.strict = false; + this.eval_with_options(include_str!("./text-encoding.js"), opts)?; + Ok::<_, Error>(()) })?; @@ -45,10 +51,10 @@ fn decode<'js>(args: Args<'js>) -> Result> { } let buffer = args[0] - .as_array() - .ok_or_else(|| anyhow!("buffer must be an array"))? - .as_typed_array::() - .ok_or_else(|| anyhow!("buffer must be a UInt8Array"))? + .as_object() + .ok_or_else(|| anyhow!("buffer must be an object"))? + .as_array_buffer() + .ok_or_else(|| anyhow!("buffer must be an ArrayBuffer"))? .as_bytes() .ok_or_else(|| anyhow!("Couldn't retrive &[u8] from buffer"))?; @@ -78,7 +84,10 @@ fn decode<'js>(args: Args<'js>) -> Result> { } let js_string = if fatal { - JSString::from_str(cx, str::from_utf8(view).map_err(JSError::Utf8)?) + JSString::from_str( + cx.clone(), + str::from_utf8(view).map_err(|_| anyhow!("The encoded data was not valid utf-8"))?, + ) } else { let str = String::from_utf8_lossy(view); JSString::from_str(cx, &str) @@ -115,13 +124,19 @@ mod tests { #[test] fn test_text_encoder_decoder() -> Result<()> { let runtime = Runtime::default(); + TextEncoding.register(&runtime, &APIConfig::default())?; runtime.context().with(|this| { - - TextEncoding.register(&runtime, &APIConfig::default())?; let result: Value<'_> = this.eval( - "let encoder = new TextEncoder(); let buffer = encoder.encode('hello'); let decoder = new TextDecoder(); decoder.decode(buffer) == 'hello';" + r#" + let encoder = new TextEncoder(); + let decoder = new TextDecoder(); + + let buffer = encoder.encode('hello'); + decoder.decode(buffer) == 'hello'; + "#, )?; + assert!(result.as_bool().unwrap()); Ok::<_, Error>(()) })?; diff --git a/crates/cli/src/wasm_generator/static.rs b/crates/cli/src/wasm_generator/static.rs index 8538d4e3..96657276 100644 --- a/crates/cli/src/wasm_generator/static.rs +++ b/crates/cli/src/wasm_generator/static.rs @@ -137,6 +137,9 @@ fn optimize_wasm(wasm: &[u8]) -> Result> { OptimizationOptions::new_opt_level_3() // Aggressively optimize for speed. .shrink_level(ShrinkLevel::Level0) // Don't optimize for size at the expense of performance. + // TODO: Make configurable? + // Making it configurable and setting it to true will enable a better + // profiling experience. .debug_info(false) .run(&tempfile_path, &tempfile_path)?; diff --git a/crates/cli/src/wasm_generator/transform.rs b/crates/cli/src/wasm_generator/transform.rs index 68023dcb..4d259f01 100644 --- a/crates/cli/src/wasm_generator/transform.rs +++ b/crates/cli/src/wasm_generator/transform.rs @@ -36,6 +36,8 @@ impl CustomSection for SourceCodeSection { pub fn module_config() -> ModuleConfig { let mut config = ModuleConfig::new(); + // TODO: configurable? + // Setting it to true will enable a better profiling experience. config.generate_name_section(false); config } diff --git a/crates/cli/tests/dylib_test.rs b/crates/cli/tests/dylib_test.rs index 851d62f3..ba750c77 100644 --- a/crates/cli/tests/dylib_test.rs +++ b/crates/cli/tests/dylib_test.rs @@ -28,7 +28,7 @@ fn test_dylib_with_error() -> Result<()> { assert!(result.is_err()); let output = stderr.try_into_inner().unwrap().into_inner(); - let expected_log_output = "Error while running JS: Uncaught Error: foo error\n at foo (function.mjs)\n at (function.mjs:1)\n\n"; + let expected_log_output = "Error:1:24 foo error\n at foo (function.mjs:1:24)\n at (function.mjs:1:50)\n\n"; assert_eq!(expected_log_output, str::from_utf8(&output)?); Ok(()) diff --git a/crates/cli/tests/integration_test.rs b/crates/cli/tests/integration_test.rs index e07ac541..0ff7b29a 100644 --- a/crates/cli/tests/integration_test.rs +++ b/crates/cli/tests/integration_test.rs @@ -54,8 +54,8 @@ fn test_encoding() { let (output, _, _) = run(&mut runner, "invalid_fatal".as_bytes()); assert_eq!("The encoded data was not valid utf-8".as_bytes(), output); - let (output, _, _) = run(&mut runner, "test".as_bytes()); - assert_eq!("test2".as_bytes(), output); + // let (output, _, _) = run(&mut runner, "test".as_bytes()); + // assert_eq!("test2".as_bytes(), output); } #[test] @@ -98,7 +98,7 @@ fn test_promises() { let err = res.err().unwrap().downcast::().unwrap(); assert!(str::from_utf8(&err.stderr) .unwrap() - .contains("Adding tasks to the event queue is not supported")); + .contains("Pending jobs in the event queue.")); } #[test] @@ -155,7 +155,7 @@ fn test_error_handling() { let result = runner.exec(&[]); let err = result.err().unwrap().downcast::().unwrap(); - let expected_log_output = "Error while running JS: Uncaught Error: error\n at error (function.mjs:2)\n at (function.mjs:5)\n\n"; + let expected_log_output = "Error:2:9 error\n at error (function.mjs:2:9)\n at (function.mjs:5:1)\n\n"; assert_eq!(expected_log_output, str::from_utf8(&err.stderr).unwrap()); } diff --git a/crates/core/src/execution.rs b/crates/core/src/execution.rs index 941ef300..2eb62b29 100644 --- a/crates/core/src/execution.rs +++ b/crates/core/src/execution.rs @@ -2,45 +2,83 @@ use std::process; use anyhow::{bail, Error, Result}; use javy::{ - quickjs::{Ctx, Module}, + from_js_error, + quickjs::{context::EvalOptions, Module, Value}, Runtime, }; +/// Evaluate the given bytecode. +/// +/// Evaluating also prepares (or "instantiates") the state of the JavaScript +/// engine given all the information encoded in the bytecode. +// TODO: Rename this? pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { - runtime.context().with(|this| { - // Module::instantiate_read_object(this.clone(), bytecode) - // .and_then(|_| process_event_loop(this)) - // .unwrap_or_else(handle_error); - }); - // context - // .eval_binary(bytecode) - // .and_then(|_| process_event_loop(context)) - // .unwrap_or_else(handle_error); + runtime + .context() + .with(|this| Module::instantiate_read_object(this, bytecode).and_then(|_| Ok(()))) + .map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e))) + // Prefer calling `process_event_loop` *outside* of the `with` callback, + // to avoid errors regarding multiple mutable borrows. + .and_then(|_| process_event_loop(runtime)) + .unwrap_or_else(handle_error) } +/// Entry point to invoke an exported JavaScript function. +/// +/// This function will evaluate a JavaScript snippet that imports and invokes +/// the target function from a previously evaluated module. It's the caller's +/// reponsibility to ensure that the module containing the target function has +/// been previously evaluated. +// TODO: +// * Consider renaming this to emit_invoke_function. +// * There's probably(?) a better way of doing this through rquickjs' module +// loaders and resolvers, in such a way that we can avoid having to eval. pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { - // let context = runtime.context(); - // let js = if fn_name == "default" { - // format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") - // } else { - // format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") - // }; - // context - // .eval_module("runtime.mjs", &js) - // .and_then(|_| process_event_loop(context)) - // .unwrap_or_else(handle_error); + let js = if fn_name == "default" { + format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") + } else { + format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") + }; + + runtime + .context() + .with(|this| { + let mut opts = EvalOptions::default(); + // TODO: config? + opts.strict = false; + // We're assuming imports and exports, therefore we want to + // evaluate as a module. + opts.global = false; + this.eval_with_options::, _>(js.into_bytes(), opts) + .map_err(|e| from_js_error(this.clone(), e)) + .and_then(|_| Ok(())) + }) + .and_then(|_: ()| process_event_loop(&runtime)) + .unwrap_or_else(handle_error) } -fn process_event_loop<'js>(cx: Ctx<'js>) -> Result<()> { - // if cfg!(feature = "experimental_event_loop") { - // context.execute_pending()?; - // } else if context.is_pending() { - // bail!("Adding tasks to the event queue is not supported"); - // } +fn process_event_loop(rt: &Runtime) -> Result<()> { + // TODO: + // I'm not sure if having a cargo feature gives us a lot here, given that + // we're not preventing the usage of the event loop at the engine level; it + // might be worth revisiting this to make it a config option instead and + // including / excluding the right engine intrinsics via rquickjs + if cfg!(feature = "experimental_event_loop") { + rt.resolve_pending_jobs()? + } else if rt.has_pending_jobs() { + bail!( + r#" + Pending jobs in the event queue. + Scheduling events is not supported when the + experimental_event_loop cargo feature is disabled. + "# + ); + } + Ok(()) } fn handle_error(e: Error) { - eprintln!("Error while running JS: {e}"); + eprintln!("{e}"); process::abort(); } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 5a56a940..b218c64c 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,4 @@ -use javy::Runtime; +use javy::{quickjs::Module, Runtime}; use once_cell::sync::OnceCell; use std::slice; use std::str; @@ -12,11 +12,16 @@ static mut COMPILE_SRC_RET_AREA: [u32; 2] = [0; 2]; static mut RUNTIME: OnceCell = OnceCell::new(); -/// Used by Wizer to preinitialize the module +/// Used by Wizer to preinitialize the module. #[export_name = "wizer.initialize"] pub extern "C" fn init() { let runtime = runtime::new_runtime().unwrap(); - unsafe { RUNTIME.set(runtime).unwrap() }; + unsafe { + match RUNTIME.set(runtime) { + Err(_) => panic!("Could not pre-initialize Runtime"), + _ => {} + } + }; } /// Compiles JS source code to QuickJS bytecode. @@ -37,15 +42,22 @@ pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) - // Use fresh runtime to avoid depending on Wizened runtime let runtime = runtime::new_runtime().unwrap(); let js_src = str::from_utf8(slice::from_raw_parts(js_src_ptr, js_src_len)).unwrap(); - let bytecode = runtime + + // TODO: Dedup this. + let bc = runtime .context() - .compile_module(FUNCTION_MODULE_NAME, js_src) + // TODO: Should `function.mjs` be configurable instead? + // Ideally it should pick-up the input file name. + .with(|this| { + unsafe { Module::unsafe_declare(this.clone(), FUNCTION_MODULE_NAME, js_src) }? + .write_object_le() + }) .unwrap(); - let bytecode_len = bytecode.len(); + // We need the bytecode buffer to live longer than this function so it can be read from memory - let bytecode_ptr = Box::leak(bytecode.into_boxed_slice()).as_ptr(); + let bytecode_ptr = Box::leak(bc.clone().into_boxed_slice()).as_ptr(); COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32; - COMPILE_SRC_RET_AREA[1] = bytecode_len.try_into().unwrap(); + COMPILE_SRC_RET_AREA[1] = bc.len().try_into().unwrap(); COMPILE_SRC_RET_AREA.as_ptr() } diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 33056bcc..64c8a4d1 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -1,4 +1,4 @@ -use javy::Runtime; +use javy::{quickjs::Module, Runtime}; use once_cell::sync::OnceCell; use std::io::{self, Read}; use std::slice; @@ -21,13 +21,23 @@ pub extern "C" fn init() { let mut contents = String::new(); io::stdin().read_to_string(&mut contents).unwrap(); + let bytecode = runtime .context() - .compile_module("function.mjs", &contents) + // TODO: Should `function.mjs` be configurable instead? + .with(|this| { + unsafe { Module::unsafe_declare(this.clone(), FUNCTION_MODULE_NAME, contents) }? + .write_object_le() + }) .unwrap(); unsafe { - RUNTIME.set(runtime).unwrap(); + // TODO: switch to `map_err` + `unwrap`? + match RUNTIME.set(runtime) { + // TODO: Comment why. + Err(_) => panic!("Could not pre-intialize Runtime"), + _ => {} + }; BYTECODE.set(bytecode).unwrap(); } } diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index 3cbbc913..07a72b6c 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -12,7 +12,7 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } quickjs-wasm-rs = { version = "3.0.1-alpha.1", path = "../quickjs-wasm-rs" } -rquickjs = {version = "0.5.1", features = ["array-buffer"] } +rquickjs = { version = "0.5.1", features = ["array-buffer"] } serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } rmp-serde = { version = "^1.1", optional = true } diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index 9f8b75b9..d59ea076 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -47,8 +47,108 @@ pub mod alloc; mod config; mod runtime; +use anyhow::{anyhow, Error, Result}; +use rquickjs::{prelude::Rest, Ctx, Error as JSError, Exception, Type, Value}; +use std::fmt::Write; + #[cfg(feature = "messagepack")] pub mod messagepack; #[cfg(feature = "json")] pub mod json; + +/// Print the given JS value. +/// +/// The implementation matches the default JavaScript display format for each value. +pub fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { + match val.type_of() { + Type::Undefined => write!(sink, "undefined").map_err(Into::into), + Type::Null => write!(sink, "null").map_err(Into::into), + Type::Bool => { + let b = val.as_bool().unwrap(); + write!(sink, "{}", b).map_err(Into::into) + } + Type::Int => { + let i = val.as_int().unwrap(); + write!(sink, "{}", i).map_err(Into::into) + } + Type::Float => { + let f = val.as_float().unwrap(); + write!(sink, "{}", f).map_err(Into::into) + } + Type::String => { + let s = val.as_string().unwrap(); + write!(sink, "{}", s.to_string()?).map_err(Into::into) + } + Type::Array => { + let inner = val.as_array().unwrap(); + for e in inner.iter() { + print(&e?, sink)? + } + Ok(()) + } + Type::Object => write!(sink, "[object Object]").map_err(Into::into), + // TODO: Implement the rest. + _ => unimplemented!(), + } +} + +/// A struct to hold the current [Ctx] and [Value]s passed as arguments to Rust +/// functions. +/// A struct here is used to explicitly tie these values with a particular +/// lifetime. +// +// See: https://github.com/rust-lang/rfcs/pull/3216 +pub struct Args<'js>(Ctx<'js>, Rest>); + +impl<'js> Args<'js> { + /// Tie the [Ctx] and [Rest]. + pub fn hold(cx: Ctx<'js>, args: Rest>) -> Self { + Self(cx, args) + } + + /// Get the [Ctx] and [Rest]. + pub fn release(self) -> (Ctx<'js>, Rest>) { + (self.0, self.1) + } +} + +/// Alias for `Args::hold(cx, args).release() +#[macro_export] +macro_rules! hold_and_release { + ($cx:expr, $args:expr) => { + Args::hold($cx, $args).release() + }; +} + +/// Alias for `Args::hold(cx, args) +#[macro_export] +macro_rules! hold { + ($cx:expr, $args:expr) => { + Args::hold($cx, $args) + }; +} + +/// Handles a JavaScript error or exception and converts to [anyhow::Error]. +pub fn from_js_error(ctx: Ctx<'_>, e: JSError) -> Error { + if e.is_exception() { + let exception = ctx.catch().into_exception().unwrap(); + anyhow!("{exception}") + } else { + Into::into(e) + } +} + +/// Converts an [anyhow::Error] to a [JSError]. +/// +/// If the error is an [anyhow::Error] this function will construct and throw +/// a JS [Exception] in order to construct the [JSError]. +pub fn to_js_error(cx: Ctx, e: Error) -> JSError { + match e.downcast::() { + Ok(e) => e, + Err(e) => cx.throw(Value::from_exception( + Exception::from_message(cx.clone(), &e.to_string()) + .expect("creating an exception to succeed"), + )), + } +} diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 5c6f095a..287b4dd3 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -1,9 +1,11 @@ // use crate::quickjs::JSContextRef; -use anyhow::Result; +use anyhow::{bail, Result}; use rquickjs::{Context, Runtime as QRuntime}; +use std::mem::ManuallyDrop; use crate::Config; +// TODO: Update documentation. /// A JavaScript Runtime. /// /// Provides a [`Self::context()`] method for working with the underlying [`JSContextRef`]. @@ -37,25 +39,57 @@ use crate::Config; pub struct Runtime { /// The QuickJS context. context: Context, + /// The inner QuickJS runtime representation. + // TODO: Document why `ManuallyDrop`. + inner: ManuallyDrop, } impl Runtime { /// Creates a new [Runtime]. pub fn new(_config: Config) -> Result { - let rt = QRuntime::new()?; - let context = Context::base(&rt)?; - Ok(Self { context }) + let rt = ManuallyDrop::new(QRuntime::new()?); + + // TODO: Make GC configurable? + rt.set_gc_threshold(usize::MAX); + // TODO: Add a comment here? + let context = Context::full(&rt)?; + Ok(Self { inner: rt, context }) } /// A reference to the inner [Context]. pub fn context(&self) -> &Context { &self.context } + + /// Resolves all the pending jobs in the queue. + pub fn resolve_pending_jobs(&self) -> Result<()> { + if self.inner.is_job_pending() { + loop { + let result = self.inner.execute_pending_job(); + if let Ok(false) = result { + break; + } + + if let Err(e) = result { + bail!("{e}") + } + } + } + + Ok(()) + } + + /// Returns true if there are pending jobs in the queue. + pub fn has_pending_jobs(&self) -> bool { + self.inner.is_job_pending() + } } impl Default for Runtime { - /// Returns a [`Runtime`] with a default configuration. Panics if there's - /// an error. + /// Returns a [`Runtime`] with a default configuration. + /// + /// # Panics + /// This function panics if there is an error setting up the runtime. fn default() -> Self { Self::new(Config::default()).unwrap() } From a126edeb3e229d9d128a1b9dbd44d9f69ae0350a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Thu, 11 Apr 2024 07:12:41 -0400 Subject: [PATCH 05/22] Uncomment integration tests --- crates/cli/tests/integration_test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli/tests/integration_test.rs b/crates/cli/tests/integration_test.rs index 0ff7b29a..c334bb5e 100644 --- a/crates/cli/tests/integration_test.rs +++ b/crates/cli/tests/integration_test.rs @@ -54,8 +54,8 @@ fn test_encoding() { let (output, _, _) = run(&mut runner, "invalid_fatal".as_bytes()); assert_eq!("The encoded data was not valid utf-8".as_bytes(), output); - // let (output, _, _) = run(&mut runner, "test".as_bytes()); - // assert_eq!("test2".as_bytes(), output); + let (output, _, _) = run(&mut runner, "test".as_bytes()); + assert_eq!("test2".as_bytes(), output); } #[test] From 85abf5741700faed7e57e5c036e8677fe4a1c401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Thu, 11 Apr 2024 14:44:39 -0400 Subject: [PATCH 06/22] Throw `TypeError` when `fatal` decoding is specified. --- crates/apis/src/text_encoding/mod.rs | 5 +++-- crates/core/src/main.rs | 3 ++- crates/javy/src/lib.rs | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index 12c8388d..aa6d73bf 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -3,7 +3,7 @@ use std::str; use anyhow::{anyhow, bail, Error, Result}; use javy::{ hold, hold_and_release, - quickjs::{context::EvalOptions, Function, String as JSString, TypedArray, Value}, + quickjs::{context::EvalOptions, Exception, Function, String as JSString, TypedArray, Value}, to_js_error, Args, Runtime, }; @@ -86,7 +86,8 @@ fn decode<'js>(args: Args<'js>) -> Result> { let js_string = if fatal { JSString::from_str( cx.clone(), - str::from_utf8(view).map_err(|_| anyhow!("The encoded data was not valid utf-8"))?, + str::from_utf8(view) + .map_err(|_| Exception::throw_type(&cx, "The encoded data was not valid utf-8"))?, ) } else { let str = String::from_utf8_lossy(view); diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 64c8a4d1..e3d97777 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -1,4 +1,4 @@ -use javy::{quickjs::Module, Runtime}; +use javy::{from_js_error, quickjs::Module, Runtime}; use once_cell::sync::OnceCell; use std::io::{self, Read}; use std::slice; @@ -29,6 +29,7 @@ pub extern "C" fn init() { unsafe { Module::unsafe_declare(this.clone(), FUNCTION_MODULE_NAME, contents) }? .write_object_le() }) + .map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e))) .unwrap(); unsafe { diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index d59ea076..04682788 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -60,7 +60,7 @@ pub mod json; /// Print the given JS value. /// /// The implementation matches the default JavaScript display format for each value. -pub fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { +pub fn print(val: &Value, sink: &mut String) -> Result<()> { match val.type_of() { Type::Undefined => write!(sink, "undefined").map_err(Into::into), Type::Null => write!(sink, "null").map_err(Into::into), @@ -89,7 +89,7 @@ pub fn print<'js>(val: &Value<'js>, sink: &mut String) -> Result<()> { } Type::Object => write!(sink, "[object Object]").map_err(Into::into), // TODO: Implement the rest. - _ => unimplemented!(), + x => unimplemented!("{x}"), } } @@ -113,7 +113,7 @@ impl<'js> Args<'js> { } } -/// Alias for `Args::hold(cx, args).release() +/// Alias for `Args::hold(cx, args).release()` #[macro_export] macro_rules! hold_and_release { ($cx:expr, $args:expr) => { @@ -121,7 +121,7 @@ macro_rules! hold_and_release { }; } -/// Alias for `Args::hold(cx, args) +/// Alias for [Args::hold] #[macro_export] macro_rules! hold { ($cx:expr, $args:expr) => { From 2bea07776884b5c2be096c9192e1a607af2f55ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Thu, 11 Apr 2024 15:42:56 -0400 Subject: [PATCH 07/22] Fix clippy errors --- crates/apis/src/console/mod.rs | 9 +--- crates/apis/src/random/mod.rs | 6 ++- crates/apis/src/stream_io/mod.rs | 14 +++--- crates/apis/src/text_encoding/mod.rs | 10 +++-- crates/cli/src/wasm_generator/static.rs | 3 -- crates/cli/src/wasm_generator/transform.rs | 2 - crates/core/src/execution.rs | 29 +++++------- crates/core/src/lib.rs | 28 +++++------- crates/core/src/main.rs | 23 ++++------ crates/javy/src/runtime.rs | 51 ++++++++++++---------- 10 files changed, 78 insertions(+), 97 deletions(-) diff --git a/crates/apis/src/console/mod.rs b/crates/apis/src/console/mod.rs index 31560be6..d4a0be17 100644 --- a/crates/apis/src/console/mod.rs +++ b/crates/apis/src/console/mod.rs @@ -14,7 +14,6 @@ pub use config::LogStream; mod config; -// TODO: #[derive(Default)] pub(super) struct Console {} impl Console { @@ -33,17 +32,11 @@ impl JSApiSet for Console { } } -fn register_console<'js, T, U>( - context: &Context, - mut log_stream: T, - mut error_stream: U, -) -> Result<()> +fn register_console(context: &Context, mut log_stream: T, mut error_stream: U) -> Result<()> where T: Write + 'static, U: Write + 'static, { - // TODO: Revisit the callback signatures, there's a possibility that we can - // actually convert from anyhow::Error to quickjs::Error. context.with(|this| { let globals = this.globals(); let console = Object::new(this.clone())?; diff --git a/crates/apis/src/random/mod.rs b/crates/apis/src/random/mod.rs index 89699cea..d1c91baa 100644 --- a/crates/apis/src/random/mod.rs +++ b/crates/apis/src/random/mod.rs @@ -36,8 +36,10 @@ mod tests { let runtime = Runtime::default(); Random.register(&runtime, &APIConfig::default())?; runtime.context().with(|this| { - let mut eval_opts = EvalOptions::default(); - eval_opts.strict = false; + let eval_opts = EvalOptions { + strict: false, + ..Default::default() + }; this.eval_with_options("result = Math.random()", eval_opts)?; let result: f64 = this .globals() diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index 4e64bb18..29bf36bf 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -37,7 +37,7 @@ fn extract_args<'a, 'js: 'a>( Ok((fd, data, offset, length)) } -fn write<'js>(args: Args<'js>) -> Result> { +fn write(args: Args<'_>) -> Result> { enum Fd { Stdout, Stderr, @@ -64,7 +64,6 @@ fn write<'js>(args: Args<'js>) -> Result> { .as_bytes() .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; - // TODO: Revisit the f64 to usize conversions. let offset = offset .as_number() .ok_or_else(|| anyhow!("offset must be a number"))? as usize; @@ -90,7 +89,7 @@ fn write<'js>(args: Args<'js>) -> Result> { Ok(Value::new_number(cx, n as f64)) } -fn read<'js>(args: Args<'js>) -> Result> { +fn read(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let (fd, data, offset, length) = extract_args(&args, "Javy.IO.readSync")?; @@ -117,7 +116,13 @@ fn read<'js>(args: Args<'js>) -> Result> { .as_bytes() .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; - // TODO: Comment on safety. + // Safety + // This is one of the unfortunate unsafe pieces of the APIs, which ideally + // should be revisited. In order to make this safe. + // This is unsafe only if the length of the buffer doesn't match the length + // and offset passed as arguments, the caller must ensure that this is true. + // We could make this API safe by changing the expectations of the + // JavaScript side of things in `io.js`. let dst = data.as_ptr() as *mut _; let dst: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(dst, length) }; let n = fd.read(&mut dst[offset..(offset + length)])?; @@ -129,7 +134,6 @@ impl JSApiSet for StreamIO { fn register<'js>(&self, runtime: &Runtime, _config: &APIConfig) -> Result<()> { runtime.context().with(|this| { let globals = this.globals(); - // TODO: Do we need this? if globals.get::<_, Object>("Javy").is_err() { globals.set("Javy", Object::new(this.clone())?)? } diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index aa6d73bf..53674beb 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -29,8 +29,10 @@ impl JSApiSet for TextEncoding { encode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; - let mut opts = EvalOptions::default(); - opts.strict = false; + let opts = EvalOptions { + strict: false, + ..Default::default() + }; this.eval_with_options(include_str!("./text-encoding.js"), opts)?; Ok::<_, Error>(()) @@ -41,7 +43,7 @@ impl JSApiSet for TextEncoding { } /// Decode a UTF-8 byte buffer as a JavaScript String. -fn decode<'js>(args: Args<'js>) -> Result> { +fn decode(args: Args<'_>) -> Result> { let (cx, args) = args.release(); if args.len() != 5 { bail!( @@ -98,7 +100,7 @@ fn decode<'js>(args: Args<'js>) -> Result> { } /// Encode a JavaScript String into a JavaScript UInt8Array. -fn encode<'js>(args: Args<'js>) -> Result> { +fn encode(args: Args<'_>) -> Result> { let (cx, args) = args.release(); if args.len() != 1 { bail!("Wrong number of arguments. Expected 1. Got {}", args.len()); diff --git a/crates/cli/src/wasm_generator/static.rs b/crates/cli/src/wasm_generator/static.rs index 96657276..8538d4e3 100644 --- a/crates/cli/src/wasm_generator/static.rs +++ b/crates/cli/src/wasm_generator/static.rs @@ -137,9 +137,6 @@ fn optimize_wasm(wasm: &[u8]) -> Result> { OptimizationOptions::new_opt_level_3() // Aggressively optimize for speed. .shrink_level(ShrinkLevel::Level0) // Don't optimize for size at the expense of performance. - // TODO: Make configurable? - // Making it configurable and setting it to true will enable a better - // profiling experience. .debug_info(false) .run(&tempfile_path, &tempfile_path)?; diff --git a/crates/cli/src/wasm_generator/transform.rs b/crates/cli/src/wasm_generator/transform.rs index 4d259f01..68023dcb 100644 --- a/crates/cli/src/wasm_generator/transform.rs +++ b/crates/cli/src/wasm_generator/transform.rs @@ -36,8 +36,6 @@ impl CustomSection for SourceCodeSection { pub fn module_config() -> ModuleConfig { let mut config = ModuleConfig::new(); - // TODO: configurable? - // Setting it to true will enable a better profiling experience. config.generate_name_section(false); config } diff --git a/crates/core/src/execution.rs b/crates/core/src/execution.rs index 2eb62b29..4c45db88 100644 --- a/crates/core/src/execution.rs +++ b/crates/core/src/execution.rs @@ -11,11 +11,10 @@ use javy::{ /// /// Evaluating also prepares (or "instantiates") the state of the JavaScript /// engine given all the information encoded in the bytecode. -// TODO: Rename this? pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { runtime .context() - .with(|this| Module::instantiate_read_object(this, bytecode).and_then(|_| Ok(()))) + .with(|this| Module::instantiate_read_object(this, bytecode).map(|_| ())) .map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e))) // Prefer calling `process_event_loop` *outside* of the `with` callback, // to avoid errors regarding multiple mutable borrows. @@ -29,10 +28,6 @@ pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { /// the target function from a previously evaluated module. It's the caller's /// reponsibility to ensure that the module containing the target function has /// been previously evaluated. -// TODO: -// * Consider renaming this to emit_invoke_function. -// * There's probably(?) a better way of doing this through rquickjs' module -// loaders and resolvers, in such a way that we can avoid having to eval. pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { let js = if fn_name == "default" { format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") @@ -43,26 +38,22 @@ pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { runtime .context() .with(|this| { - let mut opts = EvalOptions::default(); - // TODO: config? - opts.strict = false; - // We're assuming imports and exports, therefore we want to - // evaluate as a module. - opts.global = false; + let opts = EvalOptions { + strict: false, + // We're assuming imports and exports, therefore we want to evaluate + // as a module. + global: false, + ..Default::default() + }; this.eval_with_options::, _>(js.into_bytes(), opts) .map_err(|e| from_js_error(this.clone(), e)) - .and_then(|_| Ok(())) + .map(|_| ()) }) - .and_then(|_: ()| process_event_loop(&runtime)) + .and_then(|_: ()| process_event_loop(runtime)) .unwrap_or_else(handle_error) } fn process_event_loop(rt: &Runtime) -> Result<()> { - // TODO: - // I'm not sure if having a cargo feature gives us a lot here, given that - // we're not preventing the usage of the event loop at the engine level; it - // might be worth revisiting this to make it a config option instead and - // including / excluding the right engine intrinsics via rquickjs if cfg!(feature = "experimental_event_loop") { rt.resolve_pending_jobs()? } else if rt.has_pending_jobs() { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index b218c64c..a8c4c5da 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,5 @@ -use javy::{quickjs::Module, Runtime}; +use anyhow::anyhow; +use javy::Runtime; use once_cell::sync::OnceCell; use std::slice; use std::str; @@ -17,10 +18,12 @@ static mut RUNTIME: OnceCell = OnceCell::new(); pub extern "C" fn init() { let runtime = runtime::new_runtime().unwrap(); unsafe { - match RUNTIME.set(runtime) { - Err(_) => panic!("Could not pre-initialize Runtime"), - _ => {} - } + RUNTIME + .set(runtime) + // `set` requires `T` to implement `Debug` but quickjs::{Runtime, + // Context} don't. + .map_err(|_| anyhow!("Could not pre-initialize javy::Runtime")) + .unwrap(); }; } @@ -43,21 +46,14 @@ pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) - let runtime = runtime::new_runtime().unwrap(); let js_src = str::from_utf8(slice::from_raw_parts(js_src_ptr, js_src_len)).unwrap(); - // TODO: Dedup this. - let bc = runtime - .context() - // TODO: Should `function.mjs` be configurable instead? - // Ideally it should pick-up the input file name. - .with(|this| { - unsafe { Module::unsafe_declare(this.clone(), FUNCTION_MODULE_NAME, js_src) }? - .write_object_le() - }) + let bytecode = runtime + .compile_to_bytecode(FUNCTION_MODULE_NAME, js_src) .unwrap(); // We need the bytecode buffer to live longer than this function so it can be read from memory - let bytecode_ptr = Box::leak(bc.clone().into_boxed_slice()).as_ptr(); + let bytecode_ptr = Box::leak(bytecode.clone().into_boxed_slice()).as_ptr(); COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32; - COMPILE_SRC_RET_AREA[1] = bc.len().try_into().unwrap(); + COMPILE_SRC_RET_AREA[1] = bytecode.len().try_into().unwrap(); COMPILE_SRC_RET_AREA.as_ptr() } diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index e3d97777..f3698119 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -1,4 +1,5 @@ -use javy::{from_js_error, quickjs::Module, Runtime}; +use anyhow::anyhow; +use javy::Runtime; use once_cell::sync::OnceCell; use std::io::{self, Read}; use std::slice; @@ -23,22 +24,16 @@ pub extern "C" fn init() { io::stdin().read_to_string(&mut contents).unwrap(); let bytecode = runtime - .context() - // TODO: Should `function.mjs` be configurable instead? - .with(|this| { - unsafe { Module::unsafe_declare(this.clone(), FUNCTION_MODULE_NAME, contents) }? - .write_object_le() - }) - .map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e))) + .compile_to_bytecode(FUNCTION_MODULE_NAME, &contents) .unwrap(); unsafe { - // TODO: switch to `map_err` + `unwrap`? - match RUNTIME.set(runtime) { - // TODO: Comment why. - Err(_) => panic!("Could not pre-intialize Runtime"), - _ => {} - }; + RUNTIME + .set(runtime) + // `set` requires `T` to implement `Debug` but quickjs::{Runtime, + // Context} don't. + .map_err(|_| anyhow!("Could not pre-initialize javy::Runtime")) + .unwrap(); BYTECODE.set(bytecode).unwrap(); } } diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 287b4dd3..70b23bae 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -1,14 +1,16 @@ // use crate::quickjs::JSContextRef; +use super::from_js_error; use anyhow::{bail, Result}; -use rquickjs::{Context, Runtime as QRuntime}; +use rquickjs::{Context, Module, Runtime as QRuntime}; use std::mem::ManuallyDrop; use crate::Config; -// TODO: Update documentation. /// A JavaScript Runtime. /// -/// Provides a [`Self::context()`] method for working with the underlying [`JSContextRef`]. +/// Javy's [Runtime] holds a [rquickjs::Runtime] and [rquickjs::Context], +/// and provides accessors these two propoerties which enable working with +/// [rquickjs] APIs. /// /// ## Examples /// @@ -17,30 +19,23 @@ use crate::Config; /// # use javy::{quickjs::JSValue, Runtime}; /// let runtime = Runtime::default(); /// let context = runtime.context(); -/// context -/// .global_object() -/// .unwrap() -/// .set_property( -/// "print", -/// context -/// .wrap_callback(move |_ctx, _this, args| { -/// let str = args -/// .first() -/// .ok_or(anyhow!("Need to pass an argument"))? -/// .to_string(); -/// println!("{str}"); -/// Ok(JSValue::Undefined) -/// }) -/// .unwrap(), -/// ) -/// .unwrap(); -/// context.eval_global("hello.js", "print('hello!');").unwrap(); +/// +/// +/// /// ``` pub struct Runtime { /// The QuickJS context. context: Context, /// The inner QuickJS runtime representation. - // TODO: Document why `ManuallyDrop`. + // We use `ManuallyDrop` to avoid incurring in the cost of dropping the + // [rquickjs::Runtime] and its associated objects, which takes a substantial + // time. + // This assumes that Javy is used for short-lived programs were the host + // will collect the instance's memory when execution ends, making these + // drops unnecessary. + // + // This might not be suitable for all use-cases, so we'll make this + // behaviour configurable. inner: ManuallyDrop, } @@ -49,9 +44,8 @@ impl Runtime { pub fn new(_config: Config) -> Result { let rt = ManuallyDrop::new(QRuntime::new()?); - // TODO: Make GC configurable? + // See comment above about configuring GC behaviour. rt.set_gc_threshold(usize::MAX); - // TODO: Add a comment here? let context = Context::full(&rt)?; Ok(Self { inner: rt, context }) } @@ -83,6 +77,15 @@ impl Runtime { pub fn has_pending_jobs(&self) -> bool { self.inner.is_job_pending() } + + /// Compiles the given module to bytecode. + pub fn compile_to_bytecode(&self, name: &str, contents: &str) -> Result> { + self.context() + .with(|this| { + unsafe { Module::unsafe_declare(this.clone(), name, contents) }?.write_object_le() + }) + .map_err(|e| self.context().with(|cx| from_js_error(cx.clone(), e))) + } } impl Default for Runtime { From d599ed44136358ba7e98c28333e9a4050335881d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Thu, 11 Apr 2024 16:12:13 -0400 Subject: [PATCH 08/22] Update documentation where applicable and remove reference to older crates --- crates/apis/README.md | 15 +++++------ crates/core/src/execution.rs | 2 +- crates/javy/README.md | 48 ++++++++++++++++++------------------ crates/javy/src/lib.rs | 41 +++++++++++++++--------------- crates/javy/src/runtime.rs | 12 --------- 5 files changed, 54 insertions(+), 64 deletions(-) diff --git a/crates/apis/README.md b/crates/apis/README.md index 824e926b..cc492007 100644 --- a/crates/apis/README.md +++ b/crates/apis/README.md @@ -7,14 +7,19 @@ APIs are registered by enabling crate features. ## Example usage ```rust -use javy::{quickjs::JSValue, Runtime}; -// with `console` feature enabled + +// With the `console` feature enabled. +use javy::{Runtime, from_js_error}; use javy_apis::RuntimeExt; +use anyhow::Result; fn main() -> Result<()> { let runtime = Runtime::new_with_defaults()?; let context = runtime.context(); - context.eval_global("hello.js", "console.log('hello!');")?; + context.with(|cx| { + cx.eval_with_options(Default::default(), "console.log('hello!');") + .map_err(|e| to_js_error(cx.clone(), e))? + }); Ok(()) } ``` @@ -30,7 +35,3 @@ If you want to customize the runtime or the APIs, you can use the `Runtime::new_ ## Publishing to crates.io To publish this crate to crates.io, run `./publish.sh`. - -## Using a custom WASI SDK - -This crate can be compiled using a custom [WASI SDK](https://github.com/WebAssembly/wasi-sdk). When building this crate, set the `QUICKJS_WASM_SYS_WASI_SDK_PATH` environment variable to the absolute path where you installed the SDK. diff --git a/crates/core/src/execution.rs b/crates/core/src/execution.rs index 4c45db88..7da0193b 100644 --- a/crates/core/src/execution.rs +++ b/crates/core/src/execution.rs @@ -45,7 +45,7 @@ pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { global: false, ..Default::default() }; - this.eval_with_options::, _>(js.into_bytes(), opts) + this.eval_with_options::, _>(js, opts) .map_err(|e| from_js_error(this.clone(), e)) .map(|_| ()) }) diff --git a/crates/javy/README.md b/crates/javy/README.md index 0a0760ff..43ad13b5 100644 --- a/crates/javy/README.md +++ b/crates/javy/README.md @@ -2,31 +2,35 @@ A configurable JavaScript runtime for WebAssembly. -Uses QuickJS through the `quickjs-wasm-rs` crate to evalulate JavaScript source code or QuickJS bytecode. +Uses QuickJS through the [`rquickjs`](https://docs.rs/rquickjs/latest/rquickjs/) +crate to evalulate JavaScript source code or QuickJS bytecode. ## Example usage ```rust -use anyhow::{anyhow, Result}; -use javy::{quickjs::JSValue, Runtime}; - -fn main() -> Result<()> { - let runtime = Runtime::default(); - let context = runtime.context(); - context.global_object()?.set_property( - "print", - context.wrap_callback(move |_ctx, _this, args| { - let str = args - .first() - .ok_or(anyhow!("Need to pass an argument"))? - .to_string(); - println!("{str}"); - Ok(JSValue::Undefined) - })?, +use anyhow::anyhow; +use javy::{Runtime, from_js_error}; +let runtime = Runtime::default(); +let context = runtime.context(); + +context.with(|cx| { + let globals = this.globals(); + globals.set( + "print_hello", + Function::new( + this.clone(), + MutFn::new(move |_, _| { + println!("Hello, world!"); + }), + )?, )?; - context.eval_global("hello.js", "print('hello!');")?; - Ok(()) -} + }); + +context.with(|cx| { + cx.eval_with_options(Default::default(), "print_hello();") + .map_err(|e| from_js_error(cx.clone(), e)) + .map(|_| ()) +}); ``` Create a `Runtime` and use the reference returned by `context()` to add functions and evaluate source code. @@ -40,7 +44,3 @@ Create a `Runtime` and use the reference returned by `context()` to add function ## Publishing to crates.io To publish this crate to crates.io, run `./publish.sh`. - -## Using a custom WASI SDK - -This crate can be compiled using a custom [WASI SDK](https://github.com/WebAssembly/wasi-sdk). When building this crate, set the `QUICKJS_WASM_SYS_WASI_SDK_PATH` environment variable to the absolute path where you installed the SDK. diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index 04682788..b384974e 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -2,28 +2,29 @@ //! //! Example usage: //! ``` -//! # use anyhow::anyhow; -//! # use javy::{quickjs::JSValue, Runtime}; +//! use anyhow::anyhow; +//! use javy::{Runtime, from_js_error}; //! let runtime = Runtime::default(); //! let context = runtime.context(); -//! context -//! .global_object() -//! .unwrap() -//! .set_property( -//! "print", -//! context -//! .wrap_callback(move |_ctx, _this, args| { -//! let str = args -//! .first() -//! .ok_or(anyhow!("Need to pass an argument"))? -//! .to_string(); -//! println!("{str}"); -//! Ok(JSValue::Undefined) -//! }) -//! .unwrap(), -//! ) -//! .unwrap(); -//! context.eval_global("hello.js", "print('hello!');").unwrap(); +//! +//! context.with(|cx| { +//! let globals = this.globals(); +//! globals.set( +//! "print_hello", +//! Function::new( +//! this.clone(), +//! MutFn::new(move |_, _| { +//! println!("Hello, world!"); +//! }), +//! )?, +//! )?; +//! }); +//! +//! context.with(|cx| { +//! cx.eval_with_options(Default::default(), "print_hello();") +//! .map_err(|e| from_js_error(cx.clone(), e)) +//! .map(|_| ()) +//! }); //! ``` //! //! ## Core concepts diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 70b23bae..6f226484 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -11,18 +11,6 @@ use crate::Config; /// Javy's [Runtime] holds a [rquickjs::Runtime] and [rquickjs::Context], /// and provides accessors these two propoerties which enable working with /// [rquickjs] APIs. -/// -/// ## Examples -/// -/// ``` -/// # use anyhow::anyhow; -/// # use javy::{quickjs::JSValue, Runtime}; -/// let runtime = Runtime::default(); -/// let context = runtime.context(); -/// -/// -/// -/// ``` pub struct Runtime { /// The QuickJS context. context: Context, From b255b559a5016a6c277878ac5596fa5c302900bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Fri, 12 Apr 2024 13:29:36 -0400 Subject: [PATCH 09/22] Handle converting from JS strings to Rust strings for text encoding --- crates/apis/src/text_encoding/mod.rs | 68 ++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index 53674beb..e5d43947 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -1,14 +1,16 @@ use std::str; +use crate::{APIConfig, JSApiSet}; use anyhow::{anyhow, bail, Error, Result}; use javy::{ hold, hold_and_release, - quickjs::{context::EvalOptions, Exception, Function, String as JSString, TypedArray, Value}, + quickjs::{ + context::EvalOptions, qjs, Error as JSError, Exception, Function, String as JSString, + TypedArray, Value, + }, to_js_error, Args, Runtime, }; -use crate::{APIConfig, JSApiSet}; - pub(super) struct TextEncoding; impl JSApiSet for TextEncoding { @@ -109,13 +111,71 @@ fn encode(args: Args<'_>) -> Result> { let js_string = args[0] .as_string() .ok_or_else(|| anyhow!("Argument must be a String"))? - .to_string()?; + // This is the fast path. + // The string is already utf-8. + .to_string() + .unwrap_or_else(|error| { + let mut len: qjs::size_t = 0; + let ptr = unsafe { + qjs::JS_ToCStringLen2(cx.as_raw().as_ptr(), &mut len, args[0].as_raw(), 0) + }; + let buf = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }; + to_string_lossy(error, buf) + }); Ok(TypedArray::new(cx, js_string.into_bytes())? .as_value() .to_owned()) } +/// Converts the JavaScript value to a string, replacing any invalid UTF-8 sequences with the +/// Unicode replacement character (U+FFFD). +fn to_string_lossy(error: JSError, buffer: &[u8]) -> String { + // The error here *must* be a Utf8 error; the `JSString::to_string()` may + // return `JSError::Unknown`, but at that point, something else has gone + // wrong too. + let mut utf8_error = match error { + JSError::Utf8(e) => e, + _ => unreachable!("expected Utf8 error"), + }; + let mut res = String::new(); + let mut buffer = buffer; + loop { + let (valid, after_valid) = buffer.split_at(utf8_error.valid_up_to()); + res.push_str(unsafe { str::from_utf8_unchecked(valid) }); + res.push(char::REPLACEMENT_CHARACTER); + + // see https://simonsapin.github.io/wtf-8/#surrogate-byte-sequence + let lone_surrogate = matches!(after_valid, [0xED, 0xA0..=0xBF, 0x80..=0xBF, ..]); + + // https://simonsapin.github.io/wtf-8/#converting-wtf-8-utf-8 states that each + // 3-byte lone surrogate sequence should be replaced by 1 UTF-8 replacement + // char. Rust's `Utf8Error` reports each byte in the lone surrogate byte + // sequence as a separate error with an `error_len` of 1. Since we insert a + // replacement char for each error, this results in 3 replacement chars being + // inserted. So we use an `error_len` of 3 instead of 1 to treat the entire + // 3-byte sequence as 1 error instead of as 3 errors and only emit 1 + // replacement char. + let error_len = if lone_surrogate { + 3 + } else { + utf8_error + .error_len() + .expect("Error length should always be available on underlying buffer") + }; + + buffer = &after_valid[error_len..]; + match str::from_utf8(buffer) { + Ok(valid) => { + res.push_str(valid); + break; + } + Err(e) => utf8_error = e, + } + } + res +} + #[cfg(test)] mod tests { use crate::{APIConfig, JSApiSet}; From c03962548f85d39135b211694d9c96e93fe87a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Mon, 15 Apr 2024 20:31:30 -0400 Subject: [PATCH 10/22] Migrate serializers and deserializers to use rquickjs and move them under the javy crate --- Cargo.lock | 3 +- crates/apis/src/text_encoding/mod.rs | 69 +-- crates/javy/src/json.rs | 16 +- crates/javy/src/lib.rs | 60 ++- crates/javy/src/messagepack.rs | 9 +- crates/javy/src/serde/de.rs | 428 ++++++++++++++++ crates/javy/src/serde/err.rs | 23 + crates/javy/src/serde/mod.rs | 304 ++++++++++++ crates/javy/src/serde/ser.rs | 704 +++++++++++++++++++++++++++ 9 files changed, 1537 insertions(+), 79 deletions(-) create mode 100644 crates/javy/src/serde/de.rs create mode 100644 crates/javy/src/serde/err.rs create mode 100644 crates/javy/src/serde/mod.rs create mode 100644 crates/javy/src/serde/ser.rs diff --git a/Cargo.lock b/Cargo.lock index ad78d965..00017e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1479,9 +1479,10 @@ name = "javy" version = "2.2.1-alpha.1" dependencies = [ "anyhow", - "quickjs-wasm-rs", + "quickcheck", "rmp-serde", "rquickjs", + "serde", "serde-transcode", "serde_json", ] diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index e5d43947..3933c9a7 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -4,11 +4,8 @@ use crate::{APIConfig, JSApiSet}; use anyhow::{anyhow, bail, Error, Result}; use javy::{ hold, hold_and_release, - quickjs::{ - context::EvalOptions, qjs, Error as JSError, Exception, Function, String as JSString, - TypedArray, Value, - }, - to_js_error, Args, Runtime, + quickjs::{context::EvalOptions, Exception, Function, String as JSString, TypedArray, Value}, + to_js_error, to_string_lossy, Args, Runtime, }; pub(super) struct TextEncoding; @@ -110,72 +107,18 @@ fn encode(args: Args<'_>) -> Result> { let js_string = args[0] .as_string() - .ok_or_else(|| anyhow!("Argument must be a String"))? + .ok_or_else(|| anyhow!("Argument must be a String"))?; + let encoded = js_string // This is the fast path. // The string is already utf-8. .to_string() - .unwrap_or_else(|error| { - let mut len: qjs::size_t = 0; - let ptr = unsafe { - qjs::JS_ToCStringLen2(cx.as_raw().as_ptr(), &mut len, args[0].as_raw(), 0) - }; - let buf = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }; - to_string_lossy(error, buf) - }); + .unwrap_or_else(|error| to_string_lossy(&cx, js_string, error)); - Ok(TypedArray::new(cx, js_string.into_bytes())? + Ok(TypedArray::new(cx, encoded.into_bytes())? .as_value() .to_owned()) } -/// Converts the JavaScript value to a string, replacing any invalid UTF-8 sequences with the -/// Unicode replacement character (U+FFFD). -fn to_string_lossy(error: JSError, buffer: &[u8]) -> String { - // The error here *must* be a Utf8 error; the `JSString::to_string()` may - // return `JSError::Unknown`, but at that point, something else has gone - // wrong too. - let mut utf8_error = match error { - JSError::Utf8(e) => e, - _ => unreachable!("expected Utf8 error"), - }; - let mut res = String::new(); - let mut buffer = buffer; - loop { - let (valid, after_valid) = buffer.split_at(utf8_error.valid_up_to()); - res.push_str(unsafe { str::from_utf8_unchecked(valid) }); - res.push(char::REPLACEMENT_CHARACTER); - - // see https://simonsapin.github.io/wtf-8/#surrogate-byte-sequence - let lone_surrogate = matches!(after_valid, [0xED, 0xA0..=0xBF, 0x80..=0xBF, ..]); - - // https://simonsapin.github.io/wtf-8/#converting-wtf-8-utf-8 states that each - // 3-byte lone surrogate sequence should be replaced by 1 UTF-8 replacement - // char. Rust's `Utf8Error` reports each byte in the lone surrogate byte - // sequence as a separate error with an `error_len` of 1. Since we insert a - // replacement char for each error, this results in 3 replacement chars being - // inserted. So we use an `error_len` of 3 instead of 1 to treat the entire - // 3-byte sequence as 1 error instead of as 3 errors and only emit 1 - // replacement char. - let error_len = if lone_surrogate { - 3 - } else { - utf8_error - .error_len() - .expect("Error length should always be available on underlying buffer") - }; - - buffer = &after_valid[error_len..]; - match str::from_utf8(buffer) { - Ok(valid) => { - res.push_str(valid); - break; - } - Err(e) => utf8_error = e, - } - } - res -} - #[cfg(test)] mod tests { use crate::{APIConfig, JSApiSet}; diff --git a/crates/javy/src/json.rs b/crates/javy/src/json.rs index 1ef15ffd..6d337321 100644 --- a/crates/javy/src/json.rs +++ b/crates/javy/src/json.rs @@ -1,21 +1,17 @@ +use crate::quickjs::{Ctx, Value}; +use crate::serde::{de::Deserializer, ser::Serializer}; use anyhow::Result; -use quickjs_wasm_rs::{Deserializer, JSContextRef, JSValueRef, Serializer}; -/// Transcodes a byte slice containing a JSON encoded payload into a [`JSValueRef`]. -/// -/// Arguments: -/// * `context` - A reference to the [`JSContextRef`] that will contain the -/// returned [`JSValueRef`]. -/// * `bytes` - A byte slice containing a JSON encoded payload. -pub fn transcode_input<'a>(context: &'a JSContextRef, bytes: &[u8]) -> Result> { +/// Transcodes a byte slice containing a JSON encoded payload into a [Value]. +pub fn transcode_input<'js>(context: Ctx<'js>, bytes: &[u8]) -> Result> { let mut deserializer = serde_json::Deserializer::from_slice(bytes); let mut serializer = Serializer::from_context(context)?; serde_transcode::transcode(&mut deserializer, &mut serializer)?; Ok(serializer.value) } -/// Transcodes a [`JSValueRef`] into a JSON encoded byte vector. -pub fn transcode_output(val: JSValueRef) -> Result> { +/// Transcodes a [Value] into a slice of JSON bytes. +pub fn transcode_output(val: Value<'_>) -> Result> { let mut output = Vec::new(); let mut deserializer = Deserializer::from(val); let mut serializer = serde_json::Serializer::new(&mut output); diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index b384974e..13a2fe86 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -43,13 +43,17 @@ pub use config::Config; pub use rquickjs as quickjs; pub use runtime::Runtime; +use std::str; pub mod alloc; mod config; mod runtime; +mod serde; use anyhow::{anyhow, Error, Result}; -use rquickjs::{prelude::Rest, Ctx, Error as JSError, Exception, Type, Value}; +use rquickjs::{ + prelude::Rest, qjs, Ctx, Error as JSError, Exception, String as JSString, Type, Value, +}; use std::fmt::Write; #[cfg(feature = "messagepack")] @@ -153,3 +157,57 @@ pub fn to_js_error(cx: Ctx, e: Error) -> JSError { )), } } + +/// Converts the JavaScript value to a string, replacing any invalid UTF-8 sequences with the +/// Unicode replacement character (U+FFFD). +// TODO: Upstream this? +pub fn to_string_lossy<'js>(cx: &Ctx<'js>, string: &JSString<'js>, error: JSError) -> String { + let mut len: qjs::size_t = 0; + let ptr = unsafe { qjs::JS_ToCStringLen2(cx.as_raw().as_ptr(), &mut len, string.as_raw(), 0) }; + let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }; + + // The error here *must* be a Utf8 error; the `JSString::to_string()` may + // return `JSError::Unknown`, but at that point, something else has gone + // wrong too. + + let mut utf8_error = match error { + JSError::Utf8(e) => e, + _ => unreachable!("expected Utf8 error"), + }; + let mut res = String::new(); + let mut buffer = buffer; + loop { + let (valid, after_valid) = buffer.split_at(utf8_error.valid_up_to()); + res.push_str(unsafe { str::from_utf8_unchecked(valid) }); + res.push(char::REPLACEMENT_CHARACTER); + + // see https://simonsapin.github.io/wtf-8/#surrogate-byte-sequence + let lone_surrogate = matches!(after_valid, [0xED, 0xA0..=0xBF, 0x80..=0xBF, ..]); + + // https://simonsapin.github.io/wtf-8/#converting-wtf-8-utf-8 states that each + // 3-byte lone surrogate sequence should be replaced by 1 UTF-8 replacement + // char. Rust's `Utf8Error` reports each byte in the lone surrogate byte + // sequence as a separate error with an `error_len` of 1. Since we insert a + // replacement char for each error, this results in 3 replacement chars being + // inserted. So we use an `error_len` of 3 instead of 1 to treat the entire + // 3-byte sequence as 1 error instead of as 3 errors and only emit 1 + // replacement char. + let error_len = if lone_surrogate { + 3 + } else { + utf8_error + .error_len() + .expect("Error length should always be available on underlying buffer") + }; + + buffer = &after_valid[error_len..]; + match str::from_utf8(buffer) { + Ok(valid) => { + res.push_str(valid); + break; + } + Err(e) => utf8_error = e, + } + } + res +} diff --git a/crates/javy/src/messagepack.rs b/crates/javy/src/messagepack.rs index 8f68648b..a0c42952 100644 --- a/crates/javy/src/messagepack.rs +++ b/crates/javy/src/messagepack.rs @@ -1,5 +1,6 @@ +use crate::quickjs::{Ctx, Value}; +use crate::serde::{de::Deserializer, ser::Serializer}; use anyhow::Result; -use quickjs_wasm_rs::{Deserializer, JSContextRef, JSValueRef, Serializer}; /// Transcodes a byte slice containing a MessagePack encoded payload into a [`JSValueRef`]. /// @@ -7,15 +8,15 @@ use quickjs_wasm_rs::{Deserializer, JSContextRef, JSValueRef, Serializer}; /// * `context` - A reference to the [`JSContextRef`] that will contain the /// returned [`JSValueRef`]. /// * `bytes` - A byte slice containing a MessagePack encoded payload. -pub fn transcode_input<'a>(context: &'a JSContextRef, bytes: &[u8]) -> Result> { +pub fn transcode_input<'js>(context: Ctx<'js>, bytes: &[u8]) -> Result> { let mut deserializer = rmp_serde::Deserializer::from_read_ref(bytes); - let mut serializer = Serializer::from_context(context)?; + let mut serializer = Serializer::from_context(context.clone())?; serde_transcode::transcode(&mut deserializer, &mut serializer)?; Ok(serializer.value) } /// Transcodes a [`JSValueRef`] into a MessagePack encoded byte vector. -pub fn transcode_output(val: JSValueRef) -> Result> { +pub fn transcode_output(val: Value<'_>) -> Result> { let mut output = Vec::new(); let mut deserializer = Deserializer::from(val); let mut serializer = rmp_serde::Serializer::new(&mut output); diff --git a/crates/javy/src/serde/de.rs b/crates/javy/src/serde/de.rs new file mode 100644 index 00000000..6b638e3b --- /dev/null +++ b/crates/javy/src/serde/de.rs @@ -0,0 +1,428 @@ +use crate::quickjs::{object::ObjectIter, Array, Filter, Value}; +use crate::serde::err::{Error, Result}; +use crate::serde::{MAX_SAFE_INTEGER, MIN_SAFE_INTEGER}; +use crate::{from_js_error, to_string_lossy}; +use anyhow::anyhow; +use serde::de::{self, Error as SerError}; +use serde::forward_to_deserialize_any; + +use super::as_key; + +impl SerError for Error { + fn custom(msg: T) -> Self { + Error::Custom(anyhow!(msg.to_string())) + } +} + +/// `Deserializer` is a deserializer for [Value] values, implementing the `serde::Deserializer` trait. +/// +/// This struct is responsible for converting [Value], into Rust types using the Serde deserialization framework. +/// +/// # Example +/// +/// ``` +/// // Assuming you have a [Value] instance named value containing an i32. +/// let mut deserializer = Deserializer::from(value); +/// +/// // Use deserializer to deserialize the JavaScript value into a Rust type. +/// let number: i32 = serde::Deserialize::deserialize(deserializer)?; +/// ``` +pub struct Deserializer<'js> { + value: Value<'js>, + map_key: bool, + current_kv: Option<(Value<'js>, Value<'js>)>, +} + +impl<'de> From> for Deserializer<'de> { + fn from(value: Value<'de>) -> Self { + Self { + value, + map_key: false, + current_kv: None, + } + } +} +impl<'js> Deserializer<'js> { + fn deserialize_number<'de, V>(&mut self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + if self.value.is_int() { + return visitor.visit_i32( + self.value + .as_int() + .ok_or_else(|| anyhow!("Failed to convert value to i32"))?, + ); + } + + if self.value.is_float() { + let f64_representation = self + .value + .as_float() + .ok_or_else(|| anyhow!("Failed to convert value to f64"))?; + let is_positive = f64_representation.is_sign_positive(); + let safe_integer_range = (MIN_SAFE_INTEGER as f64)..=(MAX_SAFE_INTEGER as f64); + let whole = f64_representation.fract() == 0.0; + + if whole && is_positive && f64_representation <= u32::MAX as f64 { + return visitor.visit_u32(f64_representation as u32); + } + + if whole && safe_integer_range.contains(&f64_representation) { + let x = f64_representation as i64; + return visitor.visit_i64(x); + } + + return visitor.visit_f64(f64_representation); + } + unreachable!() + } +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + if self.value.is_number() { + return self.deserialize_number(visitor); + } + + if self.value.is_bool() { + let val = self.value.as_bool().unwrap(); + return visitor.visit_bool(val); + } + + if self.value.is_null() || self.value.is_undefined() { + return visitor.visit_unit(); + } + + if self.value.is_string() { + if self.map_key { + self.map_key = false; + let key = as_key(&self.value)?; + return visitor.visit_str(&key); + } else { + let val = self + .value + .as_string() + .map(|s| { + s.to_string() + .unwrap_or_else(|e| to_string_lossy(self.value.ctx(), s, e)) + }) + .unwrap(); + return visitor.visit_str(&val); + } + } + + if self.value.is_array() { + let arr = self.value.as_array().unwrap().clone(); + let length = arr.len(); + let seq_access = SeqAccess { + de: self, + length, + seq: arr, + index: 0, + }; + return visitor.visit_seq(seq_access); + } + + if self.value.is_object() { + let filter = Filter::new().enum_only().symbol().string(); + let obj = self.value.as_object().unwrap(); + let properties: ObjectIter<'_, _, Value<'_>> = + obj.own_props::, Value<'_>>(filter); + let map_access = MapAccess { + de: self, + properties, + }; + return visitor.visit_map(map_access); + } + + Err(Error::Custom(anyhow!( + "Couldn't deserialize value: {:?}", + self.value + ))) + } + + fn is_human_readable(&self) -> bool { + false + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + if self.value.is_null() || self.value.is_undefined() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + unimplemented!() + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string + bytes byte_buf unit unit_struct seq tuple + tuple_struct map struct identifier ignored_any + } +} + +struct MapAccess<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + properties: ObjectIter<'de, Value<'de>, Value<'de>>, +} + +impl<'a, 'de> de::MapAccess<'de> for MapAccess<'a, 'de> { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result> + where + K: de::DeserializeSeed<'de>, + { + if let Some(kv) = self.properties.next() { + let (k, v) = kv.map_err(|e| from_js_error(self.de.value.ctx().clone(), e))?; + self.de.value = k.clone(); + self.de.map_key = true; + self.de.current_kv = Some((k, v)); + seed.deserialize(&mut *self.de).map(Some) + } else { + Ok(None) + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + self.de.value = self.de.current_kv.clone().unwrap().1; + seed.deserialize(&mut *self.de) + } +} + +struct SeqAccess<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + seq: Array<'de>, + length: usize, + index: usize, +} + +impl<'a, 'de> de::SeqAccess<'de> for SeqAccess<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + if self.index < self.length { + self.de.value = self + .seq + .get(self.index) + .map_err(|e| from_js_error(self.seq.ctx().clone(), e))?; + self.index += 1; + seed.deserialize(&mut *self.de).map(Some) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::Deserializer as ValueDeserializer; + use crate::{quickjs::Value, serde::MAX_SAFE_INTEGER, Runtime}; + use serde::de::DeserializeOwned; + + fn deserialize_value(v: Value<'_>) -> T + where + T: DeserializeOwned, + { + let mut deserializer = ValueDeserializer::from(v); + T::deserialize(&mut deserializer).unwrap() + } + + #[test] + fn test_null() { + let rt = Runtime::default(); + rt.context().with(|cx| { + let val = Value::new_null(cx); + deserialize_value::<()>(val); + }); + } + + #[test] + fn test_undefined() { + let rt = Runtime::default(); + rt.context().with(|cx| { + let val = Value::new_undefined(cx); + deserialize_value::<()>(val); + }); + } + + #[test] + fn test_nan() { + let rt = Runtime::default(); + rt.context().with(|cx| { + let val = Value::new_float(cx, f64::NAN); + let actual = deserialize_value::(val); + assert!(actual.is_nan()); + }); + } + + #[test] + fn test_infinity() { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let val = Value::new_float(cx, f64::INFINITY); + let actual = deserialize_value::(val); + assert!(actual.is_infinite() && actual.is_sign_positive()); + }); + } + + #[test] + fn test_negative_infinity() { + let rt = Runtime::default(); + rt.context().with(|cx| { + let val = Value::new_float(cx, f64::NEG_INFINITY); + let actual = deserialize_value::(val); + assert!(actual.is_infinite() && actual.is_sign_negative()); + }) + } + + #[test] + fn test_map_always_converts_keys_to_string() { + let rt = Runtime::default(); + // Sanity check to make sure the quickjs VM always store object + // object keys as a string an not a numerical value. + rt.context().with(|c| { + c.eval::, _>("var a = {1337: 42};").unwrap(); + let val = c.globals().get("a").unwrap(); + let actual = deserialize_value::>(val); + + assert_eq!(42, *actual.get("1337").unwrap()) + }); + } + + #[test] + #[should_panic] + fn test_map_does_not_support_non_string_keys() { + let rt = Runtime::default(); + // Sanity check to make sure it's not possible to deserialize + // to a map where keys are not strings (e.g. numerical value). + rt.context().with(|c| { + c.eval::, _>("var a = {1337: 42};").unwrap(); + let val = c.globals().get("a").unwrap(); + deserialize_value::>(val); + }); + } + + #[test] + fn test_u64_bounds() { + let rt = Runtime::default(); + rt.context().with(|c| { + let max = u64::MAX; + let val = Value::new_number(c.clone(), max as f64); + let actual = deserialize_value::(val); + assert_eq!(max as f64, actual); + + let min = u64::MIN; + let val = Value::new_number(c.clone(), min as f64); + let actual = deserialize_value::(val); + assert_eq!(min as f64, actual); + }); + } + + #[test] + fn test_i64_bounds() { + let rt = Runtime::default(); + + rt.context().with(|c| { + let max = i64::MAX; + let val = Value::new_number(c.clone(), max as _); + let actual = deserialize_value::(val); + assert_eq!(max as f64, actual); + + let min = i64::MIN; + let val = Value::new_number(c.clone(), min as _); + let actual = deserialize_value::(val); + assert_eq!(min as f64, actual); + }); + } + + #[test] + fn test_float_to_integer_conversion() { + let rt = Runtime::default(); + + rt.context().with(|c| { + let expected = MAX_SAFE_INTEGER - 1; + let val = Value::new_float(c.clone(), expected as _); + let actual = deserialize_value::(val); + assert_eq!(expected, actual); + + let expected = MAX_SAFE_INTEGER + 1; + let val = Value::new_float(c.clone(), expected as _); + let actual = deserialize_value::(val); + assert_eq!(expected as f64, actual); + }); + } + + #[test] + fn test_u32_upper_bound() { + let rt = Runtime::default(); + + rt.context().with(|c| { + let expected = u32::MAX; + let val = Value::new_number(c, expected as _); + let actual = deserialize_value::(val); + assert_eq!(expected, actual); + }); + } + + #[test] + fn test_u32_lower_bound() { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let expected = i32::MAX as u32 + 1; + let val = Value::new_number(cx, expected as _); + let actual = deserialize_value::(val); + assert_eq!(expected, actual); + }); + } + + #[test] + fn test_array() { + let rt = Runtime::default(); + rt.context().with(|cx| { + cx.eval::, _>("var a = [1, 2, 3];").unwrap(); + let v = cx.globals().get("a").unwrap(); + + let val = deserialize_value::>(v); + + assert_eq!(vec![1, 2, 3], val); + }); + } +} diff --git a/crates/javy/src/serde/err.rs b/crates/javy/src/serde/err.rs new file mode 100644 index 00000000..622ca6e1 --- /dev/null +++ b/crates/javy/src/serde/err.rs @@ -0,0 +1,23 @@ +use std::{error, fmt}; +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + Custom(anyhow::Error), +} + +impl error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Custom(e) => formatter.write_str(&e.to_string()), + } + } +} + +impl From for Error { + fn from(e: anyhow::Error) -> Self { + Error::Custom(e) + } +} diff --git a/crates/javy/src/serde/mod.rs b/crates/javy/src/serde/mod.rs new file mode 100644 index 00000000..93e9fb72 --- /dev/null +++ b/crates/javy/src/serde/mod.rs @@ -0,0 +1,304 @@ +pub mod de; +pub mod err; +pub mod ser; + +use crate::quickjs::Value; +use crate::to_string_lossy; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER#description +pub const MAX_SAFE_INTEGER: i64 = 2_i64.pow(53) - 1; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER#description +pub const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER; + +fn as_key(v: &Value) -> anyhow::Result { + if v.is_string() { + let js_str = v.as_string().unwrap(); + let v = js_str + .to_string() + .unwrap_or_else(|e| to_string_lossy(js_str.ctx(), js_str, e)); + Ok(v) + } else { + anyhow::bail!("map keys must be a string") + } +} + +#[cfg(test)] +mod tests { + use super::de::Deserializer as ValueDeserializer; + use super::ser::Serializer as ValueSerializer; + use crate::serde::{MAX_SAFE_INTEGER, MIN_SAFE_INTEGER}; + use crate::Runtime; + use anyhow::Result; + use quickcheck::quickcheck; + use serde::de::DeserializeOwned; + use serde::{Deserialize, Serialize}; + use std::collections::BTreeMap; + + quickcheck! { + fn test_str(expected: String) -> Result { + let actual = do_roundtrip::<_, String>(&expected); + Ok(expected == actual) + } + + fn test_u8(expected: u8) -> Result { + let actual = do_roundtrip::<_, u8>(&expected); + Ok(expected == actual) + } + + fn test_u16(expected: u16) -> Result { + let actual = do_roundtrip::<_, u16>(&expected); + Ok(expected == actual) + } + + fn test_f32(expected: f32) -> quickcheck::TestResult { + if expected.is_nan() { + return quickcheck::TestResult::discard(); + } + + let actual = do_roundtrip::<_, f32>(&expected); + quickcheck::TestResult::from_bool(expected == actual) + } + + fn test_i32(expected: i32) -> Result { + let actual = do_roundtrip::<_, i32>(&expected); + Ok(expected == actual) + } + + // This test is not representative of what is happening in the real world. Since we are transcoding + // from msgpack, only values greather than or equal to u32::MAX would be serialized as `BigInt`. Any other values would + // be serialized as a `number`. + // + // See https://github.com/3Hren/msgpack-rust/blob/aa3c4a77b2b901fe73a555c615b92773b40905fc/rmp/src/encode/sint.rs#L170. + // + // This test works here since we are explicitly calling serialize_i64 and deserialize_i64. + fn test_i64(expected: i64) -> Result { + if (MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&expected) { + let actual = do_roundtrip::<_, i64>(&expected); + Ok(expected == actual) + } else { + let expected_f64 = expected as f64; + let actual = do_roundtrip::<_, f64>(&expected_f64); + Ok(expected_f64 == actual) + } + } + + fn test_u32(expected: u32) -> Result { + let actual = do_roundtrip::<_, u32>(&expected); + Ok(expected == actual) + } + + // This test is not representative of what is happening in the real world. Since we are transcoding + // from msgpack, only values larger than i64::MAX would be serialized as BigInt. Any other values would + // be serialized as a number. + // + // See https://github.com/3Hren/msgpack-rust/blob/aa3c4a77b2b901fe73a555c615b92773b40905fc/rmp/src/encode/sint.rs#L170. + // + // This test works here since we are explicitly calling serialize_u64 and deserialize_u64. + fn test_u64(expected: u64) -> Result { + if expected <= MAX_SAFE_INTEGER as u64 { + let actual = do_roundtrip::<_, u64>(&expected); + Ok(expected == actual) + } else { + let expected_f64 = expected as f64; + let actual = do_roundtrip::<_, f64>(&expected_f64); + Ok(expected_f64 == actual) + } + } + + fn test_bool(expected: bool) -> Result { + let actual = do_roundtrip::<_, bool>(&expected); + Ok(expected == actual) + } + } + + #[test] + fn test_map() { + let mut expected = BTreeMap::::new(); + expected.insert("foo".to_string(), "bar".to_string()); + expected.insert("hello".to_string(), "world".to_string()); + + let actual = do_roundtrip::<_, BTreeMap>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_struct_into_map() { + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObject { + foo: String, + bar: u32, + } + let expected = MyObject { + foo: "hello".to_string(), + bar: 1337, + }; + + let actual = do_roundtrip::<_, MyObject>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_nested_maps() { + let mut expected = BTreeMap::>::new(); + let mut a = BTreeMap::new(); + a.insert("foo".to_string(), "bar".to_string()); + a.insert("hello".to_string(), "world".to_string()); + let mut b = BTreeMap::new(); + b.insert("toto".to_string(), "titi".to_string()); + expected.insert("aaa".to_string(), a); + expected.insert("bbb".to_string(), b); + + let actual = do_roundtrip::<_, BTreeMap>>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_nested_structs_into_maps() { + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObjectB { + toto: String, + titi: i32, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObjectA { + foo: String, + bar: u32, + b: MyObjectB, + } + let expected = MyObjectA { + foo: "hello".to_string(), + bar: 1337, + b: MyObjectB { + toto: "world".to_string(), + titi: -42, + }, + }; + + let actual = do_roundtrip::<_, MyObjectA>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_sequence() { + let expected = vec!["hello".to_string(), "world".to_string()]; + + let actual = do_roundtrip::<_, Vec>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_nested_sequences() { + let mut expected = Vec::new(); + let a = vec!["foo".to_string(), "bar".to_string()]; + let b = vec!["toto".to_string(), "tata".to_string()]; + expected.push(a); + expected.push(b); + + let actual = do_roundtrip::<_, Vec>>(&expected); + + assert_eq!(expected, actual); + } + + #[test] + fn test_sanity() { + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObject { + a: u8, + b: u16, + c: u32, + d: u64, + e: i8, + f: i16, + g: i32, + h: i64, + i: f32, + j: f64, + k: String, + l: bool, + m: BTreeMap, + n: Vec, + o: BTreeMap>, + p: Vec>, + bb: MyObjectB, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObjectB { + a: u32, + cc: MyObjectC, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct MyObjectC { + a: Vec, + b: BTreeMap, + } + + let mut cc_b = BTreeMap::new(); + cc_b.insert("a".to_string(), 123); + cc_b.insert("b".to_string(), 456); + let cc = MyObjectC { + a: vec![1337, 42], + b: cc_b, + }; + + let bb = MyObjectB { a: 789, cc }; + + let mut m = BTreeMap::new(); + m.insert("a".to_string(), 123); + m.insert("b".to_string(), 456); + m.insert("c".to_string(), 789); + + let mut oo = BTreeMap::new(); + oo.insert("e".to_string(), 123); + + let mut o = BTreeMap::new(); + o.insert("d".to_string(), oo); + + let expected = MyObject { + a: u8::MAX, + b: u16::MAX, + c: u32::MAX, + d: MAX_SAFE_INTEGER as u64, + e: i8::MAX, + f: i16::MAX, + g: i32::MAX, + h: MIN_SAFE_INTEGER, + i: f32::MAX, + j: f64::MAX, + k: "hello world".to_string(), + l: true, + m, + n: vec![1, 2, 3, 4, 5], + o, + p: vec![vec![1, 2], vec![3, 4, 5]], + bb, + }; + + let actual = do_roundtrip::<_, MyObject>(&expected); + + assert_eq!(expected, actual); + } + + fn do_roundtrip(expected: &E) -> A + where + E: Serialize, + A: DeserializeOwned, + { + let rt = Runtime::default(); + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx).unwrap(); + expected.serialize(&mut serializer).unwrap(); + let mut deserializer = ValueDeserializer::from(serializer.value); + let actual = A::deserialize(&mut deserializer).unwrap(); + actual + }) + } +} diff --git a/crates/javy/src/serde/ser.rs b/crates/javy/src/serde/ser.rs new file mode 100644 index 00000000..1cda98aa --- /dev/null +++ b/crates/javy/src/serde/ser.rs @@ -0,0 +1,704 @@ +use crate::from_js_error; +use crate::quickjs::{Array, Ctx, Error as JSError, Object, String as JSString, Value}; +use crate::serde::err::{Error, Result}; +use anyhow::anyhow; + +use serde::{ser, ser::Error as SerError, Serialize}; + +use super::as_key; + +/// `Serializer` is a serializer for [Value] values, implementing the `serde::Serializer` trait. +/// +/// This struct is responsible for converting Rust types into [Value] using the Serde +/// serialization framework. +/// +/// ``` +/// // Assuming you have [Ctx] instance named context +/// let serializer = Serializer::from_context(context)?; +/// let value: Value = serializer.serialize_u32(42)?; +/// ``` +pub struct Serializer<'js> { + pub context: Ctx<'js>, + pub value: Value<'js>, + pub key: Value<'js>, +} + +impl SerError for Error { + fn custom(msg: T) -> Self { + Error::Custom(anyhow!(msg.to_string())) + } +} + +impl<'js> Serializer<'js> { + pub fn from_context(context: Ctx<'js>) -> Result { + Ok(Self { + context: context.clone(), + value: Value::new_undefined(context.clone()), + key: Value::new_undefined(context), + }) + } +} + +impl<'a> ser::Serializer for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Self; + type SerializeMap = Self; + type SerializeStruct = Self; + type SerializeStructVariant = Self; + + fn serialize_i8(self, v: i8) -> Result<()> { + self.serialize_i32(i32::from(v)) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + self.serialize_i32(i32::from(v)) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + self.value = Value::new_int(self.context.clone(), v); + Ok(()) + } + + fn serialize_i64(self, v: i64) -> Result<()> { + self.value = Value::new_number(self.context.clone(), v as _); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result<()> { + self.serialize_i32(i32::from(v)) + } + + fn serialize_u16(self, v: u16) -> Result<()> { + self.serialize_i32(i32::from(v)) + } + + fn serialize_u32(self, v: u32) -> Result<()> { + // NOTE: See optimization note in serialize_f64. + self.serialize_f64(f64::from(v)) + } + + fn serialize_u64(self, v: u64) -> Result<()> { + self.value = Value::new_number(self.context.clone(), v as _); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + // NOTE: See optimization note in serialize_f64. + self.serialize_f64(f64::from(v)) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + // NOTE: QuickJS will create a number value backed by an i32 when the value is within + // the i32::MIN..=i32::MAX as an optimization. Otherwise the value will be backed by a f64. + self.value = Value::new_float(self.context.clone(), v); + Ok(()) + } + + fn serialize_bool(self, b: bool) -> Result<()> { + self.value = Value::new_bool(self.context.clone(), b); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result<()> { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result<()> { + let js_string = JSString::from_str(self.context.clone(), v) + .map_err(|e: JSError| Error::custom(e.to_string()))?; + self.value = Value::from(js_string); + Ok(()) + } + + fn serialize_none(self) -> Result<()> { + self.serialize_unit() + } + + fn serialize_unit(self) -> Result<()> { + self.value = Value::new_null(self.context.clone()); + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.serialize_str(variant) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_seq(self, _len: Option) -> Result { + let arr = Array::new(self.context.clone()).map_err(|e| Error::custom(e.to_string()))?; + self.value = Value::from(arr); + Ok(self) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_map(self, _len: Option) -> Result { + let obj = Object::new(self.context.clone()).map_err(|e| Error::custom(e.to_string()))?; + self.value = Value::from(obj); + Ok(self) + } + + fn serialize_struct(self, _name: &'static str, len: usize) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + let obj = Object::new(self.context.clone()).map_err(|e| Error::custom(anyhow!("{e}")))?; + value.serialize(&mut *self)?; + obj.set(variant, self.value.clone()) + .map_err(|e| Error::custom(e.to_string()))?; + self.value = Value::from(obj); + + Ok(()) + } + + fn serialize_bytes(self, _: &[u8]) -> Result<()> { + Err(Error::custom(anyhow!("Cannot serialize bytes"))) + } +} + +impl<'a> ser::SerializeSeq for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut element_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut element_serializer)?; + + if let Some(v) = self.value.as_array() { + return v + .set(v.len(), element_serializer.value.clone()) + .map_err(|e| { + let e = from_js_error(element_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + Err(Error::custom("Expected to be an array")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeTuple for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut element_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut element_serializer)?; + + if let Some(v) = self.value.as_array() { + return v + .set(v.len(), element_serializer.value.clone()) + .map_err(|e| { + let e = from_js_error(element_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an array")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeTupleStruct for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut field_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut field_serializer)?; + if let Some(v) = self.value.as_array() { + return v.set(v.len(), field_serializer.value.clone()).map_err(|e| { + let e = from_js_error(field_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an array")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeTupleVariant for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut field_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut field_serializer)?; + + if let Some(v) = self.value.as_array() { + return v.set(v.len(), field_serializer.value.clone()).map_err(|e| { + let e = from_js_error(field_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an array")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeMap for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut key_serializer = Serializer::from_context(self.context.clone())?; + key.serialize(&mut key_serializer)?; + self.key = key_serializer.value; + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut map_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut map_serializer)?; + let key = as_key(&self.key)?; + + if let Some(o) = self.value.as_object() { + return o.set(key, map_serializer.value.clone()).map_err(|e| { + let e = from_js_error(map_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an object")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeStruct for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut field_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut field_serializer)?; + + if let Some(o) = self.value.as_object() { + return o.set(key, field_serializer.value.clone()).map_err(|e| { + let e = from_js_error(field_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an object")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +impl<'a> ser::SerializeStructVariant for &'a mut Serializer<'_> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let mut field_serializer = Serializer::from_context(self.context.clone())?; + value.serialize(&mut field_serializer)?; + + if let Some(o) = self.value.as_object() { + return o.set(key, field_serializer.value.clone()).map_err(|e| { + let e = from_js_error(field_serializer.value.ctx().clone(), e); + Error::custom(e.to_string()) + }); + } + + Err(Error::custom("Expected to be an object")) + } + + fn end(self) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::Serializer as ValueSerializer; + use crate::serde::{MAX_SAFE_INTEGER, MIN_SAFE_INTEGER}; + use crate::Runtime; + use anyhow::Result; + use quickcheck::quickcheck; + use serde::{Serialize, Serializer}; + + fn with_serializer Result>( + rt: &Runtime, + mut w: F, + ) -> Result { + rt.context().with(|c| { + let mut serializer = ValueSerializer::from_context(c.clone()).unwrap(); + w(&mut serializer) + }) + } + + quickcheck! { + fn test_i16(v: i16) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_i16(v)?; + Ok(serializer.value.is_int()) + }) + } + + fn test_i32(v: i32) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_i32(v)?; + Ok(serializer.value.is_int()) + }) + } + + fn test_i64(v: i64) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + if (MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&v) { + serializer.serialize_i64(v)?; + Ok(serializer.value.is_number()) + } else { + serializer.serialize_f64(v as f64)?; + Ok(serializer.value.is_number()) + } + }) + } + + fn test_u64(v: u64) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + if v <= MAX_SAFE_INTEGER as u64 { + serializer.serialize_u64(v)?; + Ok(serializer.value.is_number()) + } else { + serializer.serialize_f64(v as f64)?; + Ok(serializer.value.is_number()) + } + }) + } + + fn test_u16(v: u16) -> Result { + let rt = Runtime::default(); + + with_serializer(&rt, |serializer| { + serializer.serialize_u16(v)?; + Ok(serializer.value.is_int()) + }) + } + + fn test_u32(v: u32) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_u32(v)?; + // QuickJS optimizes numbers in the range of [i32::MIN..=i32::MAX] + // as ints + if v > i32::MAX as u32 { + Ok(serializer.value.is_float()) + } else { + Ok(serializer.value.is_int()) + } + }) + + } + + fn test_f32(v: f32) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_f32(v)?; + + if v == 0.0_f32 { + if v.is_sign_positive() { + return Ok(serializer.value.is_int()); + } + + + if v.is_sign_negative() { + return Ok(serializer.value.is_float()); + } + } + + // The same (int) optimization is happening at this point, + // but here we need to account for signs + let zero_fractional_part = v.fract() == 0.0; + let range = (i32::MIN as f32)..=(i32::MAX as f32); + + if zero_fractional_part && range.contains(&v) { + Ok(serializer.value.is_int()) + } else { + Ok(serializer.value.is_float()) + } + }) + } + + fn test_f64(v: f64) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_f64(v)?; + + if v == 0.0_f64 { + if v.is_sign_positive() { + return Ok(serializer.value.is_int()); + } + + + if v.is_sign_negative() { + return Ok(serializer.value.is_float()); + } + } + + // The same (int) optimization is happening at this point, + // but here we need to account for signs + let zero_fractional_part = v.fract() == 0.0; + let range = (i32::MIN as f64)..=(i32::MAX as f64); + + if zero_fractional_part && range.contains(&v) { + Ok(serializer.value.is_int()) + } else { + Ok(serializer.value.is_float()) + } + }) + } + + fn test_bool(v: bool) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_bool(v)?; + Ok(serializer.value.is_bool()) + }) + } + + fn test_str(v: String) -> Result { + let rt = Runtime::default(); + with_serializer(&rt, |serializer| { + serializer.serialize_str(v.as_str())?; + + Ok(serializer.value.is_string()) + }) + } + } + + #[test] + fn test_null() -> Result<()> { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + serializer.serialize_unit().unwrap(); + + assert!(serializer.value.is_null()); + }); + Ok(()) + } + + #[test] + fn test_nan() -> Result<()> { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + serializer.serialize_f64(f64::NAN).unwrap(); + assert!(serializer.value.is_number()); + }); + Ok(()) + } + + #[test] + fn test_infinity() -> Result<()> { + let rt = Runtime::default(); + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + serializer.serialize_f64(f64::INFINITY).unwrap(); + assert!(serializer.value.is_number()); + }); + Ok(()) + } + + #[test] + fn test_negative_infinity() -> Result<()> { + let rt = Runtime::default(); + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + serializer.serialize_f64(f64::NEG_INFINITY).unwrap(); + assert!(serializer.value.is_number()); + }); + Ok(()) + } + + #[test] + fn test_map_with_invalid_key_type() { + // This is technically possible since msgpack supports maps + // with any other valid msgpack type. However, we try to enforce + // using `K: String` since it allow transcoding from json<->msgpack. + let rt = Runtime::default(); + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + + let mut map = BTreeMap::new(); + map.insert(42, "bar"); + map.insert(43, "titi"); + + let err = map.serialize(&mut serializer).unwrap_err(); + assert_eq!("map keys must be a string".to_string(), err.to_string()); + }); + } + + #[test] + fn test_map() { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + + let mut map = BTreeMap::new(); + map.insert("foo", "bar"); + map.insert("toto", "titi"); + + map.serialize(&mut serializer).unwrap(); + + assert!(serializer.value.is_object()) + }); + } + + #[test] + fn test_struct_into_map() { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + + #[derive(serde::Serialize)] + struct MyObject { + foo: String, + bar: u32, + } + + let my_object = MyObject { + foo: "hello".to_string(), + bar: 1337, + }; + my_object.serialize(&mut serializer).unwrap(); + + assert!(serializer.value.is_object()); + }); + } + + #[test] + fn test_sequence() { + let rt = Runtime::default(); + + rt.context().with(|cx| { + let mut serializer = ValueSerializer::from_context(cx.clone()).unwrap(); + + let sequence = vec!["hello", "world"]; + + sequence.serialize(&mut serializer).unwrap(); + + assert!(serializer.value.is_array()); + }); + } +} From 7e4ec6f7506ccd78e11f0d51994b3c33533d39d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Mon, 15 Apr 2024 20:32:28 -0400 Subject: [PATCH 11/22] Move quickcheck to the top level Cargo.toml `cargo` doesn't seem to pickup the fact that `quickcheck` is only used in tests. --- crates/javy/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index fb026438..e71d1996 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -11,11 +11,14 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } -quickjs-wasm-rs = { version = "3.1.0-alpha.1", path = "../quickjs-wasm-rs" } rquickjs = { version = "0.5.1", features = ["array-buffer"] } +serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } rmp-serde = { version = "^1.1", optional = true } +# TODO: cargo doesn't seem to pickup the fact that quickcheck is only used for +# tests. +quickcheck = "1" [features] export_alloc_fns = [] From 9af48b0d2ae2c0d7e043be6d925a8e8a6a0f59d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Mon, 15 Apr 2024 21:01:02 -0400 Subject: [PATCH 12/22] Update versions and CHANGELOGs --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/apis/CHANGELOG.md | 5 +++++ crates/apis/Cargo.toml | 2 +- crates/javy/CHANGELOG.md | 5 +++++ crates/javy/Cargo.toml | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00017e5f..9e5a1dc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,7 +1476,7 @@ dependencies = [ [[package]] name = "javy" -version = "2.2.1-alpha.1" +version = "3.0.0-alpha.1" dependencies = [ "anyhow", "quickcheck", @@ -1489,7 +1489,7 @@ dependencies = [ [[package]] name = "javy-apis" -version = "2.2.1-alpha.1" +version = "3.0.0-alpha.1" dependencies = [ "anyhow", "fastrand", diff --git a/Cargo.toml b/Cargo.toml index 3548b91d..d059fa9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ wasmtime-wasi = "16" wasi-common = "16" anyhow = "1.0" once_cell = "1.19" -javy = { path = "crates/javy", version = "2.2.1-alpha.1" } +javy = { path = "crates/javy", version = "3.0.0-alpha.1" } [profile.release] lto = true diff --git a/crates/apis/CHANGELOG.md b/crates/apis/CHANGELOG.md index 8cb293e6..888bcc22 100644 --- a/crates/apis/CHANGELOG.md +++ b/crates/apis/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Rewrite the APIs on top of Javy v3.0.0, which drops support for + `quickjs-wasm-rs` in favor of `rquickjs` + ## [2.2.0] - 2024-01-31 ### Changed diff --git a/crates/apis/Cargo.toml b/crates/apis/Cargo.toml index dea826fa..05f23fe3 100644 --- a/crates/apis/Cargo.toml +++ b/crates/apis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "javy-apis" -version = "2.2.1-alpha.1" +version = "3.0.0-alpha.1" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/javy/CHANGELOG.md b/crates/javy/CHANGELOG.md index 68f15a2d..79e711b0 100644 --- a/crates/javy/CHANGELOG.md +++ b/crates/javy/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Introduce `rquickjs` to interface with QuickJS instead of `quickjs-wasm-rs`; + this version no longer includes re-exports from `quickjs-wasm-rs`. + ## [2.2.0] - 2024-01-31 ### Fixed diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index e71d1996..60a8da3a 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "javy" -version = "2.2.1-alpha.1" +version = "3.0.0-alpha.1" authors.workspace = true edition.workspace = true license.workspace = true From cdcac4dc6dccc9ef4a30de8a68a655592b0b0b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Mon, 15 Apr 2024 21:01:20 -0400 Subject: [PATCH 13/22] Audit dependencies And prune unused ones --- supply-chain/audits.toml | 12 + supply-chain/config.toml | 288 ++++++++---- supply-chain/imports.lock | 962 ++++++++++++++++---------------------- 3 files changed, 622 insertions(+), 640 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 416b30d8..3ea2e537 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -237,6 +237,12 @@ user-id = 6825 # Dan Gohman (sunfishcode) start = "2021-06-12" end = "2024-07-25" +[[trusted.is-terminal]] +criteria = "safe-to-deploy" +user-id = 6825 # Dan Gohman (sunfishcode) +start = "2022-01-22" +end = "2025-04-16" + [[trusted.itoa]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) @@ -531,6 +537,12 @@ user-id = 73222 # wasmtime-publish start = "2023-08-21" end = "2025-01-03" +[[trusted.web-sys]] +criteria = "safe-to-deploy" +user-id = 1 # Alex Crichton (alexcrichton) +start = "2019-03-04" +end = "2025-04-16" + [[trusted.winapi-util]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 71ea613e..46603951 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -10,11 +10,11 @@ url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-c [imports.embark-studios] url = "https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml" +[imports.fermyon] +url = "https://raw.githubusercontent.com/fermyon/spin/main/supply-chain/audits.toml" + [imports.google] -url = [ - "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT", - "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT", -] +url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml" [imports.isrg] url = "https://raw.githubusercontent.com/divviup/libprio-rs/main/supply-chain/audits.toml" @@ -43,7 +43,7 @@ version = "0.19.0" criteria = "safe-to-deploy" [[exemptions.ahash]] -version = "0.8.3" +version = "0.8.11" criteria = "safe-to-deploy" [[exemptions.alloc-no-stdlib]] @@ -63,7 +63,11 @@ version = "0.9.6" criteria = "safe-to-deploy" [[exemptions.backtrace]] -version = "0.3.69" +version = "0.3.71" +criteria = "safe-to-deploy" + +[[exemptions.base64]] +version = "0.21.7" criteria = "safe-to-deploy" [[exemptions.base64-simd]] @@ -95,55 +99,71 @@ version = "3.5.0" criteria = "safe-to-deploy" [[exemptions.brotli-decompressor]] -version = "2.5.0" +version = "2.5.1" +criteria = "safe-to-deploy" + +[[exemptions.bumpalo]] +version = "3.16.0" criteria = "safe-to-deploy" [[exemptions.bytes]] -version = "1.5.0" +version = "1.6.0" +criteria = "safe-to-deploy" + +[[exemptions.camino]] +version = "1.1.6" +criteria = "safe-to-deploy" + +[[exemptions.cargo-platform]] +version = "0.1.8" criteria = "safe-to-deploy" [[exemptions.cast]] version = "0.3.0" criteria = "safe-to-run" +[[exemptions.cc]] +version = "1.0.92" +criteria = "safe-to-deploy" + [[exemptions.ciborium]] -version = "0.2.1" +version = "0.2.2" criteria = "safe-to-run" [[exemptions.ciborium-io]] -version = "0.2.1" +version = "0.2.2" criteria = "safe-to-run" [[exemptions.ciborium-ll]] -version = "0.2.1" +version = "0.2.2" criteria = "safe-to-run" [[exemptions.clang-sys]] -version = "1.3.1" +version = "1.7.0" criteria = "safe-to-deploy" [[exemptions.clap]] version = "2.34.0" criteria = "safe-to-deploy" -[[exemptions.clap]] -version = "4.4.7" -criteria = "safe-to-run" - [[exemptions.convert_case]] version = "0.6.0" criteria = "safe-to-deploy" +[[exemptions.core-foundation]] +version = "0.9.4" +criteria = "safe-to-deploy" + [[exemptions.cpp_demangle]] version = "0.3.5" criteria = "safe-to-deploy" [[exemptions.cpufeatures]] -version = "0.2.1" +version = "0.2.12" criteria = "safe-to-deploy" [[exemptions.crc32fast]] -version = "1.3.2" +version = "1.4.0" criteria = "safe-to-deploy" [[exemptions.criterion]] @@ -154,24 +174,20 @@ criteria = "safe-to-run" version = "0.4.5" criteria = "safe-to-run" -[[exemptions.crossbeam-channel]] -version = "0.5.2" -criteria = "safe-to-deploy" - [[exemptions.crossbeam-deque]] -version = "0.8.1" +version = "0.8.5" criteria = "safe-to-deploy" [[exemptions.crossbeam-epoch]] -version = "0.9.7" +version = "0.9.18" criteria = "safe-to-deploy" [[exemptions.crossbeam-utils]] -version = "0.8.7" +version = "0.8.8" criteria = "safe-to-deploy" [[exemptions.data-encoding]] -version = "2.4.0" +version = "2.5.0" criteria = "safe-to-deploy" [[exemptions.digest]] @@ -182,10 +198,26 @@ criteria = "safe-to-deploy" version = "2.0.0" criteria = "safe-to-deploy" +[[exemptions.dirs]] +version = "4.0.0" +criteria = "safe-to-deploy" + +[[exemptions.dirs-sys]] +version = "0.3.7" +criteria = "safe-to-deploy" + [[exemptions.dirs-sys-next]] version = "0.1.2" criteria = "safe-to-deploy" +[[exemptions.either]] +version = "1.10.0" +criteria = "safe-to-deploy" + +[[exemptions.env_logger]] +version = "0.8.4" +criteria = "safe-to-deploy" + [[exemptions.fallible-iterator]] version = "0.2.0" criteria = "safe-to-deploy" @@ -231,21 +263,25 @@ version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.generic-array]] -version = "0.14.5" +version = "0.14.7" criteria = "safe-to-deploy" [[exemptions.getrandom]] -version = "0.2.5" +version = "0.2.2" criteria = "safe-to-deploy" [[exemptions.gimli]] -version = "0.26.1" +version = "0.26.2" criteria = "safe-to-deploy" [[exemptions.gimli]] -version = "0.26.2" +version = "0.28.1" criteria = "safe-to-deploy" +[[exemptions.half]] +version = "2.4.1" +criteria = "safe-to-run" + [[exemptions.heck]] version = "0.3.3" criteria = "safe-to-deploy" @@ -255,11 +291,19 @@ version = "0.1.19" criteria = "safe-to-deploy" [[exemptions.hermit-abi]] -version = "0.3.1" +version = "0.3.9" +criteria = "safe-to-deploy" + +[[exemptions.home]] +version = "0.5.9" criteria = "safe-to-deploy" [[exemptions.hstr]] -version = "0.2.6" +version = "0.2.9" +criteria = "safe-to-deploy" + +[[exemptions.iana-time-zone]] +version = "0.1.60" criteria = "safe-to-deploy" [[exemptions.if_chain]] @@ -271,7 +315,7 @@ version = "1.9.3" criteria = "safe-to-deploy" [[exemptions.indexmap]] -version = "2.0.2" +version = "2.2.6" criteria = "safe-to-deploy" [[exemptions.ipnet]] @@ -279,7 +323,7 @@ version = "2.9.0" criteria = "safe-to-deploy" [[exemptions.is-macro]] -version = "0.3.0" +version = "0.3.5" criteria = "safe-to-deploy" [[exemptions.itertools]] @@ -295,7 +339,15 @@ version = "1.3.0" criteria = "safe-to-deploy" [[exemptions.libloading]] -version = "0.7.3" +version = "0.8.3" +criteria = "safe-to-deploy" + +[[exemptions.libredox]] +version = "0.1.3" +criteria = "safe-to-deploy" + +[[exemptions.log]] +version = "0.4.21" criteria = "safe-to-deploy" [[exemptions.mach]] @@ -307,19 +359,23 @@ version = "0.3.4" criteria = "safe-to-deploy" [[exemptions.memoffset]] -version = "0.6.5" +version = "0.9.1" criteria = "safe-to-deploy" [[exemptions.minimal-lexical]] version = "0.2.1" criteria = "safe-to-deploy" +[[exemptions.miniz_oxide]] +version = "0.7.2" +criteria = "safe-to-deploy" + [[exemptions.mio]] version = "0.8.11" criteria = "safe-to-deploy" -[[exemptions.nom]] -version = "7.1.0" +[[exemptions.new_debug_unreachable]] +version = "1.0.6" criteria = "safe-to-deploy" [[exemptions.num-format]] @@ -327,23 +383,19 @@ version = "0.4.4" criteria = "safe-to-run" [[exemptions.object]] -version = "0.32.1" +version = "0.32.2" criteria = "safe-to-deploy" [[exemptions.once_cell]] version = "1.16.0" criteria = "safe-to-deploy" -[[exemptions.oorandom]] -version = "11.1.3" -criteria = "safe-to-run" - [[exemptions.openssl]] -version = "0.10.60" +version = "0.10.64" criteria = "safe-to-deploy" [[exemptions.openssl-sys]] -version = "0.9.96" +version = "0.9.102" criteria = "safe-to-deploy" [[exemptions.outref]] @@ -367,29 +419,33 @@ version = "0.10.0" criteria = "safe-to-deploy" [[exemptions.pin-project]] -version = "1.1.3" +version = "1.1.5" criteria = "safe-to-deploy" [[exemptions.pin-project-internal]] -version = "1.1.3" +version = "1.1.5" +criteria = "safe-to-deploy" + +[[exemptions.pin-project-lite]] +version = "0.2.14" +criteria = "safe-to-deploy" + +[[exemptions.pkg-config]] +version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.plotters]] -version = "0.3.4" +version = "0.3.5" criteria = "safe-to-run" [[exemptions.plotters-backend]] -version = "0.3.4" +version = "0.3.5" criteria = "safe-to-run" [[exemptions.plotters-svg]] -version = "0.3.3" +version = "0.3.5" criteria = "safe-to-run" -[[exemptions.pmutil]] -version = "0.6.1" -criteria = "safe-to-deploy" - [[exemptions.ppv-lite86]] version = "0.2.16" criteria = "safe-to-deploy" @@ -399,7 +455,7 @@ version = "1.0.4" criteria = "safe-to-deploy" [[exemptions.psm]] -version = "0.1.17" +version = "0.1.21" criteria = "safe-to-deploy" [[exemptions.radium]] @@ -410,28 +466,40 @@ criteria = "safe-to-deploy" version = "0.8.5" criteria = "safe-to-deploy" -[[exemptions.redox_syscall]] -version = "0.2.10" -criteria = "safe-to-deploy" - [[exemptions.redox_users]] -version = "0.4.0" +version = "0.4.5" criteria = "safe-to-deploy" [[exemptions.rmp]] -version = "0.8.11" +version = "0.8.12" criteria = "safe-to-deploy" [[exemptions.rmp-serde]] version = "1.1.2" criteria = "safe-to-deploy" +[[exemptions.rquickjs]] +version = "0.5.1" +criteria = "safe-to-deploy" + +[[exemptions.rquickjs-core]] +version = "0.5.1" +criteria = "safe-to-deploy" + +[[exemptions.rquickjs-sys]] +version = "0.5.1" +criteria = "safe-to-deploy" + +[[exemptions.rustc-demangle]] +version = "0.1.23" +criteria = "safe-to-deploy" + [[exemptions.rustc_version]] version = "0.2.3" criteria = "safe-to-deploy" [[exemptions.schannel]] -version = "0.1.21" +version = "0.1.23" criteria = "safe-to-deploy" [[exemptions.scoped-tls]] @@ -439,17 +507,21 @@ version = "1.0.1" criteria = "safe-to-deploy" [[exemptions.security-framework]] -version = "2.9.1" +version = "2.10.0" criteria = "safe-to-deploy" [[exemptions.security-framework-sys]] -version = "2.9.0" +version = "2.10.0" criteria = "safe-to-deploy" [[exemptions.semver]] version = "0.9.0" criteria = "safe-to-deploy" +[[exemptions.semver]] +version = "1.0.22" +criteria = "safe-to-deploy" + [[exemptions.semver-parser]] version = "0.7.0" criteria = "safe-to-deploy" @@ -458,8 +530,12 @@ criteria = "safe-to-deploy" version = "1.1.1" criteria = "safe-to-deploy" +[[exemptions.sha2]] +version = "0.10.8" +criteria = "safe-to-deploy" + [[exemptions.shellexpand]] -version = "2.1.0" +version = "2.1.2" criteria = "safe-to-deploy" [[exemptions.shlex]] @@ -471,7 +547,7 @@ version = "0.7.1" criteria = "safe-to-deploy" [[exemptions.siphasher]] -version = "0.3.10" +version = "0.3.11" criteria = "safe-to-deploy" [[exemptions.slice-group-by]] @@ -483,11 +559,11 @@ version = "1.0.1" criteria = "safe-to-deploy" [[exemptions.socket2]] -version = "0.5.5" +version = "0.5.6" criteria = "safe-to-deploy" [[exemptions.sourcemap]] -version = "8.0.0" +version = "8.0.1" criteria = "safe-to-deploy" [[exemptions.stable_deref_trait]] @@ -515,31 +591,31 @@ version = "0.4.18" criteria = "safe-to-deploy" [[exemptions.swc_atoms]] -version = "0.6.5" +version = "0.6.6" criteria = "safe-to-deploy" [[exemptions.swc_common]] -version = "0.33.21" +version = "0.33.22" criteria = "safe-to-deploy" [[exemptions.swc_core]] -version = "0.90.26" +version = "0.90.30" criteria = "safe-to-deploy" [[exemptions.swc_ecma_ast]] -version = "0.112.6" +version = "0.112.7" criteria = "safe-to-deploy" [[exemptions.swc_ecma_parser]] -version = "0.143.11" +version = "0.143.13" criteria = "safe-to-deploy" [[exemptions.swc_ecma_transforms_base]] -version = "0.137.16" +version = "0.137.17" criteria = "safe-to-deploy" [[exemptions.swc_ecma_utils]] -version = "0.127.15" +version = "0.127.17" criteria = "safe-to-deploy" [[exemptions.swc_ecma_visit]] @@ -555,7 +631,7 @@ version = "0.3.9" criteria = "safe-to-deploy" [[exemptions.swc_visit]] -version = "0.5.10" +version = "0.5.13" criteria = "safe-to-deploy" [[exemptions.swc_visit_macros]] @@ -574,8 +650,12 @@ criteria = "safe-to-deploy" version = "1.2.1" criteria = "safe-to-run" +[[exemptions.tinyvec_macros]] +version = "0.1.1" +criteria = "safe-to-deploy" + [[exemptions.toml]] -version = "0.5.8" +version = "0.5.7" criteria = "safe-to-deploy" [[exemptions.tower]] @@ -591,15 +671,23 @@ version = "0.3.2" criteria = "safe-to-deploy" [[exemptions.tracing]] -version = "0.1.34" +version = "0.1.40" criteria = "safe-to-deploy" [[exemptions.tracing-attributes]] -version = "0.1.26" +version = "0.1.27" criteria = "safe-to-deploy" [[exemptions.tracing-core]] -version = "0.1.31" +version = "0.1.32" +criteria = "safe-to-deploy" + +[[exemptions.triomphe]] +version = "0.1.11" +criteria = "safe-to-deploy" + +[[exemptions.try-lock]] +version = "0.2.5" criteria = "safe-to-deploy" [[exemptions.typed-arena]] @@ -607,11 +695,11 @@ version = "2.0.2" criteria = "safe-to-deploy" [[exemptions.typenum]] -version = "1.15.0" +version = "1.17.0" criteria = "safe-to-deploy" [[exemptions.unicode-id]] -version = "0.3.3" +version = "0.3.4" criteria = "safe-to-deploy" [[exemptions.unicode-id-start]] @@ -623,7 +711,7 @@ version = "1.8.0" criteria = "safe-to-deploy" [[exemptions.vergen]] -version = "8.2.6" +version = "8.3.1" criteria = "safe-to-deploy" [[exemptions.walrus]] @@ -634,14 +722,22 @@ criteria = "safe-to-deploy" version = "0.19.0" criteria = "safe-to-deploy" -[[exemptions.wasi]] -version = "0.10.2+wasi-snapshot-preview1" +[[exemptions.want]] +version = "0.3.1" criteria = "safe-to-deploy" [[exemptions.wasi]] version = "0.11.0+wasi-snapshot-preview1" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen]] +version = "0.2.92" +criteria = "safe-to-deploy" + +[[exemptions.wasm-encoder]] +version = "0.202.0" +criteria = "safe-to-deploy" + [[exemptions.wasm-opt]] version = "0.116.1" criteria = "safe-to-deploy" @@ -654,12 +750,16 @@ criteria = "safe-to-deploy" version = "0.116.0" criteria = "safe-to-deploy" -[[exemptions.web-sys]] -version = "0.3.58" -criteria = "safe-to-run" +[[exemptions.wast]] +version = "202.0.0" +criteria = "safe-to-deploy" + +[[exemptions.wat]] +version = "1.202.0" +criteria = "safe-to-deploy" [[exemptions.which]] -version = "4.2.4" +version = "4.4.2" criteria = "safe-to-deploy" [[exemptions.winapi]] @@ -682,6 +782,14 @@ criteria = "safe-to-deploy" version = "0.5.1" criteria = "safe-to-deploy" +[[exemptions.zerocopy]] +version = "0.7.32" +criteria = "safe-to-deploy" + +[[exemptions.zerocopy-derive]] +version = "0.7.32" +criteria = "safe-to-deploy" + [[exemptions.zstd]] version = "0.11.2+zstd.1.5.2" criteria = "safe-to-deploy" @@ -691,5 +799,5 @@ version = "5.0.2+zstd.1.5.2" criteria = "safe-to-deploy" [[exemptions.zstd-sys]] -version = "2.0.8+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 6d114f6a..a5708974 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -2,22 +2,22 @@ # cargo-vet imports lock [[publisher.aho-corasick]] -version = "0.7.18" -when = "2021-04-30" +version = "1.1.3" +when = "2024-03-20" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" [[publisher.anstyle]] -version = "1.0.4" -when = "2023-09-28" +version = "1.0.6" +when = "2024-02-05" user-id = 6743 user-login = "epage" user-name = "Ed Page" [[publisher.anyhow]] -version = "1.0.81" -when = "2024-03-12" +version = "1.0.82" +when = "2024-04-10" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" @@ -30,15 +30,15 @@ user-login = "fitzgen" user-name = "Nick Fitzgerald" [[publisher.async-trait]] -version = "0.1.77" -when = "2024-01-02" +version = "0.1.79" +when = "2024-03-24" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.byteorder]] -version = "1.4.3" -when = "2021-03-10" +version = "1.5.0" +when = "2023-10-06" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" @@ -85,13 +85,6 @@ user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" -[[publisher.cc]] -version = "1.0.90" -when = "2024-03-06" -user-id = 2915 -user-login = "Amanieu" -user-name = "Amanieu d'Antras" - [[publisher.cexpr]] version = "0.6.0" when = "2021-10-11" @@ -100,26 +93,19 @@ user-login = "emilio" user-name = "Emilio Cobos Álvarez" [[publisher.clap_builder]] -version = "4.4.7" -when = "2023-10-24" +version = "4.5.2" +when = "2024-03-06" user-id = 6743 user-login = "epage" user-name = "Ed Page" [[publisher.clap_lex]] -version = "0.6.0" -when = "2023-10-24" +version = "0.7.0" +when = "2024-02-08" user-id = 6743 user-login = "epage" user-name = "Ed Page" -[[publisher.core-foundation]] -version = "0.9.3" -when = "2022-02-07" -user-id = 5946 -user-login = "jrmuizel" -user-name = "Jeff Muizelaar" - [[publisher.core-foundation-sys]] version = "0.8.4" when = "2023-04-03" @@ -188,36 +174,36 @@ user-id = 73222 user-login = "wasmtime-publish" [[publisher.cxx]] -version = "1.0.115" -when = "2024-01-06" +version = "1.0.121" +when = "2024-04-08" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.cxx-build]] -version = "1.0.115" -when = "2024-01-06" +version = "1.0.121" +when = "2024-04-08" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.cxxbridge-flags]] -version = "1.0.115" -when = "2024-01-06" +version = "1.0.121" +when = "2024-04-08" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.cxxbridge-macro]] -version = "1.0.115" -when = "2024-01-06" +version = "1.0.121" +when = "2024-04-08" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.encoding_rs]] -version = "0.8.33" -when = "2023-08-23" +version = "0.8.34" +when = "2024-04-10" user-id = 4484 user-login = "hsivonen" user-name = "Henri Sivonen" @@ -251,15 +237,15 @@ user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.hashbrown]] -version = "0.14.1" -when = "2023-09-29" +version = "0.14.3" +when = "2023-11-26" user-id = 2915 user-login = "Amanieu" user-name = "Amanieu d'Antras" [[publisher.http]] -version = "1.0.0" -when = "2023-11-15" +version = "1.1.0" +when = "2024-03-04" user-id = 359 user-login = "seanmonstar" user-name = "Sean McArthur" @@ -279,8 +265,8 @@ user-login = "seanmonstar" user-name = "Sean McArthur" [[publisher.hyper]] -version = "1.1.0" -when = "2023-12-18" +version = "1.2.0" +when = "2024-02-21" user-id = 359 user-login = "seanmonstar" user-name = "Sean McArthur" @@ -300,36 +286,36 @@ user-login = "seanmonstar" user-name = "Sean McArthur" [[publisher.io-extras]] -version = "0.18.1" -when = "2023-12-01" +version = "0.18.2" +when = "2024-03-29" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.io-lifetimes]] -version = "1.0.11" -when = "2023-05-24" +version = "2.0.3" +when = "2023-12-01" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" -[[publisher.io-lifetimes]] -version = "2.0.3" -when = "2023-12-01" +[[publisher.is-terminal]] +version = "0.4.12" +when = "2024-02-09" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.itoa]] -version = "1.0.4" -when = "2022-10-06" +version = "1.0.11" +when = "2024-03-26" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.js-sys]] -version = "0.3.58" -when = "2022-06-14" +version = "0.3.69" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -342,57 +328,50 @@ user-login = "JohnTitor" user-name = "Yuki Okushi" [[publisher.linux-raw-sys]] -version = "0.3.8" -when = "2023-05-19" -user-id = 6825 -user-login = "sunfishcode" -user-name = "Dan Gohman" - -[[publisher.linux-raw-sys]] -version = "0.4.12" -when = "2023-11-30" +version = "0.4.13" +when = "2024-01-16" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.memchr]] -version = "2.6.4" -when = "2023-10-01" +version = "2.7.2" +when = "2024-03-27" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" [[publisher.num-traits]] -version = "0.2.16" -when = "2023-07-20" +version = "0.2.18" +when = "2024-02-08" user-id = 539 user-login = "cuviper" user-name = "Josh Stone" [[publisher.num_cpus]] -version = "1.13.1" -when = "2021-12-20" +version = "1.16.0" +when = "2023-06-29" user-id = 359 user-login = "seanmonstar" user-name = "Sean McArthur" [[publisher.paste]] -version = "1.0.6" -when = "2021-11-07" +version = "1.0.14" +when = "2023-07-15" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.prettyplease]] -version = "0.2.15" -when = "2023-09-07" +version = "0.2.17" +when = "2024-03-25" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.proc-macro2]] -version = "1.0.74" -when = "2024-01-02" +version = "1.0.79" +when = "2024-03-12" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" @@ -405,22 +384,15 @@ user-login = "BurntSushi" user-name = "Andrew Gallant" [[publisher.quote]] -version = "1.0.35" -when = "2024-01-02" +version = "1.0.36" +when = "2024-04-10" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.rayon]] -version = "1.5.1" -when = "2021-05-18" -user-id = 539 -user-login = "cuviper" -user-name = "Josh Stone" - -[[publisher.rayon-core]] -version = "1.9.1" -when = "2021-05-18" +version = "1.10.0" +when = "2024-03-24" user-id = 539 user-login = "cuviper" user-name = "Josh Stone" @@ -433,43 +405,43 @@ user-login = "cfallin" user-name = "Chris Fallin" [[publisher.regex]] -version = "1.5.6" -when = "2022-05-20" +version = "1.10.4" +when = "2024-03-23" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" -[[publisher.regex-syntax]] -version = "0.6.26" -when = "2022-05-20" +[[publisher.regex-automata]] +version = "0.4.6" +when = "2024-03-04" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" -[[publisher.rustix]] -version = "0.37.26" -when = "2023-10-19" -user-id = 6825 -user-login = "sunfishcode" -user-name = "Dan Gohman" +[[publisher.regex-syntax]] +version = "0.8.3" +when = "2024-03-26" +user-id = 189 +user-login = "BurntSushi" +user-name = "Andrew Gallant" [[publisher.rustix]] -version = "0.38.31" -when = "2024-02-01" +version = "0.38.32" +when = "2024-03-19" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.rustversion]] -version = "1.0.14" -when = "2023-07-15" +version = "1.0.15" +when = "2024-04-06" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.ryu]] -version = "1.0.9" -when = "2021-12-12" +version = "1.0.17" +when = "2024-02-19" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" @@ -481,13 +453,6 @@ user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" -[[publisher.scopeguard]] -version = "1.1.0" -when = "2020-02-16" -user-id = 2915 -user-login = "Amanieu" -user-name = "Amanieu d'Antras" - [[publisher.scratch]] version = "1.0.7" when = "2023-07-15" @@ -524,8 +489,8 @@ user-login = "dtolnay" user-name = "David Tolnay" [[publisher.smallvec]] -version = "1.11.2" -when = "2023-11-09" +version = "1.13.2" +when = "2024-03-20" user-id = 2017 user-login = "mbrubeck" user-name = "Matt Brubeck" @@ -538,8 +503,8 @@ user-login = "dtolnay" user-name = "David Tolnay" [[publisher.syn]] -version = "2.0.46" -when = "2024-01-02" +version = "2.0.58" +when = "2024-04-03" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" @@ -552,8 +517,8 @@ user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.target-lexicon]] -version = "0.12.13" -when = "2024-01-02" +version = "0.12.14" +when = "2024-02-22" user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" @@ -566,15 +531,15 @@ user-login = "BurntSushi" user-name = "Andrew Gallant" [[publisher.thiserror]] -version = "1.0.56" -when = "2024-01-02" +version = "1.0.58" +when = "2024-03-12" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" [[publisher.thiserror-impl]] -version = "1.0.56" -when = "2024-01-02" +version = "1.0.58" +when = "2024-03-12" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" @@ -594,22 +559,29 @@ user-login = "carllerche" user-name = "Carl Lerche" [[publisher.unicode-ident]] -version = "1.0.6" -when = "2022-12-17" +version = "1.0.12" +when = "2023-09-13" user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" +[[publisher.unicode-normalization]] +version = "0.1.23" +when = "2024-02-20" +user-id = 1139 +user-login = "Manishearth" +user-name = "Manish Goregaokar" + [[publisher.unicode-segmentation]] -version = "1.9.0" -when = "2022-02-07" +version = "1.11.0" +when = "2024-02-07" user-id = 1139 user-login = "Manishearth" user-name = "Manish Goregaokar" [[publisher.unicode-width]] -version = "0.1.9" -when = "2021-09-16" +version = "0.1.11" +when = "2023-09-19" user-id = 1139 user-login = "Manishearth" user-name = "Manish Goregaokar" @@ -640,37 +612,30 @@ when = "2023-12-20" user-id = 73222 user-login = "wasmtime-publish" -[[publisher.wasm-bindgen]] -version = "0.2.81" -when = "2022-06-14" -user-id = 1 -user-login = "alexcrichton" -user-name = "Alex Crichton" - [[publisher.wasm-bindgen-backend]] -version = "0.2.81" -when = "2022-06-14" +version = "0.2.92" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wasm-bindgen-macro]] -version = "0.2.81" -when = "2022-06-14" +version = "0.2.92" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wasm-bindgen-macro-support]] -version = "0.2.81" -when = "2022-06-14" +version = "0.2.92" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wasm-bindgen-shared]] -version = "0.2.81" -when = "2022-06-14" +version = "0.2.92" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -711,8 +676,8 @@ user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wasmparser]] -version = "0.118.1" -when = "2023-11-29" +version = "0.118.2" +when = "2024-02-12" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -731,8 +696,8 @@ user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmprinter]] -version = "0.2.78" -when = "2024-01-29" +version = "0.2.80" +when = "2024-02-12" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -857,16 +822,9 @@ when = "2023-12-20" user-id = 73222 user-login = "wasmtime-publish" -[[publisher.wast]] -version = "69.0.1" -when = "2023-11-29" -user-id = 1 -user-login = "alexcrichton" -user-name = "Alex Crichton" - -[[publisher.wat]] -version = "1.0.82" -when = "2023-11-29" +[[publisher.web-sys]] +version = "0.3.69" +when = "2024-03-04" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -890,8 +848,8 @@ user-id = 73222 user-login = "wasmtime-publish" [[publisher.winapi-util]] -version = "0.1.5" -when = "2020-04-20" +version = "0.1.6" +when = "2023-09-20" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" @@ -909,13 +867,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-sys]] -version = "0.42.0" -when = "2022-09-27" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-sys]] version = "0.48.0" when = "2023-03-31" @@ -931,162 +882,113 @@ user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows-targets]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows-targets]] -version = "0.52.0" -when = "2023-11-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_aarch64_gnullvm]] -version = "0.42.1" -when = "2023-01-12" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_aarch64_gnullvm]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_aarch64_gnullvm]] -version = "0.52.0" -when = "2023-11-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_aarch64_msvc]] -version = "0.42.1" -when = "2023-01-12" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_aarch64_msvc]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_aarch64_msvc]] -version = "0.52.0" -when = "2023-11-15" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_i686_gnu]] -version = "0.42.1" -when = "2023-01-12" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_i686_gnu]] -version = "0.48.0" -when = "2023-03-31" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_i686_gnu]] -version = "0.52.0" -when = "2023-11-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_i686_msvc]] -version = "0.42.1" -when = "2023-01-12" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_i686_msvc]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_i686_msvc]] -version = "0.52.0" -when = "2023-11-15" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_gnu]] -version = "0.42.1" -when = "2023-01-12" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_gnu]] -version = "0.48.0" -when = "2023-03-31" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_x86_64_gnu]] -version = "0.52.0" -when = "2023-11-15" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_gnullvm]] -version = "0.42.1" -when = "2023-01-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_x86_64_gnullvm]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_gnullvm]] -version = "0.52.0" -when = "2023-11-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows_x86_64_msvc]] -version = "0.42.1" -when = "2023-01-12" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_msvc]] -version = "0.48.0" -when = "2023-03-31" +version = "0.48.5" +when = "2023-08-18" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" [[publisher.windows_x86_64_msvc]] -version = "0.52.0" -when = "2023-11-15" +version = "0.52.4" +when = "2024-02-28" user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" @@ -1413,32 +1315,6 @@ start = "2022-11-27" end = "2024-06-26" notes = "The Bytecode Alliance is the author of this crate." -[[audits.bytecode-alliance.wildcard-audits.wast]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -user-id = 1 # Alex Crichton (alexcrichton) -start = "2019-10-16" -end = "2024-04-14" -notes = """ -This is a Bytecode Alliance authored crate maintained in the `wasm-tools` -repository of which I'm one of the primary maintainers and publishers for. -I am employed by a member of the Bytecode Alliance and plan to continue doing -so and will actively maintain this crate over time. -""" - -[[audits.bytecode-alliance.wildcard-audits.wat]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -user-id = 1 # Alex Crichton (alexcrichton) -start = "2019-10-18" -end = "2024-04-14" -notes = """ -This is a Bytecode Alliance authored crate maintained in the `wasm-tools` -repository of which I'm one of the primary maintainers and publishers for. -I am employed by a member of the Bytecode Alliance and plan to continue doing -so and will actively maintain this crate over time. -""" - [[audits.bytecode-alliance.wildcard-audits.wiggle]] who = "Bobby Holley " criteria = "safe-to-deploy" @@ -1514,15 +1390,6 @@ criteria = "safe-to-deploy" version = "0.1.6" notes = "Contains no unsafe code, no IO, no build.rs." -[[audits.bytecode-alliance.audits.arrayvec]] -who = "Nick Fitzgerald " -criteria = "safe-to-deploy" -version = "0.7.2" -notes = """ -Well documented invariants, good assertions for those invariants in unsafe code, -and tested with MIRI to boot. LGTM. -""" - [[audits.bytecode-alliance.audits.atty]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -1533,36 +1400,17 @@ the environment's terminal information when asked. Does its stated purpose and no more. """ -[[audits.bytecode-alliance.audits.base64]] +[[audits.bytecode-alliance.audits.cargo_metadata]] who = "Pat Hickey " criteria = "safe-to-deploy" -version = "0.21.0" -notes = "This crate has no dependencies, no build.rs, and contains no unsafe code." +version = "0.15.3" +notes = "no build, no unsafe, inputs to cargo command are reasonably sanitized" -[[audits.bytecode-alliance.audits.bitflags]] -who = "Jamey Sharp " -criteria = "safe-to-deploy" -delta = "2.1.0 -> 2.2.1" -notes = """ -This version adds unsafe impls of traits from the bytemuck crate when built -with that library enabled, but I believe the impls satisfy the documented -safety requirements for bytemuck. The other changes are minor. -""" - -[[audits.bytecode-alliance.audits.bitflags]] +[[audits.bytecode-alliance.audits.cargo_metadata]] who = "Alex Crichton " criteria = "safe-to-deploy" -delta = "2.3.2 -> 2.3.3" -notes = """ -Nothing outside the realm of what one would expect from a bitflags generator, -all as expected. -""" - -[[audits.bytecode-alliance.audits.bumpalo]] -who = "Nick Fitzgerald " -criteria = "safe-to-deploy" -version = "3.11.1" -notes = "I am the author of this crate." +delta = "0.17.0 -> 0.18.1" +notes = "No major changes, no unsafe code here." [[audits.bytecode-alliance.audits.cfg-if]] who = "Alex Crichton " @@ -1576,6 +1424,15 @@ criteria = "safe-to-deploy" version = "0.11.1" notes = "This library uses `forbid(unsafe_code)` and has no filesystem or network I/O." +[[audits.bytecode-alliance.audits.core-foundation-sys]] +who = "Dan Gohman " +criteria = "safe-to-deploy" +delta = "0.8.4 -> 0.8.6" +notes = """ +The changes here are all typical bindings updates: new functions, types, and +constants. I have not audited all the bindings for ABI conformance. +""" + [[audits.bytecode-alliance.audits.criterion]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -1628,36 +1485,6 @@ serialization support. All logic is trivial: either unit conversion, or hash-consing to support de-duplication required by the format. """ -[[audits.bytecode-alliance.audits.gimli]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.26.1 -> 0.27.0" -notes = """ -This is a standard update to gimli for more DWARF support for more platforms, -more features, etc. Some minor `unsafe` code was added that does not appear -incorrect. Otherwise looks like someone probably ran clippy and/or rustfmt. -""" - -[[audits.bytecode-alliance.audits.gimli]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.27.0 -> 0.27.3" -notes = "More support for more DWARF, nothing major in this update. Some small refactorings and updates to publication of the package but otherwise everything's in order." - -[[audits.bytecode-alliance.audits.gimli]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.27.3 -> 0.28.0" -notes = """ -Still looks like a good DWARF-parsing crate, nothing major was added or deleted -and no `unsafe` code to review here. -""" - -[[audits.bytecode-alliance.audits.glob]] -who = "Jamey Sharp " -criteria = "safe-to-deploy" -delta = "0.3.1 -> 0.3.0" - [[audits.bytecode-alliance.audits.hashbrown]] who = "Chris Fallin " criteria = "safe-to-deploy" @@ -1687,15 +1514,6 @@ criteria = "safe-to-deploy" delta = "1.0.0-rc.2 -> 1.0.0" notes = "Only minor changes made for a stable release." -[[audits.bytecode-alliance.audits.iana-time-zone]] -who = "Dan Gohman " -criteria = "safe-to-deploy" -version = "0.1.59" -notes = """ -I also manually ran windows-bindgen and confirmed that the output matches -the bindings checked into the repo. -""" - [[audits.bytecode-alliance.audits.iana-time-zone-haiku]] who = "Dan Gohman " criteria = "safe-to-deploy" @@ -1718,13 +1536,13 @@ crate is broadly used throughout the ecosystem and does not contain anything suspicious. """ -[[audits.bytecode-alliance.audits.is-terminal]] -who = "Dan Gohman " +[[audits.bytecode-alliance.audits.itertools]] +who = "Nick Fitzgerald " criteria = "safe-to-deploy" -version = "0.4.7" +delta = "0.10.5 -> 0.12.1" notes = """ -The is-terminal implementation code is now sync'd up with the prototype -implementation in the Rust standard library. +Minimal `unsafe` usage. Few blocks that existed looked reasonable. Does what it +says on the tin: lots of iterators. """ [[audits.bytecode-alliance.audits.ittapi]] @@ -1768,31 +1586,11 @@ criteria = "safe-to-deploy" delta = "0.6.2 -> 0.6.3" notes = "Just a dependency version bump and documentation update" -[[audits.bytecode-alliance.audits.memoffset]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.7.1 -> 0.8.0" -notes = "This was a small update to the crate which has to do with Rust language features and compiler versions, no substantial changes." - -[[audits.bytecode-alliance.audits.memoffset]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.8.0 -> 0.9.0" -notes = "No major changes in the crate, mostly updates to use new nightly Rust features." - -[[audits.bytecode-alliance.audits.miniz_oxide]] +[[audits.bytecode-alliance.audits.memfd]] who = "Alex Crichton " criteria = "safe-to-deploy" -version = "0.7.1" -notes = """ -This crate is a Rust implementation of zlib compression/decompression and has -been used by default by the Rust standard library for quite some time. It's also -a default dependency of the popular `backtrace` crate for decompressing debug -information. This crate forbids unsafe code and does not otherwise access system -resources. It's originally a port of the `miniz.c` library as well, and given -its own longevity should be relatively hardened against some of the more common -compression-related issues. -""" +delta = "0.6.3 -> 0.6.4" +notes = "This commit only updated the dependency `rustix`, so same as before." [[audits.bytecode-alliance.audits.native-tls]] who = "Pat Hickey " @@ -1826,23 +1624,15 @@ who = "Pat Hickey " criteria = "safe-to-deploy" version = "0.1.0" -[[audits.bytecode-alliance.audits.pkg-config]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.3.25" -notes = "This crate shells out to the pkg-config executable, but it appears to sanitize inputs reasonably." - -[[audits.bytecode-alliance.audits.rustc-demangle]] +[[audits.bytecode-alliance.audits.slice-group-by]] who = "Alex Crichton " criteria = "safe-to-deploy" -version = "0.1.21" -notes = "I am the author of this crate." - -[[audits.bytecode-alliance.audits.semver]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "1.0.17" -notes = "plenty of unsafe pointer and vec tricks, but in well-structured and commented code that appears to be correct" +delta = "0.3.0 -> 0.3.1" +notes = """ +This update runs `rustfmt` for the first time in awhile and additionally fixes a +few minor issues related to Stacked Borrows and running in MIRI. No fundamental +change to any preexisting unsafe code is happening here. +""" [[audits.bytecode-alliance.audits.sptr]] who = "Alex Crichton " @@ -1870,38 +1660,12 @@ without `unsafe`. Skimming the crate everything looks reasonable and what one would expect from idiomatic safe collections in Rust. """ -[[audits.bytecode-alliance.audits.tinyvec_macros]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "0.1.0" -notes = """ -This is a trivial crate which only contains a singular macro definition which is -intended to multiplex across the internal representation of a tinyvec, -presumably. This trivially doesn't contain anything bad. -""" - [[audits.bytecode-alliance.audits.tokio-native-tls]] who = "Pat Hickey " criteria = "safe-to-deploy" version = "0.3.1" notes = "unsafety is used for smuggling std::task::Context as a raw pointer. Lifetime and type safety appears to be taken care of correctly." -[[audits.bytecode-alliance.audits.tracing]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.34 -> 0.1.37" -notes = """ -A routine set of updates for the tracing crate this includes minor refactorings, -addition of benchmarks, some test updates, but overall nothing out of the -ordinary. -""" - -[[audits.bytecode-alliance.audits.try-lock]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.2.4" -notes = "Implements a concurrency primitive with atomics, and is not obviously incorrect" - [[audits.bytecode-alliance.audits.unicode-bidi]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -1911,35 +1675,30 @@ This crate has no unsafe code and does not use `std::*`. Skimming the crate it does not attempt to out of the bounds of what it's already supposed to be doing. """ -[[audits.bytecode-alliance.audits.unicode-normalization]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "0.1.19" -notes = """ -This crate contains one usage of `unsafe` which I have manually checked to see -it as correct. This crate's size comes in large part due to the generated -unicode tables that it contains. This crate is additionally widely used -throughout the ecosystem and skimming the crate shows no usage of `std::*` APIs -and nothing suspicious. -""" - [[audits.bytecode-alliance.audits.vcpkg]] who = "Pat Hickey " criteria = "safe-to-deploy" version = "0.2.15" notes = "no build.rs, no macros, no unsafe. It reads the filesystem and makes copies of DLLs into OUT_DIR." -[[audits.bytecode-alliance.audits.want]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.3.0" - [[audits.bytecode-alliance.audits.wast]] who = "Alex Crichton " criteria = "safe-to-deploy" version = "35.0.2" notes = "The Bytecode Alliance is the author of this crate." +[[audits.embark-studios.audits.cargo_metadata]] +who = "Johan Andersson " +criteria = "safe-to-deploy" +delta = "0.15.3 -> 0.15.4" +notes = "No notable changes" + +[[audits.embark-studios.audits.cargo_metadata]] +who = "Johan Andersson " +criteria = "safe-to-deploy" +delta = "0.15.4 -> 0.17.0" +notes = "No notable changes" + [[audits.embark-studios.audits.idna]] who = "Johan Andersson " criteria = "safe-to-deploy" @@ -1970,29 +1729,127 @@ criteria = "safe-to-deploy" version = "0.8.2" notes = "No unsafe usage or ambient capabilities" -[[audits.google.audits.dirs-next]] -who = "George Burgess IV " +[[audits.fermyon.audits.oorandom]] +who = "Radu Matei " +criteria = "safe-to-run" +version = "11.1.3" + +[[audits.google.audits.arrayvec]] +who = "Nicholas Bishop " +criteria = "safe-to-run" +version = "0.7.4" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.autocfg]] +who = "Lukasz Anforowicz " criteria = "safe-to-deploy" -version = "2.0.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +version = "1.1.0" +notes = """ +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'``, `'\bnet\b'``, `'\bunsafe\b'`` +and there were no hits except for reasonable, client-controlled usage of +`std::fs` in `AutoCfg::with_dir`. -[[audits.google.audits.env_logger]] -who = "George Burgess IV " +This crate has been added to Chromium in +https://source.chromium.org/chromium/chromium/src/+/591a0f30c5eac93b6a3d981c2714ffa4db28dbcb +The CL description contains a link to a Google-internal document with audit details. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.autocfg]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.1.0 -> 1.2.0" +notes = ''' +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'``, `'\bnet\b'``, `'\bunsafe\b'`` +and nothing changed from the baseline audit of 1.1.0. Skimmed through the +1.1.0 => 1.2.0 delta and everything seemed okay. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bitflags]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "2.4.2" +notes = """ +Audit notes: + +* I've checked for any discussion in Google-internal cl/546819168 (where audit + of version 2.3.3 happened) +* `src/lib.rs` contains `#![cfg_attr(not(test), forbid(unsafe_code))]` +* There are 2 cases of `unsafe` in `src/external.rs` but they seem to be + correct in a straightforward way - they just propagate the marker trait's + impl (e.g. `impl bytemuck::Pod`) from the inner to the outer type +* Additional discussion and/or notes may be found in https://crrev.com/c/5238056 +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bitflags]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "2.4.2 -> 2.5.0" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "danakj@chromium.org" criteria = "safe-to-run" -version = "0.9.3" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +version = "4.4.8" +notes = """ +Reviewed in https://crrev.com/c/5171063 -[[audits.google.audits.env_logger]] -who = "George Burgess IV " +Previously reviewed during security review and the audit is grandparented in. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " criteria = "safe-to-run" -delta = "0.9.3 -> 0.8.4" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +delta = "4.4.8 -> 4.4.14" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "4.4.14 -> 4.5.0" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "4.5.0 -> 4.5.1" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "danakj " +criteria = "safe-to-run" +delta = "4.5.1 -> 4.5.2" +notes = "Reviewed in https://crrev.com/c/5362201" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Adrian Taylor " +criteria = "safe-to-run" +delta = "4.5.2 -> 4.5.3" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.clap]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "4.5.3 -> 4.5.4" +notes = "Minimal diff - only module naming/nesting-related changes." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.getrandom]] +who = "David Koloski " +criteria = "safe-to-deploy" +delta = "0.2.2 -> 0.2.12" +notes = "Audited at https://fxrev.dev/932979" +aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.glob]] who = "George Burgess IV " criteria = "safe-to-deploy" version = "0.3.1" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.google.audits.link-cplusplus]] who = "George Burgess IV " @@ -2002,49 +1859,64 @@ notes = """ This crate exists simply to link with libcxx or libstdcxx. No assertions are made about the safety of either of those libraries. :) """ -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.nom]] +who = "danakj@chromium.org" +criteria = "safe-to-deploy" +version = "7.1.3" +notes = """ +Reviewed in https://chromium-review.googlesource.com/c/chromium/src/+/5046153 +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.openssl-macros]] who = "George Burgess IV " criteria = "safe-to-deploy" delta = "0.1.0 -> 0.1.1" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.pin-project-lite]] -who = "David Koloski " -criteria = "safe-to-deploy" -version = "0.2.9" -notes = "Reviewed on https://fxrev.dev/824504" -aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.pin-project-lite]] -who = "David Koloski " -criteria = "safe-to-deploy" -delta = "0.2.9 -> 0.2.13" -notes = "Audited at https://fxrev.dev/946396" -aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.google.audits.proc-macro-error-attr]] who = "George Burgess IV " criteria = "safe-to-deploy" version = "1.0.4" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.google.audits.version_check]] who = "George Burgess IV " criteria = "safe-to-deploy" version = "0.9.4" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.isrg.audits.criterion]] who = "Brandon Pitman " criteria = "safe-to-run" delta = "0.4.0 -> 0.5.1" -[[audits.isrg.audits.either]] +[[audits.isrg.audits.crunchy]] +who = "David Cook " +criteria = "safe-to-deploy" +version = "0.2.2" + +[[audits.isrg.audits.digest]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.10.6 -> 0.10.7" + +[[audits.isrg.audits.getrandom]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.2.12 -> 0.2.14" + +[[audits.isrg.audits.num-bigint]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.4.3 -> 0.4.4" + +[[audits.isrg.audits.num-integer]] who = "David Cook " criteria = "safe-to-deploy" -version = "1.6.1" +delta = "0.1.45 -> 0.1.46" [[audits.isrg.audits.once_cell]] who = "Brandon Pitman " @@ -2071,10 +1943,10 @@ who = "David Cook " criteria = "safe-to-deploy" version = "0.6.3" -[[audits.isrg.audits.sha2]] -who = "David Cook " +[[audits.isrg.audits.rayon-core]] +who = "Ameer Ghani " criteria = "safe-to-deploy" -version = "0.10.2" +version = "1.12.1" [[audits.mozilla.wildcard-audits.cexpr]] who = "Emilio Cobos Álvarez " @@ -2085,16 +1957,6 @@ end = "2024-04-21" notes = "No unsafe code, rather straight-forward parser." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.wildcard-audits.core-foundation]] -who = "Bobby Holley " -criteria = "safe-to-deploy" -user-id = 5946 # Jeff Muizelaar (jrmuizel) -start = "2019-03-29" -end = "2023-05-04" -renew = false -notes = "I've reviewed every source contribution that was neither authored nor reviewed by Mozilla." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.wildcard-audits.core-foundation-sys]] who = "Bobby Holley " criteria = "safe-to-deploy" @@ -2114,6 +1976,15 @@ end = "2024-08-28" notes = "I, Henri Sivonen, wrote encoding_rs for Gecko and have reviewed contributions by others. There are two caveats to the certification: 1) The crate does things that are documented to be UB but that do not appear to actually be UB due to integer types differing from the general rule; https://github.com/hsivonen/encoding_rs/issues/79 . 2) It would be prudent to re-review the code that reinterprets buffers of integers as SIMD vectors; see https://github.com/hsivonen/encoding_rs/issues/87 ." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.wildcard-audits.unicode-normalization]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +user-id = 1139 # Manish Goregaokar (Manishearth) +start = "2019-11-06" +end = "2024-05-03" +notes = "All code written or reviewed by Manish" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.wildcard-audits.unicode-segmentation]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -2160,13 +2031,6 @@ criteria = "safe-to-deploy" delta = "0.1.4 -> 0.1.5" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.autocfg]] -who = "Josh Stone " -criteria = "safe-to-deploy" -version = "1.1.0" -notes = "All code written or reviewed by Josh Stone." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.bindgen]] who = "Emilio Cobos Álvarez " criteria = "safe-to-deploy" @@ -2216,36 +2080,22 @@ criteria = "safe-to-deploy" delta = "0.69.2 -> 0.69.4" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.bitflags]] -who = "Alex Franchuk " -criteria = "safe-to-deploy" -delta = "1.3.2 -> 2.0.2" -notes = "Removal of some unsafe code/methods. No changes to externals, just some refactoring (mostly internal)." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.bitflags]] -who = "Nicolas Silva " -criteria = "safe-to-deploy" -delta = "2.0.2 -> 2.1.0" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.bitflags]] -who = "Teodor Tanasoaia " +[[audits.mozilla.audits.crossbeam-utils]] +who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "2.2.1 -> 2.3.2" +delta = "0.8.8 -> 0.8.11" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.bitflags]] +[[audits.mozilla.audits.crossbeam-utils]] who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "2.3.3 -> 2.4.0" +delta = "0.8.11 -> 0.8.14" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.bitflags]] +[[audits.mozilla.audits.crossbeam-utils]] who = "Jan-Erik Rediger " criteria = "safe-to-deploy" -delta = "2.4.0 -> 2.4.1" -notes = "Only allowing new clippy lints" +delta = "0.8.14 -> 0.8.19" aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" [[audits.mozilla.audits.crypto-common]] @@ -2274,6 +2124,12 @@ criteria = "safe-to-deploy" version = "1.2.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.form_urlencoded]] +who = "Valentin Gosu " +criteria = "safe-to-deploy" +delta = "1.2.0 -> 1.2.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.fxhash]] who = "Bobby Holley " criteria = "safe-to-deploy" @@ -2281,17 +2137,6 @@ version = "0.2.1" notes = "Straightforward crate with no unsafe code, does what it says on the tin." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.half]] -who = "John M. Schanck " -criteria = "safe-to-deploy" -version = "1.8.2" -notes = """ -This crate contains unsafe code for bitwise casts to/from binary16 floating-point -format. I've reviewed these and found no issues. There are no uses of ambient -capabilities. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.hashbrown]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -2305,31 +2150,24 @@ criteria = "safe-to-deploy" delta = "0.4.0 -> 0.4.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.lazy_static]] -who = "Nika Layzell " -criteria = "safe-to-deploy" -version = "1.4.0" -notes = "I have read over the macros, and audited the unsafe code." -aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.log]] -who = "Mike Hommey " +[[audits.mozilla.audits.idna]] +who = "Valentin Gosu " criteria = "safe-to-deploy" -version = "0.4.17" +delta = "0.4.0 -> 0.5.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.memoffset]] -who = "Gabriele Svelto " +[[audits.mozilla.audits.itertools]] +who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "0.6.5 -> 0.7.1" +delta = "0.10.3 -> 0.10.5" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.new_debug_unreachable]] -who = "Bobby Holley " +[[audits.mozilla.audits.lazy_static]] +who = "Nika Layzell " criteria = "safe-to-deploy" -version = "1.0.4" -notes = "This is a trivial crate." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +version = "1.4.0" +notes = "I have read over the macros, and audited the unsafe code." +aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" [[audits.mozilla.audits.num-bigint]] who = "Josh Stone " @@ -2357,6 +2195,12 @@ criteria = "safe-to-deploy" delta = "2.2.0 -> 2.3.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.percent-encoding]] +who = "Valentin Gosu " +criteria = "safe-to-deploy" +delta = "2.3.0 -> 2.3.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.phf]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -2381,10 +2225,16 @@ criteria = "safe-to-deploy" delta = "0.10.0 -> 0.11.2" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.pkg-config]] +[[audits.mozilla.audits.ppv-lite86]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.2.16 -> 0.2.17" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.rand_core]] who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "0.3.25 -> 0.3.26" +delta = "0.6.3 -> 0.6.4" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.rustc-hash]] @@ -2394,10 +2244,22 @@ version = "1.1.0" notes = "Straightforward crate with no unsafe code, does what it says on the tin." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.sha2]] +[[audits.mozilla.audits.toml]] +who = "Bobby Holley " +criteria = "safe-to-deploy" +delta = "0.5.7 -> 0.5.9" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.toml]] who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "0.10.2 -> 0.10.6" +delta = "0.5.9 -> 0.5.10" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.toml]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.5.10 -> 0.5.11" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.unicode-bidi]] @@ -2406,33 +2268,33 @@ criteria = "safe-to-deploy" delta = "0.3.8 -> 0.3.13" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.unicode-normalization]] -who = "Mike Hommey " +[[audits.mozilla.audits.unicode-bidi]] +who = "Jonathan Kew " criteria = "safe-to-deploy" -delta = "0.1.19 -> 0.1.20" -notes = "I am the author of most of these changes upstream, and prepared the release myself, at which point I looked at the other changes since 0.1.19." +delta = "0.3.13 -> 0.3.14" +notes = "I am the author of the bulk of the upstream changes in this version, and also checked the remaining post-0.3.13 changes." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.unicode-normalization]] -who = "Mike Hommey " +[[audits.mozilla.audits.unicode-bidi]] +who = "Jonathan Kew " criteria = "safe-to-deploy" -delta = "0.1.20 -> 0.1.21" +delta = "0.3.14 -> 0.3.15" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.unicode-normalization]] -who = "Mike Hommey " +[[audits.mozilla.audits.url]] +who = "Valentin Gosu " criteria = "safe-to-deploy" -delta = "0.1.21 -> 0.1.22" +version = "2.4.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.url]] who = "Valentin Gosu " criteria = "safe-to-deploy" -version = "2.4.0" +delta = "2.4.0 -> 2.4.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.url]] who = "Valentin Gosu " criteria = "safe-to-deploy" -delta = "2.4.0 -> 2.4.1" +delta = "2.4.1 -> 2.5.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" From 9eec07bca52b4fb54b461507d844ef029a222025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Mon, 15 Apr 2024 21:03:54 -0400 Subject: [PATCH 14/22] Improve safety comment --- crates/apis/src/stream_io/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index 29bf36bf..c959970b 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -117,8 +117,8 @@ fn read(args: Args<'_>) -> Result> { .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; // Safety - // This is one of the unfortunate unsafe pieces of the APIs, which ideally - // should be revisited. In order to make this safe. + // This is one of the unfortunate unsafe pieces of the APIs, currently. + // This should ideally be revisited in order to make it safe. // This is unsafe only if the length of the buffer doesn't match the length // and offset passed as arguments, the caller must ensure that this is true. // We could make this API safe by changing the expectations of the From 055de24f9477786833957e0d54761932121a42b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 16 Apr 2024 10:20:40 -0400 Subject: [PATCH 15/22] Port previous implementation of read sync I wanted to avoid as much as possible using the underlying QuickJS unsafe APIs, but that introduced behavioral changes and bugs. So for now, I'm sticking to the previous implementation. --- crates/apis/src/stream_io/io.js | 4 ++-- crates/apis/src/stream_io/mod.rs | 30 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/apis/src/stream_io/io.js b/crates/apis/src/stream_io/io.js index 2bc96a63..8777c80a 100644 --- a/crates/apis/src/stream_io/io.js +++ b/crates/apis/src/stream_io/io.js @@ -8,7 +8,7 @@ } return __javy_io_readSync( fd, - data, + data.buffer, data.byteOffset, data.byteLength ); @@ -19,7 +19,7 @@ } return __javy_io_writeSync( fd, - data, + data.buffer, data.byteOffset, data.byteLength ); diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index c959970b..8705d19a 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -3,7 +3,7 @@ use std::io::{Read, Stdin, Write}; use javy::{ hold, hold_and_release, - quickjs::{Function, Object, Value}, + quickjs::{qjs::JS_GetArrayBuffer, Function, Object, Value}, to_js_error, Args, Runtime, }; @@ -59,8 +59,8 @@ fn write(args: Args<'_>) -> Result> { let data = data .as_object() .ok_or_else(|| anyhow!("Data must be an Object"))? - .as_typed_array::() - .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? + .as_array_buffer() + .ok_or_else(|| anyhow!("Data must be an ArrayBuffer"))? .as_bytes() .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; @@ -108,24 +108,26 @@ fn read(args: Args<'_>) -> Result> { .as_number() .ok_or_else(|| anyhow!("length must be a number"))? as usize; - let data = data - .as_object() - .ok_or_else(|| anyhow!("Data must be an Object"))? - .as_typed_array::() - .ok_or_else(|| anyhow!("Data must be a UInt8Array"))? - .as_bytes() - .ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?; - // Safety // This is one of the unfortunate unsafe pieces of the APIs, currently. + // This is a port of the previous implementation. // This should ideally be revisited in order to make it safe. // This is unsafe only if the length of the buffer doesn't match the length // and offset passed as arguments, the caller must ensure that this is true. // We could make this API safe by changing the expectations of the // JavaScript side of things in `io.js`. - let dst = data.as_ptr() as *mut _; - let dst: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(dst, length) }; - let n = fd.read(&mut dst[offset..(offset + length)])?; + let data = unsafe { + let mut len = 0; + let ptr = JS_GetArrayBuffer(cx.as_raw().as_ptr(), &mut len, data.as_raw()); + if ptr.is_null() { + bail!("Data must be an ArrayBuffer"); + } + + Ok::<_, Error>(std::slice::from_raw_parts_mut(ptr, len as _)) + }?; + + let data = &mut data[offset..(offset + length)]; + let n = fd.read(data)?; Ok(Value::new_number(cx, n as f64)) } From 2aa4f6f85ce86a0e6999ffc6b6863c3cbc84184f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 16 Apr 2024 17:11:28 -0400 Subject: [PATCH 16/22] Review comments --- crates/apis/src/stream_io/mod.rs | 5 ++--- crates/core/src/lib.rs | 5 +++-- crates/javy/src/runtime.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/apis/src/stream_io/mod.rs b/crates/apis/src/stream_io/mod.rs index 8705d19a..7496e150 100644 --- a/crates/apis/src/stream_io/mod.rs +++ b/crates/apis/src/stream_io/mod.rs @@ -52,8 +52,7 @@ fn write(args: Args<'_>) -> Result> { 1 => Fd::Stdout, 2 => Fd::Stderr, x => anyhow::bail!( - "Wrong file descriptor: {}. Only stdin(1) and stderr(2) are supported", - x + "Unsupported file descriptor: {x}. Only stdout(1) and stderr(2) are supported" ), }; let data = data @@ -98,7 +97,7 @@ fn read(args: Args<'_>) -> Result> { .ok_or_else(|| anyhow!("File descriptor must be a number"))? { 0 => std::io::stdin(), - x => anyhow::bail!("Wrong file descriptor: {}. Only stdin(1) is supported", x), + x => anyhow::bail!("Unsupported file descriptor: {x}. Only stdin(0) is supported"), }; let offset = offset diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index a8c4c5da..c4a53db9 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -51,9 +51,10 @@ pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) - .unwrap(); // We need the bytecode buffer to live longer than this function so it can be read from memory - let bytecode_ptr = Box::leak(bytecode.clone().into_boxed_slice()).as_ptr(); + let len = bytecode.len(); + let bytecode_ptr = Box::leak(bytecode.into_boxed_slice()).as_ptr(); COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32; - COMPILE_SRC_RET_AREA[1] = bytecode.len().try_into().unwrap(); + COMPILE_SRC_RET_AREA[1] = len.try_into().unwrap(); COMPILE_SRC_RET_AREA.as_ptr() } diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 6f226484..522edb0b 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -16,9 +16,9 @@ pub struct Runtime { context: Context, /// The inner QuickJS runtime representation. // We use `ManuallyDrop` to avoid incurring in the cost of dropping the - // [rquickjs::Runtime] and its associated objects, which takes a substantial + // `rquickjs::Runtime` and its associated objects, which takes a substantial // time. - // This assumes that Javy is used for short-lived programs were the host + // This assumes that Javy is used for short-lived programs where the host // will collect the instance's memory when execution ends, making these // drops unnecessary. // From 29361f3e694283c6bddddc29e3fee5a57782595d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 16 Apr 2024 19:39:15 -0400 Subject: [PATCH 17/22] Improve comment for why `ManuallyDrop` --- crates/javy/src/runtime.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 522edb0b..5afeeb5f 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -17,7 +17,8 @@ pub struct Runtime { /// The inner QuickJS runtime representation. // We use `ManuallyDrop` to avoid incurring in the cost of dropping the // `rquickjs::Runtime` and its associated objects, which takes a substantial - // time. + // amount of time. + // // This assumes that Javy is used for short-lived programs where the host // will collect the instance's memory when execution ends, making these // drops unnecessary. From 5fbcf8a9cb040faa5b9b731d81802f2a3d762b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 23 Apr 2024 10:21:14 -0400 Subject: [PATCH 18/22] Use `ManuallyDrop` for Context --- crates/javy/src/runtime.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 5afeeb5f..5c76985a 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -13,10 +13,8 @@ use crate::Config; /// [rquickjs] APIs. pub struct Runtime { /// The QuickJS context. - context: Context, - /// The inner QuickJS runtime representation. // We use `ManuallyDrop` to avoid incurring in the cost of dropping the - // `rquickjs::Runtime` and its associated objects, which takes a substantial + // `rquickjs::Context` and its associated objects, which takes a substantial // amount of time. // // This assumes that Javy is used for short-lived programs where the host @@ -25,6 +23,9 @@ pub struct Runtime { // // This might not be suitable for all use-cases, so we'll make this // behaviour configurable. + context: ManuallyDrop, + /// The inner QuickJS runtime representation. + // Read above on the usage of `ManuallyDrop`. inner: ManuallyDrop, } @@ -35,7 +36,7 @@ impl Runtime { // See comment above about configuring GC behaviour. rt.set_gc_threshold(usize::MAX); - let context = Context::full(&rt)?; + let context = ManuallyDrop::new(Context::full(&rt)?); Ok(Self { inner: rt, context }) } From 6d7fec5be5a875f31cf583a4623783693519ddba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Tue, 23 Apr 2024 10:27:39 -0400 Subject: [PATCH 19/22] Format comments where applicable --- crates/javy/src/lib.rs | 8 ++++---- crates/javy/src/runtime.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/javy/src/lib.rs b/crates/javy/src/lib.rs index 13a2fe86..3471a110 100644 --- a/crates/javy/src/lib.rs +++ b/crates/javy/src/lib.rs @@ -98,7 +98,7 @@ pub fn print(val: &Value, sink: &mut String) -> Result<()> { } } -/// A struct to hold the current [Ctx] and [Value]s passed as arguments to Rust +/// A struct to hold the current [`Ctx`] and [`Value`]s passed as arguments to Rust /// functions. /// A struct here is used to explicitly tie these values with a particular /// lifetime. @@ -144,10 +144,10 @@ pub fn from_js_error(ctx: Ctx<'_>, e: JSError) -> Error { } } -/// Converts an [anyhow::Error] to a [JSError]. +/// Converts an [`anyhow::Error`] to a [`JSError`]. /// -/// If the error is an [anyhow::Error] this function will construct and throw -/// a JS [Exception] in order to construct the [JSError]. +/// If the error is an [`anyhow::Error`] this function will construct and throw +/// a JS [`Exception`] in order to construct the [`JSError`]. pub fn to_js_error(cx: Ctx, e: Error) -> JSError { match e.downcast::() { Ok(e) => e, diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 5c76985a..967d975a 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -8,9 +8,9 @@ use crate::Config; /// A JavaScript Runtime. /// -/// Javy's [Runtime] holds a [rquickjs::Runtime] and [rquickjs::Context], +/// Javy's [`Runtime`] holds a [`rquickjs::Runtime`] and [`rquickjs::Context`], /// and provides accessors these two propoerties which enable working with -/// [rquickjs] APIs. +/// [`rquickjs`] APIs. pub struct Runtime { /// The QuickJS context. // We use `ManuallyDrop` to avoid incurring in the cost of dropping the From 5b2f0adeaf826653b475805db771fd1b87e5b938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Wed, 1 May 2024 16:44:50 -0400 Subject: [PATCH 20/22] Use rquickjs 0.6.0 through a fork This commit introduces rquickjs 0.6.0 through a fork that contains Wasm specific performance improvements. We intend to upstream these improvements. --- Cargo.lock | 83 +++++++++++++++++++++++++--- crates/apis/src/random/mod.rs | 6 +- crates/apis/src/text_encoding/mod.rs | 6 +- crates/cli/tests/integration_test.rs | 14 ++--- crates/core/src/execution.rs | 44 +++++++++------ crates/javy/Cargo.toml | 2 +- crates/javy/src/runtime.rs | 4 +- 7 files changed, 114 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e5a1dc0..cfb8a5c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,6 +1348,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1975,6 +1981,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2173,6 +2189,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "rmp" version = "0.8.12" @@ -2197,28 +2219,45 @@ dependencies = [ [[package]] name = "rquickjs" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7f63201fa6f2ff8173e4758ea552549d687d8f63003361a8b5c50f7c446ded" +version = "0.6.1" +source = "git+https://github.com/Shopify/rquickjs?branch=improved-wasm-support#3d80f5757db52e9e1af2605cb6e1aaba6d44ceac" dependencies = [ "rquickjs-core", + "rquickjs-macro", ] [[package]] name = "rquickjs-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad00eeddc0f88af54ee202c8385fb214fe0423897c056a7df8369fb482e3695" +version = "0.6.1" +source = "git+https://github.com/Shopify/rquickjs?branch=improved-wasm-support#3d80f5757db52e9e1af2605cb6e1aaba6d44ceac" dependencies = [ + "relative-path", "rquickjs-sys", ] +[[package]] +name = "rquickjs-macro" +version = "0.6.1" +source = "git+https://github.com/Shopify/rquickjs?branch=improved-wasm-support#3d80f5757db52e9e1af2605cb6e1aaba6d44ceac" +dependencies = [ + "convert_case", + "fnv", + "ident_case", + "indexmap 2.2.6", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "rquickjs-core", + "syn 2.0.58", +] + [[package]] name = "rquickjs-sys" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120dbbc3296de9b96de8890091635d46f3506cd38b4e8f21800c386c035d64fa" +version = "0.6.1" +source = "git+https://github.com/Shopify/rquickjs?branch=improved-wasm-support#3d80f5757db52e9e1af2605cb6e1aaba6d44ceac" dependencies = [ + "bindgen", "cc", ] @@ -2944,6 +2983,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -4052,6 +4108,15 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winx" version = "0.36.3" diff --git a/crates/apis/src/random/mod.rs b/crates/apis/src/random/mod.rs index d1c91baa..89699cea 100644 --- a/crates/apis/src/random/mod.rs +++ b/crates/apis/src/random/mod.rs @@ -36,10 +36,8 @@ mod tests { let runtime = Runtime::default(); Random.register(&runtime, &APIConfig::default())?; runtime.context().with(|this| { - let eval_opts = EvalOptions { - strict: false, - ..Default::default() - }; + let mut eval_opts = EvalOptions::default(); + eval_opts.strict = false; this.eval_with_options("result = Math.random()", eval_opts)?; let result: f64 = this .globals() diff --git a/crates/apis/src/text_encoding/mod.rs b/crates/apis/src/text_encoding/mod.rs index 3933c9a7..c7b8528a 100644 --- a/crates/apis/src/text_encoding/mod.rs +++ b/crates/apis/src/text_encoding/mod.rs @@ -28,10 +28,8 @@ impl JSApiSet for TextEncoding { encode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e)) }), )?; - let opts = EvalOptions { - strict: false, - ..Default::default() - }; + let mut opts = EvalOptions::default(); + opts.strict = false; this.eval_with_options(include_str!("./text-encoding.js"), opts)?; Ok::<_, Error>(()) diff --git a/crates/cli/tests/integration_test.rs b/crates/cli/tests/integration_test.rs index c334bb5e..2fb8e410 100644 --- a/crates/cli/tests/integration_test.rs +++ b/crates/cli/tests/integration_test.rs @@ -10,7 +10,7 @@ fn test_identity() { let (output, _, fuel_consumed) = run_with_u8s(&mut runner, 42); assert_eq!(42, output); - assert_fuel_consumed_within_threshold(37907, fuel_consumed); + assert_fuel_consumed_within_threshold(43239, fuel_consumed); } #[test] @@ -46,7 +46,7 @@ fn test_encoding() { let (output, _, fuel_consumed) = run(&mut runner, "hello".as_bytes()); assert_eq!("el".as_bytes(), output); - assert_fuel_consumed_within_threshold(258_852, fuel_consumed); + assert_fuel_consumed_within_threshold(218_527, fuel_consumed); let (output, _, _) = run(&mut runner, "invalid".as_bytes()); assert_eq!("true".as_bytes(), output); @@ -67,7 +67,7 @@ fn test_logging() { "hello world from console.log\nhello world from console.error\n", logs.as_str(), ); - assert_fuel_consumed_within_threshold(22_296, fuel_consumed); + assert_fuel_consumed_within_threshold(34169, fuel_consumed); } #[test] @@ -76,7 +76,7 @@ fn test_readme_script() { let (output, _, fuel_consumed) = run(&mut runner, r#"{ "n": 2, "bar": "baz" }"#.as_bytes()); assert_eq!(r#"{"foo":3,"newBar":"baz!"}"#.as_bytes(), output); - assert_fuel_consumed_within_threshold(284_736, fuel_consumed); + assert_fuel_consumed_within_threshold(235_476, fuel_consumed); } #[cfg(feature = "experimental_event_loop")] @@ -106,7 +106,7 @@ fn test_exported_functions() { let mut runner = Runner::new_with_exports("exported-fn.js", "exported-fn.wit", "exported-fn"); let (_, logs, fuel_consumed) = run_fn(&mut runner, "foo", &[]); assert_eq!("Hello from top-level\nHello from foo\n", logs); - assert_fuel_consumed_within_threshold(54610, fuel_consumed); + assert_fuel_consumed_within_threshold(72552, fuel_consumed); let (_, logs, _) = run_fn(&mut runner, "foo-bar", &[]); assert_eq!("Hello from top-level\nHello from fooBar\n", logs); } @@ -180,7 +180,7 @@ fn test_exported_default_arrow_fn() { ); let (_, logs, fuel_consumed) = run_fn(&mut runner, "default", &[]); assert_eq!(logs, "42\n"); - assert_fuel_consumed_within_threshold(48_628, fuel_consumed); + assert_fuel_consumed_within_threshold(67547, fuel_consumed); } #[test] @@ -192,7 +192,7 @@ fn test_exported_default_fn() { ); let (_, logs, fuel_consumed) = run_fn(&mut runner, "default", &[]); assert_eq!(logs, "42\n"); - assert_fuel_consumed_within_threshold(49_748, fuel_consumed); + assert_fuel_consumed_within_threshold(67792, fuel_consumed); } fn run_with_u8s(r: &mut Runner, stdin: u8) -> (u8, String, u64) { diff --git a/crates/core/src/execution.rs b/crates/core/src/execution.rs index 7da0193b..9e026b39 100644 --- a/crates/core/src/execution.rs +++ b/crates/core/src/execution.rs @@ -1,12 +1,18 @@ use std::process; -use anyhow::{bail, Error, Result}; +use anyhow::{anyhow, bail, Error, Result}; use javy::{ from_js_error, quickjs::{context::EvalOptions, Module, Value}, - Runtime, + to_js_error, Runtime, }; +static EVENT_LOOP_ERR: &str = r#" + Pending jobs in the event queue. + Scheduling events is not supported when the + experimental_event_loop cargo feature is disabled. + "#; + /// Evaluate the given bytecode. /// /// Evaluating also prepares (or "instantiates") the state of the JavaScript @@ -14,7 +20,21 @@ use javy::{ pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { runtime .context() - .with(|this| Module::instantiate_read_object(this, bytecode).map(|_| ())) + .with(|this| { + let module = unsafe { Module::load(this.clone(), bytecode)? }; + let (_, promise) = module.eval()?; + + if cfg!(feature = "experimental_event_loop") { + // If the experimental event loop is enabled, trigger it. + promise.finish::().map(|_| ()) + } else { + // Else we simply expect the promise to resolve immediately. + match promise.result() { + None => Err(to_js_error(this, anyhow!(EVENT_LOOP_ERR))), + Some(r) => r, + } + } + }) .map_err(|e| runtime.context().with(|cx| from_js_error(cx.clone(), e))) // Prefer calling `process_event_loop` *outside* of the `with` callback, // to avoid errors regarding multiple mutable borrows. @@ -38,13 +58,9 @@ pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { runtime .context() .with(|this| { - let opts = EvalOptions { - strict: false, - // We're assuming imports and exports, therefore we want to evaluate - // as a module. - global: false, - ..Default::default() - }; + let mut opts = EvalOptions::default(); + opts.strict = false; + opts.global = false; this.eval_with_options::, _>(js, opts) .map_err(|e| from_js_error(this.clone(), e)) .map(|_| ()) @@ -57,13 +73,7 @@ fn process_event_loop(rt: &Runtime) -> Result<()> { if cfg!(feature = "experimental_event_loop") { rt.resolve_pending_jobs()? } else if rt.has_pending_jobs() { - bail!( - r#" - Pending jobs in the event queue. - Scheduling events is not supported when the - experimental_event_loop cargo feature is disabled. - "# - ); + bail!(EVENT_LOOP_ERR); } Ok(()) diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index 60a8da3a..ffa02f2f 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -11,7 +11,7 @@ categories = ["wasm"] [dependencies] anyhow = { workspace = true } -rquickjs = { version = "0.5.1", features = ["array-buffer"] } +rquickjs = { git = "https://github.com/Shopify/rquickjs", branch = "improved-wasm-support", features = ["array-buffer", "bindgen", "no-free"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", optional = true } serde-transcode = { version = "1.1", optional = true } diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 967d975a..0355501d 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -71,9 +71,7 @@ impl Runtime { /// Compiles the given module to bytecode. pub fn compile_to_bytecode(&self, name: &str, contents: &str) -> Result> { self.context() - .with(|this| { - unsafe { Module::unsafe_declare(this.clone(), name, contents) }?.write_object_le() - }) + .with(|this| Module::declare(this.clone(), name, contents)?.write_le()) .map_err(|e| self.context().with(|cx| from_js_error(cx.clone(), e))) } } From 3b0d041965dbedc36c659ddd3ae7ec5eec016b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Wed, 1 May 2024 16:56:27 -0400 Subject: [PATCH 21/22] Cargo vet config Adds special policy for rquickjs given that we're currently in a fork. --- supply-chain/config.toml | 53 ++++++++++++++++++++++++--------------- supply-chain/imports.lock | 36 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 46603951..fa8b8b8d 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -34,6 +34,19 @@ audit-as-crates-io = false [policy.quickjs-wasm-sys] audit-as-crates-io = false +# Temporary until we're off the fork. +[policy.rquickjs] +audit-as-crates-io = false + +[policy.rquickjs-core] +audit-as-crates-io = false + +[policy.rquickjs-macro] +audit-as-crates-io = false + +[policy.rquickjs-sys] +audit-as-crates-io = false + [[exemptions.Inflector]] version = "0.11.4" criteria = "safe-to-deploy" @@ -150,10 +163,6 @@ criteria = "safe-to-deploy" version = "0.6.0" criteria = "safe-to-deploy" -[[exemptions.core-foundation]] -version = "0.9.4" -criteria = "safe-to-deploy" - [[exemptions.cpp_demangle]] version = "0.3.5" criteria = "safe-to-deploy" @@ -450,6 +459,10 @@ criteria = "safe-to-run" version = "0.2.16" criteria = "safe-to-deploy" +[[exemptions.proc-macro-crate]] +version = "1.3.1" +criteria = "safe-to-deploy" + [[exemptions.proc-macro-error]] version = "1.0.4" criteria = "safe-to-deploy" @@ -470,6 +483,10 @@ criteria = "safe-to-deploy" version = "0.4.5" criteria = "safe-to-deploy" +[[exemptions.relative-path]] +version = "1.9.2" +criteria = "safe-to-deploy" + [[exemptions.rmp]] version = "0.8.12" criteria = "safe-to-deploy" @@ -478,18 +495,6 @@ criteria = "safe-to-deploy" version = "1.1.2" criteria = "safe-to-deploy" -[[exemptions.rquickjs]] -version = "0.5.1" -criteria = "safe-to-deploy" - -[[exemptions.rquickjs-core]] -version = "0.5.1" -criteria = "safe-to-deploy" - -[[exemptions.rquickjs-sys]] -version = "0.5.1" -criteria = "safe-to-deploy" - [[exemptions.rustc-demangle]] version = "0.1.23" criteria = "safe-to-deploy" @@ -646,10 +651,6 @@ criteria = "safe-to-deploy" version = "0.11.0" criteria = "safe-to-deploy" -[[exemptions.tinytemplate]] -version = "1.2.1" -criteria = "safe-to-run" - [[exemptions.tinyvec_macros]] version = "0.1.1" criteria = "safe-to-deploy" @@ -658,6 +659,14 @@ criteria = "safe-to-deploy" version = "0.5.7" criteria = "safe-to-deploy" +[[exemptions.toml_datetime]] +version = "0.6.5" +criteria = "safe-to-deploy" + +[[exemptions.toml_edit]] +version = "0.19.15" +criteria = "safe-to-deploy" + [[exemptions.tower]] version = "0.4.13" criteria = "safe-to-deploy" @@ -774,6 +783,10 @@ criteria = "safe-to-deploy" version = "0.4.0" criteria = "safe-to-deploy" +[[exemptions.winnow]] +version = "0.5.40" +criteria = "safe-to-deploy" + [[exemptions.witx]] version = "0.9.1" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index a5708974..43a5ac16 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -106,6 +106,13 @@ user-id = 6743 user-login = "epage" user-name = "Ed Page" +[[publisher.core-foundation]] +version = "0.9.3" +when = "2022-02-07" +user-id = 5946 +user-login = "jrmuizel" +user-name = "Jeff Muizelaar" + [[publisher.core-foundation-sys]] version = "0.8.4" when = "2023-04-03" @@ -1699,6 +1706,12 @@ criteria = "safe-to-deploy" delta = "0.15.4 -> 0.17.0" notes = "No notable changes" +[[audits.embark-studios.audits.ident_case]] +who = "Johan Andersson " +criteria = "safe-to-deploy" +version = "1.0.1" +notes = "No unsafe usage or ambient capabilities" + [[audits.embark-studios.audits.idna]] who = "Johan Andersson " criteria = "safe-to-deploy" @@ -1882,6 +1895,12 @@ criteria = "safe-to-deploy" version = "1.0.4" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.tinytemplate]] +who = "Ying Hsu " +criteria = "safe-to-run" +version = "1.2.1" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.version_check]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -1957,6 +1976,16 @@ end = "2024-04-21" notes = "No unsafe code, rather straight-forward parser." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.wildcard-audits.core-foundation]] +who = "Bobby Holley " +criteria = "safe-to-deploy" +user-id = 5946 # Jeff Muizelaar (jrmuizel) +start = "2019-03-29" +end = "2023-05-04" +renew = false +notes = "I've reviewed every source contribution that was neither authored nor reviewed by Mozilla." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.wildcard-audits.core-foundation-sys]] who = "Bobby Holley " criteria = "safe-to-deploy" @@ -2080,6 +2109,13 @@ criteria = "safe-to-deploy" delta = "0.69.2 -> 0.69.4" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.core-foundation]] +who = "Teodor Tanasoaia " +criteria = "safe-to-deploy" +delta = "0.9.3 -> 0.9.4" +notes = "I've reviewed every source contribution that was neither authored nor reviewed by Mozilla." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.crossbeam-utils]] who = "Mike Hommey " criteria = "safe-to-deploy" From d51ae1975f897cd646191d555ab782502cb27c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Cabrera?= Date: Wed, 1 May 2024 17:00:53 -0400 Subject: [PATCH 22/22] Remove comment, cargo vet doesn't like them --- supply-chain/config.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index fa8b8b8d..6c55ad46 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -34,7 +34,6 @@ audit-as-crates-io = false [policy.quickjs-wasm-sys] audit-as-crates-io = false -# Temporary until we're off the fork. [policy.rquickjs] audit-as-crates-io = false