From dbc0509f2af327509cd7b799d9bfc88713eec960 Mon Sep 17 00:00:00 2001 From: Inteon <42113979+inteon@users.noreply.github.com> Date: Tue, 6 Oct 2020 18:39:38 +0200 Subject: [PATCH] add basic serializer bindings (#442) --- src/binding.cc | 242 ++++++++++++++++++- src/lib.rs | 8 + src/support.h | 10 + src/support.rs | 12 +- src/value_deserializer.rs | 422 ++++++++++++++++++++++++++++++++++ src/value_serializer.rs | 473 ++++++++++++++++++++++++++++++++++++++ tests/test_api.rs | 331 ++++++++++++++++++++++++++ 7 files changed, 1496 insertions(+), 2 deletions(-) create mode 100644 src/value_deserializer.rs create mode 100644 src/value_serializer.rs diff --git a/src/binding.cc b/src/binding.cc index cd78429c..6dab2f38 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -2025,5 +2025,245 @@ V(number_of_detached_contexts) V(does_zap_garbage) // Returns size_t, not bool like you'd expect. #undef V - +} // extern "C" + +// v8::ValueSerializer::Delegate + +extern "C" { +void v8__ValueSerializer__Delegate__ThrowDataCloneError( + v8::ValueSerializer::Delegate* self, + v8::Local message); + +MaybeBool v8__ValueSerializer__Delegate__WriteHostObject( + v8::ValueSerializer::Delegate* self, v8::Isolate* isolate, + v8::Local object); + +bool v8__ValueSerializer__Delegate__GetSharedArrayBufferId( + v8::ValueSerializer::Delegate* self, v8::Isolate* isolate, + v8::Local shared_array_buffer, uint32_t* result); + +bool v8__ValueSerializer__Delegate__GetWasmModuleTransferId( + v8::ValueSerializer::Delegate* self, v8::Isolate* isolate, + v8::Local module, uint32_t* result); + +void* v8__ValueSerializer__Delegate__ReallocateBufferMemory( + v8::ValueSerializer::Delegate* self, void* old_buffer, size_t size, + size_t* actual_size); + +void v8__ValueSerializer__Delegate__FreeBufferMemory( + v8::ValueSerializer::Delegate* self, void* buffer); +} + +struct v8__ValueSerializer__Delegate : public v8::ValueSerializer::Delegate { + void ThrowDataCloneError(v8::Local message) override { + v8__ValueSerializer__Delegate__ThrowDataCloneError(this, message); + } + + v8::Maybe WriteHostObject(v8::Isolate* isolate, + v8::Local object) override { + return maybe_bool_to_maybe( + v8__ValueSerializer__Delegate__WriteHostObject(this, isolate, + object)); + } + + v8::Maybe GetSharedArrayBufferId( + v8::Isolate* isolate, + v8::Local shared_array_buffer) override { + uint32_t result = 0; + if (!v8__ValueSerializer__Delegate__GetSharedArrayBufferId( + this, isolate, shared_array_buffer, &result)) + return v8::Nothing(); + return v8::Just(result); + } + + v8::Maybe GetWasmModuleTransferId( + v8::Isolate* isolate, v8::Local module) override { + uint32_t result = 0; + if (!v8__ValueSerializer__Delegate__GetWasmModuleTransferId( + this, isolate, module, &result)) + return v8::Nothing(); + return v8::Just(result); + } + + void* ReallocateBufferMemory(void* old_buffer, size_t size, + size_t* actual_size) override { + return v8__ValueSerializer__Delegate__ReallocateBufferMemory( + this, old_buffer, size, actual_size); + } + + void FreeBufferMemory(void* buffer) override { + v8__ValueSerializer__Delegate__FreeBufferMemory(this, buffer); + } +}; + +extern "C" { +void v8__ValueSerializer__Delegate__CONSTRUCT( + uninit_t* buf) { + static_assert(sizeof(v8__ValueSerializer__Delegate) == sizeof(size_t), + "v8__ValueSerializer__Delegate size mismatch"); + construct_in_place(buf); +} +} + +// v8::ValueSerializer + +extern "C" { +void v8__ValueSerializer__CONSTRUCT(uninit_t* buf, + v8::Isolate* isolate, + v8::ValueSerializer::Delegate* delegate) { + static_assert(sizeof(v8::ValueSerializer) == sizeof(size_t), + "v8::ValueSerializer size mismatch"); + construct_in_place(buf, isolate, delegate); +} + +void v8__ValueSerializer__DESTRUCT(v8::ValueSerializer* self) { + self->~ValueSerializer(); +} + +void v8__ValueSerializer__Release(v8::ValueSerializer* self, + uint8_t** ptr, size_t* size) { + auto result = self->Release(); + *ptr = result.first; + *size = result.second; +} + +void v8__ValueSerializer__WriteHeader(v8::ValueSerializer* self) { + self->WriteHeader(); +} + +MaybeBool v8__ValueSerializer__WriteValue(v8::ValueSerializer* self, + v8::Local context, + v8::Local value) { + return maybe_to_maybe_bool(self->WriteValue(context, value)); +} + +void v8__ValueSerializer__TransferArrayBuffer( + v8::ValueSerializer* self, uint32_t transfer_id, + v8::Local array_buffer) { + self->TransferArrayBuffer(transfer_id, array_buffer); +} + +void v8__ValueSerializer__WriteUint32(v8::ValueSerializer* self, + uint32_t value) { + self->WriteUint32(value); +} + +void v8__ValueSerializer__WriteUint64(v8::ValueSerializer* self, + uint64_t value) { + self->WriteUint64(value); +} + +void v8__ValueSerializer__WriteDouble(v8::ValueSerializer* self, double value) { + self->WriteDouble(value); +} + +void v8__ValueSerializer__WriteRawBytes(v8::ValueSerializer* self, + const void* source, size_t length) { + self->WriteRawBytes(source, length); +} +} + +// v8::ValueDeserializer::Delegate + +extern "C" { +v8::Object* v8__ValueDeserializer__Delegate__ReadHostObject( + v8::ValueDeserializer::Delegate* self, v8::Isolate* isolate); + +v8::SharedArrayBuffer* +v8__ValueDeserializer__Delegate__GetSharedArrayBufferFromId( + v8::ValueDeserializer::Delegate* self, v8::Isolate* isolate, + uint32_t transfer_id); + +v8::WasmModuleObject* +v8__ValueDeserializer__Delegate__GetWasmModuleFromId( + v8::ValueDeserializer::Delegate* self, v8::Isolate* isolate, + uint32_t clone_id); +} + +struct v8__ValueDeserializer__Delegate : public v8::ValueDeserializer::Delegate { + v8::MaybeLocal ReadHostObject(v8::Isolate* isolate) override { + return ptr_to_maybe_local( + v8__ValueDeserializer__Delegate__ReadHostObject(this, isolate)); + } + + v8::MaybeLocal GetSharedArrayBufferFromId( + v8::Isolate* isolate, uint32_t transfer_id) override { + return ptr_to_maybe_local( + v8__ValueDeserializer__Delegate__GetSharedArrayBufferFromId( + this, isolate, transfer_id)); + } + + v8::MaybeLocal GetWasmModuleFromId( + v8::Isolate* isolate, uint32_t clone_id) override { + return ptr_to_maybe_local( + v8__ValueDeserializer__Delegate__GetWasmModuleFromId( + this, isolate, clone_id)); + } +}; + +extern "C" { +void v8__ValueDeserializer__Delegate__CONSTRUCT( + uninit_t* buf) { + static_assert(sizeof(v8__ValueDeserializer__Delegate) == sizeof(size_t), + "v8__ValueDeserializer__Delegate size mismatch"); + construct_in_place(buf); +} +} + +// v8::ValueDeserializer + +extern "C" { +void v8__ValueDeserializer__CONSTRUCT( + uninit_t* buf, v8::Isolate* isolate, + const uint8_t* data, size_t size, + v8::ValueDeserializer::Delegate* delegate) { + static_assert(sizeof(v8::ValueDeserializer) == sizeof(size_t), + "v8::ValueDeserializer size mismatch"); + construct_in_place(buf, isolate, data, size, delegate); +} + +void v8__ValueDeserializer__DESTRUCT(v8::ValueDeserializer* self) { + self->~ValueDeserializer(); +} + +MaybeBool v8__ValueDeserializer__ReadHeader(v8::ValueDeserializer* self, + v8::Local context) { + return maybe_to_maybe_bool(self->ReadHeader(context)); +} + +v8::Value* v8__ValueDeserializer__ReadValue(v8::ValueDeserializer* self, + v8::Local context) { + return maybe_local_to_ptr(self->ReadValue(context)); +} + +void v8__ValueDeserializer__TransferArrayBuffer( + v8::ValueDeserializer* self, uint32_t transfer_id, + v8::Local array_buffer) { + self->TransferArrayBuffer(transfer_id, array_buffer); +} + +void v8__ValueDeserializer__SetSupportsLegacyWireFormat( + v8::ValueDeserializer* self, bool supports_legacy_wire_format) { + self->SetSupportsLegacyWireFormat(supports_legacy_wire_format); +} + +bool v8__ValueDeserializer__ReadUint32(v8::ValueDeserializer* self, + uint32_t* value) { + return self->ReadUint32(value); +} + +bool v8__ValueDeserializer__ReadUint64(v8::ValueDeserializer* self, + uint64_t* value) { + return self->ReadUint64(value); +} + +bool v8__ValueDeserializer__ReadDouble(v8::ValueDeserializer* self, + double* value) { + return self->ReadDouble(value); +} + +bool v8__ValueDeserializer__ReadRawBytes(v8::ValueDeserializer* self, + size_t length, const void** data) { + return self->ReadRawBytes(length, data); +} } // extern "C" diff --git a/src/lib.rs b/src/lib.rs index 726f03fc..a30975ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,8 @@ mod symbol; mod template; mod uint8_array; mod value; +mod value_deserializer; +mod value_serializer; pub mod inspector; pub mod json; @@ -127,6 +129,12 @@ pub use support::UniquePtr; pub use support::UniqueRef; pub use symbol::*; pub use template::*; +pub use value_deserializer::ValueDeserializer; +pub use value_deserializer::ValueDeserializerHelper; +pub use value_deserializer::ValueDeserializerImpl; +pub use value_serializer::ValueSerializer; +pub use value_serializer::ValueSerializerHelper; +pub use value_serializer::ValueSerializerImpl; // TODO(piscisaureus): Ideally this trait would not be exported. pub use support::MapFnTo; diff --git a/src/support.h b/src/support.h index 884204ca..6bfdab32 100644 --- a/src/support.h +++ b/src/support.h @@ -111,6 +111,16 @@ inline static MaybeBool maybe_to_maybe_bool(v8::Maybe maybe) { } } +inline static v8::Maybe maybe_bool_to_maybe(MaybeBool maybe) { + switch(maybe) { + case MaybeBool::JustTrue: + case MaybeBool::JustFalse: + return v8::Just(maybe == MaybeBool::JustTrue); + default: + return v8::Nothing(); + } +} + template inline static T* local_to_ptr(v8::Local local) { return *local; diff --git a/src/support.rs b/src/support.rs index 19daa355..30a1eae3 100644 --- a/src/support.rs +++ b/src/support.rs @@ -451,7 +451,7 @@ impl Borrow for Allocation { #[repr(C)] #[derive(Debug, PartialEq)] -pub(crate) enum MaybeBool { +pub enum MaybeBool { JustFalse = 0, JustTrue = 1, Nothing = 2, @@ -467,6 +467,16 @@ impl Into> for MaybeBool { } } +impl From> for MaybeBool { + fn from(option: Option) -> Self { + match option { + Some(false) => MaybeBool::JustFalse, + Some(true) => MaybeBool::JustTrue, + None => MaybeBool::Nothing, + } + } +} + #[derive(Copy, Clone)] #[repr(transparent)] pub struct CxxVTable(pub *const Opaque); diff --git a/src/value_deserializer.rs b/src/value_deserializer.rs new file mode 100644 index 00000000..f0a71bb0 --- /dev/null +++ b/src/value_deserializer.rs @@ -0,0 +1,422 @@ +use crate::ArrayBuffer; +use crate::Context; +use crate::Exception; +use crate::HandleScope; +use crate::Isolate; +use crate::Local; +use crate::Object; +use crate::SharedArrayBuffer; +use crate::String; +use crate::Value; +use crate::WasmModuleObject; + +use crate::support::CxxVTable; +use crate::support::FieldOffset; +use crate::support::MaybeBool; + +use std::ffi::c_void; +use std::mem::MaybeUninit; +use std::pin::Pin; + +// Must be == sizeof(v8::ValueDeserializer::Delegate), +// see v8__ValueDeserializer__Delegate__CONSTRUCT(). +#[repr(C)] +pub struct CxxValueDeserializerDelegate { + _cxx_vtable: CxxVTable, +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueDeserializer__Delegate__ReadHostObject( + this: &mut CxxValueDeserializerDelegate, + _isolate: *mut Isolate, +) -> *const Object { + let value_deserializer_heap = ValueDeserializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_deserializer_heap.context); + let value_deserializer_impl = + value_deserializer_heap.value_deserializer_impl.as_mut(); + match value_deserializer_impl + .read_host_object(scope, &value_deserializer_heap.cxx_value_deserializer) + { + None => std::ptr::null(), + Some(x) => x.as_non_null().as_ptr(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueDeserializer__Delegate__GetSharedArrayBufferFromId( + this: &mut CxxValueDeserializerDelegate, + _isolate: *mut Isolate, + transfer_id: u32, +) -> *const SharedArrayBuffer { + let value_deserializer_heap = ValueDeserializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_deserializer_heap.context); + let value_deserializer_impl = + value_deserializer_heap.value_deserializer_impl.as_mut(); + match value_deserializer_impl + .get_shared_array_buffer_from_id(scope, transfer_id) + { + None => std::ptr::null(), + Some(x) => x.as_non_null().as_ptr(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueDeserializer__Delegate__GetWasmModuleFromId( + this: &mut CxxValueDeserializerDelegate, + _isolate: *mut Isolate, + clone_id: u32, +) -> *const WasmModuleObject { + let value_deserializer_heap = ValueDeserializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_deserializer_heap.context); + let value_deserializer_impl = + value_deserializer_heap.value_deserializer_impl.as_mut(); + match value_deserializer_impl.get_wasm_module_from_id(scope, clone_id) { + None => std::ptr::null(), + Some(x) => x.as_non_null().as_ptr(), + } +} + +extern "C" { + fn v8__ValueDeserializer__Delegate__CONSTRUCT( + buf: *mut MaybeUninit, + ); +} + +// Must be == sizeof(v8::ValueDeserializer), +// see v8__ValueDeserializer__CONSTRUCT(). +#[repr(C)] +pub struct CxxValueDeserializer { + _cxx_vtable: CxxVTable, +} + +extern "C" { + fn v8__ValueDeserializer__CONSTRUCT( + buf: *mut MaybeUninit, + isolate: *mut Isolate, + data: *const u8, + size: usize, + delegate: *mut CxxValueDeserializerDelegate, + ); + + fn v8__ValueDeserializer__DESTRUCT(this: *mut CxxValueDeserializer); + + fn v8__ValueDeserializer__TransferArrayBuffer( + this: *mut CxxValueDeserializer, + transfer_id: u32, + array_buffer: Local, + ); + + fn v8__ValueDeserializer__SetSupportsLegacyWireFormat( + this: *mut CxxValueDeserializer, + supports_legacy_wire_format: bool, + ); + + fn v8__ValueDeserializer__ReadHeader( + this: *mut CxxValueDeserializer, + context: Local, + ) -> MaybeBool; + + fn v8__ValueDeserializer__ReadValue( + this: *mut CxxValueDeserializer, + context: Local, + ) -> *const Value; + + fn v8__ValueDeserializer__ReadUint32( + this: *mut CxxValueDeserializer, + value: *mut u32, + ) -> bool; + + fn v8__ValueDeserializer__ReadUint64( + this: *mut CxxValueDeserializer, + value: *mut u64, + ) -> bool; + + fn v8__ValueDeserializer__ReadDouble( + this: *mut CxxValueDeserializer, + value: *mut f64, + ) -> bool; + + fn v8__ValueDeserializer__ReadRawBytes( + this: *mut CxxValueDeserializer, + length: usize, + data: *mut *const c_void, + ) -> bool; +} + +/// The ValueDeserializerImpl trait allows for +/// custom callback functions used by v8. +pub trait ValueDeserializerImpl { + #[allow(unused_variables)] + fn read_host_object<'s>( + &mut self, + scope: &mut HandleScope<'s>, + value_deserializer: &dyn ValueDeserializerHelper, + ) -> Option> { + let msg = + String::new(scope, "Deno deserializer: read_host_object not implemented") + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } + + #[allow(unused_variables)] + fn get_shared_array_buffer_from_id<'s>( + &mut self, + scope: &mut HandleScope<'s>, + transfer_id: u32, + ) -> Option> { + let msg = String::new( + scope, + "Deno deserializer: get_shared_array_buffer_from_id not implemented", + ) + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } + + #[allow(unused_variables)] + fn get_wasm_module_from_id<'s>( + &mut self, + scope: &mut HandleScope<'s>, + clone_id: u32, + ) -> Option> { + let msg = String::new( + scope, + "Deno deserializer: get_wasm_module_from_id not implemented", + ) + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } +} + +/// The ValueDeserializerHeap object contains all objects related to a +/// deserializer. This object has to be pinned to the heap because of the Cpp +/// pointers that have to remain valid. Moving this object would result in the +/// Cpp pointer to the delegate to become invalid and thus causing the delegate +/// callback to fail. Additionally the deserializer and implementation are also +/// pinned in memory because these have to be accessable from within the +/// delegate callback methods. +pub struct ValueDeserializerHeap<'a, 's> { + value_deserializer_impl: Box, + cxx_value_deserializer: CxxValueDeserializer, + cxx_value_deserializer_delegate: CxxValueDeserializerDelegate, + context: Local<'s, Context>, +} + +impl<'a, 's> ValueDeserializerHeap<'a, 's> { + fn get_cxx_value_deserializer_delegate_offset( + ) -> FieldOffset { + let buf = std::mem::MaybeUninit::::uninit(); + FieldOffset::from_ptrs(buf.as_ptr(), unsafe { + &(*buf.as_ptr()).cxx_value_deserializer_delegate + }) + } + + /// Starting from 'this' pointer a ValueDeserializerHeap ref can be created + pub unsafe fn dispatch( + value_serializer_delegate: &'s CxxValueDeserializerDelegate, + ) -> &Self { + Self::get_cxx_value_deserializer_delegate_offset() + .to_embedder::(value_serializer_delegate) + } + + /// Starting from 'this' pointer the ValueDeserializerHeap mut ref can be + /// created + pub unsafe fn dispatch_mut( + value_serializer_delegate: &'s mut CxxValueDeserializerDelegate, + ) -> &mut Self { + Self::get_cxx_value_deserializer_delegate_offset() + .to_embedder_mut::(value_serializer_delegate) + } +} + +impl<'a, 's> Drop for ValueDeserializerHeap<'a, 's> { + fn drop(&mut self) { + unsafe { + v8__ValueDeserializer__DESTRUCT(&mut self.cxx_value_deserializer) + }; + } +} + +/// Trait used for direct read from the deserialization buffer. +/// Mostly used by the read_host_object callback function in the +/// ValueDeserializerImpl trait to create custom deserialization logic. +pub trait ValueDeserializerHelper { + fn get_cxx_value_deserializer(&mut self) -> &mut CxxValueDeserializer; + + fn read_header(&mut self, context: Local) -> Option { + unsafe { + v8__ValueDeserializer__ReadHeader( + self.get_cxx_value_deserializer(), + context, + ) + } + .into() + } + + fn read_value(&mut self, context: Local) -> Option> { + unsafe { + Local::from_raw(v8__ValueDeserializer__ReadValue( + self.get_cxx_value_deserializer(), + context, + )) + } + } + + fn read_uint32(&mut self, value: &mut u32) -> bool { + unsafe { + v8__ValueDeserializer__ReadUint32( + self.get_cxx_value_deserializer(), + value, + ) + } + } + + fn read_uint64(&mut self, value: &mut u64) -> bool { + unsafe { + v8__ValueDeserializer__ReadUint64( + self.get_cxx_value_deserializer(), + value, + ) + } + } + + fn read_double(&mut self, value: &mut f64) -> bool { + unsafe { + v8__ValueDeserializer__ReadDouble( + self.get_cxx_value_deserializer(), + value, + ) + } + } + + fn read_raw_bytes(&mut self, length: usize) -> Option<&[u8]> { + let mut data: *const c_void = std::ptr::null_mut(); + let ok = unsafe { + v8__ValueDeserializer__ReadRawBytes( + self.get_cxx_value_deserializer(), + length, + &mut data, + ) + }; + if ok { + assert!(!data.is_null()); + unsafe { Some(std::slice::from_raw_parts(data as *const u8, length)) } + } else { + None + } + } + + fn transfer_array_buffer( + &mut self, + transfer_id: u32, + array_buffer: Local, + ) { + unsafe { + v8__ValueDeserializer__TransferArrayBuffer( + self.get_cxx_value_deserializer(), + transfer_id, + array_buffer, + ) + }; + } +} + +impl ValueDeserializerHelper for CxxValueDeserializer { + fn get_cxx_value_deserializer(&mut self) -> &mut CxxValueDeserializer { + self + } +} + +impl<'a, 's> ValueDeserializerHelper for ValueDeserializerHeap<'a, 's> { + fn get_cxx_value_deserializer(&mut self) -> &mut CxxValueDeserializer { + &mut self.cxx_value_deserializer + } +} + +impl<'a, 's> ValueDeserializerHelper for ValueDeserializer<'a, 's> { + fn get_cxx_value_deserializer(&mut self) -> &mut CxxValueDeserializer { + &mut (*self.value_deserializer_heap).cxx_value_deserializer + } +} + +/// ValueDeserializer is a stack object used as entry-point for an owned and +/// pinned heap object ValueDeserializerHeap. +/// The 'a lifetime is the lifetime of the ValueDeserializerImpl implementation. +/// The 's lifetime is the lifetime of the HandleScope which is used to retrieve +/// a Local<'s, Context> for the CallbackScopes +pub struct ValueDeserializer<'a, 's> { + value_deserializer_heap: Pin>>, +} + +impl<'a, 's> ValueDeserializer<'a, 's> { + pub fn new( + scope: &mut HandleScope<'s>, + value_deserializer_impl: Box, + data: &[u8], + ) -> Self { + // create dummy ValueDeserializerHeap and move to heap + pin to address + let mut value_deserializer_heap = Box::pin(ValueDeserializerHeap { + value_deserializer_impl, + cxx_value_deserializer: CxxValueDeserializer { + _cxx_vtable: CxxVTable { + 0: std::ptr::null(), + }, + }, + cxx_value_deserializer_delegate: CxxValueDeserializerDelegate { + _cxx_vtable: CxxVTable { + 0: std::ptr::null(), + }, + }, + context: scope.get_current_context(), + }); + + unsafe { + v8__ValueDeserializer__Delegate__CONSTRUCT(core::mem::transmute( + &mut (*value_deserializer_heap).cxx_value_deserializer_delegate, + )); + + v8__ValueDeserializer__CONSTRUCT( + core::mem::transmute( + &mut (*value_deserializer_heap).cxx_value_deserializer, + ), + scope.get_isolate_ptr(), + data.as_ptr(), + data.len(), + &mut (*value_deserializer_heap).cxx_value_deserializer_delegate, + ); + }; + + ValueDeserializer { + value_deserializer_heap, + } + } +} + +impl<'a, 's> ValueDeserializer<'a, 's> { + pub fn set_supports_legacy_wire_format( + &mut self, + supports_legacy_wire_format: bool, + ) { + unsafe { + v8__ValueDeserializer__SetSupportsLegacyWireFormat( + &mut (*self.value_deserializer_heap).cxx_value_deserializer, + supports_legacy_wire_format, + ); + } + } + + pub fn read_value( + &mut self, + context: Local, + ) -> Option> { + (*self.value_deserializer_heap).read_value(context) + } +} diff --git a/src/value_serializer.rs b/src/value_serializer.rs new file mode 100644 index 00000000..c98d95c5 --- /dev/null +++ b/src/value_serializer.rs @@ -0,0 +1,473 @@ +use crate::ArrayBuffer; +use crate::Context; +use crate::Exception; +use crate::HandleScope; +use crate::Isolate; +use crate::Local; +use crate::Object; +use crate::SharedArrayBuffer; +use crate::String; +use crate::Value; +use crate::WasmModuleObject; + +use std::alloc::alloc; +use std::alloc::dealloc; +use std::alloc::realloc; +use std::alloc::Layout; +use std::mem::MaybeUninit; + +use crate::support::CxxVTable; +use crate::support::FieldOffset; +use crate::support::MaybeBool; + +use std::ffi::c_void; +use std::pin::Pin; + +// Must be == sizeof(v8::ValueSerializer::Delegate), +// see v8__ValueSerializer__Delegate__CONSTRUCT(). +#[repr(C)] +pub struct CxxValueSerializerDelegate { + _cxx_vtable: CxxVTable, +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__ThrowDataCloneError( + this: &mut CxxValueSerializerDelegate, + message: Local, +) { + let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_serializer_heap.context); + value_serializer_heap + .value_serializer_impl + .as_mut() + .throw_data_clone_error(scope, message) +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__WriteHostObject( + this: &mut CxxValueSerializerDelegate, + _isolate: *mut Isolate, + object: Local, +) -> MaybeBool { + let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_serializer_heap.context); + let value_serializer_impl = + value_serializer_heap.value_serializer_impl.as_mut(); + MaybeBool::from(value_serializer_impl.write_host_object( + scope, + object, + &value_serializer_heap.cxx_value_serializer, + )) +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__GetSharedArrayBufferId( + this: &mut CxxValueSerializerDelegate, + _isolate: *mut Isolate, + shared_array_buffer: Local, + clone_id: *mut u32, +) -> bool { + let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_serializer_heap.context); + match value_serializer_heap + .value_serializer_impl + .as_mut() + .get_shared_array_buffer_id(scope, shared_array_buffer) + { + Some(x) => { + *clone_id = x; + true + } + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__GetWasmModuleTransferId( + this: &mut CxxValueSerializerDelegate, + _isolate: *mut Isolate, + module: Local, + transfer_id: *mut u32, +) -> bool { + let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this); + let scope = + &mut crate::scope::CallbackScope::new(value_serializer_heap.context); + match value_serializer_heap + .value_serializer_impl + .as_mut() + .get_wasm_module_transfer_id(scope, module) + { + Some(x) => { + *transfer_id = x; + true + } + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__ReallocateBufferMemory( + this: &mut CxxValueSerializerDelegate, + old_buffer: *mut c_void, + size: usize, + actual_size: *mut usize, +) -> *mut c_void { + let base = ValueSerializerHeap::dispatch_mut(this); + + let new_buffer = if old_buffer.is_null() { + let layout = Layout::from_size_align(size, 1).unwrap(); + alloc(layout) + } else { + let old_layout = Layout::from_size_align(base.buffer_size, 1).unwrap(); + realloc(old_buffer as *mut _, old_layout, size) + }; + + base.buffer_size = size; + + *actual_size = size; + new_buffer as *mut c_void +} + +#[no_mangle] +pub unsafe extern "C" fn v8__ValueSerializer__Delegate__FreeBufferMemory( + this: &mut CxxValueSerializerDelegate, + buffer: *mut c_void, +) { + let base = ValueSerializerHeap::dispatch_mut(this); + if !buffer.is_null() { + let layout = Layout::from_size_align(base.buffer_size, 1).unwrap(); + dealloc(buffer as *mut _, layout) + }; +} + +extern "C" { + fn v8__ValueSerializer__Delegate__CONSTRUCT( + buf: *mut MaybeUninit, + ); +} + +// Must be == sizeof(v8::ValueSerializer), see v8__ValueSerializer__CONSTRUCT(). +#[repr(C)] +pub struct CxxValueSerializer { + _cxx_vtable: CxxVTable, +} + +extern "C" { + fn v8__ValueSerializer__CONSTRUCT( + buf: *mut MaybeUninit, + isolate: *mut Isolate, + delegate: *mut CxxValueSerializerDelegate, + ); + + fn v8__ValueSerializer__DESTRUCT(this: *mut CxxValueSerializer); + + fn v8__ValueSerializer__Release( + this: *mut CxxValueSerializer, + ptr: *mut *mut u8, + size: *mut usize, + ); + + fn v8__ValueSerializer__TransferArrayBuffer( + this: *mut CxxValueSerializer, + transfer_id: u32, + array_buffer: Local, + ); + + fn v8__ValueSerializer__WriteHeader(this: *mut CxxValueSerializer); + fn v8__ValueSerializer__WriteValue( + this: *mut CxxValueSerializer, + context: Local, + value: Local, + ) -> MaybeBool; + fn v8__ValueSerializer__WriteUint32( + this: *mut CxxValueSerializer, + value: u32, + ); + fn v8__ValueSerializer__WriteUint64( + this: *mut CxxValueSerializer, + value: u64, + ); + fn v8__ValueSerializer__WriteDouble( + this: *mut CxxValueSerializer, + value: f64, + ); + fn v8__ValueSerializer__WriteRawBytes( + this: *mut CxxValueSerializer, + source: *const c_void, + length: usize, + ); +} + +/// The ValueSerializerImpl trait allows for +/// custom callback functions used by v8. +pub trait ValueSerializerImpl { + fn throw_data_clone_error<'s>( + &mut self, + scope: &mut HandleScope<'s>, + message: Local<'s, String>, + ); + + #[allow(unused_variables)] + fn write_host_object<'s>( + &mut self, + scope: &mut HandleScope<'s>, + object: Local<'s, Object>, + value_serializer: &dyn ValueSerializerHelper, + ) -> Option { + let msg = + String::new(scope, "Deno serializer: write_host_object not implemented") + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } + + #[allow(unused_variables)] + fn get_shared_array_buffer_id<'s>( + &mut self, + scope: &mut HandleScope<'s>, + shared_array_buffer: Local<'s, SharedArrayBuffer>, + ) -> Option { + let msg = String::new( + scope, + "Deno serializer: get_shared_array_buffer_id not implemented", + ) + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } + + #[allow(unused_variables)] + fn get_wasm_module_transfer_id( + &mut self, + scope: &mut HandleScope<'_>, + module: Local, + ) -> Option { + let msg = String::new( + scope, + "Deno serializer: get_wasm_module_transfer_id not implemented", + ) + .unwrap(); + let exc = Exception::error(scope, msg); + scope.throw_exception(exc); + None + } +} + +/// The ValueSerializerHeap object contains all objects related to serializer. +/// This object has to be pinned to the heap because of the Cpp pointers that +/// have to remain valid. Moving this object would result in the Cpp pointer +/// to the delegate to become invalid and thus causing the delegate callback +/// to fail. Additionally the serializer and implementation are also pinned +/// in memory because these have to be accessable from within the delegate +/// callback methods. +pub struct ValueSerializerHeap<'a, 's> { + value_serializer_impl: Box, + cxx_value_serializer_delegate: CxxValueSerializerDelegate, + cxx_value_serializer: CxxValueSerializer, + buffer_size: usize, + context: Local<'s, Context>, +} + +impl<'a, 's> ValueSerializerHeap<'a, 's> { + fn get_cxx_value_serializer_delegate_offset( + ) -> FieldOffset { + let buf = std::mem::MaybeUninit::::uninit(); + FieldOffset::from_ptrs(buf.as_ptr(), unsafe { + &(*buf.as_ptr()).cxx_value_serializer_delegate + }) + } + + /// Starting from 'this' pointer a ValueSerializerHeap ref can be created + pub unsafe fn dispatch( + value_serializer_delegate: &'s CxxValueSerializerDelegate, + ) -> &Self { + Self::get_cxx_value_serializer_delegate_offset() + .to_embedder::(value_serializer_delegate) + } + + /// Starting from 'this' pointer the ValueSerializerHeap mut ref can be + /// created + pub unsafe fn dispatch_mut( + value_serializer_delegate: &'s mut CxxValueSerializerDelegate, + ) -> &mut Self { + Self::get_cxx_value_serializer_delegate_offset() + .to_embedder_mut::(value_serializer_delegate) + } +} + +impl<'a, 's> Drop for ValueSerializerHeap<'a, 's> { + fn drop(&mut self) { + unsafe { v8__ValueSerializer__DESTRUCT(&mut self.cxx_value_serializer) }; + } +} + +/// Trait used for direct write to the serialization buffer. +/// Mostly used by the write_host_object callback function in the +/// ValueSerializerImpl trait to create custom serialization logic. +pub trait ValueSerializerHelper { + fn get_cxx_value_serializer(&mut self) -> &mut CxxValueSerializer; + + fn write_header(&mut self) { + unsafe { + v8__ValueSerializer__WriteHeader(self.get_cxx_value_serializer()) + }; + } + + fn write_value( + &mut self, + context: Local, + value: Local, + ) -> Option { + unsafe { + v8__ValueSerializer__WriteValue( + self.get_cxx_value_serializer(), + context, + value, + ) + } + .into() + } + + fn write_uint32(&mut self, value: u32) { + unsafe { + v8__ValueSerializer__WriteUint32(self.get_cxx_value_serializer(), value) + }; + } + + fn write_uint64(&mut self, value: u64) { + unsafe { + v8__ValueSerializer__WriteUint64(self.get_cxx_value_serializer(), value) + }; + } + + fn write_double(&mut self, value: f64) { + unsafe { + v8__ValueSerializer__WriteDouble(self.get_cxx_value_serializer(), value) + }; + } + + fn write_raw_bytes(&mut self, source: &[u8]) { + unsafe { + v8__ValueSerializer__WriteRawBytes( + self.get_cxx_value_serializer(), + source.as_ptr() as *const _, + source.len(), + ) + }; + } + + fn transfer_array_buffer( + &mut self, + transfer_id: u32, + array_buffer: Local, + ) { + unsafe { + v8__ValueSerializer__TransferArrayBuffer( + self.get_cxx_value_serializer(), + transfer_id, + array_buffer, + ) + }; + } +} + +impl ValueSerializerHelper for CxxValueSerializer { + fn get_cxx_value_serializer(&mut self) -> &mut CxxValueSerializer { + self + } +} + +impl<'a, 's> ValueSerializerHelper for ValueSerializerHeap<'a, 's> { + fn get_cxx_value_serializer(&mut self) -> &mut CxxValueSerializer { + &mut self.cxx_value_serializer + } +} + +impl<'a, 's> ValueSerializerHelper for ValueSerializer<'a, 's> { + fn get_cxx_value_serializer(&mut self) -> &mut CxxValueSerializer { + &mut (*self.value_serializer_heap).cxx_value_serializer + } +} + +pub struct ValueSerializer<'a, 's> { + value_serializer_heap: Pin>>, +} + +/// ValueSerializer is a stack object used as entry-point for an owned and +/// pinned heap object ValueSerializerHeap. +/// The 'a lifetime is the lifetime of the ValueSerializerImpl implementation. +/// The 's lifetime is the lifetime of the HandleScope which is used to retrieve +/// a Local<'s, Context> for the CallbackScopes +impl<'a, 's> ValueSerializer<'a, 's> { + pub fn new( + scope: &mut HandleScope<'s>, + value_serializer_impl: Box, + ) -> Self { + // create dummy ValueSerializerHeap 'a, and move to heap + pin to address + let mut value_serializer_heap = Box::pin(ValueSerializerHeap { + value_serializer_impl, + cxx_value_serializer: CxxValueSerializer { + _cxx_vtable: CxxVTable { + 0: std::ptr::null(), + }, + }, + cxx_value_serializer_delegate: CxxValueSerializerDelegate { + _cxx_vtable: CxxVTable { + 0: std::ptr::null(), + }, + }, + buffer_size: 0, + context: scope.get_current_context(), + }); + + unsafe { + v8__ValueSerializer__Delegate__CONSTRUCT(core::mem::transmute( + &mut (*value_serializer_heap).cxx_value_serializer_delegate, + )); + + v8__ValueSerializer__CONSTRUCT( + core::mem::transmute( + &mut (*value_serializer_heap).cxx_value_serializer, + ), + scope.get_isolate_ptr(), + &mut (*value_serializer_heap).cxx_value_serializer_delegate, + ); + }; + + Self { + value_serializer_heap, + } + } +} + +impl<'a, 's> ValueSerializer<'a, 's> { + pub fn release(mut self) -> Vec { + unsafe { + let mut size: usize = 0; + let mut ptr: *mut u8 = &mut 0; + v8__ValueSerializer__Release( + &mut (*self.value_serializer_heap).cxx_value_serializer, + &mut ptr, + &mut size, + ); + Vec::from_raw_parts( + ptr as *mut u8, + size, + (*self.value_serializer_heap).buffer_size, + ) + } + } + + pub fn write_value( + &mut self, + context: Local, + value: Local, + ) -> Option { + (*self.value_serializer_heap).write_value(context, value) + } +} diff --git a/tests/test_api.rs b/tests/test_api.rs index b2f61495..aeff2e14 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3744,3 +3744,334 @@ fn bigint() { vec.resize(20, 1337); assert_eq!(raw_b.to_words_array(&mut vec), (true, &mut [10, 10][..])); } + +// SerDes testing +type ArrayBuffers = Vec>; + +struct Custom1Value<'a> { + array_buffers: &'a mut ArrayBuffers, +} + +impl<'a> Custom1Value<'a> { + fn serializer<'s>( + scope: &mut v8::HandleScope<'s>, + array_buffers: &'a mut ArrayBuffers, + ) -> v8::ValueSerializer<'a, 's> { + v8::ValueSerializer::new(scope, Box::new(Self { array_buffers })) + } + + fn deserializer<'s>( + scope: &mut v8::HandleScope<'s>, + data: &[u8], + array_buffers: &'a mut ArrayBuffers, + ) -> v8::ValueDeserializer<'a, 's> { + v8::ValueDeserializer::new(scope, Box::new(Self { array_buffers }), data) + } +} + +impl<'a> v8::ValueSerializerImpl for Custom1Value<'a> { + #[allow(unused_variables)] + fn throw_data_clone_error<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + message: v8::Local<'s, v8::String>, + ) { + let error = v8::Exception::error(scope, message); + scope.throw_exception(error); + } + + #[allow(unused_variables)] + fn get_shared_array_buffer_id<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + shared_array_buffer: v8::Local<'s, v8::SharedArrayBuffer>, + ) -> Option { + self + .array_buffers + .push(v8::SharedArrayBuffer::get_backing_store( + &shared_array_buffer, + )); + Some((self.array_buffers.len() as u32) - 1) + } +} + +impl<'a> v8::ValueDeserializerImpl for Custom1Value<'a> { + #[allow(unused_variables)] + fn get_shared_array_buffer_from_id<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + transfer_id: u32, + ) -> Option> { + let backing_store = self.array_buffers.get(transfer_id as usize).unwrap(); + Some(v8::SharedArrayBuffer::with_backing_store( + scope, + backing_store, + )) + } +} + +#[test] +fn value_serializer_and_deserializer() { + use v8::ValueDeserializerHelper; + use v8::ValueSerializerHelper; + + let _setup_guard = setup(); + let mut array_buffers = ArrayBuffers::new(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + let buffer; + { + let mut value_serializer = + Custom1Value::serializer(scope, &mut array_buffers); + value_serializer.write_header(); + value_serializer.write_double(55.44); + value_serializer.write_uint32(22); + buffer = value_serializer.release(); + } + + let mut double: f64 = 0.0; + let mut int32: u32 = 0; + { + let mut value_deserializer = + Custom1Value::deserializer(scope, &buffer, &mut array_buffers); + assert_eq!(value_deserializer.read_header(context), Some(true)); + assert_eq!(value_deserializer.read_double(&mut double), true); + assert_eq!(value_deserializer.read_uint32(&mut int32), true); + + assert_eq!(value_deserializer.read_uint32(&mut int32), false); + } + + assert_eq!((double - 55.44).abs() < f64::EPSILON, true); + assert_eq!(int32, 22); +} + +#[test] +fn value_serializer_and_deserializer_js_objects() { + let buffer; + let mut array_buffers = ArrayBuffers::new(); + { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let objects: v8::Local = eval( + scope, + r#"[ + undefined, + true, + false, + null, + 33, + 44.444, + 99999.55434344, + "test", + [1, 2, 3], + {a: "tt", add: "tsqqqss"} + ]"#, + ) + .unwrap(); + let mut value_serializer = + Custom1Value::serializer(scope, &mut array_buffers); + assert_eq!(value_serializer.write_value(context, objects), Some(true)); + + buffer = value_serializer.release(); + } + + { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let mut value_deserializer = + Custom1Value::deserializer(scope, &buffer, &mut array_buffers); + let name = v8::String::new(scope, "objects").unwrap(); + let objects: v8::Local = + value_deserializer.read_value(context).unwrap(); + + context.global(scope).set(scope, name.into(), objects); + + let result: v8::Local = eval( + scope, + r#" + { + const compare = [ + undefined, + true, + false, + null, + 33, + 44.444, + 99999.55434344, + "test", + [1, 2, 3], + {a: "tt", add: "tsqqqss"} + ]; + let equal = true; + function obj_isEquivalent(a, b) { + if (a == null) return b == null; + let aProps = Object.getOwnPropertyNames(a); + let bProps = Object.getOwnPropertyNames(b); + if (aProps.length != bProps.length) return false; + for (let i = 0; i < aProps.length; i++) { + let propName = aProps[i]; + if (a[propName] !== b[propName]) return false; + } + return true; + } + function arr_isEquivalent(a, b) { + if (a.length != b.length) return false; + for (let i = 0; i < Math.max(a.length, b.length); i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + objects.forEach(function (item, index) { + let other = compare[index]; + if (Array.isArray(item)) { + equal = equal && arr_isEquivalent(item, other); + } else if (typeof item == 'object') { + equal = equal && obj_isEquivalent(item, other); + } else { + equal = equal && (item == objects[index]); + } + }); + equal.toString() + } + "#, + ) + .unwrap(); + + let expected = v8::String::new(scope, "true").unwrap(); + assert!(expected.strict_equals(result)); + } +} + +#[test] +fn value_serializer_and_deserializer_array_buffers() { + let buffer; + let mut array_buffers = ArrayBuffers::new(); + { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let objects: v8::Local = eval( + scope, + r#"{ + var sab = new SharedArrayBuffer(10); + var arr = new Int8Array(sab); + arr[3] = 4; + sab + }"#, + ) + .unwrap(); + let mut value_serializer = + Custom1Value::serializer(scope, &mut array_buffers); + assert_eq!(value_serializer.write_value(context, objects), Some(true)); + + buffer = value_serializer.release(); + } + + { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let mut value_deserializer = + Custom1Value::deserializer(scope, &buffer, &mut array_buffers); + let name = v8::String::new(scope, "objects").unwrap(); + let objects: v8::Local = + value_deserializer.read_value(context).unwrap(); + + context.global(scope).set(scope, name.into(), objects); + + let result: v8::Local = eval( + scope, + r#" + { + var arr = new Int8Array(objects); + arr.toString() + } + "#, + ) + .unwrap(); + + let expected = v8::String::new(scope, "0,0,0,4,0,0,0,0,0,0").unwrap(); + assert!(expected.strict_equals(result)); + } +} + +struct Custom2Value {} + +impl<'a> Custom2Value { + fn serializer<'s>( + scope: &mut v8::HandleScope<'s>, + ) -> v8::ValueSerializer<'a, 's> { + v8::ValueSerializer::new(scope, Box::new(Self {})) + } +} + +impl<'a> v8::ValueSerializerImpl for Custom2Value { + #[allow(unused_variables)] + fn throw_data_clone_error<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + message: v8::Local<'s, v8::String>, + ) { + let error = v8::Exception::error(scope, message); + scope.throw_exception(error); + } +} + +#[test] +fn value_serializer_not_implemented() { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + let scope = &mut v8::TryCatch::new(scope); + + let objects: v8::Local = eval( + scope, + r#"{ + var sab = new SharedArrayBuffer(10); + var arr = new Int8Array(sab); + arr[3] = 4; + sab + }"#, + ) + .unwrap(); + let mut value_serializer = Custom2Value::serializer(scope); + assert_eq!(value_serializer.write_value(context, objects), None); + + assert!(scope.exception().is_some()); + assert!(scope.stack_trace().is_some()); + assert!(scope.message().is_some()); + assert_eq!( + scope.message().unwrap().get(scope).to_rust_string_lossy(scope), + "Uncaught Error: Deno serializer: get_shared_array_buffer_id not implemented" + ); +}