diff options
author | Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> | 2024-09-10 14:50:21 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-10 21:50:21 +0000 |
commit | be0ba6d84f190f4fc1b4517e62d9d8ad30c8cfb1 (patch) | |
tree | 7e088e66f70d69e7074ff20fb192e73a661facfa /ext/node/ops | |
parent | e522f4b65a3439030506733b104498f21422ede0 (diff) |
fix(ext/node): Rewrite `node:v8` serialize/deserialize (#25439)
Closes #20613.
Reimplements the serialization on top of the v8 APIs instead of
deno_core. Implements `v8.Serializer`, `v8.DefaultSerializer`,
`v8.Deserializer`, and `v8.DefaultSerializer`.
Diffstat (limited to 'ext/node/ops')
-rw-r--r-- | ext/node/ops/v8.rs | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/ext/node/ops/v8.rs b/ext/node/ops/v8.rs index ebcf6b080..8813d2e18 100644 --- a/ext/node/ops/v8.rs +++ b/ext/node/ops/v8.rs @@ -1,6 +1,15 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use deno_core::error::generic_error; +use deno_core::error::type_error; +use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; +use deno_core::FastString; +use deno_core::GarbageCollected; +use deno_core::ToJsBuffer; +use std::ptr::NonNull; +use v8::ValueDeserializerHelper; +use v8::ValueSerializerHelper; #[op2(fast)] pub fn op_v8_cached_data_version_tag() -> u32 { @@ -30,3 +39,355 @@ pub fn op_v8_get_heap_statistics( buffer[12] = stats.used_global_handles_size() as f64; buffer[13] = stats.external_memory() as f64; } + +pub struct Serializer<'a> { + inner: v8::ValueSerializer<'a>, +} + +pub struct SerializerDelegate { + obj: v8::Global<v8::Object>, +} + +impl<'a> v8::cppgc::GarbageCollected for Serializer<'a> { + fn trace(&self, _visitor: &v8::cppgc::Visitor) {} +} + +impl SerializerDelegate { + fn obj<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + ) -> v8::Local<'s, v8::Object> { + v8::Local::new(scope, &self.obj) + } +} + +impl v8::ValueSerializerImpl for SerializerDelegate { + fn get_shared_array_buffer_id<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + shared_array_buffer: v8::Local<'s, v8::SharedArrayBuffer>, + ) -> Option<u32> { + let obj = self.obj(scope); + let key = FastString::from_static("_getSharedArrayBufferId") + .v8_string(scope) + .into(); + if let Some(v) = obj.get(scope, key) { + if let Ok(fun) = v.try_cast::<v8::Function>() { + return fun + .call(scope, obj.into(), &[shared_array_buffer.into()]) + .and_then(|ret| ret.uint32_value(scope)); + } + } + None + } + fn has_custom_host_object(&self, _isolate: &mut v8::Isolate) -> bool { + false + } + fn throw_data_clone_error<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + message: v8::Local<'s, v8::String>, + ) { + let obj = self.obj(scope); + let key = FastString::from_static("_getDataCloneError") + .v8_string(scope) + .into(); + if let Some(v) = obj.get(scope, key) { + let fun = v + .try_cast::<v8::Function>() + .expect("_getDataCloneError should be a function"); + if let Some(error) = fun.call(scope, obj.into(), &[message.into()]) { + scope.throw_exception(error); + return; + } + } + let error = v8::Exception::type_error(scope, message); + scope.throw_exception(error); + } + + fn write_host_object<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + object: v8::Local<'s, v8::Object>, + _value_serializer: &dyn ValueSerializerHelper, + ) -> Option<bool> { + let obj = self.obj(scope); + let key = FastString::from_static("_writeHostObject") + .v8_string(scope) + .into(); + if let Some(v) = obj.get(scope, key) { + if let Ok(v) = v.try_cast::<v8::Function>() { + v.call(scope, obj.into(), &[object.into()])?; + return Some(true); + } + } + + None + } + + fn is_host_object<'s>( + &self, + _scope: &mut v8::HandleScope<'s>, + _object: v8::Local<'s, v8::Object>, + ) -> Option<bool> { + // should never be called because has_custom_host_object returns false + None + } +} + +#[op2] +#[cppgc] +pub fn op_v8_new_serializer( + scope: &mut v8::HandleScope, + obj: v8::Local<v8::Object>, +) -> Serializer<'static> { + let obj = v8::Global::new(scope, obj); + let inner = + v8::ValueSerializer::new(scope, Box::new(SerializerDelegate { obj })); + Serializer { inner } +} + +#[op2(fast)] +pub fn op_v8_set_treat_array_buffer_views_as_host_objects( + #[cppgc] ser: &Serializer, + value: bool, +) { + ser + .inner + .set_treat_array_buffer_views_as_host_objects(value); +} + +#[op2] +#[serde] +pub fn op_v8_release_buffer(#[cppgc] ser: &Serializer) -> ToJsBuffer { + ser.inner.release().into() +} + +#[op2(fast)] +pub fn op_v8_transfer_array_buffer( + #[cppgc] ser: &Serializer, + #[smi] id: u32, + array_buffer: v8::Local<v8::ArrayBuffer>, +) { + ser.inner.transfer_array_buffer(id, array_buffer); +} + +#[op2(fast)] +pub fn op_v8_write_double(#[cppgc] ser: &Serializer, double: f64) { + ser.inner.write_double(double); +} + +#[op2(fast)] +pub fn op_v8_write_header(#[cppgc] ser: &Serializer) { + ser.inner.write_header(); +} + +#[op2] +pub fn op_v8_write_raw_bytes( + #[cppgc] ser: &Serializer, + #[anybuffer] source: &[u8], +) { + ser.inner.write_raw_bytes(source); +} + +#[op2(fast)] +pub fn op_v8_write_uint32(#[cppgc] ser: &Serializer, num: u32) { + ser.inner.write_uint32(num); +} + +#[op2(fast)] +pub fn op_v8_write_uint64(#[cppgc] ser: &Serializer, hi: u32, lo: u32) { + let num = ((hi as u64) << 32) | (lo as u64); + ser.inner.write_uint64(num); +} + +#[op2(nofast, reentrant)] +pub fn op_v8_write_value( + scope: &mut v8::HandleScope, + #[cppgc] ser: &Serializer, + value: v8::Local<v8::Value>, +) -> Result<(), AnyError> { + let context = scope.get_current_context(); + ser.inner.write_value(context, value); + Ok(()) +} + +struct DeserBuffer { + ptr: Option<NonNull<u8>>, + // Hold onto backing store to keep the underlying buffer + // alive while we hold a reference to it. + _backing_store: v8::SharedRef<v8::BackingStore>, +} + +pub struct Deserializer<'a> { + buf: DeserBuffer, + inner: v8::ValueDeserializer<'a>, +} + +impl<'a> deno_core::GarbageCollected for Deserializer<'a> {} + +pub struct DeserializerDelegate { + obj: v8::Global<v8::Object>, +} + +impl GarbageCollected for DeserializerDelegate { + fn trace(&self, _visitor: &v8::cppgc::Visitor) {} +} + +impl v8::ValueDeserializerImpl for DeserializerDelegate { + fn read_host_object<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + _value_deserializer: &dyn v8::ValueDeserializerHelper, + ) -> Option<v8::Local<'s, v8::Object>> { + let obj = v8::Local::new(scope, &self.obj); + let key = FastString::from_static("_readHostObject") + .v8_string(scope) + .into(); + let scope = &mut v8::AllowJavascriptExecutionScope::new(scope); + if let Some(v) = obj.get(scope, key) { + if let Ok(v) = v.try_cast::<v8::Function>() { + let result = v.call(scope, obj.into(), &[])?; + match result.try_cast() { + Ok(res) => return Some(res), + Err(_) => { + let msg = + FastString::from_static("readHostObject must return an object") + .v8_string(scope); + let error = v8::Exception::type_error(scope, msg); + scope.throw_exception(error); + return None; + } + } + } + } + None + } +} + +#[op2] +#[cppgc] +pub fn op_v8_new_deserializer( + scope: &mut v8::HandleScope, + obj: v8::Local<v8::Object>, + buffer: v8::Local<v8::ArrayBufferView>, +) -> Result<Deserializer<'static>, AnyError> { + let offset = buffer.byte_offset(); + let len = buffer.byte_length(); + let backing_store = buffer.get_backing_store().ok_or_else(|| { + generic_error("deserialization buffer has no backing store") + })?; + let (buf_slice, buf_ptr) = if let Some(data) = backing_store.data() { + // SAFETY: the offset is valid for the underlying buffer because we're getting it directly from v8 + let data_ptr = unsafe { data.as_ptr().cast::<u8>().add(offset) }; + ( + // SAFETY: the len is valid, from v8, and the data_ptr is valid (as above) + unsafe { std::slice::from_raw_parts(data_ptr.cast_const().cast(), len) }, + Some(data.cast()), + ) + } else { + (&[] as &[u8], None::<NonNull<u8>>) + }; + let obj = v8::Global::new(scope, obj); + let inner = v8::ValueDeserializer::new( + scope, + Box::new(DeserializerDelegate { obj }), + buf_slice, + ); + Ok(Deserializer { + inner, + buf: DeserBuffer { + _backing_store: backing_store, + ptr: buf_ptr, + }, + }) +} + +#[op2(fast)] +pub fn op_v8_transfer_array_buffer_de( + #[cppgc] deser: &Deserializer, + #[smi] id: u32, + array_buffer: v8::Local<v8::ArrayBuffer>, +) { + // TODO(nathanwhit): also need binding for TransferSharedArrayBuffer, then call that if + // array_buffer is shared + deser.inner.transfer_array_buffer(id, array_buffer); +} + +#[op2(fast)] +pub fn op_v8_read_double( + #[cppgc] deser: &Deserializer, +) -> Result<f64, AnyError> { + let mut double = 0f64; + if !deser.inner.read_double(&mut double) { + return Err(type_error("ReadDouble() failed")); + } + Ok(double) +} + +#[op2(nofast)] +pub fn op_v8_read_header( + scope: &mut v8::HandleScope, + #[cppgc] deser: &Deserializer, +) -> bool { + let context = scope.get_current_context(); + let res = deser.inner.read_header(context); + res.unwrap_or_default() +} + +#[op2(fast)] +#[number] +pub fn op_v8_read_raw_bytes( + #[cppgc] deser: &Deserializer, + #[number] length: usize, +) -> usize { + let Some(buf_ptr) = deser.buf.ptr else { + return 0; + }; + if let Some(buf) = deser.inner.read_raw_bytes(length) { + let ptr = buf.as_ptr(); + (ptr as usize) - (buf_ptr.as_ptr() as usize) + } else { + 0 + } +} + +#[op2(fast)] +pub fn op_v8_read_uint32( + #[cppgc] deser: &Deserializer, +) -> Result<u32, AnyError> { + let mut value = 0; + if !deser.inner.read_uint32(&mut value) { + return Err(type_error("ReadUint32() failed")); + } + + Ok(value) +} + +#[op2] +#[serde] +pub fn op_v8_read_uint64( + #[cppgc] deser: &Deserializer, +) -> Result<(u32, u32), AnyError> { + let mut val = 0; + if !deser.inner.read_uint64(&mut val) { + return Err(type_error("ReadUint64() failed")); + } + + Ok(((val >> 32) as u32, val as u32)) +} + +#[op2(fast)] +pub fn op_v8_get_wire_format_version(#[cppgc] deser: &Deserializer) -> u32 { + deser.inner.get_wire_format_version() +} + +#[op2(reentrant)] +pub fn op_v8_read_value<'s>( + scope: &mut v8::HandleScope<'s>, + #[cppgc] deser: &Deserializer, +) -> v8::Local<'s, v8::Value> { + let context = scope.get_current_context(); + let val = deser.inner.read_value(context); + val.unwrap_or_else(|| v8::null(scope).into()) +} |