mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
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`.
This commit is contained in:
parent
e522f4b65a
commit
be0ba6d84f
5 changed files with 876 additions and 29 deletions
|
@ -286,6 +286,25 @@ deno_core::extension!(deno_node,
|
||||||
ops::winerror::op_node_sys_to_uv_error,
|
ops::winerror::op_node_sys_to_uv_error,
|
||||||
ops::v8::op_v8_cached_data_version_tag,
|
ops::v8::op_v8_cached_data_version_tag,
|
||||||
ops::v8::op_v8_get_heap_statistics,
|
ops::v8::op_v8_get_heap_statistics,
|
||||||
|
ops::v8::op_v8_get_wire_format_version,
|
||||||
|
ops::v8::op_v8_new_deserializer,
|
||||||
|
ops::v8::op_v8_new_serializer,
|
||||||
|
ops::v8::op_v8_read_double,
|
||||||
|
ops::v8::op_v8_read_header,
|
||||||
|
ops::v8::op_v8_read_raw_bytes,
|
||||||
|
ops::v8::op_v8_read_uint32,
|
||||||
|
ops::v8::op_v8_read_uint64,
|
||||||
|
ops::v8::op_v8_read_value,
|
||||||
|
ops::v8::op_v8_release_buffer,
|
||||||
|
ops::v8::op_v8_set_treat_array_buffer_views_as_host_objects,
|
||||||
|
ops::v8::op_v8_transfer_array_buffer,
|
||||||
|
ops::v8::op_v8_transfer_array_buffer_de,
|
||||||
|
ops::v8::op_v8_write_double,
|
||||||
|
ops::v8::op_v8_write_header,
|
||||||
|
ops::v8::op_v8_write_raw_bytes,
|
||||||
|
ops::v8::op_v8_write_uint32,
|
||||||
|
ops::v8::op_v8_write_uint64,
|
||||||
|
ops::v8::op_v8_write_value,
|
||||||
ops::vm::op_vm_create_script,
|
ops::vm::op_vm_create_script,
|
||||||
ops::vm::op_vm_create_context,
|
ops::vm::op_vm_create_context,
|
||||||
ops::vm::op_vm_script_run_in_context,
|
ops::vm::op_vm_script_run_in_context,
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// 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::op2;
|
||||||
use deno_core::v8;
|
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)]
|
#[op2(fast)]
|
||||||
pub fn op_v8_cached_data_version_tag() -> u32 {
|
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[12] = stats.used_global_handles_size() as f64;
|
||||||
buffer[13] = stats.external_memory() 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())
|
||||||
|
}
|
||||||
|
|
|
@ -6,15 +6,36 @@
|
||||||
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
||||||
// deno-lint-ignore-file prefer-primordials
|
// deno-lint-ignore-file prefer-primordials
|
||||||
|
|
||||||
import { core } from "ext:core/mod.js";
|
import { primordials } from "ext:core/mod.js";
|
||||||
|
const { ObjectPrototypeToString } = primordials;
|
||||||
import {
|
import {
|
||||||
op_v8_cached_data_version_tag,
|
op_v8_cached_data_version_tag,
|
||||||
op_v8_get_heap_statistics,
|
op_v8_get_heap_statistics,
|
||||||
|
op_v8_get_wire_format_version,
|
||||||
|
op_v8_new_deserializer,
|
||||||
|
op_v8_new_serializer,
|
||||||
|
op_v8_read_double,
|
||||||
|
op_v8_read_header,
|
||||||
|
op_v8_read_raw_bytes,
|
||||||
|
op_v8_read_uint32,
|
||||||
|
op_v8_read_uint64,
|
||||||
|
op_v8_read_value,
|
||||||
|
op_v8_release_buffer,
|
||||||
|
op_v8_set_treat_array_buffer_views_as_host_objects,
|
||||||
|
op_v8_transfer_array_buffer,
|
||||||
|
op_v8_transfer_array_buffer_de,
|
||||||
|
op_v8_write_double,
|
||||||
|
op_v8_write_header,
|
||||||
|
op_v8_write_raw_bytes,
|
||||||
|
op_v8_write_uint32,
|
||||||
|
op_v8_write_uint64,
|
||||||
|
op_v8_write_value,
|
||||||
} from "ext:core/ops";
|
} from "ext:core/ops";
|
||||||
|
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
|
|
||||||
import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts";
|
import { notImplemented } from "ext:deno_node/_utils.ts";
|
||||||
|
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
|
||||||
|
|
||||||
export function cachedDataVersionTag() {
|
export function cachedDataVersionTag() {
|
||||||
return op_v8_cached_data_version_tag();
|
return op_v8_cached_data_version_tag();
|
||||||
|
@ -71,65 +92,225 @@ export function takeCoverage() {
|
||||||
export function writeHeapSnapshot() {
|
export function writeHeapSnapshot() {
|
||||||
notImplemented("v8.writeHeapSnapshot");
|
notImplemented("v8.writeHeapSnapshot");
|
||||||
}
|
}
|
||||||
export function serialize(value) {
|
// deno-lint-ignore no-explicit-any
|
||||||
return Buffer.from(core.serialize(value));
|
export function serialize(value: any) {
|
||||||
|
const ser = new DefaultSerializer();
|
||||||
|
ser.writeHeader();
|
||||||
|
ser.writeValue(value);
|
||||||
|
return ser.releaseBuffer();
|
||||||
}
|
}
|
||||||
export function deserialize(data) {
|
export function deserialize(buffer: Buffer | ArrayBufferView | DataView) {
|
||||||
return core.deserialize(data);
|
if (!isArrayBufferView(buffer)) {
|
||||||
|
throw new TypeError(
|
||||||
|
"buffer must be a TypedArray or a DataView",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const der = new DefaultDeserializer(buffer);
|
||||||
|
der.readHeader();
|
||||||
|
return der.readValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kHandle = Symbol("kHandle");
|
||||||
|
|
||||||
export class Serializer {
|
export class Serializer {
|
||||||
|
[kHandle]: object;
|
||||||
constructor() {
|
constructor() {
|
||||||
warnNotImplemented("v8.Serializer.prototype.constructor");
|
this[kHandle] = op_v8_new_serializer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setTreatArrayBufferViewsAsHostObjects(value: boolean): void {
|
||||||
|
op_v8_set_treat_array_buffer_views_as_host_objects(this[kHandle], value);
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseBuffer(): Buffer {
|
releaseBuffer(): Buffer {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.releaseBuffer");
|
return Buffer.from(op_v8_release_buffer(this[kHandle]));
|
||||||
return Buffer.from("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void {
|
transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.transferArrayBuffer");
|
op_v8_transfer_array_buffer(this[kHandle], _id, _arrayBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDouble(_value: number): void {
|
writeDouble(value: number): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeDouble");
|
op_v8_write_double(this[kHandle], value);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHeader(): void {
|
writeHeader(): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeHeader");
|
op_v8_write_header(this[kHandle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeRawBytes(_value: ArrayBufferView): void {
|
writeRawBytes(source: ArrayBufferView): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeRawBytes");
|
if (!isArrayBufferView(source)) {
|
||||||
|
throw new TypeError(
|
||||||
|
"source must be a TypedArray or a DataView",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
op_v8_write_raw_bytes(this[kHandle], source);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeUint32(_value: number): void {
|
writeUint32(value: number): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeUint32");
|
op_v8_write_uint32(this[kHandle], value);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeUint64(_hi: number, _lo: number): void {
|
writeUint64(hi: number, lo: number): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeUint64");
|
op_v8_write_uint64(this[kHandle], hi, lo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
writeValue(_value: any): void {
|
writeValue(value: any): void {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.writeValue");
|
op_v8_write_value(this[kHandle], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDataCloneError = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Deserializer {
|
||||||
|
buffer: ArrayBufferView;
|
||||||
|
[kHandle]: object;
|
||||||
|
constructor(buffer: ArrayBufferView) {
|
||||||
|
if (!isArrayBufferView(buffer)) {
|
||||||
|
throw new TypeError(
|
||||||
|
"buffer must be a TypedArray or a DataView",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.buffer = buffer;
|
||||||
|
this[kHandle] = op_v8_new_deserializer(this, buffer);
|
||||||
|
}
|
||||||
|
readRawBytes(length: number): Buffer {
|
||||||
|
const offset = this._readRawBytes(length);
|
||||||
|
return Buffer.from(
|
||||||
|
this.buffer.buffer,
|
||||||
|
this.buffer.byteOffset + offset,
|
||||||
|
length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_readRawBytes(length: number): number {
|
||||||
|
return op_v8_read_raw_bytes(this[kHandle], length);
|
||||||
|
}
|
||||||
|
getWireFormatVersion(): number {
|
||||||
|
return op_v8_get_wire_format_version(this[kHandle]);
|
||||||
|
}
|
||||||
|
readDouble(): number {
|
||||||
|
return op_v8_read_double(this[kHandle]);
|
||||||
|
}
|
||||||
|
readHeader(): boolean {
|
||||||
|
return op_v8_read_header(this[kHandle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
readUint32(): number {
|
||||||
|
return op_v8_read_uint32(this[kHandle]);
|
||||||
|
}
|
||||||
|
readUint64(): [hi: number, lo: number] {
|
||||||
|
return op_v8_read_uint64(this[kHandle]);
|
||||||
|
}
|
||||||
|
readValue(): unknown {
|
||||||
|
return op_v8_read_value(this[kHandle]);
|
||||||
|
}
|
||||||
|
transferArrayBuffer(
|
||||||
|
id: number,
|
||||||
|
arrayBuffer: ArrayBuffer | SharedArrayBuffer,
|
||||||
|
): void {
|
||||||
|
return op_v8_transfer_array_buffer_de(this[kHandle], id, arrayBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class Deserializer {
|
function arrayBufferViewTypeToIndex(abView: ArrayBufferView) {
|
||||||
constructor() {
|
const type = ObjectPrototypeToString(abView);
|
||||||
notImplemented("v8.Deserializer.prototype.constructor");
|
if (type === "[object Int8Array]") return 0;
|
||||||
}
|
if (type === "[object Uint8Array]") return 1;
|
||||||
|
if (type === "[object Uint8ClampedArray]") return 2;
|
||||||
|
if (type === "[object Int16Array]") return 3;
|
||||||
|
if (type === "[object Uint16Array]") return 4;
|
||||||
|
if (type === "[object Int32Array]") return 5;
|
||||||
|
if (type === "[object Uint32Array]") return 6;
|
||||||
|
if (type === "[object Float32Array]") return 7;
|
||||||
|
if (type === "[object Float64Array]") return 8;
|
||||||
|
if (type === "[object DataView]") return 9;
|
||||||
|
// Index 10 is FastBuffer.
|
||||||
|
if (type === "[object BigInt64Array]") return 11;
|
||||||
|
if (type === "[object BigUint64Array]") return 12;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
export class DefaultSerializer extends Serializer {
|
export class DefaultSerializer extends Serializer {
|
||||||
constructor() {
|
constructor() {
|
||||||
warnNotImplemented("v8.DefaultSerializer.prototype.constructor");
|
|
||||||
super();
|
super();
|
||||||
|
this._setTreatArrayBufferViewsAsHostObjects(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
_writeHostObject(abView: any) {
|
||||||
|
// Keep track of how to handle different ArrayBufferViews. The default
|
||||||
|
// Serializer for Node does not use the V8 methods for serializing those
|
||||||
|
// objects because Node's `Buffer` objects use pooled allocation in many
|
||||||
|
// cases, and their underlying `ArrayBuffer`s would show up in the
|
||||||
|
// serialization. Because a) those may contain sensitive data and the user
|
||||||
|
// may not be aware of that and b) they are often much larger than the
|
||||||
|
// `Buffer` itself, custom serialization is applied.
|
||||||
|
let i = 10; // FastBuffer
|
||||||
|
if (abView.constructor !== Buffer) {
|
||||||
|
i = arrayBufferViewTypeToIndex(abView);
|
||||||
|
if (i === -1) {
|
||||||
|
throw new this._getDataCloneError(
|
||||||
|
`Unserializable host object: ${abView}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.writeUint32(i);
|
||||||
|
this.writeUint32(abView.byteLength);
|
||||||
|
this.writeRawBytes(
|
||||||
|
new Uint8Array(abView.buffer, abView.byteOffset, abView.byteLength),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class DefaultDeserializer {
|
|
||||||
constructor() {
|
// deno-lint-ignore no-explicit-any
|
||||||
notImplemented("v8.DefaultDeserializer.prototype.constructor");
|
function arrayBufferViewIndexToType(index: number): any {
|
||||||
|
if (index === 0) return Int8Array;
|
||||||
|
if (index === 1) return Uint8Array;
|
||||||
|
if (index === 2) return Uint8ClampedArray;
|
||||||
|
if (index === 3) return Int16Array;
|
||||||
|
if (index === 4) return Uint16Array;
|
||||||
|
if (index === 5) return Int32Array;
|
||||||
|
if (index === 6) return Uint32Array;
|
||||||
|
if (index === 7) return Float32Array;
|
||||||
|
if (index === 8) return Float64Array;
|
||||||
|
if (index === 9) return DataView;
|
||||||
|
if (index === 10) return Buffer;
|
||||||
|
if (index === 11) return BigInt64Array;
|
||||||
|
if (index === 12) return BigUint64Array;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DefaultDeserializer extends Deserializer {
|
||||||
|
constructor(buffer: ArrayBufferView) {
|
||||||
|
super(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_readHostObject() {
|
||||||
|
const typeIndex = this.readUint32();
|
||||||
|
const ctor = arrayBufferViewIndexToType(typeIndex);
|
||||||
|
const byteLength = this.readUint32();
|
||||||
|
const byteOffset = this._readRawBytes(byteLength);
|
||||||
|
const BYTES_PER_ELEMENT = ctor?.BYTES_PER_ELEMENT ?? 1;
|
||||||
|
|
||||||
|
const offset = this.buffer.byteOffset + byteOffset;
|
||||||
|
if (offset % BYTES_PER_ELEMENT === 0) {
|
||||||
|
return new ctor(
|
||||||
|
this.buffer.buffer,
|
||||||
|
offset,
|
||||||
|
byteLength / BYTES_PER_ELEMENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Copy to an aligned buffer first.
|
||||||
|
const bufferCopy = Buffer.allocUnsafe(byteLength);
|
||||||
|
Buffer.from(
|
||||||
|
this.buffer.buffer,
|
||||||
|
byteOffset,
|
||||||
|
byteLength,
|
||||||
|
).copy(bufferCopy);
|
||||||
|
return new ctor(
|
||||||
|
bufferCopy.buffer,
|
||||||
|
bufferCopy.byteOffset,
|
||||||
|
byteLength / BYTES_PER_ELEMENT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const promiseHooks = {
|
export const promiseHooks = {
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
"test-util-promisify.js",
|
"test-util-promisify.js",
|
||||||
"test-util-types.js",
|
"test-util-types.js",
|
||||||
"test-util.js",
|
"test-util.js",
|
||||||
|
"test-v8-serdes.js",
|
||||||
"test-webcrypto-sign-verify.js",
|
"test-webcrypto-sign-verify.js",
|
||||||
"test-whatwg-url-properties.js",
|
"test-whatwg-url-properties.js",
|
||||||
// needs replace ".on" => ".addEventListener" in L29
|
// needs replace ".on" => ".addEventListener" in L29
|
||||||
|
|
285
tests/node_compat/test/parallel/test-v8-serdes.js
Normal file
285
tests/node_compat/test/parallel/test-v8-serdes.js
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
// deno-fmt-ignore-file
|
||||||
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
|
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||||
|
// Taken from Node 18.12.1
|
||||||
|
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||||
|
|
||||||
|
// Flags: --expose-internals
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { internalBinding } = require('internal/test/binding');
|
||||||
|
const assert = require('assert');
|
||||||
|
const v8 = require('v8');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const circular = {};
|
||||||
|
circular.circular = circular;
|
||||||
|
|
||||||
|
const objects = [
|
||||||
|
{ foo: 'bar' },
|
||||||
|
{ bar: 'baz' },
|
||||||
|
new Int8Array([1, 2, 3, 4]),
|
||||||
|
new Uint8Array([1, 2, 3, 4]),
|
||||||
|
new Int16Array([1, 2, 3, 4]),
|
||||||
|
new Uint16Array([1, 2, 3, 4]),
|
||||||
|
new Int32Array([1, 2, 3, 4]),
|
||||||
|
new Uint32Array([1, 2, 3, 4]),
|
||||||
|
new Float32Array([1, 2, 3, 4]),
|
||||||
|
new Float64Array([1, 2, 3, 4]),
|
||||||
|
new DataView(new ArrayBuffer(42)),
|
||||||
|
Buffer.from([1, 2, 3, 4]),
|
||||||
|
new BigInt64Array([42n]),
|
||||||
|
new BigUint64Array([42n]),
|
||||||
|
undefined,
|
||||||
|
null,
|
||||||
|
42,
|
||||||
|
circular,
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO(nathanwhit): we don't have any exposed host objects, so these parts don't work
|
||||||
|
// const hostObject = new (internalBinding('js_stream').JSStream)();
|
||||||
|
|
||||||
|
{
|
||||||
|
const ser = new v8.DefaultSerializer();
|
||||||
|
ser.writeHeader();
|
||||||
|
for (const obj of objects) {
|
||||||
|
ser.writeValue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const des = new v8.DefaultDeserializer(ser.releaseBuffer());
|
||||||
|
des.readHeader();
|
||||||
|
|
||||||
|
for (const obj of objects) {
|
||||||
|
assert.deepStrictEqual(des.readValue(), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for (const obj of objects) {
|
||||||
|
assert.deepStrictEqual(v8.deserialize(v8.serialize(obj)), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const ser = new v8.DefaultSerializer();
|
||||||
|
ser._getDataCloneError = common.mustCall((message) => {
|
||||||
|
assert.strictEqual(message, '#<Object> could not be cloned.');
|
||||||
|
return new Error('foobar');
|
||||||
|
});
|
||||||
|
|
||||||
|
ser.writeHeader();
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
ser.writeValue(new Proxy({}, {}));
|
||||||
|
}, /foobar/);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(nathanwhit): we don't have any exposed host objects, so these parts don't work
|
||||||
|
// {
|
||||||
|
// const ser = new v8.DefaultSerializer();
|
||||||
|
// ser._writeHostObject = common.mustCall((object) => {
|
||||||
|
// assert.strictEqual(object, hostObject);
|
||||||
|
// const buf = Buffer.from('hostObjectTag');
|
||||||
|
|
||||||
|
// ser.writeUint32(buf.length);
|
||||||
|
// ser.writeRawBytes(buf);
|
||||||
|
|
||||||
|
// ser.writeUint64(1, 2);
|
||||||
|
// ser.writeDouble(-0.25);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ser.writeHeader();
|
||||||
|
// ser.writeValue({ val: hostObject });
|
||||||
|
|
||||||
|
// const des = new v8.DefaultDeserializer(ser.releaseBuffer());
|
||||||
|
// des._readHostObject = common.mustCall(() => {
|
||||||
|
// const length = des.readUint32();
|
||||||
|
// const buf = des.readRawBytes(length);
|
||||||
|
|
||||||
|
// assert.strictEqual(buf.toString(), 'hostObjectTag');
|
||||||
|
|
||||||
|
// assert.deepStrictEqual(des.readUint64(), [1, 2]);
|
||||||
|
// assert.strictEqual(des.readDouble(), -0.25);
|
||||||
|
// return hostObject;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// des.readHeader();
|
||||||
|
|
||||||
|
// assert.strictEqual(des.readValue().val, hostObject);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// This test ensures that `v8.Serializer.writeRawBytes()` support
|
||||||
|
// `TypedArray` and `DataView`.
|
||||||
|
// {
|
||||||
|
// const text = 'hostObjectTag';
|
||||||
|
// const data = Buffer.from(text);
|
||||||
|
// const arrayBufferViews = common.getArrayBufferViews(data);
|
||||||
|
|
||||||
|
// // `buf` is one of `TypedArray` or `DataView`.
|
||||||
|
// function testWriteRawBytes(buf) {
|
||||||
|
// let writeHostObjectCalled = false;
|
||||||
|
// const ser = new v8.DefaultSerializer();
|
||||||
|
|
||||||
|
// ser._writeHostObject = common.mustCall((object) => {
|
||||||
|
// writeHostObjectCalled = true;
|
||||||
|
// ser.writeUint32(buf.byteLength);
|
||||||
|
// ser.writeRawBytes(buf);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ser.writeHeader();
|
||||||
|
// ser.writeValue({ val: hostObject });
|
||||||
|
|
||||||
|
// const des = new v8.DefaultDeserializer(ser.releaseBuffer());
|
||||||
|
// des._readHostObject = common.mustCall(() => {
|
||||||
|
// assert.strictEqual(writeHostObjectCalled, true);
|
||||||
|
// const length = des.readUint32();
|
||||||
|
// const buf = des.readRawBytes(length);
|
||||||
|
// assert.strictEqual(buf.toString(), text);
|
||||||
|
|
||||||
|
// return hostObject;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// des.readHeader();
|
||||||
|
|
||||||
|
// assert.strictEqual(des.readValue().val, hostObject);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// arrayBufferViews.forEach((buf) => {
|
||||||
|
// testWriteRawBytes(buf);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// const ser = new v8.DefaultSerializer();
|
||||||
|
// ser._writeHostObject = common.mustCall((object) => {
|
||||||
|
// throw new Error('foobar');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ser.writeHeader();
|
||||||
|
// assert.throws(() => {
|
||||||
|
// ser.writeValue({ val: hostObject });
|
||||||
|
// }, /foobar/);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// assert.throws(() => v8.serialize(hostObject), {
|
||||||
|
// constructor: Error,
|
||||||
|
// message: 'Unserializable host object: JSStream {}'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test that an old serialized value can still be deserialized.
|
||||||
|
const buf = Buffer.from('ff0d6f2203666f6f5e007b01', 'hex');
|
||||||
|
|
||||||
|
const des = new v8.DefaultDeserializer(buf);
|
||||||
|
des.readHeader();
|
||||||
|
assert.strictEqual(des.getWireFormatVersion(), 0x0d);
|
||||||
|
|
||||||
|
const value = des.readValue();
|
||||||
|
assert.strictEqual(value, value.foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const message = `New serialization format.
|
||||||
|
|
||||||
|
This test is expected to fail when V8 changes its serialization format.
|
||||||
|
When that happens, the "desStr" variable must be updated to the new value
|
||||||
|
and the change should be mentioned in the release notes, as it is semver-major.
|
||||||
|
|
||||||
|
Consider opening an issue as a heads up at https://github.com/nodejs/node/issues/new
|
||||||
|
`;
|
||||||
|
|
||||||
|
const desStr = 'ff0f6f2203666f6f5e007b01';
|
||||||
|
|
||||||
|
const desBuf = Buffer.from(desStr, 'hex');
|
||||||
|
const des = new v8.DefaultDeserializer(desBuf);
|
||||||
|
des.readHeader();
|
||||||
|
const value = des.readValue();
|
||||||
|
|
||||||
|
const ser = new v8.DefaultSerializer();
|
||||||
|
ser.writeHeader();
|
||||||
|
ser.writeValue(value);
|
||||||
|
|
||||||
|
const serBuf = ser.releaseBuffer();
|
||||||
|
const serStr = serBuf.toString('hex');
|
||||||
|
assert.deepStrictEqual(serStr, desStr, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Unaligned Uint16Array read, with padding in the underlying array buffer.
|
||||||
|
let buf = Buffer.alloc(32 + 9);
|
||||||
|
buf.write('ff0d5c0404addeefbe', 32, 'hex');
|
||||||
|
buf = buf.slice(32);
|
||||||
|
|
||||||
|
const expectedResult = os.endianness() === 'LE' ?
|
||||||
|
new Uint16Array([0xdead, 0xbeef]) : new Uint16Array([0xadde, 0xefbe]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(v8.deserialize(buf), expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
assert.throws(() => v8.Serializer(), {
|
||||||
|
constructor: TypeError,
|
||||||
|
message: "Class constructor Serializer cannot be invoked without 'new'",
|
||||||
|
// code: 'ERR_CONSTRUCT_CALL_REQUIRED'
|
||||||
|
});
|
||||||
|
assert.throws(() => v8.Deserializer(), {
|
||||||
|
constructor: TypeError,
|
||||||
|
message: "Class constructor Deserializer cannot be invoked without 'new'",
|
||||||
|
// code: 'ERR_CONSTRUCT_CALL_REQUIRED'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// `v8.deserialize()` and `new v8.Deserializer()` should support both
|
||||||
|
// `TypedArray` and `DataView`.
|
||||||
|
{
|
||||||
|
for (const obj of objects) {
|
||||||
|
const buf = v8.serialize(obj);
|
||||||
|
|
||||||
|
for (const arrayBufferView of common.getArrayBufferViews(buf)) {
|
||||||
|
assert.deepStrictEqual(v8.deserialize(arrayBufferView), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const arrayBufferView of common.getArrayBufferViews(buf)) {
|
||||||
|
const deserializer = new v8.DefaultDeserializer(arrayBufferView);
|
||||||
|
deserializer.readHeader();
|
||||||
|
const value = deserializer.readValue();
|
||||||
|
assert.deepStrictEqual(value, obj);
|
||||||
|
|
||||||
|
const serializer = new v8.DefaultSerializer();
|
||||||
|
serializer.writeHeader();
|
||||||
|
serializer.writeValue(value);
|
||||||
|
assert.deepStrictEqual(buf, serializer.releaseBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const INVALID_SOURCE = 'INVALID_SOURCE_TYPE';
|
||||||
|
const serializer = new v8.Serializer();
|
||||||
|
serializer.writeHeader();
|
||||||
|
assert.throws(
|
||||||
|
() => serializer.writeRawBytes(INVALID_SOURCE),
|
||||||
|
/^TypeError: source must be a TypedArray or a DataView$/,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => v8.deserialize(INVALID_SOURCE),
|
||||||
|
/^TypeError: buffer must be a TypedArray or a DataView$/,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => new v8.Deserializer(INVALID_SOURCE),
|
||||||
|
/^TypeError: buffer must be a TypedArray or a DataView$/,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Regression test for https://github.com/nodejs/node/issues/37978
|
||||||
|
assert.throws(() => {
|
||||||
|
new v8.Deserializer(new v8.Serializer().releaseBuffer()).readDouble();
|
||||||
|
}, /ReadDouble\(\) failed/);
|
||||||
|
}
|
Loading…
Reference in a new issue