VYPR
Medium severity5.9NVD Advisory· Published Mar 3, 2026· Updated Apr 28, 2026

CVE-2026-27601

CVE-2026-27601

Description

Underscore.js is a utility-belt library for JavaScript. Prior to 1.13.8, the _.flatten and _.isEqual functions use recursion without a depth limit. Under very specific conditions, detailed below, an attacker could exploit this in a Denial of Service (DoS) attack by triggering a stack overflow. Untrusted input must be used to create a recursive datastructure, for example using JSON.parse, with no enforced depth limit. The datastructure thus created must be passed to _.flatten or _.isEqual. In the case of _.flatten, the vulnerability can only be exploited if it is possible for a remote client to prepare a datastructure that consists of arrays at all levels AND if no finite depth limit is passed as the second argument to _.flatten. In the case of _.isEqual, the vulnerability can only be exploited if there exists a code path in which two distinct datastructures that were submitted by the same remote client are compared using _.isEqual. For example, if a client submits data that are stored in a database, and the same client can later submit another datastructure that is then compared to the data that were saved in the database previously, OR if a client submits a single request, but its data are parsed twice, creating two non-identical but equivalent datastructures that are then compared. Exceptions originating from the call to _.flatten or _.isEqual, as a result of a stack overflow, are not being caught. This vulnerability is fixed in 1.13.8.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
underscorenpm
< 1.13.81.13.8

Affected products

1

Patches

2
a6e23ae96474

Make _.isEqual nonrecursive

