GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — util: hide duplicated stack frames when using util.inspect · nodejs/node@65e4e68

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • lib/internal/util
    • inspect.js
  • test/parallel
    • test-util-inspect.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/internal/util/inspect.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/inspect.js
+131-3Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,13 +1315,122 @@ function identicalSequenceRange(a, b) {
13151315
len++;
13161316
}
13171317
if (len > 3) {
1318-
return { len, offset: i };
1318+
return [len, i];
13191319
}
13201320
}
13211321
}
13221322
}
13231323

1324-
return { len: 0, offset: 0 };
1324+
return [0, 0];
1325+
}
1326+
1327+
function getDuplicateErrorFrameRanges(frames) {
1328+
// Build a map: frame line -> sorted list of indices where it occurs
1329+
const result = [];
1330+
const lineToPositions = new SafeMap();
1331+
1332+
for (let i = 0; i < frames.length; i++) {
1333+
const positions = lineToPositions.get(frames[i]);
1334+
if (positions === undefined) {
1335+
lineToPositions.set(frames[i], [i]);
1336+
} else {
1337+
positions[positions.length] = i;
1338+
}
1339+
}
1340+
1341+
const minimumDuplicateRange = 3;
1342+
// Not enough duplicate lines to consider collapsing
1343+
if (frames.length - lineToPositions.size <= minimumDuplicateRange) {
1344+
return result;
1345+
}
1346+
1347+
for (let i = 0; i < frames.length - minimumDuplicateRange; i++) {
1348+
const positions = lineToPositions.get(frames[i]);
1349+
// Find the next occurrence of the same line after i, if any
1350+
if (positions.length === 1 || positions[positions.length - 1] === i) {
1351+
continue;
1352+
}
1353+
1354+
const current = positions.indexOf(i) + 1;
1355+
if (current === positions.length) {
1356+
continue;
1357+
}
1358+
1359+
// Theoretical maximum range, adjusted while iterating
1360+
let range = positions[positions.length - 1] - i;
1361+
if (range < minimumDuplicateRange) {
1362+
continue;
1363+
}
1364+
let extraSteps;
1365+
if (current + 1 < positions.length) {
1366+
// Optimize initial step size by choosing the greatest common divisor (GCD)
1367+
// of all candidate distances to the same frame line. This tends to match
1368+
// the true repeating block size and minimizes fallback iterations.
1369+
let gcdRange = 0;
1370+
for (let j = current; j < positions.length; j++) {
1371+
let distance = positions[j] - i;
1372+
while (distance !== 0) {
1373+
const remainder = gcdRange % distance;
1374+
if (gcdRange !== 0) {
1375+
// Add other possible ranges as fallback
1376+
extraSteps ??= new SafeSet();
1377+
extraSteps.add(gcdRange);
1378+
}
1379+
gcdRange = distance;
1380+
distance = remainder;
1381+
}
1382+
if (gcdRange === 1) break;
1383+
}
1384+
range = gcdRange;
1385+
if (extraSteps) {
1386+
extraSteps.delete(range);
1387+
extraSteps = [...extraSteps];
1388+
}
1389+
}
1390+
let maxRange = range;
1391+
let maxDuplicates = 0;
1392+
1393+
let duplicateRanges = 0;
1394+
1395+
for (let nextStart = i + range; /* ignored */ ; nextStart += range) {
1396+
let equalFrames = 0;
1397+
for (let j = 0; j < range; j++) {
1398+
if (frames[i + j] !== frames[nextStart + j]) {
1399+
break;
1400+
}
1401+
equalFrames++;
1402+
}
1403+
// Adjust the range to match different type of ranges.
1404+
if (equalFrames !== range) {
1405+
if (!extraSteps?.length) {
1406+
break;
1407+
}
1408+
// Memorize former range in case the smaller one would hide less.
1409+
if (duplicateRanges !== 0 && maxRange * maxDuplicates < range * duplicateRanges) {
1410+
maxRange = range;
1411+
maxDuplicates = duplicateRanges;
1412+
}
1413+
range = extraSteps.pop();
1414+
nextStart = i;
1415+
duplicateRanges = 0;
1416+
continue;
1417+
}
1418+
duplicateRanges++;
1419+
}
1420+
1421+
if (maxDuplicates !== 0 && maxRange * maxDuplicates >= range * duplicateRanges) {
1422+
range = maxRange;
1423+
duplicateRanges = maxDuplicates;
1424+
}
1425+
1426+
if (duplicateRanges * range >= 3) {
1427+
result.push(i + range, range, duplicateRanges);
1428+
// Skip over the collapsed portion to avoid overlapping matches.
1429+
i += range * (duplicateRanges + 1) - 1;
1430+
}
1431+
}
1432+
1433+
return result;
13251434
}
13261435

