diff options
-rw-r--r-- | .github/workflows/ci.yml | 6 | ||||
-rw-r--r-- | serde_v8/src/de.rs | 106 | ||||
-rw-r--r-- | serde_v8/src/magic/buffer.rs | 105 | ||||
-rw-r--r-- | serde_v8/src/magic/bytestring.rs | 79 | ||||
-rw-r--r-- | serde_v8/src/magic/field.rs | 144 | ||||
-rw-r--r-- | serde_v8/src/magic/mod.rs | 6 | ||||
-rw-r--r-- | serde_v8/src/magic/transl8.rs | 143 | ||||
-rw-r--r-- | serde_v8/src/magic/u16string.rs | 57 | ||||
-rw-r--r-- | serde_v8/src/magic/value.rs | 76 | ||||
-rw-r--r-- | serde_v8/src/magic/zero_copy_buf.rs | 43 | ||||
-rw-r--r-- | serde_v8/src/ser.rs | 229 | ||||
-rw-r--r-- | serde_v8/tests/de.rs | 15 |
12 files changed, 387 insertions, 622 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ba3785c5..2303ebb1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,7 +236,7 @@ jobs: ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db - key: 2-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} + key: 3-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} # In main branch, always creates fresh cache - name: Cache build output (main) @@ -252,7 +252,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: | - 2-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }} + 3-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }} # Restore cache from the latest 'main' branch build. - name: Cache build output (PR) @@ -268,7 +268,7 @@ jobs: !./target/*/*.tar.gz key: never_saved restore-keys: | - 2-cargo-target-${{ matrix.os }}-${{ matrix.profile }}- + 3-cargo-target-${{ matrix.os }}-${{ matrix.profile }}- # Don't save cache after building PRs or branches other than 'main'. - name: Skip save cache (PR) diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index 318894c22..002b741cf 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -4,9 +4,10 @@ use serde::Deserialize; use crate::error::{Error, Result}; use crate::keys::{v8_struct_key, KeyCache}; +use crate::magic::transl8::FromV8; +use crate::magic::transl8::{visit_magic, MagicType}; use crate::payload::ValueType; - -use crate::magic; +use crate::{magic, Buffer, ByteString, U16String}; pub struct Deserializer<'a, 'b, 's> { input: v8::Local<'a, v8::Value>, @@ -131,23 +132,8 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> ValueType::Object => self.deserialize_map(visitor), // Map to Vec<u8> when deserialized via deserialize_any // e.g: for untagged enums or StringOrBuffer - ValueType::ArrayBufferView => { - v8::Local::<v8::ArrayBufferView>::try_from(self.input) - .and_then(|view| { - magic::zero_copy_buf::ZeroCopyBuf::try_from(( - &mut *self.scope, - view, - )) - }) - .map_err(|_| Error::ExpectedBuffer) - .and_then(|zb| visitor.visit_byte_buf(Vec::from(&*zb))) - } - ValueType::ArrayBuffer => { - v8::Local::<v8::ArrayBuffer>::try_from(self.input) - .and_then(|buffer| { - magic::zero_copy_buf::ZeroCopyBuf::try_from(buffer) - }) - .map_err(|_| Error::ExpectedBuffer) + ValueType::ArrayBufferView | ValueType::ArrayBuffer => { + magic::zero_copy_buf::ZeroCopyBuf::from_v8(&mut *self.scope, self.input) .and_then(|zb| visitor.visit_byte_buf(Vec::from(&*zb))) } } @@ -338,71 +324,31 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> 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 = match self.input.is_array_buffer() { - // ArrayBuffer - true => v8::Local::<v8::ArrayBuffer>::try_from(self.input) - .and_then(magic::zero_copy_buf::ZeroCopyBuf::try_from), - // maybe ArrayBufferView - false => v8::Local::<v8::ArrayBufferView>::try_from(self.input) - .and_then(|view| { - magic::zero_copy_buf::ZeroCopyBuf::try_from(( - &mut *self.scope, - view, - )) - }), + match name { + Buffer::MAGIC_NAME => { + visit_magic(visitor, Buffer::from_v8(self.scope, self.input)?) } - .map_err(|_| Error::ExpectedBuffer)?; - let data: [u8; 32] = unsafe { std::mem::transmute(zero_copy_buf) }; - return visitor.visit_bytes(&data); - } - - // Magic ByteString - if name == magic::bytestring::NAME { - let v8str = v8::Local::<v8::String>::try_from(self.input) - .map_err(|_| Error::ExpectedString)?; - if !v8str.contains_only_onebyte() { - return Err(Error::ExpectedLatin1); + ByteString::MAGIC_NAME => { + visit_magic(visitor, ByteString::from_v8(self.scope, self.input)?) + } + U16String::MAGIC_NAME => { + visit_magic(visitor, U16String::from_v8(self.scope, self.input)?) } - let len = v8str.length(); - let mut buffer = Vec::with_capacity(len); - #[allow(clippy::uninit_vec)] - unsafe { - buffer.set_len(len); + magic::Value::MAGIC_NAME => { + visit_magic(visitor, magic::Value::from_v8(self.scope, self.input)?) + } + _ => { + // Regular struct + let obj = self.input.try_into().or(Err(Error::ExpectedObject))?; + visitor.visit_seq(StructAccess { + fields, + obj, + pos: 0, + scope: self.scope, + _cache: None, + }) } - let written = v8str.write_one_byte( - self.scope, - &mut buffer, - 0, - v8::WriteOptions::NO_NULL_TERMINATION, - ); - assert!(written == len); - return visitor.visit_byte_buf(buffer); } - - // Regular struct - let obj = v8::Local::<v8::Object>::try_from(self.input) - .map_err(|_| Error::ExpectedObject)?; - let struct_access = StructAccess { - fields, - obj, - pos: 0, - scope: self.scope, - _cache: None, - }; - - visitor.visit_seq(struct_access) } /// To be compatible with `serde-json`, we expect enums to be: diff --git a/serde_v8/src/magic/buffer.rs b/serde_v8/src/magic/buffer.rs index e6f85324e..484984ac5 100644 --- a/serde_v8/src/magic/buffer.rs +++ b/serde_v8/src/magic/buffer.rs @@ -1,12 +1,13 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -use std::convert::TryFrom; -use std::fmt; use std::ops::Deref; use std::ops::DerefMut; use std::sync::Mutex; +use super::transl8::FromV8; +use super::transl8::ToV8; use super::zero_copy_buf::ZeroCopyBuf; +use crate::magic::transl8::impl_magic; // An asymmetric wrapper around ZeroCopyBuf, // allowing us to use a single type for familiarity @@ -14,6 +15,7 @@ pub enum MagicBuffer { FromV8(ZeroCopyBuf), ToV8(Mutex<Option<Box<[u8]>>>), } +impl_magic!(MagicBuffer); impl MagicBuffer { pub fn empty() -> Self { @@ -21,27 +23,6 @@ impl MagicBuffer { } } -impl<'s> TryFrom<v8::Local<'s, v8::ArrayBuffer>> for MagicBuffer { - type Error = v8::DataError; - fn try_from(buffer: v8::Local<v8::ArrayBuffer>) -> Result<Self, Self::Error> { - Ok(Self::FromV8(ZeroCopyBuf::try_from(buffer)?)) - } -} - -// TODO(@AaronO): consider streamlining this as "ScopedValue" ? -type ScopedView<'a, 'b, 's> = ( - &'s mut v8::HandleScope<'a>, - v8::Local<'b, v8::ArrayBufferView>, -); -impl<'a, 'b, 's> TryFrom<ScopedView<'a, 'b, 's>> for MagicBuffer { - type Error = v8::DataError; - fn try_from( - scoped_view: ScopedView<'a, 'b, 's>, - ) -> Result<Self, Self::Error> { - Ok(Self::FromV8(ZeroCopyBuf::try_from(scoped_view)?)) - } -} - impl Clone for MagicBuffer { fn clone(&self) -> Self { match self { @@ -94,61 +75,45 @@ impl From<Vec<u8>> for MagicBuffer { } } -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - - let mut s = serializer.serialize_struct(BUF_NAME, 1)?; - let boxed: Box<[u8]> = match self { +impl ToV8 for MagicBuffer { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result<v8::Local<'a, v8::Value>, crate::Error> { + let buf: 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<D>(deserializer: D) -> Result<MagicBuffer, D::Error> - 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<E>(self, v: &[u8]) -> Result<Self::Value, E> - 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)) - } + if buf.is_empty() { + let ab = v8::ArrayBuffer::new(scope, 0); + return Ok( + v8::Uint8Array::new(scope, ab, 0, 0) + .expect("Failed to create Uint8Array") + .into(), + ); } + 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); + Ok( + v8::Uint8Array::new(scope, ab, 0, buf_len) + .expect("Failed to create Uint8Array") + .into(), + ) + } +} - static FIELDS: [&str; 0] = []; - let visitor = ValueVisitor {}; - deserializer.deserialize_struct(BUF_NAME, &FIELDS, visitor) +impl FromV8 for MagicBuffer { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error> { + Ok(Self::FromV8(ZeroCopyBuf::from_v8(scope, value)?)) } } diff --git a/serde_v8/src/magic/bytestring.rs b/serde_v8/src/magic/bytestring.rs index 942aec64a..43ac54b97 100644 --- a/serde_v8/src/magic/bytestring.rs +++ b/serde_v8/src/magic/bytestring.rs @@ -2,14 +2,13 @@ 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"; +use super::transl8::{FromV8, ToV8}; +use crate::magic::transl8::impl_magic; +use crate::Error; #[derive(PartialEq, Eq, Clone, Debug)] pub struct ByteString(pub Vec<u8>); +impl_magic!(ByteString); impl ByteString { pub fn new() -> ByteString { @@ -81,45 +80,43 @@ impl AsMut<[u8]> for ByteString { } } -impl Serialize for ByteString { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - 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 ToV8 for ByteString { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result<v8::Local<'a, v8::Value>, crate::Error> { + let v = + v8::String::new_from_one_byte(scope, self, v8::NewStringType::Normal) + .unwrap(); + Ok(v.into()) } } -impl<'de> Deserialize<'de> for ByteString { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - 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<E>(self, v: Vec<u8>) -> Result<Self::Value, E> - where - E: serde::de::Error, - { - Ok(ByteString(v)) - } +impl FromV8 for ByteString { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error> { + let v8str = v8::Local::<v8::String>::try_from(value) + .map_err(|_| Error::ExpectedString)?; + if !v8str.contains_only_onebyte() { + return Err(Error::ExpectedLatin1); } - - deserializer.deserialize_struct(NAME, &[], ValueVisitor {}) + let len = v8str.length(); + let mut buffer = Vec::with_capacity(len); + // SAFETY: we set length == capacity (see previous line), + // before immediately writing into that buffer and sanity check with an assert + #[allow(clippy::uninit_vec)] + unsafe { + buffer.set_len(len); + let written = v8str.write_one_byte( + scope, + &mut buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + assert!(written == len); + } + Ok(ByteString(buffer)) } } diff --git a/serde_v8/src/magic/field.rs b/serde_v8/src/magic/field.rs deleted file mode 100644 index a188ed0e5..000000000 --- a/serde_v8/src/magic/field.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018-2022 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<T>) -pub type TransmutedField = u64; -pub type FieldResult = Result<TransmutedField>; - -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<TransmutedField, Error>; - type SerializeTuple = Impossible<TransmutedField, Error>; - type SerializeTupleStruct = Impossible<TransmutedField, Error>; - type SerializeTupleVariant = Impossible<TransmutedField, Error>; - type SerializeMap = Impossible<TransmutedField, Error>; - type SerializeStruct = Impossible<TransmutedField, Error>; - type SerializeStructVariant = Impossible<TransmutedField, Error>; - - 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<T: ?Sized + Serialize>(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<T: ?Sized + Serialize>( - self, - _name: &'static str, - _value: &T, - ) -> FieldResult { - unreachable!(); - } - - fn serialize_newtype_variant<T: ?Sized + Serialize>( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T, - ) -> FieldResult { - unreachable!(); - } - fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> { - unreachable!(); - } - - fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> { - unreachable!(); - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result<Self::SerializeTupleStruct> { - unreachable!(); - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result<Self::SerializeTupleVariant> { - unreachable!(); - } - - fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { - unreachable!(); - } - - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result<Self::SerializeStruct> { - unreachable!(); - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result<Self::SerializeStructVariant> { - unreachable!(); - } -} diff --git a/serde_v8/src/magic/mod.rs b/serde_v8/src/magic/mod.rs index e90b5ab60..bc86c6a7c 100644 --- a/serde_v8/src/magic/mod.rs +++ b/serde_v8/src/magic/mod.rs @@ -1,11 +1,9 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. pub mod buffer; pub mod bytestring; -mod field; pub mod string_or_buffer; +pub mod transl8; pub mod u16string; mod value; pub mod zero_copy_buf; - -pub use field::FieldSerializer; -pub use value::{Value, FIELD, NAME}; +pub use value::Value; diff --git a/serde_v8/src/magic/transl8.rs b/serde_v8/src/magic/transl8.rs new file mode 100644 index 000000000..458b82129 --- /dev/null +++ b/serde_v8/src/magic/transl8.rs @@ -0,0 +1,143 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +//! Transerialization extends the set of serde-compatible types (for given de/serializers). +//! By "hackishly" transmuting references across serde boundaries as u64s. +//! Type-safety is enforced using special struct names for each "magic type". +//! Memory-safety relies on transerialized values being "pinned" during de/serialization. + +pub(crate) const MAGIC_FIELD: &str = "$__v8_magic_field"; + +pub(crate) trait MagicType { + const NAME: &'static str; + const MAGIC_NAME: &'static str; +} + +pub(crate) trait ToV8 { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result<v8::Local<'a, v8::Value>, crate::Error>; +} + +pub(crate) trait FromV8: Sized { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error>; +} + +pub(crate) fn magic_serialize<T, S>( + serializer: S, + x: &T, +) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, + T: MagicType, +{ + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct(T::MAGIC_NAME, 1)?; + let ptr = opaque_send(x); + s.serialize_field(MAGIC_FIELD, &ptr)?; + s.end() +} + +pub(crate) fn magic_deserialize<'de, T, D>( + deserializer: D, +) -> Result<T, D::Error> +where + D: serde::Deserializer<'de>, + T: MagicType, +{ + struct ValueVisitor<T> { + p1: std::marker::PhantomData<T>, + } + + impl<'de, T: MagicType> serde::de::Visitor<'de> for ValueVisitor<T> { + type Value = T; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("a ")?; + formatter.write_str(T::NAME) + } + + fn visit_u64<E>(self, ptr: u64) -> Result<Self::Value, E> + where + E: serde::de::Error, + { + // SAFETY: opaque ptr originates from visit_magic, which forgets ownership so we can take it + Ok(unsafe { opaque_take(ptr) }) + } + } + + deserializer.deserialize_struct( + T::MAGIC_NAME, + &[MAGIC_FIELD], + ValueVisitor::<T> { + p1: std::marker::PhantomData, + }, + ) +} + +pub(crate) fn visit_magic<'de, T, V, E>(visitor: V, x: T) -> Result<V::Value, E> +where + V: serde::de::Visitor<'de>, + E: serde::de::Error, +{ + let y = visitor.visit_u64::<E>(opaque_send(&x)); + std::mem::forget(x); + y +} + +/// Constructs an "opaque" ptr from a reference to transerialize +pub(crate) fn opaque_send<T: Sized>(x: &T) -> u64 { + (x as *const T) as u64 +} + +/// Copies an "opaque" ptr from a reference to an opaque ptr (transerialized) +/// NOTE: ptr-to-ptr, extra indirection +pub(crate) unsafe fn opaque_recv<T: ?Sized>(ptr: &T) -> u64 { + *(ptr as *const T as *const u64) +} + +/// Transmutes an "opaque" ptr back into a reference +pub(crate) unsafe fn opaque_deref<'a, T>(ptr: u64) -> &'a T { + std::mem::transmute(ptr) +} + +/// Transmutes & copies the value from the "opaque" ptr +/// NOTE: takes ownership & requires other end to forget its ownership +pub(crate) unsafe fn opaque_take<T>(ptr: u64) -> T { + std::mem::transmute_copy::<T, T>(std::mem::transmute(ptr)) +} + +macro_rules! impl_magic { + ($t:ty) => { + impl crate::magic::transl8::MagicType for $t { + const NAME: &'static str = stringify!($t); + const MAGIC_NAME: &'static str = concat!("$__v8_magic_", stringify!($t)); + } + + impl serde::Serialize for $t { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + crate::magic::transl8::magic_serialize(serializer, self) + } + } + + impl<'de> serde::Deserialize<'de> for $t { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + crate::magic::transl8::magic_deserialize(deserializer) + } + } + }; +} +pub(crate) use impl_magic; diff --git a/serde_v8/src/magic/u16string.rs b/serde_v8/src/magic/u16string.rs index a75af016b..39ebf88d9 100644 --- a/serde_v8/src/magic/u16string.rs +++ b/serde_v8/src/magic/u16string.rs @@ -1,13 +1,12 @@ +use crate::magic::transl8::impl_magic; +use crate::Error; use std::ops::{Deref, DerefMut}; -use serde::Serialize; +use super::transl8::{FromV8, ToV8}; -pub const NAME: &str = "$__v8_magic_u16string"; -pub const FIELD_PTR: &str = "$__v8_magic_u16string_ptr"; -pub const FIELD_LEN: &str = "$__v8_magic_u16string_len"; - -#[derive(Default, PartialEq, Eq)] +#[derive(Default, PartialEq, Eq, Debug)] pub struct U16String(pub Vec<u16>); +impl_magic!(U16String); impl U16String { pub fn with_zeroes(length: usize) -> U16String { @@ -45,18 +44,40 @@ impl AsMut<[u16]> for U16String { } } -impl Serialize for U16String { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - - let mut s = serializer.serialize_struct(NAME, 3)?; - s.serialize_field(FIELD_PTR, &(self.0.as_ptr() as usize))?; - s.serialize_field(FIELD_LEN, &self.0.len())?; - s.end() +impl ToV8 for U16String { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result<v8::Local<'a, v8::Value>, crate::Error> { + let v = + v8::String::new_from_two_byte(scope, self, v8::NewStringType::Normal) + .unwrap(); + Ok(v.into()) } } -// TODO: Deserialize +impl FromV8 for U16String { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error> { + let v8str = v8::Local::<v8::String>::try_from(value) + .map_err(|_| Error::ExpectedString)?; + let len = v8str.length(); + let mut buffer = Vec::with_capacity(len); + // SAFETY: we set length == capacity (see previous line), + // before immediately writing into that buffer and sanity check with an assert + #[allow(clippy::uninit_vec)] + unsafe { + buffer.set_len(len); + let written = v8str.write( + scope, + &mut buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + assert!(written == len); + } + Ok(U16String(buffer)) + } +} diff --git a/serde_v8/src/magic/value.rs b/serde_v8/src/magic/value.rs index 7bd9a4059..5d844fbda 100644 --- a/serde_v8/src/magic/value.rs +++ b/serde_v8/src/magic/value.rs @@ -1,21 +1,19 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -use std::fmt; -use std::marker::PhantomData; - -pub const FIELD: &str = "$__v8_magic_value"; -pub const NAME: &str = "$__v8_magic_Value"; +use crate::magic::transl8::impl_magic; +use crate::magic::transl8::FromV8; +use crate::magic::transl8::ToV8; +use std::mem::transmute; /// 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" +/// when de/serializing & allows mixing rust & v8 values in structs, tuples... +// +// SAFETY: caveat emptor, the rust-compiler can no longer link lifetimes to their +// original scope, you must take special care in ensuring your handles don't outlive their scope pub struct Value<'s> { pub v8_value: v8::Local<'s, v8::Value>, } +impl_magic!(Value<'_>); impl<'s> From<v8::Local<'s, v8::Value>> for Value<'s> { fn from(v8_value: v8::Local<'s, v8::Value>) -> Self { @@ -29,50 +27,22 @@ impl<'s> From<Value<'s>> for v8::Local<'s, v8::Value> { } } -impl serde::Serialize for Value<'_> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - 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 ToV8 for Value<'_> { + fn to_v8<'a>( + &self, + _scope: &mut v8::HandleScope<'a>, + ) -> Result<v8::Local<'a, v8::Value>, crate::Error> { + // SAFETY: not fully safe, since lifetimes are detached from original scope + Ok(unsafe { transmute(self.v8_value) }) } } -impl<'de, 's> serde::Deserialize<'de> for Value<'s> { - fn deserialize<D>(deserializer: D) -> Result<Value<'s>, 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<E>(self, v: u64) -> Result<Self::Value, E> - 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) +impl FromV8 for Value<'_> { + fn from_v8( + _scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error> { + // SAFETY: not fully safe, since lifetimes are detached from original scope + Ok(unsafe { transmute::<Value, Value>(value.into()) }) } } diff --git a/serde_v8/src/magic/zero_copy_buf.rs b/serde_v8/src/magic/zero_copy_buf.rs index 30acd8137..c63d4ba66 100644 --- a/serde_v8/src/magic/zero_copy_buf.rs +++ b/serde_v8/src/magic/zero_copy_buf.rs @@ -4,6 +4,8 @@ use std::cell::Cell; use std::ops::Deref; use std::ops::DerefMut; +use super::transl8::FromV8; + /// 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 @@ -43,28 +45,33 @@ impl ZeroCopyBuf { }), } } -} -impl<'s> TryFrom<v8::Local<'s, v8::ArrayBuffer>> for ZeroCopyBuf { - type Error = v8::DataError; - fn try_from(buffer: v8::Local<v8::ArrayBuffer>) -> Result<Self, Self::Error> { - Self::from_buffer(buffer, 0, buffer.byte_length()) + pub fn from_view( + scope: &mut v8::HandleScope, + view: v8::Local<v8::ArrayBufferView>, + ) -> Result<Self, v8::DataError> { + let buffer = view.buffer(scope).ok_or(v8::DataError::NoData { + expected: "view to have a buffer", + })?; + Self::from_buffer(buffer, view.byte_offset(), view.byte_length()) } } -// TODO(@AaronO): consider streamlining this as "ScopedValue" ? -type ScopedView<'a, 'b, 's> = ( - &'s mut v8::HandleScope<'a>, - v8::Local<'b, v8::ArrayBufferView>, -); -impl<'a, 'b, 's> TryFrom<ScopedView<'a, 'b, 's>> for ZeroCopyBuf { - type Error = v8::DataError; - fn try_from( - scoped_view: ScopedView<'a, 'b, 's>, - ) -> Result<Self, Self::Error> { - let (scope, view) = scoped_view; - let buffer = view.buffer(scope).unwrap(); - Self::from_buffer(buffer, view.byte_offset(), view.byte_length()) +impl FromV8 for ZeroCopyBuf { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local<v8::Value>, + ) -> Result<Self, crate::Error> { + if value.is_array_buffer() { + value + .try_into() + .and_then(|b| Self::from_buffer(b, 0, b.byte_length())) + } else { + value + .try_into() + .and_then(|view| Self::from_view(scope, view)) + } + .map_err(|_| crate::Error::ExpectedBuffer) } } diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs index ce7e8c707..5241aeaef 100644 --- a/serde_v8/src/ser.rs +++ b/serde_v8/src/ser.rs @@ -6,7 +6,9 @@ use std::cell::RefCell; use crate::error::{Error, Result}; use crate::keys::v8_struct_key; -use crate::magic; +use crate::magic::transl8::MAGIC_FIELD; +use crate::magic::transl8::{opaque_deref, opaque_recv, MagicType, ToV8}; +use crate::{magic, Buffer, ByteString, U16String}; type JsValue<'s> = v8::Local<'s, v8::Value>; type JsResult<'s> = Result<JsValue<'s>>; @@ -212,191 +214,55 @@ impl<'a, 'b, 'c> ser::SerializeStruct for ObjectSerializer<'a, 'b, 'c> { } } -pub struct MagicSerializer<'a> { - v8_value: Option<v8::Local<'a, v8::Value>>, -} - -impl<'a> ser::SerializeStruct for MagicSerializer<'a> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field<T: ?Sized + Serialize>( - &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<T: ?Sized + Serialize>( - &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> { +pub struct MagicalSerializer<'a, 'b, 'c, T> { scope: ScopePtr<'a, 'b, 'c>, - ptr: Option<std::ptr::NonNull<u8>>, - len: Option<usize>, + opaque: u64, + p1: std::marker::PhantomData<T>, } -impl<'a, 'b, 'c> MagicByteStringSerializer<'a, 'b, 'c> { - pub fn new(scope: ScopePtr<'a, 'b, 'c>) -> Self { +impl<'a, 'b, 'c, T> MagicalSerializer<'a, 'b, 'c, T> { + pub fn new(scope: ScopePtr<'a, 'b, 'c>) -> MagicalSerializer<'a, 'b, 'c, T> { Self { scope, - ptr: None, - len: None, + opaque: 0, + p1: std::marker::PhantomData::<T> {}, } } } -impl<'a, 'b, 'c> ser::SerializeStruct - for MagicByteStringSerializer<'a, 'b, 'c> +impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct + for MagicalSerializer<'a, 'b, 'c, T> { type Ok = JsValue<'a>; type Error = Error; - fn serialize_field<T: ?Sized + Serialize>( - &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()) - } -} - -pub struct MagicU16StringSerializer<'a, 'b, 'c> { - scope: ScopePtr<'a, 'b, 'c>, - ptr: Option<std::ptr::NonNull<u16>>, - len: Option<usize>, -} - -impl<'a, 'b, 'c> MagicU16StringSerializer<'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 MagicU16StringSerializer<'a, 'b, 'c> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field<T: ?Sized + Serialize>( + fn serialize_field<U: ?Sized + Serialize>( &mut self, key: &'static str, - value: &T, + value: &U, ) -> Result<()> { - // Get u64 chunk - let transmuted = value.serialize(magic::FieldSerializer {})?; - match key { - magic::u16string::FIELD_PTR => { - self.ptr = std::ptr::NonNull::new(transmuted as *mut u16) - } - magic::u16string::FIELD_LEN => self.len = Some(transmuted as usize), - _ => unreachable!(), - } + assert_eq!(key, MAGIC_FIELD); + let ptr: &U = value; + // SAFETY: MagicalSerializer only ever receives single field u64s, + // type-safety is ensured by MAGIC_NAME checks in `serialize_struct()` + self.opaque = unsafe { opaque_recv(ptr) }; Ok(()) } fn end(self) -> JsResult<'a> { - // SAFETY: This function is only called from U16String::serialize(), which - // guarantees the Vec is still alive. - let slice = unsafe { - std::slice::from_raw_parts(self.ptr.unwrap().as_ptr(), self.len.unwrap()) - }; + // SAFETY: transerialization assumptions imply `T` is still alive. + let x: &T = unsafe { opaque_deref(self.opaque) }; let scope = &mut *self.scope.borrow_mut(); - - let v8_value = - v8::String::new_from_two_byte(scope, slice, v8::NewStringType::Normal) - .unwrap(); - Ok(v8_value.into()) + x.to_v8(scope) } } // 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>), - MagicU16String(MagicU16StringSerializer<'a, 'b, 'c>), + Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>), + MagicBuffer(MagicalSerializer<'a, 'b, 'c, Buffer>), + MagicByteString(MagicalSerializer<'a, 'b, 'c, ByteString>), + MagicU16String(MagicalSerializer<'a, 'b, 'c, U16String>), Regular(ObjectSerializer<'a, 'b, 'c>), } @@ -650,23 +516,24 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { len: usize, ) -> Result<Self::SerializeStruct> { 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); + ByteString::MAGIC_NAME => { + let m = MagicalSerializer::<ByteString>::new(self.scope); Ok(StructSerializers::MagicByteString(m)) } - magic::u16string::NAME => { - let m = MagicU16StringSerializer::new(self.scope); + U16String::MAGIC_NAME => { + let m = MagicalSerializer::<U16String>::new(self.scope); Ok(StructSerializers::MagicU16String(m)) } + Buffer::MAGIC_NAME => { + let m = MagicalSerializer::<Buffer>::new(self.scope); + Ok(StructSerializers::MagicBuffer(m)) + } + magic::Value::MAGIC_NAME => { + let m = MagicalSerializer::<magic::Value<'a>>::new(self.scope); + Ok(StructSerializers::Magic(m)) + } _ => { + // Regular structs let o = ObjectSerializer::new(self.scope, len); Ok(StructSerializers::Regular(o)) } @@ -685,21 +552,3 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { 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/tests/de.rs b/serde_v8/tests/de.rs index 525089849..0c784aced 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -2,9 +2,9 @@ use serde::Deserialize; use serde_v8::utils::{js_exec, v8_do}; -use serde_v8::Buffer; use serde_v8::ByteString; use serde_v8::Error; +use serde_v8::{Buffer, U16String}; #[derive(Debug, Deserialize, PartialEq)] struct MathOp { @@ -316,3 +316,16 @@ detest!( detest!(de_bstr, ByteString, "'hello'", ByteString("hello".into())); defail!(defail_bstr, ByteString, "'👋bye'", |e| e == Err(Error::ExpectedLatin1)); + +detest!( + de_u16str, + U16String, + "'hello'", + U16String("hello".encode_utf16().collect()) +); +detest!( + de_u16str_non_latin1, + U16String, + "'👋bye'", + U16String("👋bye".encode_utf16().collect()) +); |