Skip to content

Commit 4758184

Browse files
panvanodejs-github-bot
authored andcommitted
lib: harden webidl dictionary member reads
Member descriptors are plain object literals that spell out only the members they need, so createDictionaryConverter() reading the optional validator, defaultValue and required members off them resolves through %Object.prototype%. Copy each descriptor once at construction time with every key present. They keep an ordinary prototype because a null-prototype object literal lands in V8 dictionary mode, and dictionaries with no defaults and no required members now skip steps 4.1.5 and 4.1.6. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #65115 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Aviv Keller <me@aviv.sh>
1 parent abbf086 commit 4758184

1 file changed

Lines changed: 45 additions & 11 deletions

File tree

lib/internal/webidl.js

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const {
2020
NumberIsNaN,
2121
NumberMAX_SAFE_INTEGER,
2222
NumberMIN_SAFE_INTEGER,
23+
ObjectPrototypeHasOwnProperty,
2324
ObjectPrototypeIsPrototypeOf,
2425
SafeArrayIterator,
2526
SafeSet,
@@ -699,16 +700,47 @@ function createDictionaryConverter(
699700
const dictionaries = ArrayIsArray(members[0]) ? members : [members];
700701
const sortedDictionaries = [];
701702

703+
function ownMember(member, key) {
704+
return ObjectPrototypeHasOwnProperty(member, key) ? member[key] : undefined;
705+
}
706+
707+
// Dictionaries with no defaults and no required members skip steps
708+
// 4.1.5/4.1.6 entirely, keeping the absent-member path free.
709+
let anyMissingMemberHandling = false;
710+
702711
// Web IDL dictionary conversion steps 3-4 process inherited dictionaries
703712
// from least-derived to most-derived and sort only within each dictionary.
704713
// Callers with inheritance pass one member array per dictionary level.
705714
for (let i = 0; i < dictionaries.length; i++) {
706-
ArrayPrototypePush(
707-
sortedDictionaries,
708-
ArrayPrototypeToSorted(dictionaries[i], compareMembers),
715+
const sortedMembers = ArrayPrototypeToSorted(
716+
dictionaries[i],
717+
compareMembers,
709718
);
719+
// Definition sites spell out only the members they need, so reading the
720+
// optional ones below would resolve through %Object.prototype%.
721+
// Re-materialize each descriptor once with every key present, copied from
722+
// own properties only. The ordinary prototype is deliberate: nothing
723+
// consults it now, and detaching it measurably slows these reads down.
724+
for (let j = 0; j < sortedMembers.length; j++) {
725+
const member = sortedMembers[j];
726+
const defaultValue = ownMember(member, 'defaultValue');
727+
const required = ownMember(member, 'required');
728+
if (typeof defaultValue === 'function' || required) {
729+
anyMissingMemberHandling = true;
730+
}
731+
sortedMembers[j] = {
732+
key: ownMember(member, 'key'),
733+
converter: ownMember(member, 'converter'),
734+
defaultValue,
735+
required,
736+
validator: ownMember(member, 'validator'),
737+
};
738+
}
739+
ArrayPrototypePush(sortedDictionaries, sortedMembers);
710740
}
711741

742+
const hasMissingMemberHandling = anyMissingMemberHandling;
743+
712744
return function(jsDict, options = kEmptyObject) {
713745
// Step 1: reject non-object, non-null, non-undefined values.
714746
if (jsDict != null && type(jsDict) !== 'Object') {
@@ -747,14 +779,16 @@ function createDictionaryConverter(
747779
member.validator?.(idlMemberValue, jsDict);
748780
// Step 4.1.4.2: set idlDict[key] to the IDL value.
749781
idlDict[key] = idlMemberValue;
750-
} else if (typeof member.defaultValue === 'function') {
751-
// Step 4.1.5: store the member default value.
752-
idlDict[key] = member.defaultValue();
753-
} else if (member.required) {
754-
// Step 4.1.6: required missing members throw.
755-
throw makeException(
756-
missingDictionaryMemberMessage(dictionaryName, key),
757-
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
782+
} else if (hasMissingMemberHandling) {
783+
if (typeof member.defaultValue === 'function') {
784+
// Step 4.1.5: store the member default value.
785+
idlDict[key] = member.defaultValue();
786+
} else if (member.required) {
787+
// Step 4.1.6: required missing members throw.
788+
throw makeException(
789+
missingDictionaryMemberMessage(dictionaryName, key),
790+
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
791+
}
758792
}
759793
}
760794
}

0 commit comments

Comments
 (0)