https://github.com/jashkenas/underscoreJulian GonggrijpFeb 17, 2026via ghsa
7 files changed · +481 385
  • modules/isEqual.js+118 94 modified
    @@ -12,127 +12,151 @@ import toBufferView from './_toBufferView.js';
     // We use this string twice, so give it a name for minification.
     var tagDataView = '[object DataView]';
     
    -// Internal recursive comparison function for `_.isEqual`.
    -function eq(a, b, aStack, bStack) {
    -  // Identical objects are equal. `0 === -0`, but they aren't identical.
    -  // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    -  if (a === b) return a !== 0 || 1 / a === 1 / b;
    -  // `null` or `undefined` only equal to itself (strict comparison).
    -  if (a == null || b == null) return false;
    -  // `NaN`s are equivalent, but non-reflexive.
    -  if (a !== a) return b !== b;
    -  // Exhaust primitive checks
    -  var type = typeof a;
    -  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    -  return deepEq(a, b, aStack, bStack);
    -}
    +// Perform a deep comparison to check if two objects are equal.
    +export default function isEqual(a, b) {
    +  // Keep track of which pairs of values need to be compared. We will be
    +  // trampolining on this stack instead of using function recursion.
    +  var todo = [{a: a, b: b}];
    +  // Initializing stacks of traversed objects for cycle detection.
    +  var aStack = [], bStack = [];
     
    -// Internal recursive comparison function for `_.isEqual`.
    -function deepEq(a, b, aStack, bStack) {
    -  // Unwrap any wrapped objects.
    -  if (a instanceof _) a = a._wrapped;
    -  if (b instanceof _) b = b._wrapped;
    -  // Compare `[[Class]]` names.
    -  var className = toString.call(a);
    -  if (className !== toString.call(b)) return false;
    -  // Work around a bug in IE 10 - Edge 13.
    -  if (hasDataViewBug && className == '[object Object]' && isDataView(a)) {
    -    if (!isDataView(b)) return false;
    -    className = tagDataView;
    -  }
    -  switch (className) {
    -    // These types are compared by value.
    +  // Keep traversing pairs until there is nothing left to compare.
    +  while (todo.length) {
    +    var frame = todo.pop();
    +    // As a special case, a single `true` on the todo means that we can
    +    // unwind the cycle detection stacks.
    +    if (frame === true) {
    +      // Remove the first object from the stack of traversed objects.
    +      aStack.pop();
    +      bStack.pop();
    +      continue;
    +    }
    +    a = frame.a;
    +    b = frame.b;
    +
    +    // Identical objects are equal. `0 === -0`, but they aren't identical.
    +    // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    +    if (a === b) {
    +      if (a !== 0 || 1 / a === 1 / b) continue;
    +      return false;
    +    }
    +    // `null` or `undefined` only equal to itself (strict comparison).
    +    if (a == null || b == null) return false;
    +    // `NaN`s are equivalent, but non-reflexive.
    +    if (a !== a) {
    +      if (b !== b) continue;
    +      return false;
    +    }
    +    // Exhaust primitive checks
    +    var type = typeof a;
    +    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    +
    +    // Unwrap any wrapped objects.
    +    if (a instanceof _) a = a._wrapped;
    +    if (b instanceof _) b = b._wrapped;
    +    // Compare `[[Class]]` names.
    +    var className = toString.call(a);
    +    if (className !== toString.call(b)) return false;
    +    // Work around a bug in IE 10 - Edge 13.
    +    if (hasDataViewBug && className == '[object Object]' && isDataView(a)) {
    +      if (!isDataView(b)) return false;
    +      className = tagDataView;
    +    }
    +    switch (className) {
    +      // These types are compared by value.
         case '[object RegExp]':
           // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
         case '[object String]':
           // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
           // equivalent to `new String("5")`.
    -      return '' + a === '' + b;
    +      if ('' + a === '' + b) continue;
    +      return false;
         case '[object Number]':
    -      // `NaN`s are equivalent, but non-reflexive.
    -      // Object(NaN) is equivalent to NaN.
    -      if (+a !== +a) return +b !== +b;
    -      // An `egal` comparison is performed for other numeric values.
    -      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    +      todo.push({a: +a, b: +b});
    +      continue;
         case '[object Date]':
         case '[object Boolean]':
           // Coerce dates and booleans to numeric primitive values. Dates are compared by their
           // millisecond representations. Note that invalid dates with millisecond representations
           // of `NaN` are not equivalent.
    -      return +a === +b;
    +      if (+a === +b) continue;
    +      return false;
         case '[object Symbol]':
    -      return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    +      if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
    +      return false;
         case '[object ArrayBuffer]':
         case tagDataView:
           // Coerce to typed array so we can fall through.
    -      return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
    -  }
    +      todo.push({a: toBufferView(a), b: toBufferView(b)});
    +      continue;
    +    }
     
    -  var areArrays = className === '[object Array]';
    -  if (!areArrays && isTypedArray(a)) {
    +    var areArrays = className === '[object Array]';
    +    if (!areArrays && isTypedArray(a)) {
           var byteLength = getByteLength(a);
           if (byteLength !== getByteLength(b)) return false;
    -      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
    +      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
           areArrays = true;
    -  }
    -  if (!areArrays) {
    -    if (typeof a != 'object' || typeof b != 'object') return false;
    -
    -    // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    -    // from different frames are.
    -    var aCtor = a.constructor, bCtor = b.constructor;
    -    if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
    -                             isFunction(bCtor) && bCtor instanceof bCtor)
    -                        && ('constructor' in a && 'constructor' in b)) {
    -      return false;
         }
    -  }
    -  // Assume equality for cyclic structures. The algorithm for detecting cyclic
    -  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    +    if (!areArrays) {
    +      if (typeof a != 'object' || typeof b != 'object') return false;
     
    -  // Initializing stack of traversed objects.
    -  // It's done here since we only need them for objects and arrays comparison.
    -  aStack = aStack || [];
    -  bStack = bStack || [];
    -  var length = aStack.length;
    -  while (length--) {
    -    // Linear search. Performance is inversely proportional to the number of
    -    // unique nested structures.
    -    if (aStack[length] === a) return bStack[length] === b;
    -  }
    +      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    +      // from different frames are.
    +      var aCtor = a.constructor, bCtor = b.constructor;
    +      if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
    +                               isFunction(bCtor) && bCtor instanceof bCtor)
    +          && ('constructor' in a && 'constructor' in b)) {
    +        return false;
    +      }
    +    }
     
    -  // Add the first object to the stack of traversed objects.
    -  aStack.push(a);
    -  bStack.push(b);
    +    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    +    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
     
    -  // Recursively compare objects and arrays.
    -  if (areArrays) {
    -    // Compare array lengths to determine if a deep comparison is necessary.
    -    length = a.length;
    -    if (length !== b.length) return false;
    -    // Deep compare the contents, ignoring non-numeric properties.
    +    var length = aStack.length;
         while (length--) {
    -      if (!eq(a[length], b[length], aStack, bStack)) return false;
    +      // Linear search. Performance is inversely proportional to the number of
    +      // unique nested structures.
    +      if (aStack[length] === a) {
    +        // Cycle detected. Break out of the inner loop and continue the outer
    +        // loop. Step 1:
    +        if (bStack[length] === b) break;
    +        return false;
    +      }
         }
    -  } else {
    -    // Deep compare objects.
    -    var _keys = keys(a), key;
    -    length = _keys.length;
    -    // Ensure that both objects contain the same number of properties before comparing deep equality.
    -    if (keys(b).length !== length) return false;
    -    while (length--) {
    -      // Deep compare each member
    -      key = _keys[length];
    -      if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
    +    // Step 2, use `length` to verify whether we detected a cycle:
    +    if (length >= 0) continue;
    +
    +    // Add the first object to the stack of traversed objects.
    +    aStack.push(a);
    +    bStack.push(b);
    +    // Remember to remove them again after the recursion below.
    +    todo.push(true);
    +
    +    // Recursively compare objects and arrays.
    +    if (areArrays) {
    +      // Compare array lengths to determine if a deep comparison is necessary.
    +      length = a.length;
    +      if (length !== b.length) return false;
    +      // Deep compare the contents, ignoring non-numeric properties.
    +      while (length--) {
    +        todo.push({a: a[length], b: b[length]});
    +      }
    +    } else {
    +      // Deep compare objects.
    +      var _keys = keys(a), key;
    +      length = _keys.length;
    +      // Ensure that both objects contain the same number of properties before comparing deep equality.
    +      if (keys(b).length !== length) return false;
    +      while (length--) {
    +        // Deep compare each member
    +        key = _keys[length];
    +        if (!has(b, key)) return false;
    +        todo.push({a: a[key], b: b[key]});
    +      }
         }
       }
    -  // Remove the first object from the stack of traversed objects.
    -  aStack.pop();
    -  bStack.pop();
    +  // We made it to the end and found no differences.
       return true;
     }
    -
    -// Perform a deep comparison to check if two objects are equal.
    -export default function isEqual(a, b) {
    -  return eq(a, b);
    -}
    
  • underscore-esm.js+120 96 modified
    @@ -348,131 +348,155 @@ function toBufferView(bufferSource) {
     // We use this string twice, so give it a name for minification.
     var tagDataView = '[object DataView]';
     
    -// Internal recursive comparison function for `_.isEqual`.
    -function eq(a, b, aStack, bStack) {
    -  // Identical objects are equal. `0 === -0`, but they aren't identical.
    -  // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    -  if (a === b) return a !== 0 || 1 / a === 1 / b;
    -  // `null` or `undefined` only equal to itself (strict comparison).
    -  if (a == null || b == null) return false;
    -  // `NaN`s are equivalent, but non-reflexive.
    -  if (a !== a) return b !== b;
    -  // Exhaust primitive checks
    -  var type = typeof a;
    -  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    -  return deepEq(a, b, aStack, bStack);
    -}
    -
    -// Internal recursive comparison function for `_.isEqual`.
    -function deepEq(a, b, aStack, bStack) {
    -  // Unwrap any wrapped objects.
    -  if (a instanceof _$1) a = a._wrapped;
    -  if (b instanceof _$1) b = b._wrapped;
    -  // Compare `[[Class]]` names.
    -  var className = toString.call(a);
    -  if (className !== toString.call(b)) return false;
    -  // Work around a bug in IE 10 - Edge 13.
    -  if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    -    if (!isDataView$1(b)) return false;
    -    className = tagDataView;
    -  }
    -  switch (className) {
    -    // These types are compared by value.
    +// Perform a deep comparison to check if two objects are equal.
    +function isEqual(a, b) {
    +  // Keep track of which pairs of values need to be compared. We will be
    +  // trampolining on this stack instead of using function recursion.
    +  var todo = [{a: a, b: b}];
    +  // Initializing stacks of traversed objects for cycle detection.
    +  var aStack = [], bStack = [];
    +
    +  // Keep traversing pairs until there is nothing left to compare.
    +  while (todo.length) {
    +    var frame = todo.pop();
    +    // As a special case, a single `true` on the todo means that we can
    +    // unwind the cycle detection stacks.
    +    if (frame === true) {
    +      // Remove the first object from the stack of traversed objects.
    +      aStack.pop();
    +      bStack.pop();
    +      continue;
    +    }
    +    a = frame.a;
    +    b = frame.b;
    +
    +    // Identical objects are equal. `0 === -0`, but they aren't identical.
    +    // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    +    if (a === b) {
    +      if (a !== 0 || 1 / a === 1 / b) continue;
    +      return false;
    +    }
    +    // `null` or `undefined` only equal to itself (strict comparison).
    +    if (a == null || b == null) return false;
    +    // `NaN`s are equivalent, but non-reflexive.
    +    if (a !== a) {
    +      if (b !== b) continue;
    +      return false;
    +    }
    +    // Exhaust primitive checks
    +    var type = typeof a;
    +    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    +
    +    // Unwrap any wrapped objects.
    +    if (a instanceof _$1) a = a._wrapped;
    +    if (b instanceof _$1) b = b._wrapped;
    +    // Compare `[[Class]]` names.
    +    var className = toString.call(a);
    +    if (className !== toString.call(b)) return false;
    +    // Work around a bug in IE 10 - Edge 13.
    +    if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    +      if (!isDataView$1(b)) return false;
    +      className = tagDataView;
    +    }
    +    switch (className) {
    +      // These types are compared by value.
         case '[object RegExp]':
           // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
         case '[object String]':
           // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
           // equivalent to `new String("5")`.
    -      return '' + a === '' + b;
    +      if ('' + a === '' + b) continue;
    +      return false;
         case '[object Number]':
    -      // `NaN`s are equivalent, but non-reflexive.
    -      // Object(NaN) is equivalent to NaN.
    -      if (+a !== +a) return +b !== +b;
    -      // An `egal` comparison is performed for other numeric values.
    -      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    +      todo.push({a: +a, b: +b});
    +      continue;
         case '[object Date]':
         case '[object Boolean]':
           // Coerce dates and booleans to numeric primitive values. Dates are compared by their
           // millisecond representations. Note that invalid dates with millisecond representations
           // of `NaN` are not equivalent.
    -      return +a === +b;
    +      if (+a === +b) continue;
    +      return false;
         case '[object Symbol]':
    -      return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    +      if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
    +      return false;
         case '[object ArrayBuffer]':
         case tagDataView:
           // Coerce to typed array so we can fall through.
    -      return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
    -  }
    +      todo.push({a: toBufferView(a), b: toBufferView(b)});
    +      continue;
    +    }
     
    -  var areArrays = className === '[object Array]';
    -  if (!areArrays && isTypedArray$1(a)) {
    +    var areArrays = className === '[object Array]';
    +    if (!areArrays && isTypedArray$1(a)) {
           var byteLength = getByteLength(a);
           if (byteLength !== getByteLength(b)) return false;
    -      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
    +      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
           areArrays = true;
    -  }
    -  if (!areArrays) {
    -    if (typeof a != 'object' || typeof b != 'object') return false;
    -
    -    // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    -    // from different frames are.
    -    var aCtor = a.constructor, bCtor = b.constructor;
    -    if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    -                             isFunction$1(bCtor) && bCtor instanceof bCtor)
    -                        && ('constructor' in a && 'constructor' in b)) {
    -      return false;
         }
    -  }
    -  // Assume equality for cyclic structures. The algorithm for detecting cyclic
    -  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    -
    -  // Initializing stack of traversed objects.
    -  // It's done here since we only need them for objects and arrays comparison.
    -  aStack = aStack || [];
    -  bStack = bStack || [];
    -  var length = aStack.length;
    -  while (length--) {
    -    // Linear search. Performance is inversely proportional to the number of
    -    // unique nested structures.
    -    if (aStack[length] === a) return bStack[length] === b;
    -  }
    +    if (!areArrays) {
    +      if (typeof a != 'object' || typeof b != 'object') return false;
    +
    +      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    +      // from different frames are.
    +      var aCtor = a.constructor, bCtor = b.constructor;
    +      if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    +                               isFunction$1(bCtor) && bCtor instanceof bCtor)
    +          && ('constructor' in a && 'constructor' in b)) {
    +        return false;
    +      }
    +    }
     
    -  // Add the first object to the stack of traversed objects.
    -  aStack.push(a);
    -  bStack.push(b);
    +    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    +    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
     
    -  // Recursively compare objects and arrays.
    -  if (areArrays) {
    -    // Compare array lengths to determine if a deep comparison is necessary.
    -    length = a.length;
    -    if (length !== b.length) return false;
    -    // Deep compare the contents, ignoring non-numeric properties.
    +    var length = aStack.length;
         while (length--) {
    -      if (!eq(a[length], b[length], aStack, bStack)) return false;
    +      // Linear search. Performance is inversely proportional to the number of
    +      // unique nested structures.
    +      if (aStack[length] === a) {
    +        // Cycle detected. Break out of the inner loop and continue the outer
    +        // loop. Step 1:
    +        if (bStack[length] === b) break;
    +        return false;
    +      }
         }
    -  } else {
    -    // Deep compare objects.
    -    var _keys = keys(a), key;
    -    length = _keys.length;
    -    // Ensure that both objects contain the same number of properties before comparing deep equality.
    -    if (keys(b).length !== length) return false;
    -    while (length--) {
    -      // Deep compare each member
    -      key = _keys[length];
    -      if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
    +    // Step 2, use `length` to verify whether we detected a cycle:
    +    if (length >= 0) continue;
    +
    +    // Add the first object to the stack of traversed objects.
    +    aStack.push(a);
    +    bStack.push(b);
    +    // Remember to remove them again after the recursion below.
    +    todo.push(true);
    +
    +    // Recursively compare objects and arrays.
    +    if (areArrays) {
    +      // Compare array lengths to determine if a deep comparison is necessary.
    +      length = a.length;
    +      if (length !== b.length) return false;
    +      // Deep compare the contents, ignoring non-numeric properties.
    +      while (length--) {
    +        todo.push({a: a[length], b: b[length]});
    +      }
    +    } else {
    +      // Deep compare objects.
    +      var _keys = keys(a), key;
    +      length = _keys.length;
    +      // Ensure that both objects contain the same number of properties before comparing deep equality.
    +      if (keys(b).length !== length) return false;
    +      while (length--) {
    +        // Deep compare each member
    +        key = _keys[length];
    +        if (!has$1(b, key)) return false;
    +        todo.push({a: a[key], b: b[key]});
    +      }
         }
       }
    -  // Remove the first object from the stack of traversed objects.
    -  aStack.pop();
    -  bStack.pop();
    +  // We made it to the end and found no differences.
       return true;
     }
     
    -// Perform a deep comparison to check if two objects are equal.
    -function isEqual(a, b) {
    -  return eq(a, b);
    -}
    -
     // Retrieve all the enumerable property names of an object.
     function allKeys(obj) {
       if (!isObject(obj)) return [];
    
  • underscore-esm.js.map+1 1 modified
  • underscore-node-f.cjs+120 96 modified
    @@ -350,131 +350,155 @@ function toBufferView(bufferSource) {
     // We use this string twice, so give it a name for minification.
     var tagDataView = '[object DataView]';
     
    -// Internal recursive comparison function for `_.isEqual`.
    -function eq(a, b, aStack, bStack) {
    -  // Identical objects are equal. `0 === -0`, but they aren't identical.
    -  // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    -  if (a === b) return a !== 0 || 1 / a === 1 / b;
    -  // `null` or `undefined` only equal to itself (strict comparison).
    -  if (a == null || b == null) return false;
    -  // `NaN`s are equivalent, but non-reflexive.
    -  if (a !== a) return b !== b;
    -  // Exhaust primitive checks
    -  var type = typeof a;
    -  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    -  return deepEq(a, b, aStack, bStack);
    -}
    -
    -// Internal recursive comparison function for `_.isEqual`.
    -function deepEq(a, b, aStack, bStack) {
    -  // Unwrap any wrapped objects.
    -  if (a instanceof _$1) a = a._wrapped;
    -  if (b instanceof _$1) b = b._wrapped;
    -  // Compare `[[Class]]` names.
    -  var className = toString.call(a);
    -  if (className !== toString.call(b)) return false;
    -  // Work around a bug in IE 10 - Edge 13.
    -  if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    -    if (!isDataView$1(b)) return false;
    -    className = tagDataView;
    -  }
    -  switch (className) {
    -    // These types are compared by value.
    +// Perform a deep comparison to check if two objects are equal.
    +function isEqual(a, b) {
    +  // Keep track of which pairs of values need to be compared. We will be
    +  // trampolining on this stack instead of using function recursion.
    +  var todo = [{a: a, b: b}];
    +  // Initializing stacks of traversed objects for cycle detection.
    +  var aStack = [], bStack = [];
    +
    +  // Keep traversing pairs until there is nothing left to compare.
    +  while (todo.length) {
    +    var frame = todo.pop();
    +    // As a special case, a single `true` on the todo means that we can
    +    // unwind the cycle detection stacks.
    +    if (frame === true) {
    +      // Remove the first object from the stack of traversed objects.
    +      aStack.pop();
    +      bStack.pop();
    +      continue;
    +    }
    +    a = frame.a;
    +    b = frame.b;
    +
    +    // Identical objects are equal. `0 === -0`, but they aren't identical.
    +    // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    +    if (a === b) {
    +      if (a !== 0 || 1 / a === 1 / b) continue;
    +      return false;
    +    }
    +    // `null` or `undefined` only equal to itself (strict comparison).
    +    if (a == null || b == null) return false;
    +    // `NaN`s are equivalent, but non-reflexive.
    +    if (a !== a) {
    +      if (b !== b) continue;
    +      return false;
    +    }
    +    // Exhaust primitive checks
    +    var type = typeof a;
    +    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    +
    +    // Unwrap any wrapped objects.
    +    if (a instanceof _$1) a = a._wrapped;
    +    if (b instanceof _$1) b = b._wrapped;
    +    // Compare `[[Class]]` names.
    +    var className = toString.call(a);
    +    if (className !== toString.call(b)) return false;
    +    // Work around a bug in IE 10 - Edge 13.
    +    if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    +      if (!isDataView$1(b)) return false;
    +      className = tagDataView;
    +    }
    +    switch (className) {
    +      // These types are compared by value.
         case '[object RegExp]':
           // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
         case '[object String]':
           // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
           // equivalent to `new String("5")`.
    -      return '' + a === '' + b;
    +      if ('' + a === '' + b) continue;
    +      return false;
         case '[object Number]':
    -      // `NaN`s are equivalent, but non-reflexive.
    -      // Object(NaN) is equivalent to NaN.
    -      if (+a !== +a) return +b !== +b;
    -      // An `egal` comparison is performed for other numeric values.
    -      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    +      todo.push({a: +a, b: +b});
    +      continue;
         case '[object Date]':
         case '[object Boolean]':
           // Coerce dates and booleans to numeric primitive values. Dates are compared by their
           // millisecond representations. Note that invalid dates with millisecond representations
           // of `NaN` are not equivalent.
    -      return +a === +b;
    +      if (+a === +b) continue;
    +      return false;
         case '[object Symbol]':
    -      return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    +      if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
    +      return false;
         case '[object ArrayBuffer]':
         case tagDataView:
           // Coerce to typed array so we can fall through.
    -      return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
    -  }
    +      todo.push({a: toBufferView(a), b: toBufferView(b)});
    +      continue;
    +    }
     
    -  var areArrays = className === '[object Array]';
    -  if (!areArrays && isTypedArray$1(a)) {
    +    var areArrays = className === '[object Array]';
    +    if (!areArrays && isTypedArray$1(a)) {
           var byteLength = getByteLength(a);
           if (byteLength !== getByteLength(b)) return false;
    -      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
    +      if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
           areArrays = true;
    -  }
    -  if (!areArrays) {
    -    if (typeof a != 'object' || typeof b != 'object') return false;
    -
    -    // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    -    // from different frames are.
    -    var aCtor = a.constructor, bCtor = b.constructor;
    -    if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    -                             isFunction$1(bCtor) && bCtor instanceof bCtor)
    -                        && ('constructor' in a && 'constructor' in b)) {
    -      return false;
         }
    -  }
    -  // Assume equality for cyclic structures. The algorithm for detecting cyclic
    -  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    -
    -  // Initializing stack of traversed objects.
    -  // It's done here since we only need them for objects and arrays comparison.
    -  aStack = aStack || [];
    -  bStack = bStack || [];
    -  var length = aStack.length;
    -  while (length--) {
    -    // Linear search. Performance is inversely proportional to the number of
    -    // unique nested structures.
    -    if (aStack[length] === a) return bStack[length] === b;
    -  }
    +    if (!areArrays) {
    +      if (typeof a != 'object' || typeof b != 'object') return false;
    +
    +      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    +      // from different frames are.
    +      var aCtor = a.constructor, bCtor = b.constructor;
    +      if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    +                               isFunction$1(bCtor) && bCtor instanceof bCtor)
    +          && ('constructor' in a && 'constructor' in b)) {
    +        return false;
    +      }
    +    }
     
    -  // Add the first object to the stack of traversed objects.
    -  aStack.push(a);
    -  bStack.push(b);
    +    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    +    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
     
    -  // Recursively compare objects and arrays.
    -  if (areArrays) {
    -    // Compare array lengths to determine if a deep comparison is necessary.
    -    length = a.length;
    -    if (length !== b.length) return false;
    -    // Deep compare the contents, ignoring non-numeric properties.
    +    var length = aStack.length;
         while (length--) {
    -      if (!eq(a[length], b[length], aStack, bStack)) return false;
    +      // Linear search. Performance is inversely proportional to the number of
    +      // unique nested structures.
    +      if (aStack[length] === a) {
    +        // Cycle detected. Break out of the inner loop and continue the outer
    +        // loop. Step 1:
    +        if (bStack[length] === b) break;
    +        return false;
    +      }
         }
    -  } else {
    -    // Deep compare objects.
    -    var _keys = keys(a), key;
    -    length = _keys.length;
    -    // Ensure that both objects contain the same number of properties before comparing deep equality.
    -    if (keys(b).length !== length) return false;
    -    while (length--) {
    -      // Deep compare each member
    -      key = _keys[length];
    -      if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
    +    // Step 2, use `length` to verify whether we detected a cycle:
    +    if (length >= 0) continue;
    +
    +    // Add the first object to the stack of traversed objects.
    +    aStack.push(a);
    +    bStack.push(b);
    +    // Remember to remove them again after the recursion below.
    +    todo.push(true);
    +
    +    // Recursively compare objects and arrays.
    +    if (areArrays) {
    +      // Compare array lengths to determine if a deep comparison is necessary.
    +      length = a.length;
    +      if (length !== b.length) return false;
    +      // Deep compare the contents, ignoring non-numeric properties.
    +      while (length--) {
    +        todo.push({a: a[length], b: b[length]});
    +      }
    +    } else {
    +      // Deep compare objects.
    +      var _keys = keys(a), key;
    +      length = _keys.length;
    +      // Ensure that both objects contain the same number of properties before comparing deep equality.
    +      if (keys(b).length !== length) return false;
    +      while (length--) {
    +        // Deep compare each member
    +        key = _keys[length];
    +        if (!has$1(b, key)) return false;
    +        todo.push({a: a[key], b: b[key]});
    +      }
         }
       }
    -  // Remove the first object from the stack of traversed objects.
    -  aStack.pop();
    -  bStack.pop();
    +  // We made it to the end and found no differences.
       return true;
     }
     
    -// Perform a deep comparison to check if two objects are equal.
    -function isEqual(a, b) {
    -  return eq(a, b);
    -}
    -
     // Retrieve all the enumerable property names of an object.
     function allKeys(obj) {
       if (!isObject(obj)) return [];
    
  • underscore-node-f.cjs.map+1 1 modified
  • underscore-umd.js+120 96 modified
    @@ -357,131 +357,155 @@
       // We use this string twice, so give it a name for minification.
       var tagDataView = '[object DataView]';
     
    -  // Internal recursive comparison function for `_.isEqual`.
    -  function eq(a, b, aStack, bStack) {
    -    // Identical objects are equal. `0 === -0`, but they aren't identical.
    -    // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    -    if (a === b) return a !== 0 || 1 / a === 1 / b;
    -    // `null` or `undefined` only equal to itself (strict comparison).
    -    if (a == null || b == null) return false;
    -    // `NaN`s are equivalent, but non-reflexive.
    -    if (a !== a) return b !== b;
    -    // Exhaust primitive checks
    -    var type = typeof a;
    -    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    -    return deepEq(a, b, aStack, bStack);
    -  }
    -
    -  // Internal recursive comparison function for `_.isEqual`.
    -  function deepEq(a, b, aStack, bStack) {
    -    // Unwrap any wrapped objects.
    -    if (a instanceof _$1) a = a._wrapped;
    -    if (b instanceof _$1) b = b._wrapped;
    -    // Compare `[[Class]]` names.
    -    var className = toString.call(a);
    -    if (className !== toString.call(b)) return false;
    -    // Work around a bug in IE 10 - Edge 13.
    -    if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    -      if (!isDataView$1(b)) return false;
    -      className = tagDataView;
    -    }
    -    switch (className) {
    -      // These types are compared by value.
    +  // Perform a deep comparison to check if two objects are equal.
    +  function isEqual(a, b) {
    +    // Keep track of which pairs of values need to be compared. We will be
    +    // trampolining on this stack instead of using function recursion.
    +    var todo = [{a: a, b: b}];
    +    // Initializing stacks of traversed objects for cycle detection.
    +    var aStack = [], bStack = [];
    +
    +    // Keep traversing pairs until there is nothing left to compare.
    +    while (todo.length) {
    +      var frame = todo.pop();
    +      // As a special case, a single `true` on the todo means that we can
    +      // unwind the cycle detection stacks.
    +      if (frame === true) {
    +        // Remove the first object from the stack of traversed objects.
    +        aStack.pop();
    +        bStack.pop();
    +        continue;
    +      }
    +      a = frame.a;
    +      b = frame.b;
    +
    +      // Identical objects are equal. `0 === -0`, but they aren't identical.
    +      // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
    +      if (a === b) {
    +        if (a !== 0 || 1 / a === 1 / b) continue;
    +        return false;
    +      }
    +      // `null` or `undefined` only equal to itself (strict comparison).
    +      if (a == null || b == null) return false;
    +      // `NaN`s are equivalent, but non-reflexive.
    +      if (a !== a) {
    +        if (b !== b) continue;
    +        return false;
    +      }
    +      // Exhaust primitive checks
    +      var type = typeof a;
    +      if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    +
    +      // Unwrap any wrapped objects.
    +      if (a instanceof _$1) a = a._wrapped;
    +      if (b instanceof _$1) b = b._wrapped;
    +      // Compare `[[Class]]` names.
    +      var className = toString.call(a);
    +      if (className !== toString.call(b)) return false;
    +      // Work around a bug in IE 10 - Edge 13.
    +      if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
    +        if (!isDataView$1(b)) return false;
    +        className = tagDataView;
    +      }
    +      switch (className) {
    +        // These types are compared by value.
           case '[object RegExp]':
             // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
           case '[object String]':
             // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
             // equivalent to `new String("5")`.
    -        return '' + a === '' + b;
    +        if ('' + a === '' + b) continue;
    +        return false;
           case '[object Number]':
    -        // `NaN`s are equivalent, but non-reflexive.
    -        // Object(NaN) is equivalent to NaN.
    -        if (+a !== +a) return +b !== +b;
    -        // An `egal` comparison is performed for other numeric values.
    -        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    +        todo.push({a: +a, b: +b});
    +        continue;
           case '[object Date]':
           case '[object Boolean]':
             // Coerce dates and booleans to numeric primitive values. Dates are compared by their
             // millisecond representations. Note that invalid dates with millisecond representations
             // of `NaN` are not equivalent.
    -        return +a === +b;
    +        if (+a === +b) continue;
    +        return false;
           case '[object Symbol]':
    -        return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    +        if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
    +        return false;
           case '[object ArrayBuffer]':
           case tagDataView:
             // Coerce to typed array so we can fall through.
    -        return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
    -    }
    +        todo.push({a: toBufferView(a), b: toBufferView(b)});
    +        continue;
    +      }
     
    -    var areArrays = className === '[object Array]';
    -    if (!areArrays && isTypedArray$1(a)) {
    +      var areArrays = className === '[object Array]';
    +      if (!areArrays && isTypedArray$1(a)) {
             var byteLength = getByteLength(a);
             if (byteLength !== getByteLength(b)) return false;
    -        if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
    +        if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
             areArrays = true;
    -    }
    -    if (!areArrays) {
    -      if (typeof a != 'object' || typeof b != 'object') return false;
    -
    -      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    -      // from different frames are.
    -      var aCtor = a.constructor, bCtor = b.constructor;
    -      if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    -                               isFunction$1(bCtor) && bCtor instanceof bCtor)
    -                          && ('constructor' in a && 'constructor' in b)) {
    -        return false;
           }
    -    }
    -    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    -    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    -
    -    // Initializing stack of traversed objects.
    -    // It's done here since we only need them for objects and arrays comparison.
    -    aStack = aStack || [];
    -    bStack = bStack || [];
    -    var length = aStack.length;
    -    while (length--) {
    -      // Linear search. Performance is inversely proportional to the number of
    -      // unique nested structures.
    -      if (aStack[length] === a) return bStack[length] === b;
    -    }
    +      if (!areArrays) {
    +        if (typeof a != 'object' || typeof b != 'object') return false;
    +
    +        // Objects with different constructors are not equivalent, but `Object`s or `Array`s
    +        // from different frames are.
    +        var aCtor = a.constructor, bCtor = b.constructor;
    +        if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
    +                                 isFunction$1(bCtor) && bCtor instanceof bCtor)
    +            && ('constructor' in a && 'constructor' in b)) {
    +          return false;
    +        }
    +      }
     
    -    // Add the first object to the stack of traversed objects.
    -    aStack.push(a);
    -    bStack.push(b);
    +      // Assume equality for cyclic structures. The algorithm for detecting cyclic
    +      // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
     
    -    // Recursively compare objects and arrays.
    -    if (areArrays) {
    -      // Compare array lengths to determine if a deep comparison is necessary.
    -      length = a.length;
    -      if (length !== b.length) return false;
    -      // Deep compare the contents, ignoring non-numeric properties.
    +      var length = aStack.length;
           while (length--) {
    -        if (!eq(a[length], b[length], aStack, bStack)) return false;
    +        // Linear search. Performance is inversely proportional to the number of
    +        // unique nested structures.
    +        if (aStack[length] === a) {
    +          // Cycle detected. Break out of the inner loop and continue the outer
    +          // loop. Step 1:
    +          if (bStack[length] === b) break;
    +          return false;
    +        }
           }
    -    } else {
    -      // Deep compare objects.
    -      var _keys = keys(a), key;
    -      length = _keys.length;
    -      // Ensure that both objects contain the same number of properties before comparing deep equality.
    -      if (keys(b).length !== length) return false;
    -      while (length--) {
    -        // Deep compare each member
    -        key = _keys[length];
    -        if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
    +      // Step 2, use `length` to verify whether we detected a cycle:
    +      if (length >= 0) continue;
    +
    +      // Add the first object to the stack of traversed objects.
    +      aStack.push(a);
    +      bStack.push(b);
    +      // Remember to remove them again after the recursion below.
    +      todo.push(true);
    +
    +      // Recursively compare objects and arrays.
    +      if (areArrays) {
    +        // Compare array lengths to determine if a deep comparison is necessary.
    +        length = a.length;
    +        if (length !== b.length) return false;
    +        // Deep compare the contents, ignoring non-numeric properties.
    +        while (length--) {
    +          todo.push({a: a[length], b: b[length]});
    +        }
    +      } else {
    +        // Deep compare objects.
    +        var _keys = keys(a), key;
    +        length = _keys.length;
    +        // Ensure that both objects contain the same number of properties before comparing deep equality.
    +        if (keys(b).length !== length) return false;
    +        while (length--) {
    +          // Deep compare each member
    +          key = _keys[length];
    +          if (!has$1(b, key)) return false;
    +          todo.push({a: a[key], b: b[key]});
    +        }
           }
         }
    -    // Remove the first object from the stack of traversed objects.
    -    aStack.pop();
    -    bStack.pop();
    +    // We made it to the end and found no differences.
         return true;
       }
     
    -  // Perform a deep comparison to check if two objects are equal.
    -  function isEqual(a, b) {
    -    return eq(a, b);
    -  }
    -
       // Retrieve all the enumerable property names of an object.
       function allKeys(obj) {
         if (!isObject(obj)) return [];
    
  • underscore-umd.js.map+1 1 modified
411e222eb0ca

Make internal flatten nonrecursive

https://github.com/jashkenas/underscoreJulian GonggrijpFeb 16, 2026via ghsa
7 files changed · +87 79
  • modules/_flatten.js+21 19 modified
    @@ -3,26 +3,28 @@ import isArrayLike from './_isArrayLike.js';
     import isArray from './isArray.js';
     import isArguments from './isArguments.js';
     
    -// Internal implementation of a recursive `flatten` function.
    -export default function flatten(input, depth, strict, output) {
    -  output = output || [];
    -  if (!depth && depth !== 0) {
    -    depth = Infinity;
    -  } else if (depth <= 0) {
    -    return output.concat(input);
    -  }
    -  var idx = output.length;
    -  for (var i = 0, length = getLength(input); i < length; i++) {
    -    var value = input[i];
    -    if (isArrayLike(value) && (isArray(value) || isArguments(value))) {
    +// Internal implementation of a `flatten` function.
    +export default function flatten(input, depth, strict) {
    +  if (!depth && depth !== 0) depth = Infinity;
    +  var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
    +  while (true) {
    +    if (i >= length) {
    +      if (!stack.length) break;
    +      var frame = stack.pop();
    +      i = frame.i;
    +      input = frame.v;
    +      length = getLength(input);
    +      continue;
    +    }
    +    var value = input[i++];
    +    if (stack.length >= depth) {
    +      output[idx++] = value;
    +    } else if (isArrayLike(value) && (isArray(value) || isArguments(value))) {
           // Flatten current level of array or arguments object.
    -      if (depth > 1) {
    -        flatten(value, depth - 1, strict, output);
    -        idx = output.length;
    -      } else {
    -        var j = 0, len = value.length;
    -        while (j < len) output[idx++] = value[j++];
    -      }
    +      stack.push({i: i, v: input});
    +      i = 0;
    +      input = value;
    +      length = getLength(input);
         } else if (!strict) {
           output[idx++] = value;
         }
    
  • underscore-esm.js+21 19 modified
    @@ -1022,26 +1022,28 @@ var bind = restArguments(function(func, context, args) {
     // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
     var isArrayLike = createSizePropertyCheck(getLength);
     
    -// Internal implementation of a recursive `flatten` function.
    -function flatten$1(input, depth, strict, output) {
    -  output = output || [];
    -  if (!depth && depth !== 0) {
    -    depth = Infinity;
    -  } else if (depth <= 0) {
    -    return output.concat(input);
    -  }
    -  var idx = output.length;
    -  for (var i = 0, length = getLength(input); i < length; i++) {
    -    var value = input[i];
    -    if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
    +// Internal implementation of a `flatten` function.
    +function flatten$1(input, depth, strict) {
    +  if (!depth && depth !== 0) depth = Infinity;
    +  var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
    +  while (true) {
    +    if (i >= length) {
    +      if (!stack.length) break;
    +      var frame = stack.pop();
    +      i = frame.i;
    +      input = frame.v;
    +      length = getLength(input);
    +      continue;
    +    }
    +    var value = input[i++];
    +    if (stack.length >= depth) {
    +      output[idx++] = value;
    +    } else if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
           // Flatten current level of array or arguments object.
    -      if (depth > 1) {
    -        flatten$1(value, depth - 1, strict, output);
    -        idx = output.length;
    -      } else {
    -        var j = 0, len = value.length;
    -        while (j < len) output[idx++] = value[j++];
    -      }
    +      stack.push({i: i, v: input});
    +      i = 0;
    +      input = value;
    +      length = getLength(input);
         } else if (!strict) {
           output[idx++] = value;
         }
    
  • underscore-esm.js.map+1 1 modified
  • underscore-node-f.cjs+21 19 modified
    @@ -1024,26 +1024,28 @@ var bind = restArguments(function(func, context, args) {
     // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
     var isArrayLike = createSizePropertyCheck(getLength);
     
    -// Internal implementation of a recursive `flatten` function.
    -function flatten$1(input, depth, strict, output) {
    -  output = output || [];
    -  if (!depth && depth !== 0) {
    -    depth = Infinity;
    -  } else if (depth <= 0) {
    -    return output.concat(input);
    -  }
    -  var idx = output.length;
    -  for (var i = 0, length = getLength(input); i < length; i++) {
    -    var value = input[i];
    -    if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
    +// Internal implementation of a `flatten` function.
    +function flatten$1(input, depth, strict) {
    +  if (!depth && depth !== 0) depth = Infinity;
    +  var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
    +  while (true) {
    +    if (i >= length) {
    +      if (!stack.length) break;
    +      var frame = stack.pop();
    +      i = frame.i;
    +      input = frame.v;
    +      length = getLength(input);
    +      continue;
    +    }
    +    var value = input[i++];
    +    if (stack.length >= depth) {
    +      output[idx++] = value;
    +    } else if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
           // Flatten current level of array or arguments object.
    -      if (depth > 1) {
    -        flatten$1(value, depth - 1, strict, output);
    -        idx = output.length;
    -      } else {
    -        var j = 0, len = value.length;
    -        while (j < len) output[idx++] = value[j++];
    -      }
    +      stack.push({i: i, v: input});
    +      i = 0;
    +      input = value;
    +      length = getLength(input);
         } else if (!strict) {
           output[idx++] = value;
         }
    
  • underscore-node-f.cjs.map+1 1 modified
  • underscore-umd.js+21 19 modified
    @@ -1031,26 +1031,28 @@
       // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
       var isArrayLike = createSizePropertyCheck(getLength);
     
    -  // Internal implementation of a recursive `flatten` function.
    -  function flatten$1(input, depth, strict, output) {
    -    output = output || [];
    -    if (!depth && depth !== 0) {
    -      depth = Infinity;
    -    } else if (depth <= 0) {
    -      return output.concat(input);
    -    }
    -    var idx = output.length;
    -    for (var i = 0, length = getLength(input); i < length; i++) {
    -      var value = input[i];
    -      if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
    +  // Internal implementation of a `flatten` function.
    +  function flatten$1(input, depth, strict) {
    +    if (!depth && depth !== 0) depth = Infinity;
    +    var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
    +    while (true) {
    +      if (i >= length) {
    +        if (!stack.length) break;
    +        var frame = stack.pop();
    +        i = frame.i;
    +        input = frame.v;
    +        length = getLength(input);
    +        continue;
    +      }
    +      var value = input[i++];
    +      if (stack.length >= depth) {
    +        output[idx++] = value;
    +      } else if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
             // Flatten current level of array or arguments object.
    -        if (depth > 1) {
    -          flatten$1(value, depth - 1, strict, output);
    -          idx = output.length;
    -        } else {
    -          var j = 0, len = value.length;
    -          while (j < len) output[idx++] = value[j++];
    -        }
    +        stack.push({i: i, v: input});
    +        i = 0;
    +        input = value;
    +        length = getLength(input);
           } else if (!strict) {
             output[idx++] = value;
           }
    
  • underscore-umd.js.map+1 1 modified

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

9