V8 引擎中的 Invalid array length bug

在上一篇文章中,我提到了 V8 引擎存在的一个 bug,对于数组的长度计算有点问题,导致在部分算法执行过程中出现 Invalid array length 异常而退出。有趣的是,Object 也会出现同样的 Invalid array length,尽管 Object 本身并没有 length 属性。

我写了个最简单的复现代码:

1
2
3
4
5
6
7
8
9
const n = 2 ** 24 - 1; // 16777215
// const dict = Object.create(null) // same bug
const dict = [];
for (let i = 0; i < n; i++) {
let key = i * Math.floor(Math.log(i));
dict[key] = i + 1;
}
const maxkey = (n - 1) * Math.floor(Math.log(n - 1)); // 279097901
console.log(dict[maxkey]); // should be 16777215

在最新版本的 nodejs 和 chrome 控制台中,均出现相同的报错。但在 firefox 和 safari 的控制台中都可以正常执行,这两个浏览器都有自己的独立 JS 引擎。

chrome 控制台复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
node object-dict-test.js
/Users/kaikai/Coding/object-dict-test.js:7
dict[key] = i + 1
^

RangeError: Invalid array length
at Object.<anonymous> (/Users/kaikai/Coding/hexo-blog/source/attach/2025/08/object-dict-test2.js:7:15)
at Module._compile (node:internal/modules/cjs/loader:1738:14)
at Object..js (node:internal/modules/cjs/loader:1871:10)
at Module.load (node:internal/modules/cjs/loader:1470:32)
at Module._load (node:internal/modules/cjs/loader:1290:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:238:24)
at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
at node:internal/main/run_main_module:33:47

Node.js v24.6.0

查了一下原因:

  1. V8 对 整数键(即符合数组索引规则的属性名)的 Object 有特殊处理。当普通对象使用整数作为 key 时,V8 通常会将其视作“类似数组”的元素:无论是 Array 还是普通对象,都有一个内部的 “elements” 存储空间来保存这些整数下标对应的值。这解释了 bug 为什么同时出现在了 Array 和 Object 上。

  2. V8 在管理大量元素时区分快速 Fast 和字典 Dictionary 两种存储模式。当数组规模较小并密集时,使用快速模式;若遇到稀疏或非常大的索引,就可能切换到字典模式。在快速模式下,向新索引赋值会自动更新 length 并尽量扩容底层数组;超过阈值后会退化到字典模式,用哈希表存储元素。同时 V8 为了优化性能,设置了多个内部变量用于判断数组下不同的数据类型并进行优化。官方博客

  3. 在隐式转换过程中,V8 忘了对内部变量进行判断,于是一个原本合法的 key 值因为大于内部变量限制,而导致了 Invalid array length 异常。这个属于 V8 长期存在的 bug,至今没有修正。

再深入的分析通过搜索已经找不到答案了,需要去查看庞大的 V8 源代码,超出了我的时间余裕和兴趣能力,就这样吧。