mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
lib: short-circuit WebIDL BufferSource SAB check
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/62833 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Mattias Buelens <mattias@buelens.com>
This commit is contained in:
1 parent
05d002fc4a
commit
f5808ed443
3 files changed
+283
-7
No files matched your search
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
input: [
|
||||
'arraybuffer',
|
||||
'buffer',
|
||||
'dataview',
|
||||
'uint8array',
|
||||
'uint8clampedarray',
|
||||
'int8array',
|
||||
'uint16array',
|
||||
'int16array',
|
||||
'uint32array',
|
||||
'int32array',
|
||||
'float16array',
|
||||
'float32array',
|
||||
'float64array',
|
||||
'bigint64array',
|
||||
'biguint64array',
|
||||
],
|
||||
n: [1e6],
|
||||
}, { flags: ['--expose-internals'] });
|
||||
|
||||
function main({ n, input }) {
|
||||
const { converters } = require('internal/webidl');
|
||||
|
||||
let value;
|
||||
switch (input) {
|
||||
case 'arraybuffer': value = new ArrayBuffer(32); break;
|
||||
case 'buffer': value = Buffer.alloc(32); break;
|
||||
case 'dataview': value = new DataView(new ArrayBuffer(32)); break;
|
||||
case 'uint8array': value = new Uint8Array(32); break;
|
||||
case 'uint8clampedarray': value = new Uint8ClampedArray(32); break;
|
||||
case 'int8array': value = new Int8Array(32); break;
|
||||
case 'uint16array': value = new Uint16Array(16); break;
|
||||
case 'int16array': value = new Int16Array(16); break;
|
||||
case 'uint32array': value = new Uint32Array(8); break;
|
||||
case 'int32array': value = new Int32Array(8); break;
|
||||
case 'float16array': value = new Float16Array(16); break;
|
||||
case 'float32array': value = new Float32Array(8); break;
|
||||
case 'float64array': value = new Float64Array(4); break;
|
||||
case 'bigint64array': value = new BigInt64Array(4); break;
|
||||
case 'biguint64array': value = new BigUint64Array(4); break;
|
||||
}
|
||||
|
||||
const opts = { prefix: 'test', context: 'test' };
|
||||
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++)
|
||||
converters.BufferSource(value, opts);
|
||||
bench.end(n);
|
||||
}
|
||||
+21
-7
@@ -2,6 +2,7 @@
|
||||
|
||||
const {
|
||||
ArrayBufferIsView,
|
||||
ArrayBufferPrototypeGetByteLength,
|
||||
ArrayPrototypePush,
|
||||
ArrayPrototypeToSorted,
|
||||
DataViewPrototypeGetBuffer,
|
||||
@@ -33,8 +34,7 @@ const {
|
||||
const { kEmptyObject } = require('internal/util');
|
||||
const {
|
||||
isArrayBuffer,
|
||||
isDataView,
|
||||
isSharedArrayBuffer,
|
||||
isTypedArray,
|
||||
} = require('internal/util/types');
|
||||
|
||||
const converters = { __proto__: null };
|
||||
@@ -390,9 +390,23 @@ function createInterfaceConverter(name, I) {
|
||||
};
|
||||
}
|
||||
|
||||
function getDataViewOrTypedArrayBuffer(V) {
|
||||
return isDataView(V) ?
|
||||
DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
|
||||
// Returns the [[ViewedArrayBuffer]] of an ArrayBufferView without leaving JS.
|
||||
function getViewedArrayBuffer(V) {
|
||||
return isTypedArray(V) ?
|
||||
TypedArrayPrototypeGetBuffer(V) : DataViewPrototypeGetBuffer(V);
|
||||
}
|
||||
|
||||
// Returns `true` if `buffer` is a `SharedArrayBuffer`. Uses a brand check via
|
||||
// the `ArrayBuffer.prototype.byteLength` getter, which succeeds only on real
|
||||
// (non-shared) ArrayBuffers and throws on SharedArrayBuffers — independent
|
||||
// of the receiver's prototype chain.
|
||||
function isSharedArrayBufferBacking(buffer) {
|
||||
try {
|
||||
ArrayBufferPrototypeGetByteLength(buffer);
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#ArrayBufferView
|
||||
@@ -402,7 +416,7 @@ converters.ArrayBufferView = (V, opts = kEmptyObject) => {
|
||||
'is not an ArrayBufferView.',
|
||||
opts);
|
||||
}
|
||||
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
|
||||
if (isSharedArrayBufferBacking(getViewedArrayBuffer(V))) {
|
||||
throw makeException(
|
||||
'is a view on a SharedArrayBuffer, which is not allowed.',
|
||||
opts);
|
||||
@@ -414,7 +428,7 @@ converters.ArrayBufferView = (V, opts = kEmptyObject) => {
|
||||
// https://webidl.spec.whatwg.org/#BufferSource
|
||||
converters.BufferSource = (V, opts = kEmptyObject) => {
|
||||
if (ArrayBufferIsView(V)) {
|
||||
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
|
||||
if (isSharedArrayBufferBacking(getViewedArrayBuffer(V))) {
|
||||
throw makeException(
|
||||
'is a view on a SharedArrayBuffer, which is not allowed.',
|
||||
opts);
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const { converters } = require('internal/webidl');
|
||||
|
||||
const TYPED_ARRAY_CTORS = [
|
||||
Uint8Array, Int8Array, Uint8ClampedArray,
|
||||
Uint16Array, Int16Array,
|
||||
Uint32Array, Int32Array,
|
||||
Float16Array, Float32Array, Float64Array,
|
||||
BigInt64Array, BigUint64Array,
|
||||
];
|
||||
|
||||
test('BufferSource accepts ArrayBuffer', () => {
|
||||
const ab = new ArrayBuffer(8);
|
||||
assert.strictEqual(converters.BufferSource(ab), ab);
|
||||
});
|
||||
|
||||
test('BufferSource accepts all TypedArray kinds', () => {
|
||||
for (const Ctor of TYPED_ARRAY_CTORS) {
|
||||
const ta = new Ctor(4);
|
||||
assert.strictEqual(converters.BufferSource(ta), ta);
|
||||
}
|
||||
});
|
||||
|
||||
test('BufferSource accepts Buffer', () => {
|
||||
const buf = Buffer.alloc(8);
|
||||
assert.strictEqual(converters.BufferSource(buf), buf);
|
||||
});
|
||||
|
||||
test('BufferSource accepts DataView', () => {
|
||||
const dv = new DataView(new ArrayBuffer(8));
|
||||
assert.strictEqual(converters.BufferSource(dv), dv);
|
||||
});
|
||||
|
||||
test('BufferSource accepts ArrayBuffer subclass instance', () => {
|
||||
class MyAB extends ArrayBuffer {}
|
||||
const sub = new MyAB(8);
|
||||
assert.strictEqual(converters.BufferSource(sub), sub);
|
||||
});
|
||||
|
||||
test('BufferSource accepts TypedArray with null prototype', () => {
|
||||
const ta = new Uint8Array(4);
|
||||
Object.setPrototypeOf(ta, null);
|
||||
assert.strictEqual(converters.BufferSource(ta), ta);
|
||||
});
|
||||
|
||||
test('BufferSource accepts DataView with null prototype', () => {
|
||||
const dv = new DataView(new ArrayBuffer(4));
|
||||
Object.setPrototypeOf(dv, null);
|
||||
assert.strictEqual(converters.BufferSource(dv), dv);
|
||||
});
|
||||
|
||||
test('BufferSource accepts ArrayBuffer with null prototype', () => {
|
||||
const ab = new ArrayBuffer(4);
|
||||
Object.setPrototypeOf(ab, null);
|
||||
assert.strictEqual(converters.BufferSource(ab), ab);
|
||||
});
|
||||
|
||||
test('BufferSource rejects SharedArrayBuffer', () => {
|
||||
assert.throws(
|
||||
() => converters.BufferSource(new SharedArrayBuffer(4)),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('BufferSource rejects SAB-backed TypedArray', () => {
|
||||
const view = new Uint8Array(new SharedArrayBuffer(4));
|
||||
assert.throws(
|
||||
() => converters.BufferSource(view),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('BufferSource rejects SAB-backed DataView', () => {
|
||||
const dv = new DataView(new SharedArrayBuffer(4));
|
||||
assert.throws(
|
||||
() => converters.BufferSource(dv),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('BufferSource rejects SAB view whose buffer prototype was reassigned', () => {
|
||||
const sab = new SharedArrayBuffer(4);
|
||||
Object.setPrototypeOf(sab, ArrayBuffer.prototype);
|
||||
const view = new Uint8Array(sab);
|
||||
assert.throws(
|
||||
() => converters.BufferSource(view),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('BufferSource accepts a detached ArrayBuffer', () => {
|
||||
const ab = new ArrayBuffer(8);
|
||||
structuredClone(ab, { transfer: [ab] });
|
||||
assert.strictEqual(ab.byteLength, 0);
|
||||
assert.strictEqual(converters.BufferSource(ab), ab);
|
||||
});
|
||||
|
||||
test('BufferSource rejects objects with a forged @@toStringTag', () => {
|
||||
const fake = { [Symbol.toStringTag]: 'Uint8Array' };
|
||||
assert.throws(
|
||||
() => converters.BufferSource(fake),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
for (const value of [null, undefined, 0, 1, 1n, '', 'x', true, Symbol('s'), [],
|
||||
{}, () => {}]) {
|
||||
test(`BufferSource rejects ${typeof value} ${String(value)}`, () => {
|
||||
assert.throws(
|
||||
() => converters.BufferSource(value),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test('ArrayBufferView accepts all TypedArray kinds', () => {
|
||||
for (const Ctor of TYPED_ARRAY_CTORS) {
|
||||
const ta = new Ctor(4);
|
||||
assert.strictEqual(converters.ArrayBufferView(ta), ta);
|
||||
}
|
||||
});
|
||||
|
||||
test('ArrayBufferView accepts DataView', () => {
|
||||
const dv = new DataView(new ArrayBuffer(8));
|
||||
assert.strictEqual(converters.ArrayBufferView(dv), dv);
|
||||
});
|
||||
|
||||
test('ArrayBufferView accepts TypedArray subclass instance', () => {
|
||||
class MyU8 extends Uint8Array {}
|
||||
const sub = new MyU8(4);
|
||||
assert.strictEqual(converters.ArrayBufferView(sub), sub);
|
||||
});
|
||||
|
||||
test('ArrayBufferView accepts TypedArray with null prototype', () => {
|
||||
const ta = new Uint8Array(4);
|
||||
Object.setPrototypeOf(ta, null);
|
||||
assert.strictEqual(converters.ArrayBufferView(ta), ta);
|
||||
});
|
||||
|
||||
test('ArrayBufferView accepts DataView with null prototype', () => {
|
||||
const dv = new DataView(new ArrayBuffer(4));
|
||||
Object.setPrototypeOf(dv, null);
|
||||
assert.strictEqual(converters.ArrayBufferView(dv), dv);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects raw ArrayBuffer', () => {
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(new ArrayBuffer(4)),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects raw SharedArrayBuffer', () => {
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(new SharedArrayBuffer(4)),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects SAB-backed TypedArray', () => {
|
||||
const view = new Uint8Array(new SharedArrayBuffer(4));
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(view),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects SAB-backed DataView', () => {
|
||||
const dv = new DataView(new SharedArrayBuffer(4));
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(dv),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects SAB view whose buffer prototype was reassigned', () => {
|
||||
const sab = new SharedArrayBuffer(4);
|
||||
Object.setPrototypeOf(sab, ArrayBuffer.prototype);
|
||||
const view = new Uint8Array(sab);
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(view),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
test('ArrayBufferView rejects objects with a forged @@toStringTag', () => {
|
||||
const fake = { [Symbol.toStringTag]: 'Uint8Array' };
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(fake),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
|
||||
for (const value of [null, undefined, 0, 1, 1n, '', 'x', true, Symbol('s'), [],
|
||||
{}, () => {}]) {
|
||||
test(`ArrayBufferView rejects ${typeof value} ${String(value)}`, () => {
|
||||
assert.throws(
|
||||
() => converters.ArrayBufferView(value),
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' },
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user