From 4f48efcc55b9e6cc0dd212ebd8e729909efed1ab Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 20 Oct 2021 00:00:45 +0200 Subject: [PATCH] chore: return serde_v8 to main repo (#12500) Reduces fragmentation, avoids version drift and facilitates coordinating serde_v8 and op-layer changes --- Cargo.lock | 12 +- Cargo.toml | 1 + serde_v8/Cargo.toml | 29 + serde_v8/README.md | 57 ++ serde_v8/benches/de.rs | 160 ++++++ serde_v8/benches/ser.rs | 108 ++++ serde_v8/examples/basic.rs | 58 ++ serde_v8/src/de.rs | 672 ++++++++++++++++++++++++ serde_v8/src/error.rs | 47 ++ serde_v8/src/keys.rs | 33 ++ serde_v8/src/lib.rs | 18 + serde_v8/src/magic/buffer.rs | 146 +++++ serde_v8/src/magic/bytestring.rs | 125 +++++ serde_v8/src/magic/field.rs | 144 +++++ serde_v8/src/magic/mod.rs | 9 + serde_v8/src/magic/value.rs | 79 +++ serde_v8/src/magic/zero_copy_buf.rs | 115 ++++ serde_v8/src/payload.rs | 34 ++ serde_v8/src/ser.rs | 648 +++++++++++++++++++++++ serde_v8/src/serializable.rs | 118 +++++ serde_v8/src/utils.rs | 34 ++ serde_v8/tests/de.rs | 249 +++++++++ serde_v8/tests/magic.rs | 180 +++++++ serde_v8/tests/ser.rs | 193 +++++++ tools/release/helpers/deno_workspace.ts | 5 + 25 files changed, 3273 insertions(+), 1 deletion(-) create mode 100644 serde_v8/Cargo.toml create mode 100644 serde_v8/README.md create mode 100644 serde_v8/benches/de.rs create mode 100644 serde_v8/benches/ser.rs create mode 100644 serde_v8/examples/basic.rs create mode 100644 serde_v8/src/de.rs create mode 100644 serde_v8/src/error.rs create mode 100644 serde_v8/src/keys.rs create mode 100644 serde_v8/src/lib.rs create mode 100644 serde_v8/src/magic/buffer.rs create mode 100644 serde_v8/src/magic/bytestring.rs create mode 100644 serde_v8/src/magic/field.rs create mode 100644 serde_v8/src/magic/mod.rs create mode 100644 serde_v8/src/magic/value.rs create mode 100644 serde_v8/src/magic/zero_copy_buf.rs create mode 100644 serde_v8/src/payload.rs create mode 100644 serde_v8/src/ser.rs create mode 100644 serde_v8/src/serializable.rs create mode 100644 serde_v8/src/utils.rs create mode 100644 serde_v8/tests/de.rs create mode 100644 serde_v8/tests/magic.rs create mode 100644 serde_v8/tests/ser.rs diff --git a/Cargo.lock b/Cargo.lock index 9db17c3051..112f4bcb17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,7 +771,7 @@ dependencies = [ "rusty_v8", "serde", "serde_json", - "serde_v8", + "serde_v8 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", "url", ] @@ -3326,6 +3326,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_v8" +version = "0.15.0" +dependencies = [ + "bencher", + "rusty_v8", + "serde", + "serde_json", +] + [[package]] name = "serde_v8" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index c448fb2460..83c25c9e59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "cli", "core", "runtime", + "serde_v8", "test_ffi", "test_util", "ext/broadcast_channel", diff --git a/serde_v8/Cargo.toml b/serde_v8/Cargo.toml new file mode 100644 index 0000000000..ed784edafe --- /dev/null +++ b/serde_v8/Cargo.toml @@ -0,0 +1,29 @@ +# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +[package] +name = "serde_v8" +version = "0.15.0" +authors = ["the Deno authors"] +edition = "2018" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "Rust to V8 serialization and deserialization" + +[dependencies] +rusty_v8 = "0.32.0" +serde = { version = "1.0.130", features = ["derive"] } + +[dev-dependencies] +bencher = "0.1" +serde_json = "1.0.64" + +[[example]] +name = "basic" + +[[bench]] +name = "de" +harness = false + +[[bench]] +name = "ser" +harness = false diff --git a/serde_v8/README.md b/serde_v8/README.md new file mode 100644 index 0000000000..207f8baf49 --- /dev/null +++ b/serde_v8/README.md @@ -0,0 +1,57 @@ +# serde_v8 + +Author: Aaron O'Mullan + +Serde support for encoding/decoding (rusty_)v8 values. + +Broadly `serde_v8` aims to provide an expressive but ~maximally efficient +encoding layer to biject rust & v8/js values. It's a core component of deno's +op-layer and is used to encode/decode all non-buffer values. + +**Original issue:** +[denoland/deno#9540](https://github.com/denoland/deno/issues/9540) + +## Quickstart + +`serde_v8` fits naturally into the serde ecosystem, so if you've already used +`serde` or `serde_json`, `serde_v8`'s API should be very familiar. + +`serde_v8` exposes two key-functions: + +- `to_v8`: maps `rust->v8`, similar to `serde_json::to_string`, ... +- `from_v8`: maps `v8->rust`, similar to `serde_json::from_str`, ... + +## Best practices + +Whilst `serde_v8` is compatible with `serde_json::Value` it's important to keep +in mind that `serde_json::Value` is essentially a loosely-typed value (think +nested HashMaps), so when writing ops we recommend directly using rust +structs/tuples or primitives, since mapping to `serde_json::Value` will add +extra overhead and result in slower ops. + +I also recommend avoiding unecessary "wrappers", if your op takes a single-keyed +struct, consider unwrapping that as a plain value unless you plan to add fields +in the near-future. + +Instead of returning "nothing" via `Ok(json!({}))`, change your return type to +rust's unit type `()` and returning `Ok(())`, `serde_v8` will efficiently encode +that as a JS `null`. + +## Advanced features + +If you need to mix rust & v8 values in structs/tuples, you can use the special +`serde_v8::Value` type, which will passthrough the original v8 value untouched +when encoding/decoding. + +## TODO + +- [ ] Experiment with KeyCache to optimize struct keys +- [ ] Experiment with external v8 strings +- [ ] Explore using + [json-stringifier.cc](https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/json/json-stringifier.cc)'s + fast-paths for arrays +- [ ] Improve tests to test parity with `serde_json` (should be mostly + interchangeable) +- [ ] Consider a `Payload` type that's deserializable by itself (holds scope & + value) +- [ ] Ensure we return errors instead of panicking on `.unwrap()`s diff --git a/serde_v8/benches/de.rs b/serde_v8/benches/de.rs new file mode 100644 index 0000000000..6bd4f0f9b0 --- /dev/null +++ b/serde_v8/benches/de.rs @@ -0,0 +1,160 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use bencher::{benchmark_group, benchmark_main, Bencher}; + +use rusty_v8 as v8; +use std::convert::TryFrom; + +use serde::Deserialize; + +use serde_v8::utils::{js_exec, v8_do}; + +#[derive(Debug, Deserialize, PartialEq)] +struct MathOp { + arg1: u64, + arg2: u64, + operator: Option, +} + +fn dedo( + code: &str, + f: impl FnOnce(&mut v8::HandleScope, v8::Local), +) { + v8_do(|| { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + let v = js_exec(scope, code); + + f(scope, v); + }) +} + +fn dedo_json(code: &str, f: impl FnOnce(String)) { + let code = format!("JSON.stringify({})", code); + dedo(&code[..], |scope, v| { + let s: String = serde_v8::from_v8(scope, v).unwrap(); + f(s); + }) +} + +fn de_struct_v8(b: &mut Bencher) { + dedo("({arg1: 10, arg2: 123 })", |scope, obj| { + let mut total = 0; + b.iter(move || { + let op: MathOp = serde_v8::from_v8(scope, obj).unwrap(); + total = total + op.arg1 + op.arg2; + }); + }); +} + +fn de_struct_v8_opt(b: &mut Bencher) { + dedo("({arg1: 10, arg2: 123 })", |scope, v| { + let k_arg1 = v8::String::new(scope, "arg1").unwrap().into(); + let k_arg2 = v8::String::new(scope, "arg2").unwrap().into(); + let obj = v8::Local::::try_from(v).unwrap(); + let mut total = 0; + b.iter(move || { + let v_arg1 = obj.get(scope, k_arg1).unwrap(); + let v_arg2 = obj.get(scope, k_arg2).unwrap(); + let op = MathOp { + arg1: serde_v8::from_v8(scope, v_arg1).unwrap(), + arg2: serde_v8::from_v8(scope, v_arg2).unwrap(), + operator: None, + }; + total = total + op.arg1 + op.arg2; + }); + }); +} + +fn de_struct_json(b: &mut Bencher) { + dedo_json("({arg1: 10, arg2: 123 })", |s| { + let mut total = 0; + b.iter(move || { + let op: MathOp = serde_json::from_str(&s).unwrap(); + total = total + op.arg1 + op.arg2; + }); + }); +} + +fn de_struct_json_deopt(b: &mut Bencher) { + // JSON.stringify() in loop (semi-simulating ABI loop) + dedo("({arg1: 10, arg2: 123 })", |scope, obj| { + let mut total = 0; + b.iter(move || { + let mut scope = v8::HandleScope::new(scope); + let s = v8::json::stringify(&mut scope, obj).unwrap(); + let rs = s.to_rust_string_lossy(&mut scope); + let op: MathOp = serde_json::from_str(&rs).unwrap(); + total = total + op.arg1 + op.arg2; + }); + }); +} + +macro_rules! dualbench { + ($v8_fn:ident, $json_fn:ident, $src:expr, $t:ty) => { + fn $v8_fn(b: &mut Bencher) { + dedo($src, |scope, v| { + b.iter(move || { + let _: $t = serde_v8::from_v8(scope, v).unwrap(); + }); + }); + } + + fn $json_fn(b: &mut Bencher) { + dedo_json($src, |s| { + b.iter(move || { + let _: $t = serde_json::from_str(&s).unwrap(); + }); + }); + } + }; +} + +dualbench!(de_bool_v8, de_bool_json, "true", bool); +dualbench!(de_int_v8, de_int_json, "12345", u32); +dualbench!( + de_array_v8, + de_array_json, + "[1,2,3,4,5,6,7,8,9,10]", + Vec +); +dualbench!(de_str_v8, de_str_json, "'hello world'", String); +dualbench!(de_tuple_v8, de_tuple_json, "[1,false]", (u8, bool)); + +fn de_tuple_v8_opt(b: &mut Bencher) { + dedo("[1,false]", |scope, obj| { + let arr = v8::Local::::try_from(obj).unwrap(); + let obj = v8::Local::::from(arr); + + b.iter(move || { + let v1 = obj.get_index(scope, 0).unwrap(); + let v2 = obj.get_index(scope, 1).unwrap(); + let _: (u8, bool) = ( + serde_v8::from_v8(scope, v1).unwrap(), + serde_v8::from_v8(scope, v2).unwrap(), + ); + }); + }); +} + +benchmark_group!( + benches, + de_struct_v8, + de_struct_v8_opt, + de_struct_json, + de_struct_json_deopt, + de_bool_v8, + de_bool_json, + de_int_v8, + de_int_json, + de_array_v8, + de_array_json, + de_str_v8, + de_str_json, + de_tuple_v8, + de_tuple_json, + de_tuple_v8_opt, +); + +benchmark_main!(benches); diff --git a/serde_v8/benches/ser.rs b/serde_v8/benches/ser.rs new file mode 100644 index 0000000000..d62e264cd1 --- /dev/null +++ b/serde_v8/benches/ser.rs @@ -0,0 +1,108 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use bencher::{benchmark_group, benchmark_main, Bencher}; + +use rusty_v8 as v8; + +use serde::Serialize; + +use serde_v8::utils::v8_do; + +#[derive(Serialize)] +struct MathOp { + arg1: u64, + arg2: u64, + operator: Option, +} + +fn serdo(f: impl FnOnce(&mut v8::HandleScope)) { + v8_do(|| { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + f(scope); + }) +} + +macro_rules! dualbench { + ($v8_fn:ident, $json_fn:ident, $src:expr) => { + fn $v8_fn(b: &mut Bencher) { + serdo(|scope| { + let v = $src; + b.iter(move || { + let _ = serde_v8::to_v8(scope, &v).unwrap(); + }); + }); + } + + fn $json_fn(b: &mut Bencher) { + let v = $src; + b.iter(move || { + let _ = serde_json::to_string(&v).unwrap(); + }); + } + }; +} + +dualbench!( + ser_struct_v8, + ser_struct_json, + MathOp { + arg1: 10, + arg2: 123, + operator: None + } +); +dualbench!(ser_bool_v8, ser_bool_json, true); +dualbench!(ser_int_v8, ser_int_json, 12345); +dualbench!( + ser_array_v8, + ser_array_json, + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +); +dualbench!(ser_str_v8, ser_str_json, "hello world"); +dualbench!(ser_tuple_v8, ser_tuple_json, (1, false)); + +fn ser_struct_v8_manual(b: &mut Bencher) { + serdo(|scope| { + let v = MathOp { + arg1: 10, + arg2: 123, + operator: None, + }; + b.iter(|| { + let obj = v8::Object::new(scope); + let k1 = v8::String::new(scope, "arg1").unwrap(); + let k2 = v8::String::new(scope, "arg2").unwrap(); + let k3 = v8::String::new(scope, "operator").unwrap(); + // let k1 = v8::String::new_from_utf8(scope, "arg1".as_ref(), v8::NewStringType::Internalized).unwrap(); + // let k2 = v8::String::new_from_utf8(scope, "arg2".as_ref(), v8::NewStringType::Internalized).unwrap(); + // let k3 = v8::String::new_from_utf8(scope, "operator".as_ref(), v8::NewStringType::Internalized).unwrap(); + let v1 = v8::Number::new(scope, v.arg1 as f64); + let v2 = v8::Number::new(scope, v.arg2 as f64); + let v3 = v8::null(scope); + obj.set(scope, k1.into(), v1.into()).unwrap(); + obj.set(scope, k2.into(), v2.into()).unwrap(); + obj.set(scope, k3.into(), v3.into()).unwrap(); + }); + }); +} + +benchmark_group!( + benches, + ser_struct_v8, + ser_struct_json, + ser_bool_v8, + ser_bool_json, + ser_int_v8, + ser_int_json, + ser_array_v8, + ser_array_json, + ser_str_v8, + ser_str_json, + ser_tuple_v8, + ser_tuple_json, + ser_struct_v8_manual, +); +benchmark_main!(benches); diff --git a/serde_v8/examples/basic.rs b/serde_v8/examples/basic.rs new file mode 100644 index 0000000000..6c24220d46 --- /dev/null +++ b/serde_v8/examples/basic.rs @@ -0,0 +1,58 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +fn main() { + let platform = v8::new_default_platform(0, false).make_shared(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + fn exec<'s>( + scope: &mut v8::HandleScope<'s>, + src: &str, + ) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() + } + + let v = exec(scope, "32"); + let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x32 = {}", x32); + + let v = exec(scope, "({a: 1, b: 3, c: 'ignored'})"); + let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); + println!("mop = {:?}", mop); + + let v = exec(scope, "[1,2,3,4,5]"); + let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("arr = {:?}", arr); + + let v = exec(scope, "['hello', 'world']"); + let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("hi = {:?}", hi); + + let v: v8::Local = v8::Number::new(scope, 12345.0).into(); + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x = {}", x); + } + + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); +} diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs new file mode 100644 index 0000000000..3668a4fd58 --- /dev/null +++ b/serde_v8/src/de.rs @@ -0,0 +1,672 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; +use serde::de::{self, Visitor}; +use serde::Deserialize; + +use std::convert::TryFrom; + +use crate::error::{Error, Result}; +use crate::keys::{v8_struct_key, KeyCache}; +use crate::payload::ValueType; + +use crate::magic; + +pub struct Deserializer<'a, 'b, 's> { + input: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, + _key_cache: Option<&'b mut KeyCache>, +} + +impl<'a, 'b, 's> Deserializer<'a, 'b, 's> { + pub fn new( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: Option<&'b mut KeyCache>, + ) -> Self { + Deserializer { + input, + scope, + _key_cache: key_cache, + } + } +} + +// from_v8 deserializes a v8::Value into a Deserializable / rust struct +pub fn from_v8<'de, 'a, 'b, 's, T>( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, +) -> Result +where + T: Deserialize<'de>, +{ + let mut deserializer = Deserializer::new(scope, input, None); + let t = T::deserialize(&mut deserializer)?; + Ok(t) +} + +// like from_v8 except accepts a KeyCache to optimize struct key decoding +pub fn from_v8_cached<'de, 'a, 'b, 's, T>( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: &mut KeyCache, +) -> Result +where + T: Deserialize<'de>, +{ + let mut deserializer = Deserializer::new(scope, input, Some(key_cache)); + let t = T::deserialize(&mut deserializer)?; + Ok(t) +} + +macro_rules! wip { + ($method:ident) => { + fn $method(self, _v: V) -> Result + where + V: Visitor<'de>, + { + unimplemented!() + } + }; +} + +// TODO: maybe check for BigInt truncation ? +// (i.e: values larger than i64/u64 can hold) +macro_rules! deserialize_signed { + ($dmethod:ident, $vmethod:ident, $t:tt) => { + fn $dmethod(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let value: $t = match self.input.is_big_int() { + true => { + let bigint = v8::Local::::try_from(self.input); + bigint.unwrap().i64_value().0 as $t + } + false => self.input.integer_value(&mut self.scope).unwrap() as $t, + }; + visitor.$vmethod(value) + } + }; +} + +macro_rules! deserialize_unsigned { + ($dmethod:ident, $vmethod:ident, $t:tt) => { + fn $dmethod(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let value: $t = match self.input.is_big_int() { + true => { + let bigint = v8::Local::::try_from(self.input); + bigint.unwrap().u64_value().0 as $t + } + false => self.input.integer_value(&mut self.scope).unwrap() as $t, + }; + visitor.$vmethod(value) + } + }; +} + +impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> + for &'x mut Deserializer<'a, 'b, 's> +{ + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match ValueType::from_v8(self.input) { + ValueType::Null => self.deserialize_unit(visitor), + ValueType::Bool => self.deserialize_bool(visitor), + // Handle floats & ints separately to work with loosely-typed serde_json + ValueType::Number => { + if self.input.is_uint32() { + self.deserialize_u32(visitor) + } else if self.input.is_int32() { + self.deserialize_i32(visitor) + } else { + self.deserialize_f64(visitor) + } + } + ValueType::String => self.deserialize_string(visitor), + ValueType::Array => self.deserialize_seq(visitor), + ValueType::Object => self.deserialize_map(visitor), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + // Relaxed typechecking, will map all non-true vals to false + visitor.visit_bool(self.input.is_true()) + } + + // signed + deserialize_signed!(deserialize_i8, visit_i8, i8); + deserialize_signed!(deserialize_i16, visit_i16, i16); + deserialize_signed!(deserialize_i32, visit_i32, i32); + deserialize_signed!(deserialize_i64, visit_i64, i64); + // unsigned + deserialize_unsigned!(deserialize_u8, visit_u8, u8); + deserialize_unsigned!(deserialize_u16, visit_u16, u16); + deserialize_unsigned!(deserialize_u32, visit_u32, u32); + deserialize_unsigned!(deserialize_u64, visit_u64, u64); + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f32(self.input.number_value(&mut self.scope).unwrap() as f32) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f64(self.input.number_value(&mut self.scope).unwrap()) + } + + wip!(deserialize_char); + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_string(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_string() { + let v8_string = v8::Local::::try_from(self.input).unwrap(); + let string = v8_string.to_rust_string_lossy(self.scope); + visitor.visit_string(string) + } else { + Err(Error::ExpectedString) + } + } + + wip!(deserialize_bytes); + wip!(deserialize_byte_buf); + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null_or_undefined() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null_or_undefined() { + visitor.visit_unit() + } else { + Err(Error::ExpectedNull) + } + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + // As is done here, serializers are encouraged to treat newtype structs as + // insignificant wrappers around the data they contain. That means not + // parsing anything other than the contained value. + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let arr = v8::Local::::try_from(self.input) + .map_err(|_| Error::ExpectedArray)?; + let len = arr.length(); + let obj = v8::Local::::from(arr); + let seq = SeqAccess { + pos: 0, + len, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Like deserialize_seq except it prefers tuple's length over input array's length + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + // TODO: error on length mismatch + let obj = v8::Local::::try_from(self.input).unwrap(); + let seq = SeqAccess { + pos: 0, + len: len as u32, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Tuple structs look just like sequences in JSON. + fn deserialize_tuple_struct( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_tuple(len, visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Assume object, then get_own_property_names + let obj = v8::Local::::try_from(self.input) + .map_err(|_| Error::ExpectedObject)?; + let prop_names = obj.get_own_property_names(self.scope); + let mut keys: Vec = match prop_names { + Some(names) => from_v8(self.scope, names.into()).unwrap(), + None => vec![], + }; + let keys: Vec> = keys + .drain(..) + .map(|x| x.into()) + // Filter keys to drop keys whose value is undefined + // TODO: optimize, since this doubles our get calls + .filter(|key| !obj.get(self.scope, *key).unwrap().is_undefined()) + .collect(); + + let map = MapAccess { + obj, + keys, + pos: 0, + scope: self.scope, + }; + visitor.visit_map(map) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + // Magic for serde_v8::magic::Value, to passthrough v8::Value + // TODO: ensure this is cross-platform and there's no alternative + if name == magic::NAME { + let mv = magic::Value { + v8_value: self.input, + }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + return visitor.visit_u64(hack); + } + + // Magic Buffer + if name == magic::buffer::BUF_NAME { + let zero_copy_buf = + v8::Local::::try_from(self.input) + .and_then(|view| { + magic::zero_copy_buf::ZeroCopyBuf::try_new(self.scope, view) + }) + .map_err(|_| Error::ExpectedArray)?; + let data: [u8; 32] = unsafe { std::mem::transmute(zero_copy_buf) }; + return visitor.visit_bytes(&data); + } + + // Magic ByteString + if name == magic::bytestring::NAME { + if let Some(v8_string) = self.input.to_string(self.scope) { + if v8_string.contains_only_onebyte() { + let mut buffer: Vec = vec![0u8; v8_string.length()]; + let written = v8_string.write_one_byte( + self.scope, + &mut buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + assert!(written == v8_string.length()); + return visitor.visit_byte_buf(buffer); + } else { + return Err(Error::Message( + "Expected a valid ByteString.".to_string(), + )); + } + } else { + return Err(Error::ExpectedString); + } + } + + // Regular struct + let obj = v8::Local::::try_from(self.input) + .map_err(|_| Error::ExpectedObject)?; + let map = ObjectAccess { + fields, + obj, + pos: 0, + scope: self.scope, + _cache: None, + }; + + visitor.visit_map(map) + } + + /// To be compatible with `serde-json`, we expect enums to be: + /// - `"Variant"`: strings for unit variants, i.e: Enum::Variant + /// - `{ Variant: payload }`: single K/V pairs, converted to `Enum::Variant { payload }` + fn deserialize_enum( + self, + _name: &str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + // Unit variant + if self.input.is_string() { + let payload = v8::undefined(self.scope).into(); + visitor.visit_enum(EnumAccess { + scope: self.scope, + tag: self.input, + payload, + }) + } + // Struct or tuple variant + else if self.input.is_object() { + // Assume object + let obj = v8::Local::::try_from(self.input).unwrap(); + // Unpack single-key + let tag = { + let prop_names = obj.get_own_property_names(self.scope); + let prop_names = prop_names.ok_or(Error::ExpectedEnum)?; + if prop_names.length() != 1 { + return Err(Error::LengthMismatch); + } + prop_names.get_index(self.scope, 0).unwrap() + }; + + let payload = obj.get(self.scope, tag).unwrap(); + visitor.visit_enum(EnumAccess { + scope: self.scope, + tag, + payload, + }) + } else { + // TODO: improve error + Err(Error::ExpectedEnum) + } + } + + // An identifier in Serde is the type that identifies a field of a struct or + // the variant of an enum. In JSON, struct fields and enum variants are + // represented as strings. In other formats they may be represented as + // numeric indices. + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_none() + } +} + +struct MapAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + keys: Vec>, + pos: usize, +} + +impl<'de> de::MapAccess<'de> for MapAccess<'_, '_, '_> { + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result> { + Ok(match self.keys.get(self.pos) { + Some(key) => { + let mut deserializer = Deserializer::new(self.scope, *key, None); + Some(seed.deserialize(&mut deserializer)?) + } + None => None, + }) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + if self.pos >= self.keys.len() { + return Err(Error::LengthMismatch); + } + let key = self.keys[self.pos]; + self.pos += 1; + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + seed.deserialize(&mut deserializer) + } + + fn next_entry_seed< + K: de::DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, + >( + &mut self, + kseed: K, + vseed: V, + ) -> Result> { + if self.pos >= self.keys.len() { + return Ok(None); + } + let v8_key = self.keys[self.pos]; + self.pos += 1; + let mut kdeserializer = Deserializer::new(self.scope, v8_key, None); + Ok(Some((kseed.deserialize(&mut kdeserializer)?, { + let v8_val = self.obj.get(self.scope, v8_key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + vseed.deserialize(&mut deserializer)? + }))) + } +} + +struct ObjectAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + fields: &'static [&'static str], + pos: usize, + _cache: Option<&'b mut KeyCache>, +} + +fn str_deserializer(s: &str) -> de::value::StrDeserializer { + de::IntoDeserializer::into_deserializer(s) +} + +impl<'de, 'a, 'b, 's> de::MapAccess<'de> for ObjectAccess<'a, 'b, 's> { + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result> { + Ok(match self.fields.get(self.pos) { + Some(&field) => Some(seed.deserialize(str_deserializer(field))?), + None => None, + }) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + if self.pos >= self.fields.len() { + return Err(Error::LengthMismatch); + } + let field = self.fields[self.pos]; + self.pos += 1; + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + seed.deserialize(&mut deserializer) + } + + fn next_entry_seed< + K: de::DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, + >( + &mut self, + kseed: K, + vseed: V, + ) -> Result> { + if self.pos >= self.fields.len() { + return Ok(None); + } + let field = self.fields[self.pos]; + self.pos += 1; + Ok(Some((kseed.deserialize(str_deserializer(field))?, { + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + vseed.deserialize(&mut deserializer)? + }))) + } +} + +struct SeqAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + len: u32, + pos: u32, +} + +impl<'de> de::SeqAccess<'de> for SeqAccess<'_, '_, '_> { + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result> { + let pos = self.pos; + self.pos += 1; + + if pos < self.len { + let val = self.obj.get_index(self.scope, pos).unwrap(); + let mut deserializer = Deserializer::new(self.scope, val, None); + Ok(Some(seed.deserialize(&mut deserializer)?)) + } else { + Ok(None) + } + } + + fn size_hint(&self) -> Option { + Some((self.len - self.pos) as usize) + } +} + +struct EnumAccess<'a, 'b, 's> { + tag: v8::Local<'a, v8::Value>, + payload: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, + // p1: std::marker::PhantomData<&'x ()>, +} + +impl<'de, 'a, 'b, 's, 'x> de::EnumAccess<'de> for EnumAccess<'a, 'b, 's> { + type Error = Error; + type Variant = VariantDeserializer<'a, 'b, 's>; + + fn variant_seed>( + self, + seed: V, + ) -> Result<(V::Value, Self::Variant)> { + let seed = { + let mut dtag = Deserializer::new(self.scope, self.tag, None); + seed.deserialize(&mut dtag) + }; + let dpayload = VariantDeserializer::<'a, 'b, 's> { + scope: self.scope, + value: self.payload, + }; + + Ok((seed?, dpayload)) + } +} + +struct VariantDeserializer<'a, 'b, 's> { + value: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, +} + +impl<'de, 'a, 'b, 's> de::VariantAccess<'de> + for VariantDeserializer<'a, 'b, 's> +{ + type Error = Error; + + fn unit_variant(self) -> Result<()> { + let mut d = Deserializer::new(self.scope, self.value, None); + de::Deserialize::deserialize(&mut d) + } + + fn newtype_variant_seed>( + self, + seed: T, + ) -> Result { + let mut d = Deserializer::new(self.scope, self.value, None); + seed.deserialize(&mut d) + } + + fn tuple_variant>( + self, + len: usize, + visitor: V, + ) -> Result { + let mut d = Deserializer::new(self.scope, self.value, None); + de::Deserializer::deserialize_tuple(&mut d, len, visitor) + } + + fn struct_variant>( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result { + let mut d = Deserializer::new(self.scope, self.value, None); + de::Deserializer::deserialize_struct(&mut d, "", fields, visitor) + } +} diff --git a/serde_v8/src/error.rs b/serde_v8/src/error.rs new file mode 100644 index 0000000000..39625da138 --- /dev/null +++ b/serde_v8/src/error.rs @@ -0,0 +1,47 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use std::fmt::{self, Display}; + +use serde::{de, ser}; + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + Message(String), + + ExpectedBoolean, + ExpectedInteger, + ExpectedString, + ExpectedNull, + ExpectedArray, + ExpectedMap, + ExpectedEnum, + ExpectedObject, + + ExpectedUtf8, + + LengthMismatch, +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Message(msg) => formatter.write_str(msg), + err => formatter.write_str(format!("serde_v8 error: {:?}", err).as_ref()), + } + } +} + +impl std::error::Error for Error {} diff --git a/serde_v8/src/keys.rs b/serde_v8/src/keys.rs new file mode 100644 index 0000000000..ea989086c0 --- /dev/null +++ b/serde_v8/src/keys.rs @@ -0,0 +1,33 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use std::collections::HashMap; + +// KeyCache stores a pool struct keys mapped to v8, +// to minimize allocs and speed up decoding/encoding `v8::Object`s +// TODO: experiment with in from_v8/to_v8 +pub struct KeyCache(HashMap<&'static str, v8::Global>); + +// creates an optimized v8::String for a struct field +// TODO: experiment with external strings +// TODO: evaluate if own KeyCache is better than v8's dedupe +pub fn v8_struct_key<'s>( + scope: &mut v8::HandleScope<'s>, + field: &'static str, +) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8( + scope, + field.as_ref(), + v8::NewStringType::Internalized, + ) + .unwrap() + + // TODO: consider external strings later + // right now non-deduped external strings (without KeyCache) + // are slower than the deduped internalized strings by ~2.5x + // since they're a new string in v8's eyes and needs to be hashed, etc... + // v8::String::new_external_onebyte_static(scope, field).unwrap() +} diff --git a/serde_v8/src/lib.rs b/serde_v8/src/lib.rs new file mode 100644 index 0000000000..374a27ce26 --- /dev/null +++ b/serde_v8/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +mod de; +mod error; +mod keys; +mod magic; +mod payload; +mod ser; +mod serializable; +pub mod utils; + +pub use de::{from_v8, from_v8_cached, Deserializer}; +pub use error::{Error, Result}; +pub use keys::KeyCache; +pub use magic::buffer::MagicBuffer as Buffer; +pub use magic::bytestring::ByteString; +pub use magic::Value; +pub use ser::{to_v8, Serializer}; +pub use serializable::{Serializable, SerializablePkg}; diff --git a/serde_v8/src/magic/buffer.rs b/serde_v8/src/magic/buffer.rs new file mode 100644 index 0000000000..ef1f395fe7 --- /dev/null +++ b/serde_v8/src/magic/buffer.rs @@ -0,0 +1,146 @@ +use rusty_v8 as v8; + +use std::fmt; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Mutex; + +use super::zero_copy_buf::ZeroCopyBuf; + +// An asymmetric wrapper around ZeroCopyBuf, +// allowing us to use a single type for familiarity +pub enum MagicBuffer { + FromV8(ZeroCopyBuf), + ToV8(Mutex>>), +} + +impl MagicBuffer { + pub fn new<'s>( + scope: &mut v8::HandleScope<'s>, + view: v8::Local, + ) -> Self { + Self::try_new(scope, view).unwrap() + } + + pub fn try_new<'s>( + scope: &mut v8::HandleScope<'s>, + view: v8::Local, + ) -> Result { + Ok(Self::FromV8(ZeroCopyBuf::try_new(scope, view)?)) + } + + pub fn empty() -> Self { + MagicBuffer::ToV8(Mutex::new(Some(vec![0_u8; 0].into_boxed_slice()))) + } +} + +impl Clone for MagicBuffer { + fn clone(&self) -> Self { + match self { + Self::FromV8(zbuf) => Self::FromV8(zbuf.clone()), + Self::ToV8(_) => panic!("Don't Clone a MagicBuffer sent to v8"), + } + } +} + +impl AsRef<[u8]> for MagicBuffer { + fn as_ref(&self) -> &[u8] { + &*self + } +} + +impl AsMut<[u8]> for MagicBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut *self + } +} + +impl Deref for MagicBuffer { + type Target = [u8]; + fn deref(&self) -> &[u8] { + match self { + Self::FromV8(buf) => &*buf, + Self::ToV8(_) => panic!("Don't Deref a MagicBuffer sent to v8"), + } + } +} + +impl DerefMut for MagicBuffer { + fn deref_mut(&mut self) -> &mut [u8] { + match self { + Self::FromV8(buf) => &mut *buf, + Self::ToV8(_) => panic!("Don't Deref a MagicBuffer sent to v8"), + } + } +} + +impl From> for MagicBuffer { + fn from(buf: Box<[u8]>) -> Self { + MagicBuffer::ToV8(Mutex::new(Some(buf))) + } +} + +impl From> for MagicBuffer { + fn from(vec: Vec) -> Self { + vec.into_boxed_slice().into() + } +} + +pub const BUF_NAME: &str = "$__v8_magic_Buffer"; +pub const BUF_FIELD_1: &str = "$__v8_magic_buffer_1"; +pub const BUF_FIELD_2: &str = "$__v8_magic_buffer_2"; + +impl serde::Serialize for MagicBuffer { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct(BUF_NAME, 1)?; + let boxed: Box<[u8]> = match self { + Self::FromV8(buf) => { + let value: &[u8] = buf; + value.into() + } + Self::ToV8(x) => x.lock().unwrap().take().expect("MagicBuffer was empty"), + }; + let hack: [usize; 2] = unsafe { std::mem::transmute(boxed) }; + let f1: u64 = hack[0] as u64; + let f2: u64 = hack[1] as u64; + s.serialize_field(BUF_FIELD_1, &f1)?; + s.serialize_field(BUF_FIELD_2, &f2)?; + s.end() + } +} + +impl<'de, 's> serde::Deserialize<'de> for MagicBuffer { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor {} + + impl<'de> serde::de::Visitor<'de> for ValueVisitor { + type Value = MagicBuffer; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a serde_v8::MagicBuffer") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + let p1: &[usize] = unsafe { &*(v as *const [u8] as *const [usize]) }; + let p2: [usize; 4] = [p1[0], p1[1], p1[2], p1[3]]; + let zero_copy: ZeroCopyBuf = unsafe { std::mem::transmute(p2) }; + Ok(MagicBuffer::FromV8(zero_copy)) + } + } + + static FIELDS: [&str; 0] = []; + let visitor = ValueVisitor {}; + deserializer.deserialize_struct(BUF_NAME, &FIELDS, visitor) + } +} diff --git a/serde_v8/src/magic/bytestring.rs b/serde_v8/src/magic/bytestring.rs new file mode 100644 index 0000000000..e90b7528e4 --- /dev/null +++ b/serde_v8/src/magic/bytestring.rs @@ -0,0 +1,125 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use std::ops::{Deref, DerefMut}; + +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; + +pub const NAME: &str = "$__v8_magic_bytestring"; +pub const FIELD_PTR: &str = "$__v8_magic_bytestring_ptr"; +pub const FIELD_LEN: &str = "$__v8_magic_bytestring_len"; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ByteString(pub Vec); + +impl ByteString { + pub fn new() -> ByteString { + ByteString(Vec::new()) + } + + pub fn with_capacity(capacity: usize) -> ByteString { + ByteString(Vec::with_capacity(capacity)) + } + + pub fn capacity(&self) -> usize { + self.0.capacity() + } + + pub fn reserve(&mut self, additional: usize) { + self.0.reserve(additional) + } + + pub fn reserve_exact(&mut self, additional: usize) { + self.0.reserve_exact(additional) + } + + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit() + } + + pub fn truncate(&mut self, len: usize) { + self.0.truncate(len) + } + + pub fn push(&mut self, value: u8) { + self.0.push(value) + } + + pub fn pop(&mut self) -> Option { + self.0.pop() + } +} + +impl Default for ByteString { + fn default() -> Self { + ByteString::new() + } +} + +impl Deref for ByteString { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.0.deref() + } +} + +impl DerefMut for ByteString { + fn deref_mut(&mut self) -> &mut [u8] { + self.0.deref_mut() + } +} + +impl AsRef<[u8]> for ByteString { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsMut<[u8]> for ByteString { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} + +impl Serialize for ByteString { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct(NAME, 1)?; + s.serialize_field(FIELD_PTR, &(self.0.as_ptr() as usize))?; + s.serialize_field(FIELD_LEN, &self.0.len())?; + s.end() + } +} + +impl<'de> Deserialize<'de> for ByteString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ValueVisitor {} + + impl<'de> Visitor<'de> for ValueVisitor { + type Value = ByteString; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("a serde_v8::ByteString") + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + Ok(ByteString(v)) + } + } + + deserializer.deserialize_struct(NAME, &[], ValueVisitor {}) + } +} diff --git a/serde_v8/src/magic/field.rs b/serde_v8/src/magic/field.rs new file mode 100644 index 0000000000..e6bb9ee547 --- /dev/null +++ b/serde_v8/src/magic/field.rs @@ -0,0 +1,144 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use crate::error::{Error, Result}; +use serde::ser::{Impossible, Serialize, Serializer}; + +/// All serde_v8 "magic" values are reduced to structs with 1 or 2 u64 fields +/// assuming usize==u64, most types are simply a pointer or pointer+len (e.g: Box) +pub type TransmutedField = u64; +pub type FieldResult = Result; + +macro_rules! not_reachable { + ($($name:ident($ty:ty);)*) => { + $(fn $name(self, _v: $ty) -> FieldResult { + unreachable!(); + })* + }; +} + +/// FieldSerializer is a simple serde::Serializer that only returns u64s +/// it allows the "magic" struct serializers to obtain the transmuted field values +pub struct FieldSerializer {} + +impl Serializer for FieldSerializer { + type Ok = TransmutedField; + type Error = Error; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + fn serialize_u64(self, transmuted_field: u64) -> FieldResult { + Ok(transmuted_field) + } + + not_reachable! { + serialize_i8(i8); + serialize_i16(i16); + serialize_i32(i32); + serialize_i64(i64); + serialize_u8(u8); + serialize_u16(u16); + serialize_u32(u32); + // serialize_u64(TransmutedField); the chosen one + serialize_f32(f32); + serialize_f64(f64); + serialize_bool(bool); + serialize_char(char); + serialize_str(&str); + serialize_bytes(&[u8]); + } + + fn serialize_none(self) -> FieldResult { + unreachable!(); + } + + fn serialize_some(self, _value: &T) -> FieldResult { + unreachable!(); + } + + fn serialize_unit(self) -> FieldResult { + unreachable!(); + } + + fn serialize_unit_struct(self, _name: &'static str) -> FieldResult { + unreachable!(); + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> FieldResult { + unreachable!(); + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + _value: &T, + ) -> FieldResult { + unreachable!(); + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> FieldResult { + unreachable!(); + } + fn serialize_seq(self, _len: Option) -> Result { + unreachable!(); + } + + fn serialize_tuple(self, _len: usize) -> Result { + unreachable!(); + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_map(self, _len: Option) -> Result { + unreachable!(); + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } +} diff --git a/serde_v8/src/magic/mod.rs b/serde_v8/src/magic/mod.rs new file mode 100644 index 0000000000..00c06fd4ce --- /dev/null +++ b/serde_v8/src/magic/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +pub mod buffer; +pub mod bytestring; +mod field; +mod value; +pub mod zero_copy_buf; + +pub use field::FieldSerializer; +pub use value::{Value, FIELD, NAME}; diff --git a/serde_v8/src/magic/value.rs b/serde_v8/src/magic/value.rs new file mode 100644 index 0000000000..2cb6224668 --- /dev/null +++ b/serde_v8/src/magic/value.rs @@ -0,0 +1,79 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use std::fmt; +use std::marker::PhantomData; + +pub const FIELD: &str = "$__v8_magic_value"; +pub const NAME: &str = "$__v8_magic_Value"; + +/// serde_v8::Value allows passing through `v8::Value`s untouched +/// when encoding/decoding and allows mixing rust & v8 values in +/// structs, tuples... +/// The implementation mainly breaks down to: +/// 1. Transmuting between u64 <> serde_v8::Value +/// 2. Using special struct/field names to detect these values +/// 3. Then serde "boilerplate" +pub struct Value<'s> { + pub v8_value: v8::Local<'s, v8::Value>, +} + +impl<'s> From> for Value<'s> { + fn from(v8_value: v8::Local<'s, v8::Value>) -> Self { + Self { v8_value } + } +} + +impl<'s> From> for v8::Local<'s, v8::Value> { + fn from(v: Value<'s>) -> Self { + v.v8_value + } +} + +impl serde::Serialize for Value<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct(NAME, 1)?; + let mv = Value { + v8_value: self.v8_value, + }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + s.serialize_field(FIELD, &hack)?; + s.end() + } +} + +impl<'de, 's> serde::Deserialize<'de> for Value<'s> { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor<'s> { + p1: PhantomData<&'s ()>, + } + + impl<'de, 's> serde::de::Visitor<'de> for ValueVisitor<'s> { + type Value = Value<'s>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a v8::Value") + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + let mv: Value<'s> = unsafe { std::mem::transmute(v) }; + Ok(mv) + } + } + + static FIELDS: [&str; 1] = [FIELD]; + let visitor = ValueVisitor { p1: PhantomData }; + deserializer.deserialize_struct(NAME, &FIELDS, visitor) + } +} diff --git a/serde_v8/src/magic/zero_copy_buf.rs b/serde_v8/src/magic/zero_copy_buf.rs new file mode 100644 index 0000000000..1e911c329d --- /dev/null +++ b/serde_v8/src/magic/zero_copy_buf.rs @@ -0,0 +1,115 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use rusty_v8 as v8; +use std::cell::Cell; +use std::ops::Deref; +use std::ops::DerefMut; + +/// A ZeroCopyBuf encapsulates a slice that's been borrowed from a JavaScript +/// ArrayBuffer object. JavaScript objects can normally be garbage collected, +/// but the existence of a ZeroCopyBuf inhibits this until it is dropped. It +/// behaves much like an Arc<[u8]>. +/// +/// # Cloning +/// Cloning a ZeroCopyBuf does not clone the contents of the buffer, +/// it creates a new reference to that buffer. +/// +/// To actually clone the contents of the buffer do +/// `let copy = Vec::from(&*zero_copy_buf);` +#[derive(Clone)] +pub struct ZeroCopyBuf { + backing_store: v8::SharedRef, + byte_offset: usize, + byte_length: usize, +} + +unsafe impl Send for ZeroCopyBuf {} + +impl ZeroCopyBuf { + pub fn new<'s>( + scope: &mut v8::HandleScope<'s>, + view: v8::Local, + ) -> Self { + Self::try_new(scope, view).unwrap() + } + + pub fn try_new<'s>( + scope: &mut v8::HandleScope<'s>, + view: v8::Local, + ) -> Result { + let backing_store = view.buffer(scope).unwrap().get_backing_store(); + if backing_store.is_shared() { + return Err(v8::DataError::BadType { + actual: "shared ArrayBufferView", + expected: "non-shared ArrayBufferView", + }); + } + let byte_offset = view.byte_offset(); + let byte_length = view.byte_length(); + Ok(Self { + backing_store, + byte_offset, + byte_length, + }) + } +} + +impl Deref for ZeroCopyBuf { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + get_backing_store_slice( + &self.backing_store, + self.byte_offset, + self.byte_length, + ) + } + } +} + +impl DerefMut for ZeroCopyBuf { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + get_backing_store_slice_mut( + &self.backing_store, + self.byte_offset, + self.byte_length, + ) + } + } +} + +impl AsRef<[u8]> for ZeroCopyBuf { + fn as_ref(&self) -> &[u8] { + &*self + } +} + +impl AsMut<[u8]> for ZeroCopyBuf { + fn as_mut(&mut self) -> &mut [u8] { + &mut *self + } +} + +unsafe fn get_backing_store_slice( + backing_store: &v8::SharedRef, + byte_offset: usize, + byte_length: usize, +) -> &[u8] { + let cells: *const [Cell] = + &backing_store[byte_offset..byte_offset + byte_length]; + let bytes = cells as *const [u8]; + &*bytes +} + +#[allow(clippy::mut_from_ref)] +unsafe fn get_backing_store_slice_mut( + backing_store: &v8::SharedRef, + byte_offset: usize, + byte_length: usize, +) -> &mut [u8] { + let cells: *const [Cell] = + &backing_store[byte_offset..byte_offset + byte_length]; + let bytes = cells as *const _ as *mut [u8]; + &mut *bytes +} diff --git a/serde_v8/src/payload.rs b/serde_v8/src/payload.rs new file mode 100644 index 0000000000..816158f93c --- /dev/null +++ b/serde_v8/src/payload.rs @@ -0,0 +1,34 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +// TODO: maybe add a Payload type that holds scope & v8::Value +// so it can implement Deserialize by itself + +// Classifies v8::Values into sub-types +pub enum ValueType { + Null, + Bool, + Number, + String, + Array, + Object, +} + +impl ValueType { + pub fn from_v8(v: v8::Local) -> ValueType { + if v.is_boolean() { + return Self::Bool; + } else if v.is_number() { + return Self::Number; + } else if v.is_string() { + return Self::String; + } else if v.is_array() { + return Self::Array; + } else if v.is_object() { + return Self::Object; + } else if v.is_null_or_undefined() { + return Self::Null; + } + panic!("serde_v8: unknown ValueType for v8::Value") + } +} diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs new file mode 100644 index 0000000000..a2a57d62e9 --- /dev/null +++ b/serde_v8/src/ser.rs @@ -0,0 +1,648 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; +use serde::ser; +use serde::ser::Serialize; + +use std::cell::RefCell; +use std::convert::TryInto; + +use crate::error::{Error, Result}; +use crate::keys::v8_struct_key; +use crate::magic; + +type JsValue<'s> = v8::Local<'s, v8::Value>; +type JsResult<'s> = Result>; + +type ScopePtr<'a, 'b, 'c> = &'c RefCell<&'b mut v8::HandleScope<'a>>; + +pub fn to_v8<'a, T>(scope: &mut v8::HandleScope<'a>, input: T) -> JsResult<'a> +where + T: Serialize, +{ + let scopeptr = RefCell::new(scope); + let serializer = Serializer::new(&scopeptr); + + input.serialize(serializer) +} + +/// Wraps other serializers into an enum tagged variant form. +/// Uses {"Variant": ...payload...} for compatibility with serde-json. +pub struct VariantSerializer<'a, 'b, 'c, S> { + inner: S, + scope: ScopePtr<'a, 'b, 'c>, + variant: &'static str, +} + +impl<'a, 'b, 'c, S> VariantSerializer<'a, 'b, 'c, S> { + pub fn new( + scope: ScopePtr<'a, 'b, 'c>, + variant: &'static str, + inner: S, + ) -> Self { + Self { + inner, + scope, + variant, + } + } + + fn end(self, inner: impl FnOnce(S) -> JsResult<'a>) -> JsResult<'a> { + let value = inner(self.inner)?; + let scope = &mut *self.scope.borrow_mut(); + let null = v8::null(scope).into(); + let key = v8_struct_key(scope, self.variant).into(); + let obj = + v8::Object::with_prototype_and_properties(scope, null, &[key], &[value]); + Ok(obj.into()) + } +} + +impl<'a, 'b, 'c, S> ser::SerializeTupleVariant + for VariantSerializer<'a, 'b, 'c, S> +where + S: ser::SerializeTupleStruct, Error = Error>, +{ + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<()> { + self.inner.serialize_field(value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } +} + +impl<'a, 'b, 'c, S> ser::SerializeStructVariant + for VariantSerializer<'a, 'b, 'c, S> +where + S: ser::SerializeStruct, Error = Error>, +{ + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + self.inner.serialize_field(key, value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } +} + +pub struct ArraySerializer<'a, 'b, 'c> { + pending: Vec>, + scope: ScopePtr<'a, 'b, 'c>, +} + +impl<'a, 'b, 'c> ArraySerializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>, len: Option) -> Self { + let pending = match len { + Some(len) => Vec::with_capacity(len), + None => vec![], + }; + Self { pending, scope } + } +} + +impl<'a, 'b, 'c> ser::SerializeSeq for ArraySerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<()> { + let x = value.serialize(Serializer::new(self.scope))?; + self.pending.push(x); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + let elements = self.pending.iter().as_slice(); + let scope = &mut *self.scope.borrow_mut(); + let arr = v8::Array::new_with_elements(scope, elements); + Ok(arr.into()) + } +} + +impl<'a, 'b, 'c> ser::SerializeTuple for ArraySerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<()> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeSeq::end(self) + } +} + +impl<'a, 'b, 'c> ser::SerializeTupleStruct for ArraySerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<()> { + ser::SerializeTuple::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeTuple::end(self) + } +} + +pub struct ObjectSerializer<'a, 'b, 'c> { + scope: ScopePtr<'a, 'b, 'c>, + keys: Vec>, + values: Vec>, +} + +impl<'a, 'b, 'c> ObjectSerializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>, len: usize) -> Self { + let keys = Vec::with_capacity(len); + let values = Vec::with_capacity(len); + Self { + scope, + keys, + values, + } + } +} + +impl<'a, 'b, 'c> ser::SerializeStruct for ObjectSerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + let value = value.serialize(Serializer::new(self.scope))?; + let scope = &mut *self.scope.borrow_mut(); + let key = v8_struct_key(scope, key).into(); + self.keys.push(key); + self.values.push(value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + let scope = &mut *self.scope.borrow_mut(); + let null = v8::null(scope); + let obj = v8::Object::with_prototype_and_properties( + scope, + null.into(), + &self.keys[..], + &self.values[..], + ); + Ok(obj.into()) + } +} + +pub struct MagicSerializer<'a> { + v8_value: Option>, +} + +impl<'a> ser::SerializeStruct for MagicSerializer<'a> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + if key != magic::FIELD { + unreachable!(); + } + let transmuted: u64 = value.serialize(magic::FieldSerializer {})?; + let mv: magic::Value<'a> = unsafe { std::mem::transmute(transmuted) }; + self.v8_value = Some(mv.v8_value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + Ok(self.v8_value.unwrap()) + } +} + +// TODO(@AaronO): refactor this and streamline how we transmute values +pub struct MagicBufferSerializer<'a, 'b, 'c> { + scope: ScopePtr<'a, 'b, 'c>, + f1: u64, + f2: u64, +} + +impl<'a, 'b, 'c> MagicBufferSerializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>) -> Self { + Self { + scope, + f1: 0, + f2: 0, + } + } +} + +impl<'a, 'b, 'c> ser::SerializeStruct for MagicBufferSerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + // Get u64 chunk + let transmuted: u64 = value.serialize(magic::FieldSerializer {})?; + match key { + magic::buffer::BUF_FIELD_1 => self.f1 = transmuted, + magic::buffer::BUF_FIELD_2 => self.f2 = transmuted, + _ => unreachable!(), + } + Ok(()) + } + + fn end(self) -> JsResult<'a> { + let x: [usize; 2] = [self.f1 as usize, self.f2 as usize]; + let buf: Box<[u8]> = unsafe { std::mem::transmute(x) }; + let scope = &mut *self.scope.borrow_mut(); + let v8_value = boxed_slice_to_uint8array(scope, buf); + Ok(v8_value.into()) + } +} + +pub struct MagicByteStringSerializer<'a, 'b, 'c> { + scope: ScopePtr<'a, 'b, 'c>, + ptr: Option>, + len: Option, +} + +impl<'a, 'b, 'c> MagicByteStringSerializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>) -> Self { + Self { + scope, + ptr: None, + len: None, + } + } +} + +impl<'a, 'b, 'c> ser::SerializeStruct + for MagicByteStringSerializer<'a, 'b, 'c> +{ + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + // Get u64 chunk + let transmuted: u64 = value.serialize(magic::FieldSerializer {})?; + match key { + magic::bytestring::FIELD_PTR => { + self.ptr = std::ptr::NonNull::new(transmuted as *mut u8); + } + magic::bytestring::FIELD_LEN => { + self.len = Some(transmuted as usize); + } + _ => unreachable!(), + } + Ok(()) + } + + fn end(self) -> JsResult<'a> { + // SAFETY: This function is only called from ByteString::serialize(), which + // guarantees the Vec is still alive. + let bytes = unsafe { + std::slice::from_raw_parts(self.ptr.unwrap().as_ptr(), self.len.unwrap()) + }; + let scope = &mut *self.scope.borrow_mut(); + let v8_value = + v8::String::new_from_one_byte(scope, bytes, v8::NewStringType::Normal) + .unwrap(); + Ok(v8_value.into()) + } +} + +// Dispatches between magic and regular struct serializers +pub enum StructSerializers<'a, 'b, 'c> { + Magic(MagicSerializer<'a>), + MagicBuffer(MagicBufferSerializer<'a, 'b, 'c>), + MagicByteString(MagicByteStringSerializer<'a, 'b, 'c>), + Regular(ObjectSerializer<'a, 'b, 'c>), +} + +impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + match self { + StructSerializers::Magic(s) => s.serialize_field(key, value), + StructSerializers::MagicBuffer(s) => s.serialize_field(key, value), + StructSerializers::MagicByteString(s) => s.serialize_field(key, value), + StructSerializers::Regular(s) => s.serialize_field(key, value), + } + } + + fn end(self) -> JsResult<'a> { + match self { + StructSerializers::Magic(s) => s.end(), + StructSerializers::MagicBuffer(s) => s.end(), + StructSerializers::MagicByteString(s) => s.end(), + StructSerializers::Regular(s) => s.end(), + } + } +} + +// Serializes to JS Objects, NOT JS Maps ... +pub struct MapSerializer<'a, 'b, 'c> { + scope: ScopePtr<'a, 'b, 'c>, + keys: Vec>, + values: Vec>, +} + +impl<'a, 'b, 'c> MapSerializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>, len: Option) -> Self { + let keys = Vec::with_capacity(len.unwrap_or_default()); + let values = Vec::with_capacity(len.unwrap_or_default()); + Self { + scope, + keys, + values, + } + } +} + +impl<'a, 'b, 'c> ser::SerializeMap for MapSerializer<'a, 'b, 'c> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> { + let key = key.serialize(Serializer::new(self.scope))?; + self.keys.push(key.try_into().map_err(|_| { + Error::Message("Serialized Maps expect String keys".into()) + })?); + Ok(()) + } + + fn serialize_value( + &mut self, + value: &T, + ) -> Result<()> { + let v8_value = value.serialize(Serializer::new(self.scope))?; + self.values.push(v8_value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + debug_assert!(self.keys.len() == self.values.len()); + let scope = &mut *self.scope.borrow_mut(); + let null = v8::null(scope).into(); + let obj = v8::Object::with_prototype_and_properties( + scope, + null, + &self.keys[..], + &self.values[..], + ); + Ok(obj.into()) + } +} + +pub struct Serializer<'a, 'b, 'c> { + scope: ScopePtr<'a, 'b, 'c>, +} + +impl<'a, 'b, 'c> Serializer<'a, 'b, 'c> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>) -> Self { + Serializer { scope } + } +} + +macro_rules! forward_to { + ($($name:ident($ty:ty, $to:ident, $lt:lifetime);)*) => { + $(fn $name(self, v: $ty) -> JsResult<$lt> { + self.$to(v as _) + })* + }; +} + +impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { + type Ok = v8::Local<'a, v8::Value>; + type Error = Error; + + type SerializeSeq = ArraySerializer<'a, 'b, 'c>; + type SerializeTuple = ArraySerializer<'a, 'b, 'c>; + type SerializeTupleStruct = ArraySerializer<'a, 'b, 'c>; + type SerializeTupleVariant = + VariantSerializer<'a, 'b, 'c, ArraySerializer<'a, 'b, 'c>>; + type SerializeMap = MapSerializer<'a, 'b, 'c>; + type SerializeStruct = StructSerializers<'a, 'b, 'c>; + type SerializeStructVariant = + VariantSerializer<'a, 'b, 'c, StructSerializers<'a, 'b, 'c>>; + + forward_to! { + serialize_i8(i8, serialize_i32, 'a); + serialize_i16(i16, serialize_i32, 'a); + + serialize_u8(u8, serialize_u32, 'a); + serialize_u16(u16, serialize_u32, 'a); + + serialize_f32(f32, serialize_f64, 'a); + serialize_u64(u64, serialize_f64, 'a); + serialize_i64(i64, serialize_f64, 'a); + } + + fn serialize_i32(self, v: i32) -> JsResult<'a> { + Ok(v8::Integer::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_u32(self, v: u32) -> JsResult<'a> { + Ok(v8::Integer::new_from_unsigned(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_f64(self, v: f64) -> JsResult<'a> { + Ok(v8::Number::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_bool(self, v: bool) -> JsResult<'a> { + Ok(v8::Boolean::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_char(self, _v: char) -> JsResult<'a> { + unimplemented!(); + } + + fn serialize_str(self, v: &str) -> JsResult<'a> { + v8::String::new(&mut self.scope.borrow_mut(), v) + .map(|v| v.into()) + .ok_or(Error::ExpectedString) + } + + fn serialize_bytes(self, _v: &[u8]) -> JsResult<'a> { + // TODO: investigate using Uint8Arrays + unimplemented!() + } + + fn serialize_none(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_some(self, value: &T) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_unit(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + /// For compatibility with serde-json, serialises unit variants as "Variant" strings. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> JsResult<'a> { + Ok(v8_struct_key(&mut self.scope.borrow_mut(), variant).into()) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> JsResult<'a> { + let scope = self.scope; + let x = self.serialize_newtype_struct(variant, value)?; + VariantSerializer::new(scope, variant, x).end(Ok) + } + + /// Serialises any Rust iterable into a JS Array + fn serialize_seq(self, len: Option) -> Result { + Ok(ArraySerializer::new(self.scope, len)) + } + + 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_tuple(len) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(VariantSerializer::new( + self.scope, + variant, + self.serialize_tuple_struct(variant, len)?, + )) + } + + fn serialize_map(self, len: Option) -> Result { + // Serializes a rust Map (e.g: BTreeMap, HashMap) to a v8 Object + // TODO: consider allowing serializing to v8 Maps (e.g: via a magic type) + // since they're lighter and better suited for K/V data + // and maybe restrict keys (e.g: strings and numbers) + Ok(MapSerializer::new(self.scope, len)) + } + + /// Serialises Rust typed structs into plain JS objects. + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + match name { + magic::NAME => { + let m: MagicSerializer<'a> = MagicSerializer { v8_value: None }; + Ok(StructSerializers::Magic(m)) + } + magic::buffer::BUF_NAME => { + let m = MagicBufferSerializer::new(self.scope); + Ok(StructSerializers::MagicBuffer(m)) + } + magic::bytestring::NAME => { + let m = MagicByteStringSerializer::new(self.scope); + Ok(StructSerializers::MagicByteString(m)) + } + _ => { + let o = ObjectSerializer::new(self.scope, len); + Ok(StructSerializers::Regular(o)) + } + } + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let scope = self.scope; + let x = self.serialize_struct(variant, len)?; + Ok(VariantSerializer::new(scope, variant, x)) + } +} + +// Used to map MagicBuffers to v8 +pub fn boxed_slice_to_uint8array<'a>( + scope: &mut v8::HandleScope<'a>, + buf: Box<[u8]>, +) -> v8::Local<'a, v8::Uint8Array> { + if buf.is_empty() { + let ab = v8::ArrayBuffer::new(scope, 0); + return v8::Uint8Array::new(scope, ab, 0, 0) + .expect("Failed to create UintArray8"); + } + let buf_len = buf.len(); + let backing_store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(buf); + let backing_store_shared = backing_store.make_shared(); + let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); + v8::Uint8Array::new(scope, ab, 0, buf_len) + .expect("Failed to create UintArray8") +} diff --git a/serde_v8/src/serializable.rs b/serde_v8/src/serializable.rs new file mode 100644 index 0000000000..c9182b636f --- /dev/null +++ b/serde_v8/src/serializable.rs @@ -0,0 +1,118 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; +use std::any::TypeId; +use std::mem::transmute_copy; + +/// Serializable exists to allow boxing values as "objects" to be serialized later, +/// this is particularly useful for async op-responses. This trait is a more efficient +/// replacement for erased-serde that makes less allocations, since it's specific to serde_v8 +/// (and thus doesn't have to have generic outputs, etc...) +pub trait Serializable { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error>; +} + +/// Allows all implementors of `serde::Serialize` to implement Serializable +impl Serializable for T { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error> { + crate::to_v8(scope, self) + } +} + +/// SerializablePkg exists to provide a fast path for op returns, +/// allowing them to avoid boxing primtives (ints/floats/bool/unit/...) +pub enum SerializablePkg { + Primitive(Primitive), + Serializable(Box), +} + +impl SerializablePkg { + pub fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error> { + match &*self { + Self::Primitive(x) => crate::to_v8(scope, x), + Self::Serializable(x) => x.to_v8(scope), + } + } +} + +/// Primitive serves as a lightweight serializable wrapper around primitives +/// so that we can use them for async values +#[derive(Clone, Copy)] +pub enum Primitive { + Unit, + Bool(bool), + Int8(i8), + Int16(i16), + Int32(i32), + Int64(i64), + UInt8(u8), + UInt16(u16), + UInt32(u32), + UInt64(u64), + Float32(f32), + Float64(f64), +} + +impl serde::Serialize for Primitive { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match *self { + Self::Unit => serializer.serialize_unit(), + Self::Bool(x) => serializer.serialize_bool(x), + Self::Int8(x) => serializer.serialize_i8(x), + Self::Int16(x) => serializer.serialize_i16(x), + Self::Int32(x) => serializer.serialize_i32(x), + Self::Int64(x) => serializer.serialize_i64(x), + Self::UInt8(x) => serializer.serialize_u8(x), + Self::UInt16(x) => serializer.serialize_u16(x), + Self::UInt32(x) => serializer.serialize_u32(x), + Self::UInt64(x) => serializer.serialize_u64(x), + Self::Float32(x) => serializer.serialize_f32(x), + Self::Float64(x) => serializer.serialize_f64(x), + } + } +} + +impl From for SerializablePkg { + fn from(x: T) -> Self { + let tid = TypeId::of::(); + + if tid == TypeId::of::<()>() { + Self::Primitive(Primitive::Unit) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Bool(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Int8(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Int16(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Int32(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Int64(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::UInt8(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::UInt16(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::UInt32(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::UInt64(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Float32(unsafe { transmute_copy(&x) })) + } else if tid == TypeId::of::() { + Self::Primitive(Primitive::Float64(unsafe { transmute_copy(&x) })) + } else { + Self::Serializable(Box::new(x)) + } + } +} diff --git a/serde_v8/src/utils.rs b/serde_v8/src/utils.rs new file mode 100644 index 0000000000..6f638f7b32 --- /dev/null +++ b/serde_v8/src/utils.rs @@ -0,0 +1,34 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; +use std::sync::Once; + +pub fn js_exec<'s>( + scope: &mut v8::HandleScope<'s>, + src: &str, +) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() +} + +pub fn v8_init() { + let platform = v8::new_default_platform(0, false).make_shared(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); +} + +pub fn v8_shutdown() { + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); +} + +pub fn v8_do(f: impl FnOnce()) { + static V8_INIT: Once = Once::new(); + V8_INIT.call_once(|| { + v8_init(); + }); + f(); + // v8_shutdown(); +} diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs new file mode 100644 index 0000000000..e036fd6839 --- /dev/null +++ b/serde_v8/tests/de.rs @@ -0,0 +1,249 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use serde::Deserialize; + +use serde_v8::utils::{js_exec, v8_do}; +use serde_v8::Error; + +#[derive(Debug, Deserialize, PartialEq)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +#[derive(Debug, PartialEq, Deserialize)] +enum EnumUnit { + A, + B, + C, +} + +#[derive(Debug, PartialEq, Deserialize)] +enum EnumPayloads { + UInt(u64), + Int(i64), + Float(f64), + Point { x: i64, y: i64 }, + Tuple(bool, i64, ()), +} + +fn dedo( + code: &str, + f: impl FnOnce(&mut v8::HandleScope, v8::Local), +) { + v8_do(|| { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + let v = js_exec(scope, code); + + f(scope, v); + }) +} + +macro_rules! detest { + ($fn_name:ident, $t:ty, $src:expr, $rust:expr) => { + #[test] + fn $fn_name() { + #[allow(clippy::bool_assert_comparison)] + dedo($src, |scope, v| { + let rt = serde_v8::from_v8(scope, v); + assert!(rt.is_ok(), "from_v8(\"{}\"): {:?}", $src, rt.err()); + let t: $t = rt.unwrap(); + assert_eq!(t, $rust); + }); + } + }; +} + +macro_rules! defail { + ($fn_name:ident, $t:ty, $src:expr, $failcase:expr) => { + #[test] + fn $fn_name() { + #[allow(clippy::bool_assert_comparison)] + dedo($src, |scope, v| { + let rt: serde_v8::Result<$t> = serde_v8::from_v8(scope, v); + let rtstr = format!("{:?}", rt); + let failed_as_expected = $failcase(rt); + assert!( + failed_as_expected, + "expected failure on deserialize(\"{}\"), got: {}", + $src, rtstr + ); + }); + } + }; +} + +detest!(de_option_some, Option, "true", Some(true)); +detest!(de_option_null, Option, "null", None); +detest!(de_option_undefined, Option, "undefined", None); +detest!(de_unit_null, (), "null", ()); +detest!(de_unit_undefined, (), "undefined", ()); +detest!(de_bool, bool, "true", true); +detest!(de_u64, u64, "32", 32); +detest!(de_string, String, "'Hello'", "Hello".to_owned()); +detest!(de_vec_u64, Vec, "[1,2,3,4,5]", vec![1, 2, 3, 4, 5]); +detest!( + de_vec_str, + Vec, + "['hello', 'world']", + vec!["hello".to_owned(), "world".to_owned()] +); +detest!( + de_tuple, + (u64, bool, ()), + "[123, true, null]", + (123, true, ()) +); +detest!( + de_mathop, + MathOp, + "({a: 1, b: 3, c: 'ignored'})", + MathOp { + a: 1, + b: 3, + operator: None + } +); + +// Unit enums +detest!(de_enum_unit_a, EnumUnit, "'A'", EnumUnit::A); +detest!(de_enum_unit_b, EnumUnit, "'B'", EnumUnit::B); +detest!(de_enum_unit_c, EnumUnit, "'C'", EnumUnit::C); + +// Enums with payloads (tuples & struct) +detest!( + de_enum_payload_int, + EnumPayloads, + "({ Int: -123 })", + EnumPayloads::Int(-123) +); +detest!( + de_enum_payload_uint, + EnumPayloads, + "({ UInt: 123 })", + EnumPayloads::UInt(123) +); +detest!( + de_enum_payload_float, + EnumPayloads, + "({ Float: 1.23 })", + EnumPayloads::Float(1.23) +); +detest!( + de_enum_payload_point, + EnumPayloads, + "({ Point: { x: 1, y: 2 } })", + EnumPayloads::Point { x: 1, y: 2 } +); +detest!( + de_enum_payload_tuple, + EnumPayloads, + "({ Tuple: [true, 123, null ] })", + EnumPayloads::Tuple(true, 123, ()) +); + +#[test] +fn de_f64() { + dedo("12345.0", |scope, v| { + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + assert!((x - 12345.0).abs() < f64::EPSILON); + }); +} + +#[test] +fn de_map() { + use std::collections::HashMap; + + dedo("({a: 1, b: 2, c: 3})", |scope, v| { + let map: HashMap = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(map.get("a").cloned(), Some(1)); + assert_eq!(map.get("b").cloned(), Some(2)); + assert_eq!(map.get("c").cloned(), Some(3)); + assert_eq!(map.get("nada"), None); + }) +} + +//// +// JSON tests: serde_json::Value compatibility +//// + +detest!( + de_json_null, + serde_json::Value, + "null", + serde_json::Value::Null +); +detest!( + de_json_bool, + serde_json::Value, + "true", + serde_json::Value::Bool(true) +); +detest!( + de_json_int, + serde_json::Value, + "123", + serde_json::Value::Number(serde_json::Number::from(123)) +); +detest!( + de_json_float, + serde_json::Value, + "123.45", + serde_json::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) +); +detest!( + de_json_string, + serde_json::Value, + "'Hello'", + serde_json::Value::String("Hello".to_string()) +); +detest!( + de_json_vec_string, + serde_json::Value, + "['Hello', 'World']", + serde_json::Value::Array(vec![ + serde_json::Value::String("Hello".to_string()), + serde_json::Value::String("World".to_string()) + ]) +); +detest!( + de_json_tuple, + serde_json::Value, + "[true, 'World', 123.45, null]", + serde_json::Value::Array(vec![ + serde_json::Value::Bool(true), + serde_json::Value::String("World".to_string()), + serde_json::Value::Number(serde_json::Number::from_f64(123.45).unwrap()), + serde_json::Value::Null, + ]) +); +detest!( + de_json_object, + serde_json::Value, + "({a: 1, b: 'hello', c: true})", + serde_json::Value::Object( + vec![ + ( + "a".to_string(), + serde_json::Value::Number(serde_json::Number::from(1)), + ), + ( + "b".to_string(), + serde_json::Value::String("hello".to_string()), + ), + ("c".to_string(), serde_json::Value::Bool(true),), + ] + .drain(..) + .collect() + ) +); +detest!(de_bigint_u64, u64, "BigInt(2**59)", 1 << 59); +detest!(de_bigint_i64, i64, "BigInt(-(2**59))", -(1 << 59)); + +defail!(defail_struct, MathOp, "123", |e| e + == Err(Error::ExpectedObject)); diff --git a/serde_v8/tests/magic.rs b/serde_v8/tests/magic.rs new file mode 100644 index 0000000000..95ad3eb555 --- /dev/null +++ b/serde_v8/tests/magic.rs @@ -0,0 +1,180 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use serde::{Deserialize, Serialize}; + +use serde_v8::utils::{js_exec, v8_do}; +use serde_v8::Result; +use std::convert::TryFrom; + +#[derive(Deserialize)] +struct MagicOp<'s> { + #[allow(unused)] + pub a: u64, + #[allow(unused)] + pub b: u64, + pub c: serde_v8::Value<'s>, + #[allow(unused)] + pub operator: Option, +} + +#[derive(Serialize)] +struct MagicContainer<'s> { + pub magic: bool, + pub contains: serde_v8::Value<'s>, +} + +#[test] +fn magic_basic() { + v8_do(|| { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + // Decode + let v = js_exec(scope, "({a: 1, b: 3, c: 'abracadabra'})"); + let mop: MagicOp = serde_v8::from_v8(scope, v).unwrap(); + // Check string + let v8_value: v8::Local = mop.c.into(); + let vs = v8::Local::::try_from(v8_value).unwrap(); + let s = vs.to_rust_string_lossy(scope); + assert_eq!(s, "abracadabra"); + + // Encode + let container = MagicContainer { + magic: true, + contains: v.into(), + }; + let vc = serde_v8::to_v8(scope, container).unwrap(); + // JSON stringify & check + let json = v8::json::stringify(scope, vc).unwrap(); + let s2 = json.to_rust_string_lossy(scope); + assert_eq!( + s2, + r#"{"magic":true,"contains":{"a":1,"b":3,"c":"abracadabra"}}"# + ); + }) +} + +#[test] +fn magic_buffer() { + v8_do(|| { + // Init isolate + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + let global = context.global(scope); + + // Simple buffer + let v8_array = js_exec(scope, "new Uint8Array([1,2,3,4,5])"); + let zbuf: serde_v8::Buffer = serde_v8::from_v8(scope, v8_array).unwrap(); + assert_eq!(&*zbuf, &[1, 2, 3, 4, 5]); + + // Multi buffers + let v8_arrays = + js_exec(scope, "[new Uint8Array([1,2]), new Uint8Array([3,4,5])]"); + let (z1, z2): (serde_v8::Buffer, serde_v8::Buffer) = + serde_v8::from_v8(scope, v8_arrays).unwrap(); + assert_eq!(&*z1, &[1, 2]); + assert_eq!(&*z2, &[3, 4, 5]); + + // Wrapped in option, like our current op-ABI + let v8_array = js_exec(scope, "new Uint8Array([1,2,3,4,5])"); + let zbuf: Option = + serde_v8::from_v8(scope, v8_array).unwrap(); + assert_eq!(&*zbuf.unwrap(), &[1, 2, 3, 4, 5]); + + // Observe mutation in JS + let v8_array = js_exec(scope, "new Uint8Array([1,2,3,4,5])"); + let mut zbuf: serde_v8::Buffer = + serde_v8::from_v8(scope, v8_array).unwrap(); + let key = serde_v8::to_v8(scope, "t1").unwrap(); + global.set(scope, key, v8_array); + (&mut *zbuf)[2] = 42; + let eq = js_exec(scope, "t1[2] === 42"); + assert!(eq.is_true()); + + // Shared buffers + let v8_array = + js_exec(scope, "new Uint8Array(new SharedArrayBuffer([1,2,3,4,5]))"); + let zbuf: Result = serde_v8::from_v8(scope, v8_array); + assert!(zbuf.is_err()); + + // Serialization + let buf: Vec = vec![1, 2, 3, 99, 5]; + let zbuf: serde_v8::Buffer = buf.into(); + let v8_value = serde_v8::to_v8(scope, zbuf).unwrap(); + let key = serde_v8::to_v8(scope, "t2").unwrap(); + global.set(scope, key, v8_value); + let eq = js_exec(scope, "t2[3] === 99"); + assert!(eq.is_true()); + + // Composite Serialization + #[derive(serde::Serialize)] + struct Wrapper { + a: serde_v8::Buffer, + b: serde_v8::Buffer, + } + let buf1: Vec = vec![1, 2, 33, 4, 5]; + let buf2: Vec = vec![5, 4, 3, 2, 11]; + let wrapped = Wrapper { + a: buf1.into(), + b: buf2.into(), + }; + let v8_value = serde_v8::to_v8(scope, wrapped).unwrap(); + let key = serde_v8::to_v8(scope, "t3").unwrap(); + global.set(scope, key, v8_value); + let eq = js_exec(scope, "t3.a[2] === 33"); + assert!(eq.is_true()); + let eq = js_exec(scope, "t3.b[4] === 11"); + assert!(eq.is_true()); + }) +} + +#[test] +fn magic_byte_string() { + v8_do(|| { + // Init isolate + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + let global = context.global(scope); + + // JS string to ByteString + let v8_string = js_exec(scope, "'test \\0\\t\\n\\r\\x7F\\x80áþÆñ'"); + let rust_reflex: serde_v8::ByteString = + serde_v8::from_v8(scope, v8_string).unwrap(); + assert_eq!( + rust_reflex.as_ref(), + b"test \0\t\n\r\x7F\x80\xE1\xFE\xC6\xF1" + ); + + // Non-Latin-1 characters + let v8_string = js_exec(scope, "'日本語'"); + let rust_reflex: Result = + serde_v8::from_v8(scope, v8_string); + assert!(rust_reflex.is_err()); + + // Windows-1252 characters that aren't Latin-1 + let v8_string = js_exec(scope, "'œ'"); + let rust_reflex: Result = + serde_v8::from_v8(scope, v8_string); + assert!(rust_reflex.is_err()); + + // ByteString to JS string + let expected = "a\x00sf:~\x7Fá\u{009C}þ\u{008A}"; + let buf: Vec = b"a\x00sf:~\x7F\xE1\x9C\xFE\x8A".as_ref().into(); + let zbuf = serde_v8::ByteString(buf); + let v8_value = serde_v8::to_v8(scope, zbuf).unwrap(); + let key = serde_v8::to_v8(scope, "actual").unwrap(); + global.set(scope, key, v8_value); + let v8_value_expected = serde_v8::to_v8(scope, expected).unwrap(); + let key_expected = serde_v8::to_v8(scope, "expected").unwrap(); + global.set(scope, key_expected, v8_value_expected); + let eq = js_exec(scope, "actual === expected"); + assert!(eq.is_true()); + }) +} diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs new file mode 100644 index 0000000000..c3ada7a6e4 --- /dev/null +++ b/serde_v8/tests/ser.rs @@ -0,0 +1,193 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use rusty_v8 as v8; + +use serde::Serialize; +use serde_json::json; +use serde_v8::utils::{js_exec, v8_do}; + +#[derive(Debug, Serialize, PartialEq)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +// Utility JS code (obj equality, etc...) +const JS_UTILS: &str = r#" +// Shallow obj equality (don't use deep objs for now) +function objEqual(a, b) { + const ka = Object.keys(a); + const kb = Object.keys(b); + return ka.length === kb.length && ka.every(k => a[k] === b[k]); +} + +function arrEqual(a, b) { + return a.length === b.length && a.every((v, i) => v === b[i]); +} +"#; +const JS_POLLUTE: &str = r#" +Object.defineProperty(Array.prototype, "0", { + set: function (v) { + throw new Error("Polluted Array 0 set"); + }, +}); + +Object.defineProperty(Object.prototype, "a", { + set: (v) => { + throw new Error("Polluted Object 'a' set"); + } +}); +"#; + +fn sercheck(val: T, code: &str, pollute: bool) -> bool { + let mut equal = false; + + v8_do(|| { + // Setup isolate + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + // Load util functions + js_exec(scope, JS_UTILS); + if pollute { + js_exec(scope, JS_POLLUTE); + } + // TryCatch scope (to catch pollution exceptions) + let scope = &mut v8::TryCatch::new(scope); + + // Set value as "x" in global scope + let global = context.global(scope); + let v8_key = serde_v8::to_v8(scope, "x").unwrap(); + let v8_val = serde_v8::to_v8(scope, val).unwrap(); + global.set(scope, v8_key, v8_val); + + // Pollution check + if let Some(message) = scope.message() { + let msg = message.get(scope).to_rust_string_lossy(scope); + panic!("JS Exception: {}", msg); + } + + // Execute equality check in JS (e.g: x == ...) + let v = js_exec(scope, code); + // Cast to bool + equal = serde_v8::from_v8(scope, v).unwrap(); + }); + + equal +} + +macro_rules! sertest { + ($fn_name:ident, $rust:expr, $src:expr) => { + #[test] + fn $fn_name() { + assert!( + sercheck($rust, $src, false), + "Expected: {} where x={:?}", + $src, + $rust, + ); + } + }; +} + +macro_rules! sertest_polluted { + ($fn_name:ident, $rust:expr, $src:expr) => { + #[test] + fn $fn_name() { + assert!( + sercheck($rust, $src, true), + "Expected: {} where x={:?}", + $src, + $rust, + ); + } + }; +} + +sertest!(ser_option_some, Some(true), "x === true"); +sertest!(ser_option_null, None as Option, "x === null"); +sertest!(ser_unit_null, (), "x === null"); +sertest!(ser_bool, true, "x === true"); +sertest!(ser_u64, 32, "x === 32"); +sertest!(ser_f64, 12345.0, "x === 12345.0"); +sertest!(ser_string, "Hello".to_owned(), "x === 'Hello'"); +sertest!(ser_vec_u64, vec![1, 2, 3, 4, 5], "arrEqual(x, [1,2,3,4,5])"); +sertest!( + ser_vec_string, + vec!["hello".to_owned(), "world".to_owned(),], + "arrEqual(x, ['hello', 'world'])" +); +sertest!(ser_tuple, (123, true, ()), "arrEqual(x, [123, true, null])"); +sertest!( + ser_mathop, + MathOp { + a: 1, + b: 3, + operator: None + }, + "objEqual(x, {a: 1, b: 3, operator: null})" +); + +sertest!( + ser_map, + { + let map: std::collections::BTreeMap<&str, u32> = + vec![("a", 1), ("b", 2), ("c", 3)].drain(..).collect(); + map + }, + "objEqual(x, {a: 1, b: 2, c: 3})" +); + +//// +// JSON tests: json!() compatibility +//// +sertest!(ser_json_bool, json!(true), "x === true"); +sertest!(ser_json_null, json!(null), "x === null"); +sertest!(ser_json_int, json!(123), "x === 123"); +sertest!(ser_json_f64, json!(123.45), "x === 123.45"); +sertest!(ser_json_string, json!("Hello World"), "x === 'Hello World'"); +sertest!(ser_json_obj_empty, json!({}), "objEqual(x, {})"); +sertest!( + ser_json_obj, + json!({"a": 1, "b": 2, "c": true}), + "objEqual(x, {a: 1, b: 2, c: true})" +); +sertest!( + ser_json_vec_int, + json!([1, 2, 3, 4, 5]), + "arrEqual(x, [1,2,3,4,5])" +); +sertest!( + ser_json_vec_string, + json!(["Goodbye", "Dinosaurs 👋☄️"]), + "arrEqual(x, ['Goodbye', 'Dinosaurs 👋☄️'])" +); +sertest!( + ser_json_tuple, + json!([true, 42, "nabla"]), + "arrEqual(x, [true, 42, 'nabla'])" +); + +//// +// Pollution tests +//// + +sertest_polluted!( + ser_polluted_obj, + MathOp { + a: 1, + b: 2, + operator: None + }, + "objEqual(x, { a: 1, b: 2, operator: null })" +); + +sertest_polluted!( + ser_polluted_tuple, + (true, 123, false), + "arrEqual(x, [true, 123, false])" +); + +sertest_polluted!(ser_polluted_vec, vec![1, 2, 3], "arrEqual(x, [1, 2, 3])"); diff --git a/tools/release/helpers/deno_workspace.ts b/tools/release/helpers/deno_workspace.ts index 802063b62a..d15209adc3 100644 --- a/tools/release/helpers/deno_workspace.ts +++ b/tools/release/helpers/deno_workspace.ts @@ -43,12 +43,17 @@ export class DenoWorkspace { getDependencyCrates() { return [ this.getBenchUtilCrate(), + this.getSerdeV8Crate(), this.getCoreCrate(), ...this.getExtCrates(), this.getRuntimeCrate(), ]; } + getSerdeV8Crate() { + return this.getCrateByNameOrThrow("serde_v8"); + } + getCliCrate() { return this.getCrateByNameOrThrow("deno"); }