13271436
function getStackString(ctx, error) {
@@ -1355,14 +1464,33 @@ function getStackFrames(ctx, err, stack) {
13551464
const causeStackStart = StringPrototypeIndexOf(causeStack, '\n at');
13561465
if (causeStackStart !== -1) {
13571466
const causeFrames = StringPrototypeSplit(StringPrototypeSlice(causeStack, causeStackStart + 1), '\n');
1358-
const { len, offset } = identicalSequenceRange(frames, causeFrames);
1467+
const { 0: len, 1: offset } = identicalSequenceRange(frames, causeFrames);
13591468
if (len > 0) {
13601469
const skipped = len - 2;
13611470
const msg = ` ... ${skipped} lines matching cause stack trace ...`;
13621471
frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined'));
13631472
}
13641473
}
13651474
}
1475+
1476+
// Remove recursive repetitive stack frames in long stacks
1477+
if (frames.length > 10) {
1478+
const ranges = getDuplicateErrorFrameRanges(frames);
1479+
1480+
for (let i = ranges.length - 3; i >= 0; i -= 3) {
1481+
const offset = ranges[i];
1482+
const length = ranges[i + 1];
1483+
const duplicateRanges = ranges[i + 2];
1484+
1485+
const msg = ` ... collapsed ${length * duplicateRanges} duplicate lines ` +
1486+
'matching above ' +
1487+
(duplicateRanges > 1 ?
1488+
`${length} lines ${duplicateRanges} times...` :
1489+
'lines ...');
1490+
frames.splice(offset, length * duplicateRanges, ctx.stylize(msg, 'undefined'));
1491+
}
1492+
}
1493+
13661494
return frames;
13671495
}
13681496

Collapse file

‎test/parallel/test-util-inspect.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-util-inspect.js
+169Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,175 @@ assert.strictEqual(
29202920
process.cwd = originalCWD;
29212921
}
29222922

