Skip to content

Commit 526c011

Browse files
aduh95targos
authored andcommitted
perf_hooks: fix stack overflow error
PR-URL: #60084 Fixes: #54768 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 884fe88 commit 526c011

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,7 +6,6 @@ const {
66
ArrayPrototypeFilter,
77
ArrayPrototypeIncludes,
88
ArrayPrototypePush,
9-
ArrayPrototypePushApply,
109
ArrayPrototypeSlice,
1110
ArrayPrototypeSort,
1211
Error,
@@ -15,6 +14,7 @@ const {
1514
ObjectDefineProperties,
1615
ObjectFreeze,
1716
ObjectKeys,
17+
SafeArrayPrototypePushApply,
1818
SafeMap,
1919
SafeSet,
2020
Symbol,
@@ -307,7 +307,7 @@ class PerformanceObserver {
307307
maybeIncrementObserverCount(type);
308308
if (buffered) {
309309
const entries = filterBufferMapByNameAndType(undefined, type);
310-
ArrayPrototypePushApply(this.#buffer, entries);
310+
SafeArrayPrototypePushApply(this.#buffer, entries);
311311
kPending.add(this);
312312
if (kPending.size)
313313
queuePending();
@@ -514,9 +514,9 @@ function filterBufferMapByNameAndType(name, type) {
514514
return [];
515515
} else {
516516
bufferList = [];
517-
ArrayPrototypePushApply(bufferList, markEntryBuffer);
518-
ArrayPrototypePushApply(bufferList, measureEntryBuffer);
519-
ArrayPrototypePushApply(bufferList, resourceTimingBuffer);
517+
SafeArrayPrototypePushApply(bufferList, markEntryBuffer);
518+
SafeArrayPrototypePushApply(bufferList, measureEntryBuffer);
519+
SafeArrayPrototypePushApply(bufferList, resourceTimingBuffer);
520520
}
521521
if (name !== undefined) {
522522
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)