Skip to content

Commit 0cfebb7

Browse files
committed
fixup! lib: refactor internal webidl converters
1 parent 34f0d88 commit 0cfebb7

4 files changed

Lines changed: 259 additions & 39 deletions

File tree

lib/internal/webidl.js

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const {
3232

3333
const { kEmptyObject } = require('internal/util');
3434
const {
35+
isSharedArrayBuffer,
3536
isTypedArray,
3637
} = require('internal/util/types');
3738

@@ -47,7 +48,8 @@ const converters = { __proto__: null };
4748
* @property {string} [code] Node.js error code to assign to TypeError.
4849
* @property {boolean} [enforceRange] Web IDL [EnforceRange] attribute.
4950
* @property {boolean} [clamp] Web IDL [Clamp] attribute.
50-
* @property {boolean} [allowShared] Web IDL [AllowShared] attribute.
51+
* @property {boolean} [allowShared] Web IDL [AllowShared] attribute for
52+
* buffer view types.
5153
* @property {boolean} [allowResizable] Web IDL [AllowResizable] attribute.
5254
*/
5355

@@ -70,7 +72,7 @@ const converters = { __proto__: null };
7072
* @property {string} key Dictionary member identifier.
7173
* @property {Converter} converter Converter for the member type.
7274
* @property {boolean} [required] Whether the member is required.
73-
* @property {Function} [defaultValue] Function returning the default value.
75+
* @property {() => any} [defaultValue] Function returning the default value.
7476
* @property {DictionaryMemberValidator} [validator] Optional Node.js
7577
* extension point invoked after conversion and before storing the member.
7678
* This is for early semantic validation of known unsupported IDL values,
@@ -827,7 +829,7 @@ function createInterfaceConverter(name, prototype) {
827829
/**
828830
* Returns the ArrayBuffer or SharedArrayBuffer viewed by a typed array or
829831
* DataView.
830-
* @param {ArrayBufferView} V ArrayBuffer view.
832+
* @param {ArrayBufferView} V TypedArray or DataView.
831833
* @returns {ArrayBuffer|SharedArrayBuffer}
832834
*/
833835
function getViewedArrayBuffer(V) {
@@ -848,7 +850,8 @@ function validateBufferSourceBacking(buffer, options) {
848850
// ArrayBuffer.prototype.resizable is an ArrayBuffer brand check and the
849851
// [AllowResizable] value we need. For SharedArrayBuffer-backed views it
850852
// throws, which lets this path avoid a separate IsSharedArrayBuffer check.
851-
// Keep this in sync with the inlined BufferSource view branch below.
853+
// BufferSource has separate inline logic because [AllowShared] cannot be
854+
// used with that typedef.
852855
resizable = ArrayBufferPrototypeGetResizable(buffer);
853856
} catch {
854857
// ArrayBufferView conversion step 2: reject SharedArrayBuffer
@@ -936,32 +939,32 @@ converters.Uint8Array = (V, options = kEmptyObject) => {
936939
* @see https://webidl.spec.whatwg.org/#BufferSource
937940
* @param {any} V JavaScript value.
938941
* @param {ConversionOptions} [options] Conversion options.
939-
* @returns {ArrayBuffer|ArrayBufferView}
942+
* @returns {ArrayBuffer|ArrayBufferView<ArrayBuffer>}
940943
*/
941944
converters.BufferSource = (V, options = kEmptyObject) => {
942945
// BufferSource is a typedef for (ArrayBufferView or ArrayBuffer).
946+
// [AllowShared] cannot be used with BufferSource because the ArrayBuffer
947+
// union branch does not support it. Use AllowSharedBufferSource instead.
943948
if (ArrayBufferIsView(V)) {
944949
const buffer = getViewedArrayBuffer(V);
945950
// ArrayBufferView conversion steps 2-4: validate the viewed buffer
946951
// and return a reference to the same view.
947952
// Keep this logic inline instead of calling validateBufferSourceBacking().
948953
// BufferSource conversion is hot, and this avoids a helper call while still
949954
// using a primordial getter for the backing-buffer internal-slot check.
950-
// Keep this in sync with validateBufferSourceBacking().
955+
// Unlike validateBufferSourceBacking(), this intentionally ignores
956+
// options.allowShared because [AllowShared] cannot be used with
957+
// BufferSource.
951958
let resizable;
952959
try {
953960
// ArrayBuffer.prototype.resizable is both the ArrayBuffer brand check
954-
// and the step 3 value. It throws for SharedArrayBuffer, which gives us
955-
// the step 2 branch without an extra byteLength getter call.
961+
// and the step 3 value. It throws for SharedArrayBuffer, which lets this
962+
// path reject SAB-backed views without an extra byteLength getter call.
956963
resizable = ArrayBufferPrototypeGetResizable(buffer);
957964
} catch {
958-
if (!options.allowShared) {
959-
throw makeException(
960-
'is a view on a SharedArrayBuffer, which is not allowed.',
961-
options);
962-
}
963-
validateAllowGrowableSharedArrayBuffer(buffer, options);
964-
return V;
965+
throw makeException(
966+
'is a view on a SharedArrayBuffer, which is not allowed.',
967+
options);
965968
}
966969
if (resizable && !options.allowResizable) {
967970
throw makeException(
@@ -997,6 +1000,48 @@ converters.BufferSource = (V, options = kEmptyObject) => {
9971000
return V;
9981001
};
9991002

1003+
/**
1004+
* Converts a JavaScript value to the IDL AllowSharedBufferSource typedef.
1005+
* @see https://webidl.spec.whatwg.org/#AllowSharedBufferSource
1006+
* @param {any} V JavaScript value.
1007+
* @param {ConversionOptions} [options] Conversion options.
1008+
* @returns {ArrayBuffer|SharedArrayBuffer|ArrayBufferView}
1009+
*/
1010+
converters.AllowSharedBufferSource = (V, options = kEmptyObject) => {
1011+
// AllowSharedBufferSource is a typedef for
1012+
// (ArrayBuffer or SharedArrayBuffer or [AllowShared] ArrayBufferView).
1013+
// The union branches are disjoint, so this keeps the hot view path first.
1014+
if (ArrayBufferIsView(V)) {
1015+
const buffer = getViewedArrayBuffer(V);
1016+
let resizable;
1017+
try {
1018+
resizable = ArrayBufferPrototypeGetResizable(buffer);
1019+
} catch {
1020+
validateAllowGrowableSharedArrayBuffer(buffer, options);
1021+
return V;
1022+
}
1023+
validateAllowResizableArrayBuffer(resizable, options);
1024+
return V;
1025+
}
1026+
1027+
let resizable;
1028+
try {
1029+
resizable = ArrayBufferPrototypeGetResizable(V);
1030+
} catch {
1031+
if (isSharedArrayBuffer(V)) {
1032+
validateAllowGrowableSharedArrayBuffer(V, options);
1033+
return V;
1034+
}
1035+
throw makeException(
1036+
'is not instance of ArrayBuffer, SharedArrayBuffer, Buffer, ' +
1037+
'TypedArray, or DataView.',
1038+
options);
1039+
}
1040+
1041+
validateAllowResizableArrayBuffer(resizable, options);
1042+
return V;
1043+
};
1044+
10001045
/**
10011046
* Converts a JavaScript value to the IDL sequence<DOMString> type.
10021047
* @see https://webidl.spec.whatwg.org/#es-sequence

test/parallel/test-internal-webidl-buffer-source.js

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ const TYPED_ARRAY_CTORS = [
1717
];
1818

1919
function createGrowableSharedArrayBufferView(Ctor) {
20-
const buffer = new SharedArrayBuffer(0, { maxByteLength: 1 });
20+
const buffer = createGrowableSharedArrayBuffer();
2121
const view = new Ctor(buffer);
22-
assert.strictEqual(view.buffer.growable, true);
2322
return view;
2423
}
2524

25+
function createGrowableSharedArrayBuffer() {
26+
const buffer = new SharedArrayBuffer(0, { maxByteLength: 1 });
27+
assert.strictEqual(buffer.growable, true);
28+
return buffer;
29+
}
30+
2631
test('BufferSource accepts ArrayBuffer', () => {
2732
const ab = new ArrayBuffer(8);
2833
assert.strictEqual(converters.BufferSource(ab), ab);
@@ -104,6 +109,10 @@ test('BufferSource rejects SAB-backed TypedArray', () => {
104109
() => converters.BufferSource(view),
105110
{ code: 'ERR_INVALID_ARG_TYPE' },
106111
);
112+
assert.throws(
113+
() => converters.BufferSource(view, { allowShared: true }),
114+
{ code: 'ERR_INVALID_ARG_TYPE' },
115+
);
107116
});
108117

109118
test('BufferSource rejects SAB-backed DataView', () => {
@@ -112,6 +121,10 @@ test('BufferSource rejects SAB-backed DataView', () => {
112121
() => converters.BufferSource(dv),
113122
{ code: 'ERR_INVALID_ARG_TYPE' },
114123
);
124+
assert.throws(
125+
() => converters.BufferSource(dv, { allowShared: true }),
126+
{ code: 'ERR_INVALID_ARG_TYPE' },
127+
);
115128
});
116129

117130
test('BufferSource rejects SAB view whose buffer prototype was reassigned', () => {
@@ -166,26 +179,96 @@ test('BufferSource handles resizable-backed views with explicit options', () =>
166179
}
167180
});
168181

169-
test('BufferSource handles growable SAB-backed views with explicit options', () => {
182+
test('BufferSource rejects SAB-backed views with explicit options', () => {
170183
for (const Ctor of [DataView, ...TYPED_ARRAY_CTORS]) {
171-
{
172-
const view = createGrowableSharedArrayBufferView(Ctor);
173-
assert.throws(
174-
() => converters.BufferSource(view, {
175-
allowShared: true,
176-
allowResizable: false,
177-
}),
178-
{ code: 'ERR_INVALID_ARG_TYPE' },
179-
);
180-
}
181-
182-
{
183-
const view = createGrowableSharedArrayBufferView(Ctor);
184-
assert.strictEqual(converters.BufferSource(view, {
184+
const view = createGrowableSharedArrayBufferView(Ctor);
185+
assert.throws(
186+
() => converters.BufferSource(view, {
185187
allowShared: true,
186188
allowResizable: true,
187-
}), view);
188-
}
189+
}),
190+
{ code: 'ERR_INVALID_ARG_TYPE' },
191+
);
192+
}
193+
});
194+
195+
test('AllowSharedBufferSource accepts ArrayBuffer and SharedArrayBuffer', () => {
196+
const ab = new ArrayBuffer(8);
197+
const sab = new SharedArrayBuffer(8);
198+
199+
assert.strictEqual(converters.AllowSharedBufferSource(ab), ab);
200+
assert.strictEqual(converters.AllowSharedBufferSource(sab), sab);
201+
});
202+
203+
test('AllowSharedBufferSource accepts cross-realm buffers', () => {
204+
const context = vm.createContext();
205+
const ab = vm.runInContext('new ArrayBuffer(0)', context);
206+
const sab = vm.runInContext('new SharedArrayBuffer(0)', context);
207+
208+
assert.strictEqual(converters.AllowSharedBufferSource(ab), ab);
209+
assert.strictEqual(converters.AllowSharedBufferSource(sab), sab);
210+
});
211+
212+
test('AllowSharedBufferSource accepts ArrayBuffer and SharedArrayBuffer views', () => {
213+
const abView = new Uint8Array(new ArrayBuffer(8));
214+
const sabView = new Uint8Array(new SharedArrayBuffer(8));
215+
const abDataView = new DataView(new ArrayBuffer(8));
216+
const sabDataView = new DataView(new SharedArrayBuffer(8));
217+
218+
assert.strictEqual(converters.AllowSharedBufferSource(abView), abView);
219+
assert.strictEqual(converters.AllowSharedBufferSource(sabView), sabView);
220+
assert.strictEqual(
221+
converters.AllowSharedBufferSource(abDataView), abDataView);
222+
assert.strictEqual(
223+
converters.AllowSharedBufferSource(sabDataView), sabDataView);
224+
});
225+
226+
test('AllowSharedBufferSource handles resizable buffers with explicit options', () => {
227+
const ab = new ArrayBuffer(0, { maxByteLength: 1 });
228+
const views = [new Uint8Array(ab), new DataView(ab)];
229+
230+
assert.throws(
231+
() => converters.AllowSharedBufferSource(ab),
232+
{ code: 'ERR_INVALID_ARG_TYPE' },
233+
);
234+
for (const view of views) {
235+
assert.throws(
236+
() => converters.AllowSharedBufferSource(view),
237+
{ code: 'ERR_INVALID_ARG_TYPE' },
238+
);
239+
}
240+
assert.strictEqual(converters.AllowSharedBufferSource(ab, {
241+
allowResizable: true,
242+
}), ab);
243+
for (const view of views) {
244+
assert.strictEqual(converters.AllowSharedBufferSource(view, {
245+
allowResizable: true,
246+
}), view);
247+
}
248+
});
249+
250+
test('AllowSharedBufferSource handles growable shared buffers with explicit ' +
251+
'options', () => {
252+
const sab = createGrowableSharedArrayBuffer();
253+
const views = [new Uint8Array(sab), new DataView(sab)];
254+
255+
assert.throws(
256+
() => converters.AllowSharedBufferSource(sab),
257+
{ code: 'ERR_INVALID_ARG_TYPE' },
258+
);
259+
for (const view of views) {
260+
assert.throws(
261+
() => converters.AllowSharedBufferSource(view),
262+
{ code: 'ERR_INVALID_ARG_TYPE' },
263+
);
264+
}
265+
assert.strictEqual(converters.AllowSharedBufferSource(sab, {
266+
allowResizable: true,
267+
}), sab);
268+
for (const view of views) {
269+
assert.strictEqual(converters.AllowSharedBufferSource(view, {
270+
allowResizable: true,
271+
}), view);
189272
}
190273
});
191274

@@ -205,4 +288,11 @@ for (const value of [null, undefined, 0, 1, 1n, '', 'x', true, Symbol('s'), [],
205288
{ code: 'ERR_INVALID_ARG_TYPE' },
206289
);
207290
});
291+
292+
test(`AllowSharedBufferSource rejects ${typeof value} ${String(value)}`, () => {
293+
assert.throws(
294+
() => converters.AllowSharedBufferSource(value),
295+
{ code: 'ERR_INVALID_ARG_TYPE' },
296+
);
297+
});
208298
}

test/parallel/test-internal-webidl-converttoint.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ assert.strictEqual(convertToInt(1, 64), 1);
3030
assert.strictEqual(convertToInt(-0.5, 64), 0);
3131
assert.strictEqual(convertToInt(-0.5, 64, 'signed'), 0);
3232
assert.strictEqual(convertToInt(-1.5, 64, 'signed'), -1);
33+
assert.strictEqual(convertToInt(2 ** 12 + 1, 12), 1);
34+
assert.strictEqual(convertToInt(-1, 12), 2 ** 12 - 1);
35+
assert.strictEqual(convertToInt(2 ** 11, 12, 'signed'), -(2 ** 11));
36+
assert.strictEqual(convertToInt(-1, 12, 'signed'), -1);
3337

3438
{
3539
const options = {
@@ -314,6 +318,23 @@ assert.throws(() => convertToInt(256, 8, 'unsigned', {
314318
assert.deepStrictEqual(calls, ['enforceRange', 'clamp']);
315319
}
316320

321+
{
322+
const calls = [];
323+
const options = {
324+
get enforceRange() {
325+
calls.push('enforceRange');
326+
return false;
327+
},
328+
get clamp() {
329+
calls.push('clamp');
330+
return true;
331+
},
332+
};
333+
334+
assert.strictEqual(convertToInt({ valueOf: () => 2.5 }, 8, 'unsigned', options), 2);
335+
assert.deepStrictEqual(calls, ['enforceRange', 'clamp']);
336+
}
337+
317338
// Out of range: clamp
318339
assert.strictEqual(convertToInt(NaN, 64, 'unsigned', { clamp: true }), 0);
319340
assert.strictEqual(convertToInt(Infinity, 64, 'unsigned', { clamp: true }), Number.MAX_SAFE_INTEGER);

0 commit comments

Comments
 (0)