From 266915d5ce354fde12b20f8f5ceb5ffdfacb7983 Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Sun, 29 Jan 2023 23:15:01 +0900 Subject: [PATCH] fix(ext): internal `structuredClone` for `ArrayBuffer` and `TypedArray` subclasses (#17431) --- cli/tests/unit/performance_test.ts | 26 +++++++++ cli/tests/unit/test_util.ts | 1 + core/internal.d.ts | 23 +++++++- ext/console/02_console.js | 6 +- ext/web/02_structured_clone.js | 94 ++++++++++++++++++++++++------ ext/webidl/00_webidl.js | 8 ++- 6 files changed, 133 insertions(+), 25 deletions(-) diff --git a/cli/tests/unit/performance_test.ts b/cli/tests/unit/performance_test.ts index a7dab5f9e5..3211bb4151 100644 --- a/cli/tests/unit/performance_test.ts +++ b/cli/tests/unit/performance_test.ts @@ -2,6 +2,7 @@ import { assert, assertEquals, + assertNotStrictEquals, assertStringIncludes, assertThrows, deferred, @@ -50,6 +51,31 @@ Deno.test(function performanceMark() { assert(markEntries[markEntries.length - 1] === mark); }); +Deno.test(function performanceMarkDetail() { + const detail = { foo: "foo" }; + const mark = performance.mark("test", { detail }); + assert(mark instanceof PerformanceMark); + assertEquals(mark.detail, { foo: "foo" }); + assertNotStrictEquals(mark.detail, detail); +}); + +Deno.test(function performanceMarkDetailArrayBuffer() { + const detail = new ArrayBuffer(10); + const mark = performance.mark("test", { detail }); + assert(mark instanceof PerformanceMark); + assertEquals(mark.detail, new ArrayBuffer(10)); + assertNotStrictEquals(mark.detail, detail); +}); + +Deno.test(function performanceMarkDetailSubTypedArray() { + class SubUint8Array extends Uint8Array {} + const detail = new SubUint8Array([1, 2]); + const mark = performance.mark("test", { detail }); + assert(mark instanceof PerformanceMark); + assertEquals(mark.detail, new Uint8Array([1, 2])); + assertNotStrictEquals(mark.detail, detail); +}); + Deno.test(function performanceMeasure() { const markName1 = "mark1"; const measureName1 = "measure1"; diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts index dc7f698c53..64c399b2d5 100644 --- a/cli/tests/unit/test_util.ts +++ b/cli/tests/unit/test_util.ts @@ -9,6 +9,7 @@ export { assertFalse, assertMatch, assertNotEquals, + assertNotStrictEquals, assertRejects, assertStrictEquals, assertStringIncludes, diff --git a/core/internal.d.ts b/core/internal.d.ts index efe6ed8a8e..004e068ffb 100644 --- a/core/internal.d.ts +++ b/core/internal.d.ts @@ -255,8 +255,11 @@ declare namespace __bootstrap { export const ArrayBuffer: typeof globalThis.ArrayBuffer; export const ArrayBufferLength: typeof ArrayBuffer.length; export const ArrayBufferName: typeof ArrayBuffer.name; - export const ArrayBufferPrototype: typeof ArrayBuffer.prototype; export const ArrayBufferIsView: typeof ArrayBuffer.isView; + export const ArrayBufferPrototype: typeof ArrayBuffer.prototype; + export const ArrayBufferPrototypeGetByteLength: ( + buffer: ArrayBuffer, + ) => number; export const ArrayBufferPrototypeSlice: UncurryThis< typeof ArrayBuffer.prototype.slice >; @@ -301,6 +304,11 @@ declare namespace __bootstrap { export const DataViewLength: typeof DataView.length; export const DataViewName: typeof DataView.name; export const DataViewPrototype: typeof DataView.prototype; + export const DataViewPrototypeGetBuffer: ( + view: DataView, + ) => ArrayBuffer | SharedArrayBuffer; + export const DataViewPrototypeGetByteLength: (view: DataView) => number; + export const DataViewPrototypeGetByteOffset: (view: DataView) => number; export const DataViewPrototypeGetInt8: UncurryThis< typeof DataView.prototype.getInt8 >; @@ -979,6 +987,19 @@ declare namespace __bootstrap { constructor: Uint8ArrayConstructor, arrayLike: ArrayLike, ) => Uint8Array; + export const TypedArrayPrototypeGetBuffer: ( + array: Uint8Array, + ) => ArrayBuffer | SharedArrayBuffer; + export const TypedArrayPrototypeGetByteLength: ( + array: Uint8Array, + ) => number; + export const TypedArrayPrototypeGetByteOffset: ( + array: Uint8Array, + ) => number; + export const TypedArrayPrototypeGetLength: (array: Uint8Array) => number; + export const TypedArrayPrototypeGetSymbolToStringTag: ( + v: unknown, + ) => string | undefined; export const TypedArrayPrototypeCopyWithin: UncurryThis< typeof Uint8Array.prototype.copyWithin >; diff --git a/ext/console/02_console.js b/ext/console/02_console.js index 9b898a040f..1720fe7e2e 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -8,11 +8,9 @@ const core = window.Deno.core; const colors = window.__bootstrap.colors; const { - ArrayBufferIsView, AggregateErrorPrototype, ArrayPrototypeUnshift, isNaN, - DataViewPrototype, DatePrototype, DateNow, DatePrototypeGetTime, @@ -114,6 +112,7 @@ ReflectGetPrototypeOf, ReflectHas, TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, WeakMapPrototype, WeakSetPrototype, } = window.__bootstrap.primordials; @@ -144,8 +143,7 @@ // Forked from Node's lib/internal/cli_table.js function isTypedArray(x) { - return ArrayBufferIsView(x) && - !ObjectPrototypeIsPrototypeOf(DataViewPrototype, x); + return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined; } const tableChars = { diff --git a/ext/web/02_structured_clone.js b/ext/web/02_structured_clone.js index f8a88a7ef7..793cb1c75e 100644 --- a/ext/web/02_structured_clone.js +++ b/ext/web/02_structured_clone.js @@ -14,13 +14,32 @@ const { ArrayBuffer, ArrayBufferPrototype, + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeSlice, ArrayBufferIsView, - DataViewPrototype, + DataView, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, ObjectPrototypeIsPrototypeOf, - TypedArrayPrototypeSlice, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, TypeErrorPrototype, WeakMap, WeakMapPrototypeSet, + Int8Array, + Int16Array, + Int32Array, + BigInt64Array, + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + BigUint64Array, + Float32Array, + Float64Array, } = window.__bootstrap.primordials; const objectCloneMemo = new WeakMap(); @@ -32,14 +51,15 @@ _cloneConstructor, ) { // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway - return TypedArrayPrototypeSlice( + return ArrayBufferPrototypeSlice( srcBuffer, srcByteOffset, srcByteOffset + srcLength, ); } - /** Clone a value in a similar way to structured cloning. It is similar to a + // TODO(petamoriken): Resizable ArrayBuffer support in the future + /** Clone a value in a similar way to structured cloning. It is similar to a * StructureDeserialize(StructuredSerialize(...)). */ function structuredClone(value) { // Performance optimization for buffers, otherwise @@ -48,28 +68,64 @@ const cloned = cloneArrayBuffer( value, 0, - value.byteLength, + ArrayBufferPrototypeGetByteLength(value), ArrayBuffer, ); WeakMapPrototypeSet(objectCloneMemo, value, cloned); return cloned; } + if (ArrayBufferIsView(value)) { - const clonedBuffer = structuredClone(value.buffer); - // Use DataViewConstructor type purely for type-checking, can be a - // DataView or TypedArray. They use the same constructor signature, - // only DataView has a length in bytes and TypedArrays use a length in - // terms of elements, so we adjust for that. - let length; - if (ObjectPrototypeIsPrototypeOf(DataViewPrototype, view)) { - length = value.byteLength; - } else { - length = value.length; + const tag = TypedArrayPrototypeGetSymbolToStringTag(value); + // DataView + if (tag === undefined) { + return new DataView( + structuredClone(DataViewPrototypeGetBuffer(value)), + DataViewPrototypeGetByteOffset(value), + DataViewPrototypeGetByteLength(value), + ); } - return new (value.constructor)( - clonedBuffer, - value.byteOffset, - length, + // TypedArray + let Constructor; + switch (tag) { + case "Int8Array": + Constructor = Int8Array; + break; + case "Int16Array": + Constructor = Int16Array; + break; + case "Int32Array": + Constructor = Int32Array; + break; + case "BigInt64Array": + Constructor = BigInt64Array; + break; + case "Uint8Array": + Constructor = Uint8Array; + break; + case "Uint8ClampedArray": + Constructor = Uint8ClampedArray; + break; + case "Uint16Array": + Constructor = Uint16Array; + break; + case "Uint32Array": + Constructor = Uint32Array; + break; + case "BigUint64Array": + Constructor = BigUint64Array; + break; + case "Float32Array": + Constructor = Float32Array; + break; + case "Float64Array": + Constructor = Float64Array; + break; + } + return new Constructor( + structuredClone(TypedArrayPrototypeGetBuffer(value)), + TypedArrayPrototypeGetByteOffset(value), + TypedArrayPrototypeGetLength(value), ); } diff --git a/ext/webidl/00_webidl.js b/ext/webidl/00_webidl.js index 4127d24bf2..bb90d973ec 100644 --- a/ext/webidl/00_webidl.js +++ b/ext/webidl/00_webidl.js @@ -77,6 +77,7 @@ Symbol, SymbolIterator, SymbolToStringTag, + TypedArrayPrototypeGetSymbolToStringTag, TypeError, Uint16Array, Uint32Array, @@ -442,6 +443,11 @@ return V; } + function isDataView(V) { + return ArrayBufferIsView(V) && + TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; + } + function isNonSharedArrayBuffer(V) { return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); } @@ -467,7 +473,7 @@ }; converters.DataView = (V, opts = {}) => { - if (!(ObjectPrototypeIsPrototypeOf(DataViewPrototype, V))) { + if (!isDataView(V)) { throw makeException(TypeError, "is not a DataView", opts); }