Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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 <[email protected]>
  • Loading branch information
panva committed Aug 7, 2026
commit 4308792d022646754a6448458fb4b8ffc32c0f64
56 changes: 45 additions & 11 deletions lib/internal/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
NumberIsNaN,
NumberMAX_SAFE_INTEGER,
NumberMIN_SAFE_INTEGER,
ObjectPrototypeHasOwnProperty,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
SafeSet,
Expand Down Expand Up @@ -699,16 +700,47 @@ function createDictionaryConverter(
const dictionaries = ArrayIsArray(members[0]) ? members : [members];
const sortedDictionaries = [];

function ownMember(member, key) {
return ObjectPrototypeHasOwnProperty(member, key) ? member[key] : undefined;
}

// Dictionaries with no defaults and no required members skip steps
// 4.1.5/4.1.6 entirely, keeping the absent-member path free.
let anyMissingMemberHandling = false;

// Web IDL dictionary conversion steps 3-4 process inherited dictionaries
// from least-derived to most-derived and sort only within each dictionary.
// Callers with inheritance pass one member array per dictionary level.
for (let i = 0; i < dictionaries.length; i++) {
ArrayPrototypePush(
sortedDictionaries,
ArrayPrototypeToSorted(dictionaries[i], compareMembers),
const sortedMembers = ArrayPrototypeToSorted(
dictionaries[i],
compareMembers,
);
// Definition sites spell out only the members they need, so reading the
// optional ones below would resolve through %Object.prototype%.
// Re-materialize each descriptor once with every key present, copied from
// own properties only. The ordinary prototype is deliberate: nothing
// consults it now, and detaching it measurably slows these reads down.
for (let j = 0; j < sortedMembers.length; j++) {
const member = sortedMembers[j];
const defaultValue = ownMember(member, 'defaultValue');
const required = ownMember(member, 'required');
if (typeof defaultValue === 'function' || required) {
anyMissingMemberHandling = true;
}
sortedMembers[j] = {
key: ownMember(member, 'key'),
converter: ownMember(member, 'converter'),
defaultValue,
required,
validator: ownMember(member, 'validator'),
};
}
ArrayPrototypePush(sortedDictionaries, sortedMembers);
}

const hasMissingMemberHandling = anyMissingMemberHandling;

return function(jsDict, options = kEmptyObject) {
// Step 1: reject non-object, non-null, non-undefined values.
if (jsDict != null && type(jsDict) !== 'Object') {
Expand Down Expand Up @@ -747,14 +779,16 @@ function createDictionaryConverter(
member.validator?.(idlMemberValue, jsDict);
// Step 4.1.4.2: set idlDict[key] to the IDL value.
idlDict[key] = idlMemberValue;
} else if (typeof member.defaultValue === 'function') {
// Step 4.1.5: store the member default value.
idlDict[key] = member.defaultValue();
} else if (member.required) {
// Step 4.1.6: required missing members throw.
throw makeException(
missingDictionaryMemberMessage(dictionaryName, key),
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
} else if (hasMissingMemberHandling) {
if (typeof member.defaultValue === 'function') {
// Step 4.1.5: store the member default value.
idlDict[key] = member.defaultValue();
} else if (member.required) {
// Step 4.1.6: required missing members throw.
throw makeException(
missingDictionaryMemberMessage(dictionaryName, key),
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
}
}
}
}
Expand Down