Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
util: recover from maximum call stack size
Using util.inspect should still return values in case the maximum
call stack size is reached. This is important to inspect linked
lists and similar.
  • Loading branch information
BridgeAR committed Jun 11, 2018
commit ef4e8d39457ab6a1e62918e89982405e68e3ff57
9 changes: 7 additions & 2 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ stream.write('With ES6');
<!-- YAML
added: v0.3.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/REPLACEME
description: Inspecting linked lists and similar objects is now possible
up to the maximum call stack size.
- version: v10.0.0
pr-url: https://github.com/nodejs/node/pull/19259
description: The `WeakMap` and `WeakSet` entries can now be inspected
Expand Down Expand Up @@ -388,8 +392,9 @@ changes:
properties will be included in the formatted result as well as [`WeakMap`][]
and [`WeakSet`][] entries. **Default:** `false`.
* `depth` {number} Specifies the number of times to recurse while formatting
the `object`. This is useful for inspecting large complicated objects.
To make it recurse indefinitely pass `null`. **Default:** `2`.
the `object`. This is useful for inspecting large complicated objects. To
make it recurse up to the maximum call stack size pass `Infinity` or `null`.
**Default:** `2`.
* `colors` {boolean} If `true`, the output will be styled with ANSI color
codes. Colors are customizable, see [Customizing `util.inspect` colors][].
**Default:** `false`.
Expand Down
22 changes: 20 additions & 2 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,14 +701,32 @@ function formatValue(ctx, value, recurseTimes) {
}

ctx.seen.push(value);
const output = formatter(ctx, value, recurseTimes, keys);

let output;
// This corresponds to a depth of at least 333 and likely 500.
if (ctx.indentationLvl < 1000) {
output = formatter(ctx, value, recurseTimes, keys);
} else {
try {
output = formatter(ctx, value, recurseTimes, keys);
} catch (err) {
if (errors.isStackOverflowError(err)) {
ctx.seen.pop();
process.emitWarning(
'Inspection reached the maximum call stack size. Incomplete ' +
'inspected object returned.'
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we should be printing this warning, it’s not a good idea to have side effects (especially unexpected) like stdio coming from util.inspect().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the warning though, it would completely be silent and users would very likely not realize this at all. I guess it would not really be important / bad in this case but I believe it would be good to provide some kind of notification about it and I can not think of any alternative right now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BridgeAR Can we add this to the output instead? That seems to make more sense to me, e.g. changing in the line below from [${constructor || tag || 'Object'}] to [${constructor || tag || 'Object'}: Maximum call stack size exceeded]?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is a viable solution. Should we maybe even be more verbose by writing e.g., [...: Inspection interrupted prematurely. Maximum call stack size exceeded.]?

I also think about adding the actually reached depth. Opinions about that?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BridgeAR Seems fine to me, yup.

I also think about adding the actually reached depth.

I wouldn’t expect it to be helpful a lot of the time (because that kind of nesting usually implies some kind of self-similar structure, so exact nesting levels are irrelevant), but it’s also not like it’s terrible information to include. 🤷

return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special');
}
throw err;
}
}
if (extra !== undefined)
output.unshift(extra);

for (var i = 0; i < symbols.length; i++) {
output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0));
}

ctx.seen.pop();

return reduceToSingleString(ctx, output, base, braces);
Expand Down
21 changes: 17 additions & 4 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -1410,10 +1410,23 @@ util.inspect(process);
// Test that a long linked list can be inspected without throwing an error.
const list = {};
let head = list;
// The real cutoff value is closer to 1400 stack frames as of May 2018,
// but let's be generous here – even a linked listed of length 100k should be
// inspectable in some way.
// A linked list of length 100k should be inspectable in some way, even though
// the real cutoff value is much lower than 100k.
for (let i = 0; i < 100000; i++)
head = head.next = {};
util.inspect(list);
assert.strictEqual(
util.inspect(list),
'{ next: { next: { next: [Object] } } }'
);
common.expectWarning({
Warning: [
'Inspection reached the maximum call stack size. ' +
'Incomplete inspected object returned.',
common.noWarnCode
]
});
const longList = util.inspect(list, { depth: Infinity });
const match = longList.match(/next/g);
assert(match.length > 1000 && match.length < 10000);
assert(longList.includes('[Object]'));
}