diff --git a/doc/api/util.md b/doc/api/util.md index 529f7b6fc7e3c7..218fcf6b435ffa 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -363,13 +363,13 @@ stream.write('With ES6'); * `maxArrayLength` {number} Specifies the maximum number of `Array`, [`TypedArray`][], [`WeakMap`][] and [`WeakSet`][] elements to include when - formatting. Set to `null` or `Infinity` to show all elements. Set to `0` or + formatting. Set to `Infinity` or `null` to show all elements. Set to `0` or negative to show no elements. **Default:** `100`. * `breakLength` {number} The length at which an object's keys are split across multiple lines. Set to `Infinity` to format an object as a single @@ -421,8 +421,9 @@ changes: example below. **Default:** `true`. * `depth` {number} Specifies the number of visible nested `Object`s in an `object`. This is useful to minimize the inspection output for large - complicated objects. To make it recurse indefinitely pass `null` or - `Infinity`. **Default:** `Infinity`. + complicated objects. To make it recurse almost indefinitely pass any high + number, `Infinity` or `null`. The real value is capped at 1000. + **Default:** `10`. * Returns: {string} The representation of passed object The `util.inspect()` method returns a string representation of `object` that is diff --git a/lib/internal/errors.js b/lib/internal/errors.js index de4a566af4d889..41994f82b04082 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -658,8 +658,9 @@ function dnsException(code, syscall, hostname) { return ex; } -let maxStack_ErrorName; -let maxStack_ErrorMessage; +let maxStackErrorName; +let maxStackErrorMessage; +let maxStackLimit = 0; /** * Returns true if `err.name` and `err.message` are equal to engine-specific * values indicating max call stack size has been exceeded. @@ -669,18 +670,29 @@ let maxStack_ErrorMessage; * @returns {boolean} */ function isStackOverflowError(err) { - if (maxStack_ErrorMessage === undefined) { + if (maxStackErrorMessage === undefined) { try { - function overflowStack() { overflowStack(); } + function overflowStack() { + maxStackLimit++; + overflowStack(); + } overflowStack(); } catch (err) { - maxStack_ErrorMessage = err.message; - maxStack_ErrorName = err.name; + maxStackErrorMessage = err.message; + maxStackErrorName = err.name; } } - return err.name === maxStack_ErrorName && - err.message === maxStack_ErrorMessage; + return err.name === maxStackErrorName && + err.message === maxStackErrorMessage; +} + +function getMaxStackLimit() { + if (maxStackLimit === 0) { + // Use a fake "error" + isStackOverflowError({ name: '', message: '' }); + } + return maxStackLimit; } module.exports = { @@ -689,6 +701,7 @@ module.exports = { exceptionWithHostPort, uvException, isStackOverflowError, + getMaxStackLimit, getMessage, AssertionError, SystemError, diff --git a/lib/util.js b/lib/util.js index a47f6305f44895..4c2c562a911e8e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -21,12 +21,16 @@ 'use strict'; -const errors = require('internal/errors'); const { - ERR_FALSY_VALUE_REJECTION, - ERR_INVALID_ARG_TYPE, - ERR_OUT_OF_RANGE -} = errors.codes; + getMaxStackLimit, + errnoException: _errnoException, + exceptionWithHostPort: _exceptionWithHostPort, + codes: { + ERR_FALSY_VALUE_REJECTION, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE + } +} = require('internal/errors'); const { TextDecoder, TextEncoder } = require('internal/encoding'); const { isBuffer } = require('buffer').Buffer; @@ -80,7 +84,7 @@ const { const inspectDefaultOptions = Object.seal({ showHidden: false, - depth: null, + depth: 10, colors: false, customInspect: true, showProxy: false, @@ -105,6 +109,12 @@ const numberRegExp = /^(0|[1-9][0-9]*)$/; const readableRegExps = {}; +// The actual maximum stack limit has to be divided by the number of function +// calls that are maximal necessary for each depth level. That does not seem +// to be sufficient though, so let us just divide it by 10 and use 1000 as upper +// limit. +const MAX_DEPTH_LIMIT = Math.min(Math.floor(getMaxStackLimit() / 10), 1000); + const MIN_LINE_LENGTH = 16; // Escaped special characters. Use empty strings to fill up unused entries. @@ -287,6 +297,14 @@ function debuglog(set) { return debugs[set]; } +function getMaxDepth(depth) { + if (depth === null) + return MAX_DEPTH_LIMIT; + if (depth < MAX_DEPTH_LIMIT) + return depth; + return MAX_DEPTH_LIMIT; +} + /** * Echos the value of any input. Tries to print the value out * in the best way possible given the different types. @@ -332,7 +350,7 @@ function inspect(value, opts) { } if (ctx.colors) ctx.stylize = stylizeWithColor; if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; - return formatValue(ctx, value, ctx.depth); + return formatValue(ctx, value, getMaxDepth(ctx.depth)); } inspect.custom = customInspectSymbol; @@ -677,11 +695,9 @@ function formatValue(ctx, value, recurseTimes, ln) { } } - if (recurseTimes != null) { - if (recurseTimes < 0) - return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special'); - recurseTimes -= 1; - } + if (recurseTimes < 0) + return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special'); + recurseTimes -= 1; ctx.seen.push(value); const output = formatter(ctx, value, recurseTimes, keys); @@ -1246,8 +1262,8 @@ function getSystemErrorName(err) { // Keep the `exports =` so that various functions can still be monkeypatched module.exports = exports = { - _errnoException: errors.errnoException, - _exceptionWithHostPort: errors.exceptionWithHostPort, + _errnoException, + _exceptionWithHostPort, _extend, callbackify, debuglog, diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index b5ef8dca083b10..69119d644021b6 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -1404,3 +1404,21 @@ util.inspect(process); const args = (function() { return arguments; })('a'); assert.strictEqual(util.inspect(args), "[Arguments] { '0': 'a' }"); } + +{ + // Test that a long linked list can be inspected without throwing an error. + const list = {}; + let head = list; + // 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 = {}; + assert.strictEqual( + util.inspect(list), + '{ next: \n { next: \n { next: \n { next: \n { ' + + 'next: \n { next: { next: { next: { next: { next: { ' + + 'next: [Object] } } } } } } } } } } }' + ); + const longList = util.inspect(list, { depth: Infinity }); + assert.strictEqual(longList.match(/next/g).length, 1001); +}