diff options
Diffstat (limited to 'ext/node/polyfills/v8.ts')
-rw-r--r-- | ext/node/polyfills/v8.ts | 235 |
1 files changed, 208 insertions, 27 deletions
diff --git a/ext/node/polyfills/v8.ts b/ext/node/polyfills/v8.ts index f06227cd5..5849f3ccc 100644 --- a/ext/node/polyfills/v8.ts +++ b/ext/node/polyfills/v8.ts @@ -6,15 +6,36 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { core } from "ext:core/mod.js"; +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, 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() { return op_v8_cached_data_version_tag(); @@ -71,65 +92,225 @@ export function takeCoverage() { export function writeHeapSnapshot() { notImplemented("v8.writeHeapSnapshot"); } -export function serialize(value) { - return Buffer.from(core.serialize(value)); +// 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(data) { - return core.deserialize(data); +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() { - 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 { - warnNotImplemented("v8.DefaultSerializer.prototype.releaseBuffer"); - return Buffer.from(""); + return Buffer.from(op_v8_release_buffer(this[kHandle])); } transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void { - warnNotImplemented("v8.DefaultSerializer.prototype.transferArrayBuffer"); + op_v8_transfer_array_buffer(this[kHandle], _id, _arrayBuffer); } - writeDouble(_value: number): void { - warnNotImplemented("v8.DefaultSerializer.prototype.writeDouble"); + writeDouble(value: number): void { + op_v8_write_double(this[kHandle], value); } writeHeader(): void { - warnNotImplemented("v8.DefaultSerializer.prototype.writeHeader"); + op_v8_write_header(this[kHandle]); } - writeRawBytes(_value: ArrayBufferView): void { - warnNotImplemented("v8.DefaultSerializer.prototype.writeRawBytes"); + 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 { - warnNotImplemented("v8.DefaultSerializer.prototype.writeUint32"); + writeUint32(value: number): void { + op_v8_write_uint32(this[kHandle], value); } - writeUint64(_hi: number, _lo: number): void { - warnNotImplemented("v8.DefaultSerializer.prototype.writeUint64"); + writeUint64(hi: number, lo: number): void { + op_v8_write_uint64(this[kHandle], hi, lo); } // deno-lint-ignore no-explicit-any - writeValue(_value: any): void { - warnNotImplemented("v8.DefaultSerializer.prototype.writeValue"); + writeValue(value: any): void { + op_v8_write_value(this[kHandle], value); } + + _getDataCloneError = Error; } + export class Deserializer { - constructor() { - notImplemented("v8.Deserializer.prototype.constructor"); + 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() { - warnNotImplemented("v8.DefaultSerializer.prototype.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), + ); } } -export class DefaultDeserializer { - constructor() { - notImplemented("v8.DefaultDeserializer.prototype.constructor"); + +// 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 = { |