@@ -16728,6 +16728,26 @@ const UnitTables = exports.UnitTables = {
1672816728 }
1672916729 return smi(hashed);
1673016730 }
16731+ // Per-process seed for the secondary collision hash. Never exposed nor
16732+ // serialized, so the public `hash()` stays deterministic. An odd base in
16733+ // [3, 2^20) keeps `base * h` exact as a double (no `Math.imul`).
16734+ var COLLISION_HASH_BASE = ((Math.random() * 0x100000) | 1) % 0x100000 || 0x9e37;
16735+ // Secondary hash to index entries within a `HashCollisionNode`, where every key
16736+ // shares the same primary `hash()`. Using a different, seeded base scatters
16737+ // crafted collision families (e.g. "Aa"/"BB", which only collide under base 31)
16738+ // that an attacker cannot precompute without the seed. It only narrows
16739+ // candidates — `is()` still decides equality — so non-string keys can safely
16740+ // fall back to the (here constant) primary hash and a linear scan.
16741+ function hashCollisionKey(key) {
16742+ if (typeof key !== 'string') {
16743+ return hash(key);
16744+ }
16745+ var hashed = 0;
16746+ for (var ii = 0; ii < key.length; ii++) {
16747+ hashed = (COLLISION_HASH_BASE * hashed + key.charCodeAt(ii)) | 0;
16748+ }
16749+ return hashed;
16750+ }
1673116751 function hashSymbol(sym) {
1673216752 var hashed = symbolMap[sym];
1673316753 if (hashed !== undefined) {
@@ -18660,20 +18680,79 @@ const UnitTables = exports.UnitTables = {
1866018680 return new HashArrayMapNode(ownerID, newCount, newNodes);
1866118681 };
1866218682
18683+ /**
18684+ * Trie leaf gathering entries whose keys all share the same 32-bit `hash()`.
18685+ * The trie routes by hash, so colliding keys cannot be separated and land here
18686+ * in a flat `entries` array, disambiguated by `is()`.
18687+ *
18688+ * To guard against hash-flooding DoS (CWE-407), large buckets build a secondary
18689+ * index keyed by a per-process seeded hash (`hashCollisionKey`). `is()` still
18690+ * decides equality, so the index can only ever narrow candidates, never lose a key.
18691+ */
1866318692 var HashCollisionNode = function HashCollisionNode(ownerID, keyHash, entries) {
1866418693 this.ownerID = ownerID;
1866518694 this.keyHash = keyHash;
1866618695 this.entries = entries;
18696+ // Lazy `{ [secondaryHash]: number[] }`, built only past
18697+ // MIN_HASH_COLLISION_INDEX_SIZE so small buckets keep their linear path.
18698+ this._index = undefined;
1866718699 };
1866818700
18669- HashCollisionNode.prototype.get = function get (shift, keyHash, key, notSetValue) {
18701+ // Returns the position of `key` in `this.entries`, or -1. Uses the secondary
18702+ // index when present; builds it only when `buildIndex` is true (reads and
18703+ // transient inserts, where the node is reused so the O(n) build amortizes).
18704+ // Persistent inserts already pay an O(n) copy, so a throwaway index is skipped.
18705+ HashCollisionNode.prototype._positionOf = function _positionOf (key, buildIndex) {
18706+ var entries = this.entries;
18707+ var index = this._index;
18708+ if (
18709+ index === undefined &&
18710+ buildIndex &&
18711+ entries.length >= MIN_HASH_COLLISION_INDEX_SIZE
18712+ ) {
18713+ index = this._buildIndex();
18714+ }
18715+ if (index !== undefined) {
18716+ var positions = index[hashCollisionKey(key)];
18717+ if (positions !== undefined) {
18718+ for (var jj = 0; jj < positions.length; jj++) {
18719+ var ii = positions[jj];
18720+ if (is(key, entries[ii][0])) {
18721+ return ii;
18722+ }
18723+ }
18724+ }
18725+ return -1;
18726+ }
18727+ for (var ii$1 = 0, len = entries.length; ii$1 < len; ii$1++) {
18728+ if (is(key, entries[ii$1][0])) {
18729+ return ii$1;
18730+ }
18731+ }
18732+ return -1;
18733+ };
18734+
18735+ // Builds and memoizes the secondary index. A plain object, not `Map` — which
18736+ // in this module resolves to the *Immutable* Map, not the native one.
18737+ HashCollisionNode.prototype._buildIndex = function _buildIndex () {
18738+ var index = Object.create(null);
1867018739 var entries = this.entries;
1867118740 for (var ii = 0, len = entries.length; ii < len; ii++) {
18672- if (is(key, entries[ii][0])) {
18673- return entries[ii][1];
18741+ var secondaryHash = hashCollisionKey(entries[ii][0]);
18742+ var positions = index[secondaryHash];
18743+ if (positions !== undefined) {
18744+ positions.push(ii);
18745+ } else {
18746+ index[secondaryHash] = [ii];
1867418747 }
1867518748 }
18676- return notSetValue;
18749+ this._index = index;
18750+ return index;
18751+ };
18752+
18753+ HashCollisionNode.prototype.get = function get (shift, keyHash, key, notSetValue) {
18754+ var idx = this._positionOf(key, true);
18755+ return idx === -1 ? notSetValue : this.entries[idx][1];
1867718756 };
1867818757
1867918758 HashCollisionNode.prototype.update = function update (ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
@@ -18693,14 +18772,11 @@ const UnitTables = exports.UnitTables = {
1869318772 }
1869418773
1869518774 var entries = this.entries;
18696- var idx = 0;
1869718775 var len = entries.length;
18698- for (; idx < len; idx++) {
18699- if (is(key, entries[idx][0])) {
18700- break;
18701- }
18702- }
18703- var exists = idx < len;
18776+ var isEditable = ownerID && ownerID === this.ownerID;
18777+ var foundIdx = this._positionOf(key, isEditable);
18778+ var idx = foundIdx === -1 ? len : foundIdx;
18779+ var exists = foundIdx !== -1;
1870418780
1870518781 if (exists ? entries[idx][1] === value : removed) {
1870618782 return this;
@@ -18714,7 +18790,6 @@ const UnitTables = exports.UnitTables = {
1871418790 return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]);
1871518791 }
1871618792
18717- var isEditable = ownerID && ownerID === this.ownerID;
1871818793 var newEntries = isEditable ? entries : arrCopy(entries);
1871918794
1872018795 if (exists) {
@@ -18723,11 +18798,27 @@ const UnitTables = exports.UnitTables = {
1872318798 idx === len - 1
1872418799 ? newEntries.pop()
1872518800 : (newEntries[idx] = newEntries.pop());
18801+ // The swap-pop reshuffles positions; drop the stale index (rebuilt lazily).
18802+ if (isEditable) {
18803+ this._index = undefined;
18804+ }
1872618805 } else {
18806+ // Same key, same position: the index stays valid.
1872718807 newEntries[idx] = [key, value];
1872818808 }
1872918809 } else {
1873018810 newEntries.push([key, value]);
18811+ // Keep the index in sync on the transient insert path. Persistent inserts
18812+ // return a fresh node below whose index rebuilds lazily, so skip them.
18813+ if (isEditable && this._index !== undefined) {
18814+ var secondaryHash = hashCollisionKey(key);
18815+ var positions = this._index[secondaryHash];
18816+ if (positions !== undefined) {
18817+ positions.push(len);
18818+ } else {
18819+ this._index[secondaryHash] = [len];
18820+ }
18821+ }
1873118822 }
1873218823
1873318824 if (isEditable) {
@@ -19061,6 +19152,12 @@ const UnitTables = exports.UnitTables = {
1906119152 var MAX_BITMAP_INDEXED_SIZE = SIZE / 2;
1906219153 var MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4;
1906319154
19155+ // Above this many colliding entries, a `HashCollisionNode` builds a seeded
19156+ // secondary index instead of scanning linearly. Kept small so the rare,
19157+ // naturally-occurring collision buckets stay overhead-free, while adversarial
19158+ // hash-flooding (thousands of keys sharing one hash) degrades gracefully.
19159+ var MIN_HASH_COLLISION_INDEX_SIZE = 16;
19160+
1906419161 function coerceKeyPath(keyPath) {
1906519162 if (isArrayLike(keyPath) && typeof keyPath !== 'string') {
1906619163 return keyPath;
@@ -19249,7 +19346,7 @@ const UnitTables = exports.UnitTables = {
1924919346 assertNotInfinite(size);
1925019347 if (size > 0 && size < SIZE) {
1925119348 // eslint-disable-next-line no-constructor-return
19252- return makeList(0, size, SHIFT, null , new VNode(iter.toArray()));
19349+ return makeList(0, size, SHIFT, undefined , new VNode(iter.toArray()));
1925319350 }
1925419351 // eslint-disable-next-line no-constructor-return
1925519352 return empty.withMutations(function (list) {
@@ -19492,6 +19589,13 @@ const UnitTables = exports.UnitTables = {
1949219589 return obj.asImmutable();
1949319590 };
1949419591
19592+ /**
19593+ * A node in the List's 32-wide trie. At inner levels `array` holds child
19594+ * `VNode`s; at the leaf level it holds the List's values. Missing slots are
19595+ * `undefined` array holes.
19596+ *
19597+ * @template T
19598+ */
1949519599 var VNode = function VNode(array, ownerID) {
1949619600 this.array = array;
1949719601 this.ownerID = ownerID;
@@ -19628,6 +19732,16 @@ const UnitTables = exports.UnitTables = {
1962819732 }
1962919733 }
1963019734
19735+ /**
19736+ * @param {number} origin
19737+ * @param {number} capacity
19738+ * @param {number} level
19739+ * @param {VNode | undefined} [root] The trie root, or `undefined` when every
19740+ * in-range value lives in the tail (or is a virtual `undefined`).
19741+ * @param {VNode | undefined} [tail]
19742+ * @param {OwnerID} [ownerID]
19743+ * @param {number} [hash]
19744+ */
1963119745 function makeList(origin, capacity, level, root, tail, ownerID, hash) {
1963219746 var list = Object.create(ListPrototype);
1963319747 list.size = capacity - origin;
@@ -19745,6 +19859,14 @@ const UnitTables = exports.UnitTables = {
1974519859 return new VNode(node ? node.array.slice() : [], ownerID);
1974619860 }
1974719861
19862+ /**
19863+ * Returns the leaf `VNode` holding `rawIndex`, or `undefined` when no node is
19864+ * allocated for it (an all-`undefined` region).
19865+ *
19866+ * @param {List} list
19867+ * @param {number} rawIndex
19868+ * @returns {VNode | undefined}
19869+ */
1974819870 function listNodeFor(list, rawIndex) {
1974919871 if (rawIndex >= getTailOffset(list._capacity)) {
1975019872 return list._tail;
@@ -19760,7 +19882,39 @@ const UnitTables = exports.UnitTables = {
1976019882 }
1976119883 }
1976219884
19885+ /**
19886+ * Validates requested bounds before int32 coercion in setListBounds().
19887+ * Throws when origin/capacity would exceed the trie's safe range.
19888+ */
19889+ function validateListBoundsRequest(list, begin, end) {
19890+ var requestedOrigin = list._origin + (begin === undefined ? 0 : begin);
19891+ var requestedCapacity =
19892+ end === undefined
19893+ ? list._capacity
19894+ : end < 0
19895+ ? list._capacity + end
19896+ : list._origin + end;
19897+
19898+ // Keep origin/capacity within the trie's safe signed 32-bit range.
19899+ if (
19900+ (Number.isFinite(requestedCapacity) && requestedCapacity > MAX_LIST_SIZE) ||
19901+ (Number.isFinite(requestedOrigin) && requestedOrigin < -MAX_LIST_SIZE) ||
19902+ (Number.isFinite(requestedCapacity) &&
19903+ Number.isFinite(requestedOrigin) &&
19904+ requestedCapacity - requestedOrigin > MAX_LIST_SIZE)
19905+ ) {
19906+ throw new RangeError(
19907+ 'Invalid List size: a List cannot hold more than ' +
19908+ MAX_LIST_SIZE +
19909+ ' (2 ** 30) values.'
19910+ );
19911+ }
19912+ }
19913+
1976319914 function setListBounds(list, begin, end) {
19915+ // Validate full-precision bounds before int32 coercion.
19916+ validateListBoundsRequest(list, begin, end);
19917+
1976419918 // Sanitize begin & end using this shorthand for ToInt32(argument)
1976519919 // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32
1976619920 if (begin !== undefined) {
@@ -19799,7 +19953,8 @@ const UnitTables = exports.UnitTables = {
1979919953 owner
1980019954 );
1980119955 newLevel += SHIFT;
19802- offsetShift += 1 << newLevel;
19956+ // Shift origin into non-negative space as trie height grows.
19957+ offsetShift += levelCapacity(newLevel);
1980319958 }
1980419959 if (offsetShift) {
1980519960 newOrigin += offsetShift;
@@ -19812,7 +19967,7 @@ const UnitTables = exports.UnitTables = {
1981219967 var newTailOffset = getTailOffset(newCapacity);
1981319968
1981419969 // New size might need creating a higher root.
19815- while (newTailOffset >= 1 << (newLevel + SHIFT)) {
19970+ while (newTailOffset >= levelCapacity (newLevel + SHIFT)) {
1981619971 newRoot = new VNode(
1981719972 newRoot && newRoot.array.length ? [newRoot] : [],
1981819973 owner
@@ -19855,7 +20010,7 @@ const UnitTables = exports.UnitTables = {
1985520010 newOrigin -= newTailOffset;
1985620011 newCapacity -= newTailOffset;
1985720012 newLevel = SHIFT;
19858- newRoot = null ;
20013+ newRoot = undefined ;
1985920014 newTail = newTail && newTail.removeBefore(owner, 0, newOrigin);
1986020015
1986120016 // Otherwise, if the root has been trimmed, garbage collect.
@@ -19910,6 +20065,19 @@ const UnitTables = exports.UnitTables = {
1991020065 return size < SIZE ? 0 : ((size - 1) >>> SHIFT) << SHIFT;
1991120066 }
1991220067
20068+ // The largest number of values a List can hold. Above this the 32-bit trie math
20069+ // in setListBounds() stays in the safe signed 32-bit range.
20070+ var MAX_LIST_SIZE = Math.pow( 2, 30 ); // 1073741824
20071+
20072+ /**
20073+ * Computes 2 ** exp for the trie level-raising loops in setListBounds().
20074+ * Use the cheap bitwise operator shift whenever possible, otherwise fall back to exponentiation.
20075+ * 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.
20076+ */
20077+ function levelCapacity(exp) {
20078+ return exp < 31 ? 1 << exp : Math.pow( 2, exp );
20079+ }
20080+
1991320081 /**
1991420082 * True if `maybeOrderedMap` is an OrderedMap.
1991520083 */
@@ -20325,6 +20493,9 @@ const UnitTables = exports.UnitTables = {
2032520493 reduction = v;
2032620494 }
2032720495 else {
20496+ // `reduction` has already been seeded here (either with the provided
20497+ // initial value or with the first iterated value), so it is never the
20498+ // `undefined` placeholder — only a `V` or a `R`.
2032820499 reduction = reducer.call(context, reduction, v, k, c);
2032920500 }
2033020501 }, reverse);
@@ -21509,11 +21680,12 @@ const UnitTables = exports.UnitTables = {
2150921680
2151021681 has: function has(index) {
2151121682 index = wrapIndex(this, index);
21683+
2151221684 return (
2151321685 index >= 0 &&
2151421686 (this.size !== undefined
2151521687 ? this.size === Infinity || index < this.size
21516- : this.indexOf( index) !== -1 )
21688+ : this.find(function (_, key) { return key === index; }, undefined, NOT_SET ) !== NOT_SET )
2151721689 );
2151821690 },
2151921691
@@ -21974,15 +22146,15 @@ const UnitTables = exports.UnitTables = {
2197422146 };
2197522147
2197622148 Repeat.prototype.indexOf = function indexOf (searchValue) {
21977- if (is(this._value, searchValue)) {
22149+ if (this.size !== 0 && is(this._value, searchValue)) {
2197822150 return 0;
2197922151 }
2198022152 return -1;
2198122153 };
2198222154
2198322155 Repeat.prototype.lastIndexOf = function lastIndexOf (searchValue) {
21984- if (is(this._value, searchValue)) {
21985- return this.size;
22156+ if (this.size !== 0 && is(this._value, searchValue)) {
22157+ return this.size - 1 ;
2198622158 }
2198722159 return -1;
2198822160 };
@@ -22063,7 +22235,7 @@ const UnitTables = exports.UnitTables = {
2206322235 return isIndexed(v) ? v.toList() : isKeyed(v) ? v.toMap() : v.toSet();
2206422236 }
2206522237
22066- var version = "5.1.6 ";
22238+ var version = "5.1.9 ";
2206722239
2206822240 /* eslint-disable import/order */
2206922241
0 commit comments