1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-27 01:29:14 -05:00
denoland-deno/ext/node/polyfills/v8.ts
Nathan Whitaker be0ba6d84f
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`.
2024-09-10 21:50:21 +00:00

347 lines
9.8 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
/// <reference path="../../core/internal.d.ts" />
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { primordials } from "ext:core/mod.js";
const { ObjectPrototypeToString } = primordials;
import {
op_v8_cached_data_version_tag,
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";
import { Buffer } from "node:buffer";
import { notImplemented } from "ext:deno_node/_utils.ts";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
export function cachedDataVersionTag() {
return op_v8_cached_data_version_tag();
}
export function getHeapCodeStatistics() {
notImplemented("v8.getHeapCodeStatistics");
}
export function getHeapSnapshot() {
notImplemented("v8.getHeapSnapshot");
}
export function getHeapSpaceStatistics() {
notImplemented("v8.getHeapSpaceStatistics");
}
const buffer = new Float64Array(14);
export function getHeapStatistics() {
op_v8_get_heap_statistics(buffer);
return {
total_heap_size: buffer[0],
total_heap_size_executable: buffer[1],
total_physical_size: buffer[2],
total_available_size: buffer[3],
used_heap_size: buffer[4],
heap_size_limit: buffer[5],
malloced_memory: buffer[6],
peak_malloced_memory: buffer[7],
does_zap_garbage: buffer[8],
number_of_native_contexts: buffer[9],
number_of_detached_contexts: buffer[10],
total_global_handles_size: buffer[11],
used_global_handles_size: buffer[12],
external_memory: buffer[13],
};
}
export function setFlagsFromString() {
// NOTE(bartlomieju): From Node.js docs:
// The v8.setFlagsFromString() method can be used to programmatically set V8
// command-line flags. This method should be used with care. Changing settings
// after the VM has started may result in unpredictable behavior, including
// crashes and data loss; or it may simply do nothing.
//
// Notice: "or it may simply do nothing". This is what we're gonna do,
// this function will just be a no-op.
}
export function stopCoverage() {
notImplemented("v8.stopCoverage");
}
export function takeCoverage() {
notImplemented("v8.takeCoverage");
}
export function writeHeapSnapshot() {
notImplemented("v8.writeHeapSnapshot");
}
// deno-lint-ignore no-explicit-any
export function serialize(value: any) {
const ser = new DefaultSerializer();
ser.writeHeader();
ser.writeValue(value);
return ser.releaseBuffer();
}
export function deserialize(buffer: Buffer | ArrayBufferView | DataView) {
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 {
[kHandle]: object;
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 {
return Buffer.from(op_v8_release_buffer(this[kHandle]));
}
transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void {
op_v8_transfer_array_buffer(this[kHandle], _id, _arrayBuffer);
}
writeDouble(value: number): void {
op_v8_write_double(this[kHandle], value);
}
writeHeader(): void {
op_v8_write_header(this[kHandle]);
}
writeRawBytes(source: ArrayBufferView): void {
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 {
op_v8_write_uint32(this[kHandle], value);
}
writeUint64(hi: number, lo: number): void {
op_v8_write_uint64(this[kHandle], hi, lo);
}
// deno-lint-ignore no-explicit-any
writeValue(value: any): void {
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);
}
}
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 {
constructor() {
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),
);
}
}
// 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 = {
onInit() {
notImplemented("v8.promiseHooks.onInit");
},
onSettled() {
notImplemented("v8.promiseHooks.onSetttled");
},
onBefore() {
notImplemented("v8.promiseHooks.onBefore");
},
createHook() {
notImplemented("v8.promiseHooks.createHook");
},
};
export default {
cachedDataVersionTag,
getHeapCodeStatistics,
getHeapSnapshot,
getHeapSpaceStatistics,
getHeapStatistics,
setFlagsFromString,
stopCoverage,
takeCoverage,
writeHeapSnapshot,
serialize,
deserialize,
Serializer,
Deserializer,
DefaultSerializer,
DefaultDeserializer,
promiseHooks,
};