Skip to content

Commit 647d873

Browse files
authored
Improve buffer source type handling
* Properly prohibit SharedArrayBuffers by default * Introduce the allowShared option to allow them * Throw on detached ArrayBuffers Note that the tests for detached ArrayBuffers are not run in Node v10, since there the worker_threads module is experimental and there is no way to create a detached ArrayBuffer.
1 parent 976f15d commit 647d873

File tree

3 files changed

+223
-38
lines changed

3 files changed

+223
-38
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ Conversions for all of the basic types from the Web IDL specification are implem
5656
- [`DOMString`](https://heycam.github.io/webidl/#es-DOMString), which can additionally be provided the boolean option `{ treatNullAsEmptyString }` as a second parameter
5757
- [`ByteString`](https://heycam.github.io/webidl/#es-ByteString), [`USVString`](https://heycam.github.io/webidl/#es-USVString)
5858
- [`object`](https://heycam.github.io/webidl/#es-object)
59-
- [Buffer source types](https://heycam.github.io/webidl/#es-buffer-source-types)
59+
- [Buffer source types](https://heycam.github.io/webidl/#es-buffer-source-types), which can additionally be provided with the boolean option `{ allowShared }` as a second parameter
6060

6161
Additionally, for convenience, the following derived type definitions are implemented:
6262

63-
- [`ArrayBufferView`](https://heycam.github.io/webidl/#ArrayBufferView)
63+
- [`ArrayBufferView`](https://heycam.github.io/webidl/#ArrayBufferView), which can additionally be provided with the boolean option `{ allowShared }` as a second parameter
6464
- [`BufferSource`](https://heycam.github.io/webidl/#BufferSource)
6565
- [`DOMTimeStamp`](https://heycam.github.io/webidl/#DOMTimeStamp)
6666
- [`Function`](https://heycam.github.io/webidl/#Function)
@@ -76,6 +76,10 @@ To mitigate this, we could return the raw BigInt value from the conversion funct
7676

7777
On the other hand, `long long` conversion is always accurate, since the input value can never be more precise than the output value.
7878

79+
### A note on `BufferSource` types
80+
81+
All of the `BufferSource` types will throw when the relevant `ArrayBuffer` has been detached. This technically is not part of the [specified conversion algorithm](https://heycam.github.io/webidl/#es-buffer-source-types), but instead part of the [getting a reference/getting a copy](https://heycam.github.io/webidl/#ref-for-dfn-get-buffer-source-reference%E2%91%A0) algorithms. We've consolidated them here for convenience and ease of implementation, but if there is a need to separate them in the future, please open an issue so we can investigate.
82+
7983
## Background
8084

8185
What's actually going on here, conceptually, is pretty weird. Let's try to explain.

lib/index.js

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,7 @@ function createIntegerConversion(bitLength, typeOpts) {
107107
const twoToTheBitLength = Math.pow(2, bitLength);
108108
const twoToOneLessThanTheBitLength = Math.pow(2, bitLength - 1);
109109

110-
return (V, opts) => {
111-
if (opts === undefined) {
112-
opts = {};
113-
}
114-
110+
return (V, opts = {}) => {
115111
let x = toNumber(V, opts);
116112
x = censorNegativeZero(x);
117113

@@ -274,11 +270,7 @@ exports["unrestricted float"] = (V, opts) => {
274270
return Math.fround(x);
275271
};
276272

277-
exports.DOMString = function (V, opts) {
278-
if (opts === undefined) {
279-
opts = {};
280-
}
281-
273+
exports.DOMString = function (V, opts = {}) {
282274
if (opts.treatNullAsEmptyString && V === null) {
283275
return "";
284276
}
@@ -352,33 +344,71 @@ function convertCallbackFunction(V, opts) {
352344

353345
const abByteLengthGetter =
354346
Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
347+
const sabByteLengthGetter =
348+
Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "byteLength").get;
355349

356-
function isArrayBuffer(V) {
350+
function isNonSharedArrayBuffer(V) {
357351
try {
352+
// This will throw on SharedArrayBuffers, but not detached ArrayBuffers.
353+
// (The spec says it should throw, but the spec conflicts with implementations: https://github.com/tc39/ecma262/issues/678)
358354
abByteLengthGetter.call(V);
355+
359356
return true;
360-
} catch (e) {
357+
} catch {
358+
return false;
359+
}
360+
}
361+
362+
function isSharedArrayBuffer(V) {
363+
try {
364+
sabByteLengthGetter.call(V);
365+
return true;
366+
} catch {
361367
return false;
362368
}
363369
}
364370

365-
// I don't think we can reliably detect detached ArrayBuffers.
366-
exports.ArrayBuffer = (V, opts) => {
367-
if (!isArrayBuffer(V)) {
368-
throw makeException(TypeError, "is not a view on an ArrayBuffer object", opts);
371+
function isArrayBufferDetached(V) {
372+
try {
373+
// eslint-disable-next-line no-new
374+
new Uint8Array(V);
375+
return false;
376+
} catch {
377+
return true;
378+
}
379+
}
380+
381+
exports.ArrayBuffer = (V, opts = {}) => {
382+
if (!isNonSharedArrayBuffer(V)) {
383+
if (opts.allowShared && !isSharedArrayBuffer(V)) {
384+
throw makeException(TypeError, "is not an ArrayBuffer or SharedArrayBuffer", opts);
385+
}
386+
throw makeException(TypeError, "is not an ArrayBuffer", opts);
369387
}
388+
if (isArrayBufferDetached(V)) {
389+
throw makeException(TypeError, "is a detached ArrayBuffer", opts);
390+
}
391+
370392
return V;
371393
};
372394

373395
const dvByteLengthGetter =
374396
Object.getOwnPropertyDescriptor(DataView.prototype, "byteLength").get;
375-
exports.DataView = (V, opts) => {
397+
exports.DataView = (V, opts = {}) => {
376398
try {
377399
dvByteLengthGetter.call(V);
378-
return V;
379400
} catch (e) {
380-
throw makeException(TypeError, "is not a view on an DataView object", opts);
401+
throw makeException(TypeError, "is not a DataView", opts);
402+
}
403+
404+
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
405+
throw makeException(TypeError, "is backed by a SharedArrayBuffer, which is not allowed", opts);
406+
}
407+
if (isArrayBufferDetached(V.buffer)) {
408+
throw makeException(TypeError, "is backed by a detached ArrayBuffer", opts);
381409
}
410+
411+
return V;
382412
};
383413

384414
// Returns the unforgeable `TypedArray` constructor name or `undefined`,
@@ -395,28 +425,58 @@ const typedArrayNameGetter = Object.getOwnPropertyDescriptor(
395425
].forEach(func => {
396426
const name = func.name;
397427
const article = /^[AEIOU]/.test(name) ? "an" : "a";
398-
exports[name] = (V, opts) => {
428+
exports[name] = (V, opts = {}) => {
399429
if (!ArrayBuffer.isView(V) || typedArrayNameGetter.call(V) !== name) {
400430
throw makeException(TypeError, `is not ${article} ${name} object`, opts);
401431
}
432+
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
433+
throw makeException(TypeError, "is a view on a SharedArrayBuffer, which is not allowed", opts);
434+
}
435+
if (isArrayBufferDetached(V.buffer)) {
436+
throw makeException(TypeError, "is a view on a detached ArrayBuffer", opts);
437+
}
402438

403439
return V;
404440
};
405441
});
406442

407443
// Common definitions
408444

409-
exports.ArrayBufferView = (V, opts) => {
445+
exports.ArrayBufferView = (V, opts = {}) => {
410446
if (!ArrayBuffer.isView(V)) {
411-
throw makeException(TypeError, "is not a view on an ArrayBuffer object", opts);
447+
throw makeException(TypeError, "is not a view on an ArrayBuffer or SharedArrayBuffer", opts);
412448
}
413449

450+
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
451+
throw makeException(TypeError, "is a view on a SharedArrayBuffer, which is not allowed", opts);
452+
}
453+
454+
if (isArrayBufferDetached(V.buffer)) {
455+
throw makeException(TypeError, "is a view on a detached ArrayBuffer", opts);
456+
}
414457
return V;
415458
};
416459

417-
exports.BufferSource = (V, opts) => {
418-
if (!ArrayBuffer.isView(V) && !isArrayBuffer(V)) {
419-
throw makeException(TypeError, "is not an ArrayBuffer object or a view on one", opts);
460+
exports.BufferSource = (V, opts = {}) => {
461+
if (ArrayBuffer.isView(V)) {
462+
if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) {
463+
throw makeException(TypeError, "is a view on a SharedArrayBuffer, which is not allowed", opts);
464+
}
465+
466+
if (isArrayBufferDetached(V.buffer)) {
467+
throw makeException(TypeError, "is a view on a detached ArrayBuffer", opts);
468+
}
469+
return V;
470+
}
471+
472+
if (!opts.allowShared && !isNonSharedArrayBuffer(V)) {
473+
throw makeException(TypeError, "is not an ArrayBuffer or a view on one", opts);
474+
}
475+
if (opts.allowShared && !isSharedArrayBuffer(V) && !isNonSharedArrayBuffer(V)) {
476+
throw makeException(TypeError, "is not an ArrayBuffer, SharedArrayBufer, or a view on one", opts);
477+
}
478+
if (isArrayBufferDetached(V)) {
479+
throw makeException(TypeError, "is a detached ArrayBuffer", opts);
420480
}
421481

422482
return V;

0 commit comments

Comments
 (0)