Skip to content

Commit 638e07c

Browse files
panvaaduh95
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 7607ad4 commit 638e07c

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(
@@ -604,7 +623,7 @@ converters.Argon2Params = createDictionaryConverter(
604623
{
605624
key: 'parallelism',
606625
converter: (V, opts) =>
607-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
626+
converters['unsigned long'](V, enforceRangeOptions(opts)),
608627
validator: (V, dict) => {
609628
if (V === 0 || V > MathPow(2, 24) - 1) {
610629
throw lazyDOMException(
@@ -617,7 +636,7 @@ converters.Argon2Params = createDictionaryConverter(
617636
{
618637
key: 'memory',
619638
converter: (V, opts) =>
620-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
639+
converters['unsigned long'](V, enforceRangeOptions(opts)),
621640
validator: (V, dict) => {
622641
if (V < 8 * dict.parallelism) {
623642
throw lazyDOMException(
@@ -630,7 +649,7 @@ converters.Argon2Params = createDictionaryConverter(
630649
{
631650
key: 'passes',
632651
converter: (V, opts) =>
633-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
652+
converters['unsigned long'](V, enforceRangeOptions(opts)),
634653
validator: (V) => {
635654
if (V === 0) {
636655
throw lazyDOMException('passes must be > 0', 'OperationError');
@@ -641,7 +660,7 @@ converters.Argon2Params = createDictionaryConverter(
641660
{
642661
key: 'version',
643662
converter: (V, opts) =>
644-
converters.octet(V, { ...opts, enforceRange: true }),
663+
converters.octet(V, enforceRangeOptions(opts)),
645664
validator: (V, dict) => {
646665
if (V !== 0x13) {
647666
throw lazyDOMException(
@@ -680,7 +699,7 @@ for (const { 0: name, 1: zeroError } of [['KmacKeyGenParams', 'OperationError'],
680699
{
681700
key: 'length',
682701
converter: (V, opts) =>
683-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
702+
converters['unsigned long'](V, enforceRangeOptions(opts)),
684703
validator: validateMacKeyLength(`${name}.length`, zeroError),
685704
},
686705
],
@@ -694,7 +713,7 @@ converters.KmacParams = createDictionaryConverter(
694713
{
695714
key: 'outputLength',
696715
converter: (V, opts) =>
697-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
716+
converters['unsigned long'](V, enforceRangeOptions(opts)),
698717
validator: (V, opts) => {
699718
// The Web Crypto spec allows for KMAC output length that are not multiples of 8. We don't.
700719
if (V % 8)
@@ -716,7 +735,7 @@ converters.KangarooTwelveParams = createDictionaryConverter(
716735
{
717736
key: 'outputLength',
718737
converter: (V, opts) =>
719-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
738+
converters['unsigned long'](V, enforceRangeOptions(opts)),
720739
validator: (V, opts) => {
721740
if (V === 0 || V % 8)
722741
throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError');
@@ -737,7 +756,7 @@ converters.TurboShakeParams = createDictionaryConverter(
737756
{
738757
key: 'outputLength',
739758
converter: (V, opts) =>
740-
converters['unsigned long'](V, { ...opts, enforceRange: true }),
759+
converters['unsigned long'](V, enforceRangeOptions(opts)),
741760
validator: (V, opts) => {
742761
if (V === 0 || V % 8)
743762
throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError');
@@ -747,7 +766,7 @@ converters.TurboShakeParams = createDictionaryConverter(
747766
{
748767
key: 'domainSeparation',
749768
converter: (V, opts) =>
750-
converters.octet(V, { ...opts, enforceRange: true }),
769+
converters.octet(V, enforceRangeOptions(opts)),
751770
validator: (V) => {
752771
if (V < 0x01 || V > 0x7F) {
753772
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)