lib: convert signals to array before validation

Co-authored-by: Jake Yuesong Li <jake.yuesong@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/54714
Fixes: https://github.com/nodejs/node/issues/54674
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Jason Zhang 2024-09-07 03:10:21 +09:30 committed by GitHub
parent 1c67899ffb
commit 973144b7c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 38 deletions

View File

@ -39,6 +39,10 @@ const {
ERR_INVALID_THIS,
},
} = require('internal/errors');
const {
converters,
createSequenceConverter,
} = require('internal/webidl');
const {
validateAbortSignal,
@ -225,15 +229,19 @@ class AbortSignal extends EventTarget {
* @returns {AbortSignal}
*/
static any(signals) {
validateAbortSignalArray(signals, 'signals');
const signalsArray = createSequenceConverter(
converters.any,
)(signals);
validateAbortSignalArray(signalsArray, 'signals');
const resultSignal = new AbortSignal(kDontThrowSymbol, { composite: true });
if (!signals.length) {
if (!signalsArray.length) {
return resultSignal;
}
const resultSignalWeakRef = new WeakRef(resultSignal);
resultSignal[kSourceSignals] = new SafeSet();
for (let i = 0; i < signals.length; i++) {
const signal = signals[i];
for (let i = 0; i < signalsArray.length; i++) {
const signal = signalsArray[i];
if (signal.aborted) {
abortSignal(resultSignal, signal.reason);
return resultSignal;

View File

@ -22,7 +22,6 @@ const {
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
String,
SymbolIterator,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
globalThis: {
@ -33,6 +32,7 @@ const {
const {
makeException,
createEnumConverter,
createSequenceConverter,
} = require('internal/webidl');
const {
@ -293,39 +293,6 @@ function createDictionaryConverter(name, dictionaries) {
};
}
function createSequenceConverter(converter) {
return function(V, opts = kEmptyObject) {
if (type(V) !== 'Object') {
throw makeException(
'can not be converted to sequence.',
opts);
}
const iter = V?.[SymbolIterator]?.();
if (iter === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
const array = [];
while (true) {
const res = iter?.next?.();
if (res === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
if (res.done === true) break;
const val = converter(res.value, {
__proto__: null,
...opts,
context: `${opts.context}, index ${array.length}`,
});
ArrayPrototypePush(array, val);
}
return array;
};
}
function createInterfaceConverter(name, prototype) {
return (V, opts) => {
if (!ObjectPrototypeIsPrototypeOf(prototype, V)) {

View File

@ -1,6 +1,7 @@
'use strict';
const {
ArrayPrototypePush,
MathAbs,
MathMax,
MathMin,
@ -13,6 +14,7 @@ const {
ObjectAssign,
SafeSet,
String,
SymbolIterator,
TypeError,
} = primordials;
@ -25,6 +27,15 @@ const { kEmptyObject } = require('internal/util');
const converters = { __proto__: null };
/**
* @see https://webidl.spec.whatwg.org/#es-any
* @param {any} V
* @returns {any}
*/
converters.any = (V) => {
return V;
};
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
const integerPart = MathTrunc;
@ -209,10 +220,76 @@ function createEnumConverter(name, values) {
};
}
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
function type(V) {
if (V === null)
return 'Null';
switch (typeof V) {
case 'undefined':
return 'Undefined';
case 'boolean':
return 'Boolean';
case 'number':
return 'Number';
case 'string':
return 'String';
case 'symbol':
return 'Symbol';
case 'bigint':
return 'BigInt';
case 'object': // Fall through
case 'function': // Fall through
default:
// Per ES spec, typeof returns an implemention-defined value that is not
// any of the existing ones for uncallable non-standard exotic objects.
// Yet Type() which the Web IDL spec depends on returns Object for such
// cases. So treat the default case as an object.
return 'Object';
}
}
// https://webidl.spec.whatwg.org/#es-sequence
function createSequenceConverter(converter) {
return function(V, opts = kEmptyObject) {
if (type(V) !== 'Object') {
throw makeException(
'can not be converted to sequence.',
opts);
}
const iter = V?.[SymbolIterator]?.();
if (iter === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
const array = [];
while (true) {
const res = iter?.next?.();
if (res === undefined) {
throw makeException(
'can not be converted to sequence.',
opts);
}
if (res.done === true) break;
const val = converter(res.value, {
__proto__: null,
...opts,
context: `${opts.context}, index ${array.length}`,
});
ArrayPrototypePush(array, val);
};
return array;
};
}
module.exports = {
type,
converters,
convertToInt,
createEnumConverter,
createSequenceConverter,
evenRound,
makeException,
};

View File

@ -101,4 +101,21 @@ describe('AbortSignal.any()', { concurrency: !process.env.TEST_PARALLEL }, () =>
controller.abort();
assert.strictEqual(result, '01234');
});
it('must accept WebIDL sequence', () => {
const controller = new AbortController();
const iterable = {
*[Symbol.iterator]() {
yield controller.signal;
yield new AbortController().signal;
yield new AbortController().signal;
yield new AbortController().signal;
},
};
const signal = AbortSignal.any(iterable);
let result = 0;
signal.addEventListener('abort', () => result += 1);
controller.abort();
assert.strictEqual(result, 1);
});
});