Skip to content

Commit 822a8c3

Browse files
authored
perf_hooks: fix stack overflow error
PR-URL: #60084 Fixes: #54768 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 985e2fb commit 822a8c3

File tree

5 files changed

+59
-5
lines changed

5 files changed

+59
-5
lines changed

lib/internal/per_context/primordials.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ const {
270270
Array: ArrayConstructor,
271271
ArrayPrototypeForEach,
272272
ArrayPrototypeMap,
273+
ArrayPrototypePushApply,
274+
ArrayPrototypeSlice,
273275
FinalizationRegistry,
274276
FunctionPrototypeCall,
275277
Map,
@@ -720,5 +722,26 @@ primordials.SafeStringPrototypeSearch = (str, regexp) => {
720722
return match ? match.index : -1;
721723
};
722724

725+
/**
726+
* Variadic functions with lots of arguments will cause stack overflow errors.
727+
* Use this function when `items` can be arbitrarily large, this function splits
728+
* it into chunks of size 2**16 making stack overflow less likely.
729+
* @param {Array<unknown>} arr
730+
* @param {Parameters<typeof Array.prototype.push>} items
731+
* @returns {ReturnType<typeof Array.prototype.push>}
732+
*/
733+
primordials.SafeArrayPrototypePushApply = (arr, items) => {
734+
let end = 0x10000;
735+
if (end < items.length) {
736+
let start = 0;
737+
do {
738+
ArrayPrototypePushApply(arr, ArrayPrototypeSlice(items, start, start = end));
739+
end += 0x10000;
740+
} while (end < items.length);
741+
items = ArrayPrototypeSlice(items, start);
742+
}
743+
return ArrayPrototypePushApply(arr, items);
744+
};
745+
723746
ObjectSetPrototypeOf(primordials, null);
724747
ObjectFreeze(primordials);

lib/internal/perf/observe.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ const {
66
ArrayPrototypeFilter,
77
ArrayPrototypeIncludes,
88
ArrayPrototypePush,
9-
ArrayPrototypePushApply,
109
ArrayPrototypeSlice,
1110
ArrayPrototypeSort,
1211
Error,
1312
MathMax,
1413
MathMin,
1514
ObjectDefineProperties,
1615
ObjectFreeze,
16+
SafeArrayPrototypePushApply,
1717
SafeMap,
1818
SafeSet,
1919
Symbol,
@@ -300,7 +300,7 @@ class PerformanceObserver {
300300
maybeIncrementObserverCount(type);
301301
if (buffered) {
302302
const entries = filterBufferMapByNameAndType(undefined, type);
303-
ArrayPrototypePushApply(this.#buffer, entries);
303+
SafeArrayPrototypePushApply(this.#buffer, entries);
304304
kPending.add(this);
305305
if (kPending.size)
306306
queuePending();
@@ -507,9 +507,9 @@ function filterBufferMapByNameAndType(name, type) {
507507
return [];
508508
} else {
509509
bufferList = [];
510-
ArrayPrototypePushApply(bufferList, markEntryBuffer);
511-
ArrayPrototypePushApply(bufferList, measureEntryBuffer);
512-
ArrayPrototypePushApply(bufferList, resourceTimingBuffer);
510+
SafeArrayPrototypePushApply(bufferList, markEntryBuffer);
511+
SafeArrayPrototypePushApply(bufferList, measureEntryBuffer);
512+
SafeArrayPrototypePushApply(bufferList, resourceTimingBuffer);
513513
}
514514
if (name !== undefined) {
515515
bufferList = ArrayPrototypeFilter(bufferList, (buffer) => buffer.name === name);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
require('../common');
3+
4+
for (let i = 0; i < 1e6; i++) {
5+
performance.mark(`mark-${i}`);
6+
}
7+
8+
performance.getEntriesByName('mark-0');
9+
performance.clearMarks();

test/parallel/test-primordials-apply.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
ArrayPrototypeUnshiftApply,
1111
MathMaxApply,
1212
MathMinApply,
13+
SafeArrayPrototypePushApply,
1314
StringPrototypeConcatApply,
1415
TypedArrayOfApply,
1516
} = require('internal/test/binding').primordials;
@@ -43,6 +44,26 @@ const {
4344
assert.deepStrictEqual(arr1, expected);
4445
}
4546

47+
{
48+
const arr1 = [1, 2, 3];
49+
const arr2 = [4, 5, 6];
50+
51+
const expected = [...arr1, ...arr2];
52+
53+
assert.strictEqual(SafeArrayPrototypePushApply(arr1, arr2), expected.length);
54+
assert.deepStrictEqual(arr1, expected);
55+
}
56+
57+
{
58+
const arr1 = [1, 2, 3];
59+
const arr2 = Array.from({ length: 1e6 }, (_, i) => i);
60+
61+
const expected = [...arr1, ...arr2];
62+
63+
assert.strictEqual(SafeArrayPrototypePushApply(arr1, arr2), expected.length);
64+
assert.deepStrictEqual(arr1, expected);
65+
}
66+
4667
{
4768
const arr1 = [1, 2, 3];
4869
const arr2 = [4, 5, 6];

typings/primordials.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ declare namespace primordials {
374374
export const RegExpPrototypeGetUnicode: UncurryGetter<typeof RegExp.prototype, "unicode">;
375375
export const RegExpPrototypeSymbolReplace: UncurryMethod<typeof RegExp.prototype, typeof Symbol.replace>
376376
export const RegExpPrototypeSymbolSplit: UncurryMethod<typeof RegExp.prototype, typeof Symbol.split>
377+
export const SafeArrayPrototypePushApply: typeof ArrayPrototypePushApply;
377378
export import Set = globalThis.Set;
378379
export const SetLength: typeof Set.length
379380
export const SetName: typeof Set.name

0 commit comments

Comments
 (0)