From cf7a75dac31aa0f39a11720f1b20a3b329cce894 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 1 Oct 2025 14:55:58 +0200 Subject: [PATCH 1/3] perf_hooks: fix stack overflow error --- lib/internal/per_context/primordials.js | 20 +++++++++++++++++++ lib/internal/perf/observe.js | 10 +++++----- test/parallel/test-performance-many-marks.js | 9 +++++++++ test/parallel/test-primordials-apply.js | 21 ++++++++++++++++++++ typings/primordials.d.ts | 1 + 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 test/parallel/test-performance-many-marks.js diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index aee7de99086bd1..6166778ecd3b4c 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -270,6 +270,8 @@ const { Array: ArrayConstructor, ArrayPrototypeForEach, ArrayPrototypeMap, + ArrayPrototypePushApply, + ArrayPrototypeSlice, FinalizationRegistry, FunctionPrototypeCall, Map, @@ -720,5 +722,23 @@ primordials.SafeStringPrototypeSearch = (str, regexp) => { return match ? match.index : -1; }; +/** + * Variadic functions with lots of arguments will cause stack overflow errors. + * Use this function when `items` can be arbitrarily large, this function splits + * it into chunks of size 4096 making stack overflow less likely. + * @param {Array} arr + * @param {Parameters} items + * @returns {ReturnType} + */ +primordials.SafeArrayPrototypePushApply = (arr, items) => { + let start = 0; + let end = start + 0x1000; + while (end < items.length) { + ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start, start = end)); + end += 0x1000; + } + return ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start)); +}; + ObjectSetPrototypeOf(primordials, null); ObjectFreeze(primordials); diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index 3e992b8f3f9576..58eca95d9de710 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -6,7 +6,6 @@ const { ArrayPrototypeFilter, ArrayPrototypeIncludes, ArrayPrototypePush, - ArrayPrototypePushApply, ArrayPrototypeSlice, ArrayPrototypeSort, Error, @@ -14,6 +13,7 @@ const { MathMin, ObjectDefineProperties, ObjectFreeze, + SafeArrayPrototypePushApply, SafeMap, SafeSet, Symbol, @@ -300,7 +300,7 @@ class PerformanceObserver { maybeIncrementObserverCount(type); if (buffered) { const entries = filterBufferMapByNameAndType(undefined, type); - ArrayPrototypePushApply(this.#buffer, entries); + SafeArrayPrototypePushApply(this.#buffer, entries); kPending.add(this); if (kPending.size) queuePending(); @@ -507,9 +507,9 @@ function filterBufferMapByNameAndType(name, type) { return []; } else { bufferList = []; - ArrayPrototypePushApply(bufferList, markEntryBuffer); - ArrayPrototypePushApply(bufferList, measureEntryBuffer); - ArrayPrototypePushApply(bufferList, resourceTimingBuffer); + SafeArrayPrototypePushApply(bufferList, markEntryBuffer); + SafeArrayPrototypePushApply(bufferList, measureEntryBuffer); + SafeArrayPrototypePushApply(bufferList, resourceTimingBuffer); } if (name !== undefined) { bufferList = ArrayPrototypeFilter(bufferList, (buffer) => buffer.name === name); diff --git a/test/parallel/test-performance-many-marks.js b/test/parallel/test-performance-many-marks.js new file mode 100644 index 00000000000000..0156d31780cf09 --- /dev/null +++ b/test/parallel/test-performance-many-marks.js @@ -0,0 +1,9 @@ +'use strict'; +require('../common'); + +for (let i = 0; i < 1e6; i++) { + performance.mark(`mark-${i}`); +} + +performance.getEntriesByName('mark-0'); +performance.clearMarks(); diff --git a/test/parallel/test-primordials-apply.js b/test/parallel/test-primordials-apply.js index 0901a87ba18777..fae672f6f60c45 100644 --- a/test/parallel/test-primordials-apply.js +++ b/test/parallel/test-primordials-apply.js @@ -10,6 +10,7 @@ const { ArrayPrototypeUnshiftApply, MathMaxApply, MathMinApply, + SafeArrayPrototypePushApply, StringPrototypeConcatApply, TypedArrayOfApply, } = require('internal/test/binding').primordials; @@ -43,6 +44,26 @@ const { assert.deepStrictEqual(arr1, expected); } +{ + const arr1 = [1, 2, 3]; + const arr2 = [4, 5, 6]; + + const expected = [...arr1, ...arr2]; + + assert.strictEqual(SafeArrayPrototypePushApply(arr1, arr2), expected.length); + assert.deepStrictEqual(arr1, expected); +} + +{ + const arr1 = [1, 2, 3]; + const arr2 = Array.from({ length: 1e6 }, (_, i) => i); + + const expected = [...arr1, ...arr2]; + + assert.strictEqual(SafeArrayPrototypePushApply(arr1, arr2), expected.length); + assert.deepStrictEqual(arr1, expected); +} + { const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; diff --git a/typings/primordials.d.ts b/typings/primordials.d.ts index ed77d4112e2984..204c12b0087f91 100644 --- a/typings/primordials.d.ts +++ b/typings/primordials.d.ts @@ -374,6 +374,7 @@ declare namespace primordials { export const RegExpPrototypeGetUnicode: UncurryGetter; export const RegExpPrototypeSymbolReplace: UncurryMethod export const RegExpPrototypeSymbolSplit: UncurryMethod + export const SafeArrayPrototypePushApply: typeof ArrayPrototypePushApply; export import Set = globalThis.Set; export const SetLength: typeof Set.length export const SetName: typeof Set.name From d5b5e370d914ea65a2b676cb706af96fe1e8079b Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 2 Oct 2025 09:44:33 +0200 Subject: [PATCH 2/3] fixup! perf_hooks: fix stack overflow error --- lib/internal/per_context/primordials.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index 6166778ecd3b4c..a8dcc3537f6e4d 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -725,17 +725,17 @@ primordials.SafeStringPrototypeSearch = (str, regexp) => { /** * Variadic functions with lots of arguments will cause stack overflow errors. * Use this function when `items` can be arbitrarily large, this function splits - * it into chunks of size 4096 making stack overflow less likely. + * it into chunks of size 2**16 making stack overflow less likely. * @param {Array} arr * @param {Parameters} items * @returns {ReturnType} */ primordials.SafeArrayPrototypePushApply = (arr, items) => { let start = 0; - let end = start + 0x1000; + let end = 0x10000; while (end < items.length) { ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start, start = end)); - end += 0x1000; + end += 0x10000; } return ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start)); }; From 02feba6cebdd4fe8d2e265e0a1992c12ba46a09a Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 2 Oct 2025 13:16:12 +0200 Subject: [PATCH 3/3] fixup! perf_hooks: fix stack overflow error --- lib/internal/per_context/primordials.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index a8dcc3537f6e4d..e2b5f763df7dd9 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -731,13 +731,16 @@ primordials.SafeStringPrototypeSearch = (str, regexp) => { * @returns {ReturnType} */ primordials.SafeArrayPrototypePushApply = (arr, items) => { - let start = 0; let end = 0x10000; - while (end < items.length) { - ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start, start = end)); - end += 0x10000; + if (end < items.length) { + let start = 0; + do { + ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start, start = end)); + end += 0x10000; + } while (end < items.length); + items = ArrayPrototypeSlice(items, start); } - return ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start)); + return ArrayPrototypePushApply(arr, items); }; ObjectSetPrototypeOf(primordials, null);