@@ -16505,6 +16505,26 @@ const UnitTables = exports.UnitTables = {
1650516505 }
1650616506 return smi(hashed);
1650716507 }
16508+ // Per-process seed for the secondary collision hash. Never exposed nor
16509+ // serialized, so the public `hash()` stays deterministic. An odd base in
16510+ // [3, 2^20) keeps `base * h` exact as a double (no `Math.imul`).
16511+ var COLLISION_HASH_BASE = ((Math.random() * 0x100000) | 1) % 0x100000 || 0x9e37;
16512+ // Secondary hash to index entries within a `HashCollisionNode`, where every key
16513+ // shares the same primary `hash()`. Using a different, seeded base scatters
16514+ // crafted collision families (e.g. "Aa"/"BB", which only collide under base 31)
16515+ // that an attacker cannot precompute without the seed. It only narrows
16516+ // candidates — `is()` still decides equality — so non-string keys can safely
16517+ // fall back to the (here constant) primary hash and a linear scan.
16518+ function hashCollisionKey(key) {
16519+ if (typeof key !== 'string') {
16520+ return hash(key);
16521+ }
16522+ var hashed = 0;
16523+ for (var ii = 0; ii < key.length; ii++) {
16524+ hashed = (COLLISION_HASH_BASE * hashed + key.charCodeAt(ii)) | 0;
16525+ }
16526+ return hashed;
16527+ }
1650816528 function hashSymbol(sym) {
1650916529 var hashed = symbolMap[sym];
1651016530 if (hashed !== undefined) {
@@ -18437,20 +18457,79 @@ const UnitTables = exports.UnitTables = {
1843718457 return new HashArrayMapNode(ownerID, newCount, newNodes);
1843818458 };
1843918459
18460+ /**
18461+ * Trie leaf gathering entries whose keys all share the same 32-bit `hash()`.
18462+ * The trie routes by hash, so colliding keys cannot be separated and land here
18463+ * in a flat `entries` array, disambiguated by `is()`.
18464+ *
18465+ * To guard against hash-flooding DoS (CWE-407), large buckets build a secondary
18466+ * index keyed by a per-process seeded hash (`hashCollisionKey`). `is()` still
18467+ * decides equality, so the index can only ever narrow candidates, never lose a key.
18468+ */
1844018469 var HashCollisionNode = function HashCollisionNode(ownerID, keyHash, entries) {
1844118470 this.ownerID = ownerID;
1844218471 this.keyHash = keyHash;
1844318472 this.entries = entries;
18473+ // Lazy `{ [secondaryHash]: number[] }`, built only past
18474+ // MIN_HASH_COLLISION_INDEX_SIZE so small buckets keep their linear path.
18475+ this._index = undefined;
1844418476 };
1844518477
18446- HashCollisionNode.prototype.get = function get (shift, keyHash, key, notSetValue) {
18478+ // Returns the position of `key` in `this.entries`, or -1. Uses the secondary
18479+ // index when present; builds it only when `buildIndex` is true (reads and
18480+ // transient inserts, where the node is reused so the O(n) build amortizes).
18481+ // Persistent inserts already pay an O(n) copy, so a throwaway index is skipped.
18482+ HashCollisionNode.prototype._positionOf = function _positionOf (key, buildIndex) {
18483+ var entries = this.entries;
18484+ var index = this._index;
18485+ if (
18486+ index === undefined &&
18487+ buildIndex &&
18488+ entries.length >= MIN_HASH_COLLISION_INDEX_SIZE
18489+ ) {
18490+ index = this._buildIndex();
18491+ }
18492+ if (index !== undefined) {
18493+ var positions = index[hashCollisionKey(key)];
18494+ if (positions !== undefined) {
18495+ for (var jj = 0; jj < positions.length; jj++) {
18496+ var ii = positions[jj];
18497+ if (is(key, entries[ii][0])) {
18498+ return ii;
18499+ }
18500+ }
18501+ }
18502+ return -1;
18503+ }
18504+ for (var ii$1 = 0, len = entries.length; ii$1 < len; ii$1++) {
18505+ if (is(key, entries[ii$1][0])) {
18506+ return ii$1;
18507+ }
18508+ }
18509+ return -1;
18510+ };
18511+
18512+ // Builds and memoizes the secondary index. A plain object, not `Map` — which
18513+ // in this module resolves to the *Immutable* Map, not the native one.
18514+ HashCollisionNode.prototype._buildIndex = function _buildIndex () {
18515+ var index = Object.create(null);
1844718516 var entries = this.entries;
1844818517 for (var ii = 0, len = entries.length; ii < len; ii++) {
18449- if (is(key, entries[ii][0])) {
18450- return entries[ii][1];
18518+ var secondaryHash = hashCollisionKey(entries[ii][0]);
18519+ var positions = index[secondaryHash];
18520+ if (positions !== undefined) {
18521+ positions.push(ii);
18522+ } else {
18523+ index[secondaryHash] = [ii];
1845118524 }
1845218525 }
18453- return notSetValue;
18526+ this._index = index;
18527+ return index;
18528+ };
18529+
18530+ HashCollisionNode.prototype.get = function get (shift, keyHash, key, notSetValue) {
18531+ var idx = this._positionOf(key, true);
18532+ return idx === -1 ? notSetValue : this.entries[idx][1];
1845418533 };
1845518534
1845618535 HashCollisionNode.prototype.update = function update (ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
@@ -18470,14 +18549,11 @@ const UnitTables = exports.UnitTables = {
1847018549 }
1847118550
1847218551 var entries = this.entries;
18473- var idx = 0;
1847418552 var len = entries.length;
18475- for (; idx < len; idx++) {
18476- if (is(key, entries[idx][0])) {
18477- break;
18478- }
18479- }
18480- var exists = idx < len;
18553+ var isEditable = ownerID && ownerID === this.ownerID;
18554+ var foundIdx = this._positionOf(key, isEditable);
18555+ var idx = foundIdx === -1 ? len : foundIdx;
18556+ var exists = foundIdx !== -1;
1848118557
1848218558 if (exists ? entries[idx][1] === value : removed) {
1848318559 return this;
@@ -18491,7 +18567,6 @@ const UnitTables = exports.UnitTables = {
1849118567 return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]);
1849218568 }
1849318569
18494- var isEditable = ownerID && ownerID === this.ownerID;
1849518570 var newEntries = isEditable ? entries : arrCopy(entries);
1849618571
1849718572 if (exists) {
@@ -18500,11 +18575,27 @@ const UnitTables = exports.UnitTables = {
1850018575 idx === len - 1
1850118576 ? newEntries.pop()
1850218577 : (newEntries[idx] = newEntries.pop());
18578+ // The swap-pop reshuffles positions; drop the stale index (rebuilt lazily).
18579+ if (isEditable) {
18580+ this._index = undefined;
18581+ }
1850318582 } else {
18583+ // Same key, same position: the index stays valid.
1850418584 newEntries[idx] = [key, value];
1850518585 }
1850618586 } else {
1850718587 newEntries.push([key, value]);
18588+ // Keep the index in sync on the transient insert path. Persistent inserts
18589+ // return a fresh node below whose index rebuilds lazily, so skip them.
18590+ if (isEditable && this._index !== undefined) {
18591+ var secondaryHash = hashCollisionKey(key);
18592+ var positions = this._index[secondaryHash];
18593+ if (positions !== undefined) {
18594+ positions.push(len);
18595+ } else {
18596+ this._index[secondaryHash] = [len];
18597+ }
18598+ }
1850818599 }
1850918600
1851018601 if (isEditable) {
@@ -18838,6 +18929,12 @@ const UnitTables = exports.UnitTables = {
1883818929 var MAX_BITMAP_INDEXED_SIZE = SIZE / 2;
1883918930 var MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4;
1884018931
18932+ // Above this many colliding entries, a `HashCollisionNode` builds a seeded
18933+ // secondary index instead of scanning linearly. Kept small so the rare,
18934+ // naturally-occurring collision buckets stay overhead-free, while adversarial
18935+ // hash-flooding (thousands of keys sharing one hash) degrades gracefully.
18936+ var MIN_HASH_COLLISION_INDEX_SIZE = 16;
18937+
1884118938 function coerceKeyPath(keyPath) {
1884218939 if (isArrayLike(keyPath) && typeof keyPath !== 'string') {
1884318940 return keyPath;
@@ -19026,7 +19123,7 @@ const UnitTables = exports.UnitTables = {
1902619123 assertNotInfinite(size);
1902719124 if (size > 0 && size < SIZE) {
1902819125 // eslint-disable-next-line no-constructor-return
19029- return makeList(0, size, SHIFT, null , new VNode(iter.toArray()));
19126+ return makeList(0, size, SHIFT, undefined , new VNode(iter.toArray()));
1903019127 }
1903119128 // eslint-disable-next-line no-constructor-return
1903219129 return empty.withMutations(function (list) {
@@ -19269,6 +19366,13 @@ const UnitTables = exports.UnitTables = {
1926919366 return obj.asImmutable();
1927019367 };
1927119368
19369+ /**
19370+ * A node in the List's 32-wide trie. At inner levels `array` holds child
19371+ * `VNode`s; at the leaf level it holds the List's values. Missing slots are
19372+ * `undefined` array holes.
19373+ *
19374+ * @template T
19375+ */
1927219376 var VNode = function VNode(array, ownerID) {
1927319377 this.array = array;
1927419378 this.ownerID = ownerID;
@@ -19405,6 +19509,16 @@ const UnitTables = exports.UnitTables = {
1940519509 }
1940619510 }
1940719511
19512+ /**
19513+ * @param {number} origin
19514+ * @param {number} capacity
19515+ * @param {number} level
19516+ * @param {VNode | undefined} [root] The trie root, or `undefined` when every
19517+ * in-range value lives in the tail (or is a virtual `undefined`).
19518+ * @param {VNode | undefined} [tail]
19519+ * @param {OwnerID} [ownerID]
19520+ * @param {number} [hash]
19521+ */
1940819522 function makeList(origin, capacity, level, root, tail, ownerID, hash) {
1940919523 var list = Object.create(ListPrototype);
1941019524 list.size = capacity - origin;
@@ -19522,6 +19636,14 @@ const UnitTables = exports.UnitTables = {
1952219636 return new VNode(node ? node.array.slice() : [], ownerID);
1952319637 }
1952419638
19639+ /**
19640+ * Returns the leaf `VNode` holding `rawIndex`, or `undefined` when no node is
19641+ * allocated for it (an all-`undefined` region).
19642+ *
19643+ * @param {List} list
19644+ * @param {number} rawIndex
19645+ * @returns {VNode | undefined}
19646+ */
1952519647 function listNodeFor(list, rawIndex) {
1952619648 if (rawIndex >= getTailOffset(list._capacity)) {
1952719649 return list._tail;
@@ -19537,7 +19659,39 @@ const UnitTables = exports.UnitTables = {
1953719659 }
1953819660 }
1953919661
19662+ /**
19663+ * Validates requested bounds before int32 coercion in setListBounds().
19664+ * Throws when origin/capacity would exceed the trie's safe range.
19665+ */
19666+ function validateListBoundsRequest(list, begin, end) {
19667+ var requestedOrigin = list._origin + (begin === undefined ? 0 : begin);
19668+ var requestedCapacity =
19669+ end === undefined
19670+ ? list._capacity
19671+ : end < 0
19672+ ? list._capacity + end
19673+ : list._origin + end;
19674+
19675+ // Keep origin/capacity within the trie's safe signed 32-bit range.
19676+ if (
19677+ (Number.isFinite(requestedCapacity) && requestedCapacity > MAX_LIST_SIZE) ||
19678+ (Number.isFinite(requestedOrigin) && requestedOrigin < -MAX_LIST_SIZE) ||
19679+ (Number.isFinite(requestedCapacity) &&
19680+ Number.isFinite(requestedOrigin) &&
19681+ requestedCapacity - requestedOrigin > MAX_LIST_SIZE)
19682+ ) {
19683+ throw new RangeError(
19684+ 'Invalid List size: a List cannot hold more than ' +
19685+ MAX_LIST_SIZE +
19686+ ' (2 ** 30) values.'
19687+ );
19688+ }
19689+ }
19690+
1954019691 function setListBounds(list, begin, end) {
19692+ // Validate full-precision bounds before int32 coercion.
19693+ validateListBoundsRequest(list, begin, end);
19694+
1954119695 // Sanitize begin & end using this shorthand for ToInt32(argument)
1954219696 // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32
1954319697 if (begin !== undefined) {
@@ -19576,7 +19730,8 @@ const UnitTables = exports.UnitTables = {
1957619730 owner
1957719731 );
1957819732 newLevel += SHIFT;
19579- offsetShift += 1 << newLevel;
19733+ // Shift origin into non-negative space as trie height grows.
19734+ offsetShift += levelCapacity(newLevel);
1958019735 }
1958119736 if (offsetShift) {
1958219737 newOrigin += offsetShift;
@@ -19589,7 +19744,7 @@ const UnitTables = exports.UnitTables = {
1958919744 var newTailOffset = getTailOffset(newCapacity);
1959019745
1959119746 // New size might need creating a higher root.
19592- while (newTailOffset >= 1 << (newLevel + SHIFT)) {
19747+ while (newTailOffset >= levelCapacity (newLevel + SHIFT)) {
1959319748 newRoot = new VNode(
1959419749 newRoot && newRoot.array.length ? [newRoot] : [],
1959519750 owner
@@ -19632,7 +19787,7 @@ const UnitTables = exports.UnitTables = {
1963219787 newOrigin -= newTailOffset;
1963319788 newCapacity -= newTailOffset;
1963419789 newLevel = SHIFT;
19635- newRoot = null ;
19790+ newRoot = undefined ;
1963619791 newTail = newTail && newTail.removeBefore(owner, 0, newOrigin);
1963719792
1963819793 // Otherwise, if the root has been trimmed, garbage collect.
@@ -19687,6 +19842,19 @@ const UnitTables = exports.UnitTables = {
1968719842 return size < SIZE ? 0 : ((size - 1) >>> SHIFT) << SHIFT;
1968819843 }
1968919844
19845+ // The largest number of values a List can hold. Above this the 32-bit trie math
19846+ // in setListBounds() stays in the safe signed 32-bit range.
19847+ var MAX_LIST_SIZE = Math.pow( 2, 30 ); // 1073741824
19848+
19849+ /**
19850+ * Computes 2 ** exp for the trie level-raising loops in setListBounds().
19851+ * Use the cheap bitwise operator shift whenever possible, otherwise fall back to exponentiation.
19852+ * This is necessary because bitwise operators in JavaScript only work on 32-bit signed integers, so for exp >= 31, we need to use exponentiation to avoid overflow.
19853+ */
19854+ function levelCapacity(exp) {
19855+ return exp < 31 ? 1 << exp : Math.pow( 2, exp );
19856+ }
19857+
1969019858 /**
1969119859 * True if `maybeOrderedMap` is an OrderedMap.
1969219860 */
@@ -20102,6 +20270,9 @@ const UnitTables = exports.UnitTables = {
2010220270 reduction = v;
2010320271 }
2010420272 else {
20273+ // `reduction` has already been seeded here (either with the provided
20274+ // initial value or with the first iterated value), so it is never the
20275+ // `undefined` placeholder — only a `V` or a `R`.
2010520276 reduction = reducer.call(context, reduction, v, k, c);
2010620277 }
2010720278 }, reverse);
@@ -21286,11 +21457,12 @@ const UnitTables = exports.UnitTables = {
2128621457
2128721458 has: function has(index) {
2128821459 index = wrapIndex(this, index);
21460+
2128921461 return (
2129021462 index >= 0 &&
2129121463 (this.size !== undefined
2129221464 ? this.size === Infinity || index < this.size
21293- : this.indexOf( index) !== -1 )
21465+ : this.find(function (_, key) { return key === index; }, undefined, NOT_SET ) !== NOT_SET )
2129421466 );
2129521467 },
2129621468
@@ -21751,15 +21923,15 @@ const UnitTables = exports.UnitTables = {
2175121923 };
2175221924
2175321925 Repeat.prototype.indexOf = function indexOf (searchValue) {
21754- if (is(this._value, searchValue)) {
21926+ if (this.size !== 0 && is(this._value, searchValue)) {
2175521927 return 0;
2175621928 }
2175721929 return -1;
2175821930 };
2175921931
2176021932 Repeat.prototype.lastIndexOf = function lastIndexOf (searchValue) {
21761- if (is(this._value, searchValue)) {
21762- return this.size;
21933+ if (this.size !== 0 && is(this._value, searchValue)) {
21934+ return this.size - 1 ;
2176321935 }
2176421936 return -1;
2176521937 };
@@ -21840,7 +22012,7 @@ const UnitTables = exports.UnitTables = {
2184022012 return isIndexed(v) ? v.toList() : isKeyed(v) ? v.toMap() : v.toSet();
2184122013 }
2184222014
21843- var version = "5.1.6 ";
22015+ var version = "5.1.9 ";
2184422016
2184522017 /* eslint-disable import/order */
2184622018
0 commit comments