diff options
Diffstat (limited to 'serde_v8/magic')
-rw-r--r-- | serde_v8/magic/buffer.rs | 119 | ||||
-rw-r--r-- | serde_v8/magic/bytestring.rs | 122 | ||||
-rw-r--r-- | serde_v8/magic/mod.rs | 9 | ||||
-rw-r--r-- | serde_v8/magic/string_or_buffer.rs | 45 | ||||
-rw-r--r-- | serde_v8/magic/transl8.rs | 143 | ||||
-rw-r--r-- | serde_v8/magic/u16string.rs | 83 | ||||
-rw-r--r-- | serde_v8/magic/value.rs | 48 | ||||
-rw-r--r-- | serde_v8/magic/zero_copy_buf.rs | 136 |
8 files changed, 705 insertions, 0 deletions
diff --git a/serde_v8/magic/buffer.rs b/serde_v8/magic/buffer.rs new file mode 100644 index 000000000..484984ac5 --- /dev/null +++ b/serde_v8/magic/buffer.rs @@ -0,0 +1,119 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +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 +pub enum MagicBuffer { + FromV8(ZeroCopyBuf), + ToV8(Mutex<Option<Box<[u8]>>>), +} +impl_magic!(MagicBuffer); + +impl MagicBuffer { + 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<Box<[u8]>> for MagicBuffer { + fn from(buf: Box<[u8]>) -> Self { + MagicBuffer::ToV8(Mutex::new(Some(buf))) + } +} + +impl From<Vec<u8>> for MagicBuffer { + fn from(vec: Vec<u8>) -> Self { + vec.into_boxed_slice().into() + } +} + +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"), + }; + + 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(), + ) + } +} + +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/magic/bytestring.rs b/serde_v8/magic/bytestring.rs new file mode 100644 index 000000000..43ac54b97 --- /dev/null +++ b/serde_v8/magic/bytestring.rs @@ -0,0 +1,122 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::ops::{Deref, DerefMut}; + +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 { + 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<u8> { + 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 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 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); + } + 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/magic/mod.rs b/serde_v8/magic/mod.rs new file mode 100644 index 000000000..bc86c6a7c --- /dev/null +++ b/serde_v8/magic/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +pub mod buffer; +pub mod bytestring; +pub mod string_or_buffer; +pub mod transl8; +pub mod u16string; +mod value; +pub mod zero_copy_buf; +pub use value::Value; diff --git a/serde_v8/magic/string_or_buffer.rs b/serde_v8/magic/string_or_buffer.rs new file mode 100644 index 000000000..edde2adcd --- /dev/null +++ b/serde_v8/magic/string_or_buffer.rs @@ -0,0 +1,45 @@ +use std::ops::Deref; + +#[derive(Debug)] +pub struct StringOrBuffer(Vec<u8>); + +impl Deref for StringOrBuffer { + type Target = Vec<u8>; + fn deref(&self) -> &Vec<u8> { + &self.0 + } +} + +impl StringOrBuffer { + pub fn into_bytes(self) -> Vec<u8> { + self.0 + } +} + +impl<'de> serde::Deserialize<'de> for StringOrBuffer { + fn deserialize<D>(deserializer: D) -> Result<StringOrBuffer, D::Error> + where + D: serde::Deserializer<'de>, + { + StringOrBufferInner::deserialize(deserializer) + .map(|x| StringOrBuffer(x.into_bytes())) + } +} + +// TODO(@AaronO): explore if we can make this work with ZeroCopyBuf +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum StringOrBufferInner { + #[serde(with = "serde_bytes")] + Buffer(Vec<u8>), + String(String), +} + +impl StringOrBufferInner { + fn into_bytes(self) -> Vec<u8> { + match self { + Self::String(s) => s.into_bytes(), + Self::Buffer(b) => b, + } + } +} diff --git a/serde_v8/magic/transl8.rs b/serde_v8/magic/transl8.rs new file mode 100644 index 000000000..458b82129 --- /dev/null +++ b/serde_v8/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/magic/u16string.rs b/serde_v8/magic/u16string.rs new file mode 100644 index 000000000..39ebf88d9 --- /dev/null +++ b/serde_v8/magic/u16string.rs @@ -0,0 +1,83 @@ +use crate::magic::transl8::impl_magic; +use crate::Error; +use std::ops::{Deref, DerefMut}; + +use super::transl8::{FromV8, ToV8}; + +#[derive(Default, PartialEq, Eq, Debug)] +pub struct U16String(pub Vec<u16>); +impl_magic!(U16String); + +impl U16String { + pub fn with_zeroes(length: usize) -> U16String { + U16String(vec![0u16; length]) + } + + pub fn truncate(&mut self, new_length: usize) { + self.0.truncate(new_length); + self.0.shrink_to_fit() + } +} + +impl Deref for U16String { + type Target = [u16]; + fn deref(&self) -> &[u16] { + self.0.deref() + } +} + +impl DerefMut for U16String { + fn deref_mut(&mut self) -> &mut [u16] { + self.0.deref_mut() + } +} + +impl AsRef<[u16]> for U16String { + fn as_ref(&self) -> &[u16] { + self.0.as_ref() + } +} + +impl AsMut<[u16]> for U16String { + fn as_mut(&mut self) -> &mut [u16] { + self.0.as_mut() + } +} + +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()) + } +} + +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/magic/value.rs b/serde_v8/magic/value.rs new file mode 100644 index 000000000..5d844fbda --- /dev/null +++ b/serde_v8/magic/value.rs @@ -0,0 +1,48 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +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 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 { + Self { v8_value } + } +} + +impl<'s> From<Value<'s>> for v8::Local<'s, v8::Value> { + fn from(v: Value<'s>) -> Self { + v.v8_value + } +} + +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 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/magic/zero_copy_buf.rs b/serde_v8/magic/zero_copy_buf.rs new file mode 100644 index 000000000..c63d4ba66 --- /dev/null +++ b/serde_v8/magic/zero_copy_buf.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +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 +/// 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<v8::BackingStore>, + byte_offset: usize, + byte_length: usize, +} + +unsafe impl Send for ZeroCopyBuf {} + +impl ZeroCopyBuf { + pub fn from_buffer( + buffer: v8::Local<v8::ArrayBuffer>, + byte_offset: usize, + byte_length: usize, + ) -> Result<Self, v8::DataError> { + let backing_store = buffer.get_backing_store(); + match backing_store.is_shared() { + true => Err(v8::DataError::BadType { + actual: "shared ArrayBufferView", + expected: "non-shared ArrayBufferView", + }), + false => Ok(Self { + backing_store, + byte_offset, + 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()) + } +} + +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) + } +} + +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<v8::BackingStore>, + byte_offset: usize, + byte_length: usize, +) -> &[u8] { + let cells: *const [Cell<u8>] = + &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<v8::BackingStore>, + byte_offset: usize, + byte_length: usize, +) -> &mut [u8] { + let cells: *const [Cell<u8>] = + &backing_store[byte_offset..byte_offset + byte_length]; + let bytes = cells as *const _ as *mut [u8]; + &mut *bytes +} |