Skip to content

Commit fa17dc2

Browse files
panvanodejs-github-bot
authored andcommitted
lib: optimize webidl conversion options
Replace object spread in nested WebIDL conversion options with stable-shape ordinary objects. This keeps hot dictionary and sequence conversion paths from allocating null-prototype spread results. Apply the same pattern to Web Crypto converter wrappers that override allowResizable or enable [EnforceRange]. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62756 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent e46e0d1 commit fa17dc2

2 files changed

Lines changed: 73 additions & 37 deletions

File tree

lib/internal/crypto/webidl.js

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ converters['sequence<KeyUsage>'] = createSequenceConverter(converters.KeyUsage);
106106

107107
converters.HashAlgorithmIdentifier = converters.AlgorithmIdentifier;
108108

109+
/**
110+
* Builds conversion options for Web Crypto integer members that use Web IDL
111+
* [EnforceRange]. Keep this helper instead of spreading opts in each member
112+
* converter so the hot dictionary paths allocate stable-shape objects.
113+
* @param {object} opts Parent conversion options.
114+
* @returns {object}
115+
*/
116+
function enforceRangeOptions(opts) {
117+
return {
118+
prefix: opts.prefix,
119+
context: opts.context,
120+
code: opts.code,
121+
enforceRange: true,
122+
};
123+
}
124+
109125
const dictAlgorithm = [
110126
{
111127
key: 'name',
@@ -121,8 +137,9 @@ converters.Algorithm = createDictionaryConverter(
121137
// converters.BigInteger = webidl.Uint8Array;
122138
converters.BigInteger = (V, opts = kEmptyObject) => {
123139
return webidl.Uint8Array(V, {
124-
__proto__: null,
125-
...opts,
140+
prefix: opts.prefix,
141+
context: opts.context,
142+
code: opts.code,
126143
allowResizable: true,
127144
allowShared: false,
128145
});
@@ -132,18 +149,20 @@ converters.BigInteger = (V, opts = kEmptyObject) => {
132149
// removing this altogether.
133150
converters.BufferSource = (V, opts = kEmptyObject) => {
134151
return webidl.BufferSource(V, {
135-
__proto__: null,
136-
...opts,
152+
prefix: opts.prefix,
153+
context: opts.context,
154+
code: opts.code,
137155
allowResizable: opts.allowResizable === undefined ?
138156
true : opts.allowResizable,
157+
allowShared: opts.allowShared,
139158
});
140159
};
141160

142161
const dictRsaKeyGenParams = [
143162
{
144163
key: 'modulusLength',
145164
converter: (V, opts) =>
146-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
165+
converters['unsigned long'](V, enforceRangeOptions(opts)),
147166
required: true,
148167
},
149168
{
@@ -221,7 +240,7 @@ converters.AesKeyGenParams = createDictionaryConverter(
221240
{
222241
key: 'length',
223242
converter: (V, opts) =>
224-
converters['unsigned short'](V, { ...opts, enforceRange: true }),
243+
converters['unsigned short'](V, enforceRangeOptions(opts)),
225244
validator: AESLengthValidator,
226245
required: true,
227246
},
@@ -244,7 +263,7 @@ converters.RsaPssParams = createDictionaryConverter(
244263
{
245264
key: 'saltLength',
246265
converter: (V, opts) =>
247-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
266+
converters['unsigned long'](V, enforceRangeOptions(opts)),
248267
required: true,
249268
},
250269
],
@@ -288,7 +307,7 @@ for (const { 0: name, 1: zeroError } of [['HmacKeyGenParams', 'OperationError'],
288307
{
289308
key: 'length',
290309
converter: (V, opts) =>
291-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
310+
converters['unsigned long'](V, enforceRangeOptions(opts)),
292311
validator: validateMacKeyLength(`${name}.length`, zeroError),
293312
},
294313
],
@@ -370,7 +389,7 @@ converters.CShakeParams = createDictionaryConverter(
370389
{
371390
key: 'outputLength',
372391
converter: (V, opts) =>
373-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
392+
converters['unsigned long'](V, enforceRangeOptions(opts)),
374393
validator: (V, opts) => {
375394
// The Web Crypto spec allows for SHAKE output length that are not multiples of
376395
// 8. We don't.
@@ -404,7 +423,7 @@ converters.Pbkdf2Params = createDictionaryConverter(
404423
{
405424
key: 'iterations',
406425
converter: (V, opts) =>
407-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
426+
converters['unsigned long'](V, enforceRangeOptions(opts)),
408427
validator: (V, dict) => {
409428
if (V === 0)
410429
throw lazyDOMException('iterations cannot be zero', 'OperationError');
@@ -427,7 +446,7 @@ converters.AesDerivedKeyParams = createDictionaryConverter(
427446
{
428447
key: 'length',
429448
converter: (V, opts) =>
430-
converters['unsigned short'](V, { ...opts, enforceRange: true }),
449+
converters['unsigned short'](V, enforceRangeOptions(opts)),
431450
validator: AESLengthValidator,
432451
required: true,
433452
},
@@ -481,7 +500,7 @@ converters.AeadParams = createDictionaryConverter(
481500
{
482501
key: 'tagLength',
483502
converter: (V, opts) =>
484-
converters.octet(V, { ...opts, enforceRange: true }),
503+
converters.octet(V, enforceRangeOptions(opts)),
485504
validator: (V, dict) => {
486505
switch (StringPrototypeToLowerCase(dict.name)) {
487506
case 'chacha20-poly1305':
@@ -524,7 +543,7 @@ converters.AesCtrParams = createDictionaryConverter(
524543
{
525544
key: 'length',
526545
converter: (V, opts) =>
527-
converters.octet(V, { ...opts, enforceRange: true }),
546+
converters.octet(V, enforceRangeOptions(opts)),
528547
validator: (V, dict) => {
529548
if (V === 0 || V > 128)
530549
throw lazyDOMException(
@@ -600,7 +619,7 @@ converters.Argon2Params = createDictionaryConverter(
600619
{
601620
key: 'parallelism',
602621
converter: (V, opts) =>
603-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
622+
converters['unsigned long'](V, enforceRangeOptions(opts)),
604623
validator: (V, dict) => {
605624
if (V === 0 || V > MathPow(2, 24) - 1) {
606625
throw lazyDOMException(
@@ -613,7 +632,7 @@ converters.Argon2Params = createDictionaryConverter(
613632
{
614633
key: 'memory',
615634
converter: (V, opts) =>
616-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
635+
converters['unsigned long'](V, enforceRangeOptions(opts)),
617636
validator: (V, dict) => {
618637
if (V < 8 * dict.parallelism) {
619638
throw lazyDOMException(
@@ -626,7 +645,7 @@ converters.Argon2Params = createDictionaryConverter(
626645
{
627646
key: 'passes',
628647
converter: (V, opts) =>
629-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
648+
converters['unsigned long'](V, enforceRangeOptions(opts)),
630649
validator: (V) => {
631650
if (V === 0) {
632651
throw lazyDOMException('passes must be > 0', 'OperationError');
@@ -637,7 +656,7 @@ converters.Argon2Params = createDictionaryConverter(
637656
{
638657
key: 'version',
639658
converter: (V, opts) =>
640-
converters.octet(V, { ...opts, enforceRange: true }),
659+
converters.octet(V, enforceRangeOptions(opts)),
641660
validator: (V, dict) => {
642661
if (V !== 0x13) {
643662
throw lazyDOMException(
@@ -676,7 +695,7 @@ for (const { 0: name, 1: zeroError } of [['KmacKeyGenParams', 'OperationError'],
676695
{
677696
key: 'length',
678697
converter: (V, opts) =>
679-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
698+
converters['unsigned long'](V, enforceRangeOptions(opts)),
680699
validator: validateMacKeyLength(`${name}.length`, zeroError),
681700
},
682701
],
@@ -690,7 +709,7 @@ converters.KmacParams = createDictionaryConverter(
690709
{
691710
key: 'outputLength',
692711
converter: (V, opts) =>
693-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
712+
converters['unsigned long'](V, enforceRangeOptions(opts)),
694713
validator: (V, opts) => {
695714
// The Web Crypto spec allows for KMAC output length that are not multiples of 8. We don't.
696715
if (V % 8)
@@ -712,7 +731,7 @@ converters.KangarooTwelveParams = createDictionaryConverter(
712731
{
713732
key: 'outputLength',
714733
converter: (V, opts) =>
715-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
734+
converters['unsigned long'](V, enforceRangeOptions(opts)),
716735
validator: (V, opts) => {
717736
if (V === 0 || V % 8)
718737
throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError');
@@ -733,7 +752,7 @@ converters.TurboShakeParams = createDictionaryConverter(
733752
{
734753
key: 'outputLength',
735754
converter: (V, opts) =>
736-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
755+
converters['unsigned long'](V, enforceRangeOptions(opts)),
737756
validator: (V, opts) => {
738757
if (V === 0 || V % 8)
739758
throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError');
@@ -743,7 +762,7 @@ converters.TurboShakeParams = createDictionaryConverter(
743762
{
744763
key: 'domainSeparation',
745764
converter: (V, opts) =>
746-
converters.octet(V, { ...opts, enforceRange: true }),
765+
converters.octet(V, enforceRangeOptions(opts)),
747766
validator: (V) => {
748767
if (V < 0x01 || V > 0x7F) {
749768
throw lazyDOMException(

lib/internal/webidl.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,28 @@ function makeException(message, options = kEmptyObject) {
109109
);
110110
}
111111

112+
/**
113+
* Builds derived conversion options for nested converter calls and adjusted
114+
* error codes. These objects are allocated on dictionary/sequence conversion
115+
* hot paths, so keep their shape stable and avoid object spread and
116+
* null-prototype objects.
117+
* @param {ConversionOptions} options Parent conversion options.
118+
* @param {string} [context] Replacement context.
119+
* @param {string} [code] Replacement error code.
120+
* @returns {ConversionOptions}
121+
*/
122+
function makeOptions(options, context = options.context, code = options.code) {
123+
return {
124+
prefix: options.prefix,
125+
context,
126+
code,
127+
enforceRange: options.enforceRange,
128+
clamp: options.clamp,
129+
allowShared: options.allowShared,
130+
allowResizable: options.allowResizable,
131+
};
132+
}
133+
112134
/**
113135
* Returns the ECMAScript specification type of a JavaScript value.
114136
* @see https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
@@ -376,7 +398,7 @@ function convertToInt(
376398
if (integer < lowerBound || integer > upperBound) {
377399
throw makeException(
378400
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
379-
{ __proto__: null, ...options, code: 'ERR_OUT_OF_RANGE' });
401+
makeOptions(options, options.context, 'ERR_OUT_OF_RANGE'));
380402
}
381403

382404
return integer;
@@ -416,7 +438,7 @@ function convertToInt(
416438
if (x < lowerBound || x > upperBound) {
417439
throw makeException(
418440
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
419-
{ __proto__: null, ...options, code: 'ERR_OUT_OF_RANGE' });
441+
makeOptions(options, options.context, 'ERR_OUT_OF_RANGE'));
420442
}
421443

422444
return x;
@@ -593,7 +615,7 @@ function requiredArguments(length, required, options = kEmptyObject) {
593615
`${required} argument${
594616
required === 1 ? '' : 's'
595617
} required, but only ${length} present.`,
596-
{ __proto__: null, ...options, context: '', code: 'ERR_MISSING_ARGS' });
618+
makeOptions(options, '', 'ERR_MISSING_ARGS'));
597619
}
598620
}
599621

@@ -615,7 +637,7 @@ function createEnumConverter(name, values) {
615637
if (!E.has(S)) {
616638
throw makeException(
617639
`'${S}' is not a valid enum value of type ${name}.`,
618-
{ __proto__: null, ...options, code: 'ERR_INVALID_ARG_VALUE' });
640+
makeOptions(options, options.context, 'ERR_INVALID_ARG_VALUE'));
619641
}
620642

621643
// Step 3: return the matching enumeration value.
@@ -715,11 +737,7 @@ function createDictionaryConverter(
715737
// Step 4.1.4.1: convert the JavaScript value to IDL.
716738
const idlMemberValue = converter(
717739
jsMemberValue,
718-
{
719-
__proto__: null,
720-
...options,
721-
context: dictionaryMemberContext(key, options),
722-
},
740+
makeOptions(options, dictionaryMemberContext(key, options)),
723741
);
724742
// Validators are a Node.js extension after conversion. They let
725743
// consumers reject known unsupported values while dictionary
@@ -736,7 +754,7 @@ function createDictionaryConverter(
736754
// Step 4.1.6: required missing members throw.
737755
throw makeException(
738756
missingDictionaryMemberMessage(dictionaryName, key),
739-
{ __proto__: null, ...options, code: 'ERR_MISSING_OPTION' });
757+
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
740758
}
741759
}
742760
}
@@ -794,11 +812,10 @@ function createSequenceConverter(converter) {
794812
break;
795813
}
796814
// Step 3.3: convert next to an IDL value of type T.
797-
const idlValue = converter(next.value, {
798-
__proto__: null,
799-
...options,
800-
context: sequenceElementContext(idlSequence.length, options),
801-
});
815+
const idlValue = converter(
816+
next.value,
817+
makeOptions(options, sequenceElementContext(idlSequence.length, options)),
818+
);
802819
// Step 3.4: store the value and advance i.
803820
ArrayPrototypePush(idlSequence, idlValue);
804821
}

0 commit comments

Comments
 (0)