1
0
Fork 0
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:
Nathan Whitaker 2024-09-10 14:50:21 -07:00 committed by GitHub
parent e522f4b65a
commit be0ba6d84f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 876 additions and 29 deletions

View file

@ -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,

View file

@ -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())
}

View file

@ -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 { export class Deserializer {
constructor() { buffer: ArrayBufferView;
notImplemented("v8.Deserializer.prototype.constructor"); [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);
}
}
function arrayBufferViewTypeToIndex(abView: ArrayBufferView) {
const type = ObjectPrototypeToString(abView);
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}`,
);
} }
} }
export class DefaultDeserializer { this.writeUint32(i);
constructor() { this.writeUint32(abView.byteLength);
notImplemented("v8.DefaultDeserializer.prototype.constructor"); this.writeRawBytes(
new Uint8Array(abView.buffer, abView.byteOffset, abView.byteLength),
);
}
}
// deno-lint-ignore no-explicit-any
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 = {

View file

@ -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

View 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/);
}