perf_hooks: fix stack overflow error

PR-URL: https://github.com/nodejs/node/pull/60084
Fixes: https://github.com/nodejs/node/issues/54768
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
Antoine du Hamel 2025-10-13 10:05:45 +02:00 committed by GitHub
parent 985e2fb383
commit 822a8c3244
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 59 additions and 5 deletions

View File

@ -270,6 +270,8 @@ const {
Array: ArrayConstructor,
ArrayPrototypeForEach,
ArrayPrototypeMap,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
FinalizationRegistry,
FunctionPrototypeCall,
Map,
@ -720,5 +722,26 @@ 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 2**16 making stack overflow less likely.
* @param {Array<unknown>} arr
* @param {Parameters<typeof Array.prototype.push>} items
* @returns {ReturnType<typeof Array.prototype.push>}
*/
primordials.SafeArrayPrototypePushApply = (arr, items) => {
let 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, items);
};
ObjectSetPrototypeOf(primordials, null);
ObjectFreeze(primordials);

View File

@ -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);

View File

@ -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();

View File

@ -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];

View File

@ -374,6 +374,7 @@ declare namespace primordials {
export const RegExpPrototypeGetUnicode: UncurryGetter<typeof RegExp.prototype, "unicode">;
export const RegExpPrototypeSymbolReplace: UncurryMethod<typeof RegExp.prototype, typeof Symbol.replace>
export const RegExpPrototypeSymbolSplit: UncurryMethod<typeof RegExp.prototype, typeof Symbol.split>
export const SafeArrayPrototypePushApply: typeof ArrayPrototypePushApply;
export import Set = globalThis.Set;
export const SetLength: typeof Set.length
export const SetName: typeof Set.name