fix(ext): internal structuredClone for ArrayBuffer and TypedArray subclasses (#17431)

This commit is contained in:
Kenta Moriuchi 2023-01-29 23:15:01 +09:00 committed by GitHub
parent 04ba709b6e
commit 266915d5ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 25 deletions

View File

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

View File

@ -9,6 +9,7 @@ export {
assertFalse,
assertMatch,
assertNotEquals,
assertNotStrictEquals,
assertRejects,
assertStrictEquals,
assertStringIncludes,

23
core/internal.d.ts vendored
View File

@ -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<number>,
) => 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
>;

View File

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

View File

@ -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),
);
}

View File

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