2923+
{
2924+
// Use a fake stack to verify the expected colored outcome.
2925+
const err = new Error('Hide duplicate frames in long stack');
2926+
err.stack = [
2927+
'Error: Hide duplicate frames in long stack',
2928+
' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)',
2929+
' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)',
2930+
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
2931+
' at Fancy (node:vm:697:32)',
2932+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2933+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2934+
' at Fancy (node:vm:697:32)',
2935+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2936+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2937+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2938+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2939+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2940+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2941+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2942+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2943+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2944+
' at require (node:internal/modules/helpers:14:16)',
2945+
' at Array.forEach (<anonymous>)',
2946+
' at require (node:internal/modules/helpers:14:16)',
2947+
' at Array.forEach (<anonymous>)',
2948+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
2949+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
2950+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2951+
' at require (node:internal/modules/helpers:14:16)',
2952+
' at Array.forEach (<anonymous>)',
2953+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
2954+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
2955+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2956+
' at require (node:internal/modules/helpers:14:16)',
2957+
' at Array.forEach (<anonymous>)',
2958+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
2959+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
2960+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2961+
' at require (node:internal/modules/helpers:14:16)',
2962+
' at Array.forEach (<anonymous>)',
2963+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
2964+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
2965+
' at /test/test-util-inspect.js:2239:9',
2966+
' at getActual (node:assert:592:5)',
2967+
' at /test/test-util-inspect.js:2239:9',
2968+
' at getActual (node:assert:592:5)',
2969+
' at /test/test-util-inspect.js:2239:9',
2970+
' at getActual (node:assert:592:5)',
2971+
].join('\n');
2972+
2973+
assert.strictEqual(
2974+
util.inspect(err, { colors: true }),
2975+
'Error: Hide duplicate frames in long stack\n' +
2976+
' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' +
2977+
' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' +
2978+
'\x1B[90m at Module._compile (node:internal/modules/cjs/loader:827:30)\x1B[39m\n' +
2979+
'\x1B[90m at Fancy (node:vm:697:32)\x1B[39m\n' +
2980+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)\n' +
2981+
'\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' +
2982+
'\x1B[90m ... collapsed 3 duplicate lines matching above lines ...\x1B[39m\n' +
2983+
2984+
'\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' +
2985+
'\x1B[90m ... collapsed 5 duplicate lines matching above 1 lines 5 times...\x1B[39m\n' +
2986+
2987+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' +
2988+
'\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' +
2989+
' at Array.forEach (<anonymous>)\n' +
2990+
'\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' +
2991+
' at Array.forEach (<anonymous>)\n' +
2992+
' at foobar/test/parallel/test-util-inspect.js:2760:12\n' +
2993+
' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' +
2994+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' +
2995+
'\x1B[90m ... collapsed 10 duplicate lines matching above 5 lines 2 times...\x1B[39m\n' +
2996+
2997+
'\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' +
2998+
' at Array.forEach (<anonymous>)\n' +
2999+
' at foobar/test/parallel/test-util-inspect.js:2760:12\n' +
3000+
' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' +
3001+
' at /test/test-util-inspect.js:2239:9\n' +
3002+
'\x1B[90m at getActual (node:assert:592:5)\x1B[39m\n' +
3003+
'\x1B[90m ... collapsed 4 duplicate lines matching above 2 lines 2 times...\x1B[39m',
3004+
);
3005+
3006+
// Use a fake stack to verify the expected colored outcome.
3007+
const err2 = new Error('Hide duplicate frames in long stack');
3008+
err2.stack = [
3009+
'Error: Hide duplicate frames in long stack',
3010+
' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)',
3011+
' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)',
3012+
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
3013+
3014+
// 3
3015+
' at Fancy (node:vm:697:32)',
3016+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
3017+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3018+
' at Fancy (node:vm:697:32)',
3019+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
3020+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3021+
3022+
// 6 * 1
3023+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3024+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3025+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3026+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3027+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3028+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3029+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
3030+
3031+
// 10
3032+
' at require (node:internal/modules/helpers:14:16)',
3033+
' at Array.forEach (<anonymous>)',
3034+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
3035+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
3036+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
3037+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
3038+
' at require (node:internal/modules/helpers:14:16)',
3039+
' at Array.forEach (<anonymous>)',
3040+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
3041+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
3042+
3043+
' at require (node:internal/modules/helpers:14:16)',
3044+
' at Array.forEach (<anonymous>)',
3045+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
3046+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
3047+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
3048+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
3049+
' at require (node:internal/modules/helpers:14:16)',
3050+
' at Array.forEach (<anonymous>)',
3051+
` at foobar/test/parallel/test-util-inspect.js:2760:12`,
3052+
` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`,
3053+
3054+
// 2 * 2
3055+
' at /test/test-util-inspect.js:2239:9',
3056+
' at getActual (node:assert:592:5)',
3057+
' at /test/test-util-inspect.js:2239:9',
3058+
' at getActual (node:assert:592:5)',
3059+
' at /test/test-util-inspect.js:2239:9',
3060+
' at getActual (node:assert:592:5)',
3061+
].join('\n');
3062+
3063+
assert.strictEqual(
3064+
util.inspect(err2, { colors: true }),
3065+
'Error: Hide duplicate frames in long stack\n' +
3066+
' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' +
3067+
' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' +
3068+
'\x1B[90m at Module._compile (node:internal/modules/cjs/loader:827:30)\x1B[39m\n' +
3069+
'\x1B[90m at Fancy (node:vm:697:32)\x1B[39m\n' +
3070+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)\n' +
3071+
'\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' +
3072+
'\x1B[90m ... collapsed 3 duplicate lines matching above lines ...\x1B[39m\n' +
3073+
'\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' +
3074+
'\x1B[90m ... collapsed 6 duplicate lines matching above 1 lines 6 times...\x1B[39m\n' +
3075+
'\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' +
3076+
' at Array.forEach (<anonymous>)\n' +
3077+
' at foobar/test/parallel/test-util-inspect.js:2760:12\n' +
3078+
' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' +
3079+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' +
3080+
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' +
3081+
'\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' +
3082+
' at Array.forEach (<anonymous>)\n' +
3083+
' at foobar/test/parallel/test-util-inspect.js:2760:12\n' +
3084+
' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' +
3085+
'\x1B[90m ... collapsed 10 duplicate lines matching above lines ...\x1B[39m\n' +
3086+
' at /test/test-util-inspect.js:2239:9\n' +
3087+
'\x1B[90m at getActual (node:assert:592:5)\x1B[39m\n' +
3088+
'\x1B[90m ... collapsed 4 duplicate lines matching above 2 lines 2 times...\x1B[39m',
3089+
);
3090+
}
3091+
29233092
{
29243093
// Cross platform checks.
29253094
const err = new Error('foo');

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.