-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
util: limit inspection output size to 128 MB #22756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -406,24 +406,27 @@ function inspect(value, opts) { | |
maxArrayLength: inspectDefaultOptions.maxArrayLength, | ||
breakLength: inspectDefaultOptions.breakLength, | ||
indentationLvl: 0, | ||
compact: inspectDefaultOptions.compact | ||
compact: inspectDefaultOptions.compact, | ||
budget: {} | ||
}; | ||
// Legacy... | ||
if (arguments.length > 2) { | ||
if (arguments[2] !== undefined) { | ||
ctx.depth = arguments[2]; | ||
} | ||
if (arguments.length > 3 && arguments[3] !== undefined) { | ||
ctx.colors = arguments[3]; | ||
if (arguments.length > 1) { | ||
// Legacy... | ||
if (arguments.length > 2) { | ||
if (arguments[2] !== undefined) { | ||
ctx.depth = arguments[2]; | ||
} | ||
if (arguments.length > 3 && arguments[3] !== undefined) { | ||
ctx.colors = arguments[3]; | ||
} | ||
} | ||
} | ||
// Set user-specified options | ||
if (typeof opts === 'boolean') { | ||
ctx.showHidden = opts; | ||
} else if (opts) { | ||
const optKeys = Object.keys(opts); | ||
for (var i = 0; i < optKeys.length; i++) { | ||
ctx[optKeys[i]] = opts[optKeys[i]]; | ||
// Set user-specified options | ||
if (typeof opts === 'boolean') { | ||
ctx.showHidden = opts; | ||
} else if (opts) { | ||
const optKeys = Object.keys(opts); | ||
for (var i = 0; i < optKeys.length; i++) { | ||
ctx[optKeys[i]] = opts[optKeys[i]]; | ||
} | ||
} | ||
} | ||
if (ctx.colors) ctx.stylize = stylizeWithColor; | ||
|
@@ -623,14 +626,19 @@ function noPrototypeIterator(ctx, value, recurseTimes) { | |
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the | ||
// value afterwards again. | ||
function formatValue(ctx, value, recurseTimes) { | ||
// Primitive types cannot have properties | ||
// Primitive types cannot have properties. | ||
if (typeof value !== 'object' && typeof value !== 'function') { | ||
return formatPrimitive(ctx.stylize, value, ctx); | ||
} | ||
if (value === null) { | ||
return ctx.stylize('null', 'null'); | ||
} | ||
|
||
if (ctx.stop !== undefined) { | ||
const name = getConstructorName(value) || value[Symbol.toStringTag]; | ||
return ctx.stylize(`[${name || 'Object'}]`, 'special'); | ||
} | ||
|
||
if (ctx.showProxy) { | ||
const proxy = getProxyDetails(value); | ||
if (proxy !== undefined) { | ||
|
@@ -639,11 +647,11 @@ function formatValue(ctx, value, recurseTimes) { | |
} | ||
|
||
// Provide a hook for user-specified inspect functions. | ||
// Check that value is an object with an inspect function on it | ||
// Check that value is an object with an inspect function on it. | ||
if (ctx.customInspect) { | ||
const maybeCustom = value[customInspectSymbol]; | ||
if (typeof maybeCustom === 'function' && | ||
// Filter out the util module, its inspect function is special | ||
// Filter out the util module, its inspect function is special. | ||
maybeCustom !== exports.inspect && | ||
// Also filter out any prototype objects using the circular check. | ||
!(value.constructor && value.constructor.prototype === value)) { | ||
|
@@ -685,7 +693,7 @@ function formatRaw(ctx, value, recurseTimes) { | |
|
||
let extrasType = kObjectType; | ||
|
||
// Iterators and the rest are split to reduce checks | ||
// Iterators and the rest are split to reduce checks. | ||
if (value[Symbol.iterator]) { | ||
noIterator = false; | ||
if (Array.isArray(value)) { | ||
|
@@ -766,7 +774,7 @@ function formatRaw(ctx, value, recurseTimes) { | |
} | ||
base = dateToISOString(value); | ||
} else if (isError(value)) { | ||
// Make error with message first say the error | ||
// Make error with message first say the error. | ||
base = formatError(value); | ||
// Wrap the error in brackets in case it has no stack trace. | ||
const stackStart = base.indexOf('\n at'); | ||
|
@@ -885,7 +893,21 @@ function formatRaw(ctx, value, recurseTimes) { | |
} | ||
ctx.seen.pop(); | ||
|
||
return reduceToSingleString(ctx, output, base, braces); | ||
const res = reduceToSingleString(ctx, output, base, braces); | ||
const budget = ctx.budget[ctx.indentationLvl] || 0; | ||
const newLength = budget + res.length; | ||
ctx.budget[ctx.indentationLvl] = newLength; | ||
// If any indentationLvl exceeds this limit, limit further inspecting to the | ||
// minimum. Otherwise the recursive algorithm might continue inspecting the | ||
// object even though the maximum string size (~2 ** 28 on 32 bit systems and | ||
// ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at | ||
// exactly 2 ** 27 but a bit higher. This depends on the object shape. | ||
// This limit also makes sure that huge objects don't block the event loop | ||
// significantly. | ||
if (newLength > 2 ** 27) { | ||
ctx.stop = true; | ||
} | ||
return res; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get wanting to limit string construction to avoid memory issues and it looks like when this is implemented objects just won't be crawled (something like
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this again: the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's cool. This is a good first attempt at least. |
||
|
||
function handleMaxCallStackSize(ctx, err, constructor, tag) { | ||
|
@@ -1057,8 +1079,9 @@ function formatTypedArray(ctx, value, recurseTimes) { | |
formatBigInt; | ||
for (var i = 0; i < maxLength; ++i) | ||
output[i] = elementFormatter(ctx.stylize, value[i]); | ||
if (remaining > 0) | ||
if (remaining > 0) { | ||
output[i] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; | ||
} | ||
if (ctx.showHidden) { | ||
// .buffer goes last, it's not a primitive like the others. | ||
ctx.indentationLvl += 2; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict'; | ||
|
||
require('../common'); | ||
|
||
// Test that huge objects don't crash due to exceeding the maximum heap size. | ||
|
||
const util = require('util'); | ||
|
||
// Create a difficult to stringify object. Without the artificial limitation | ||
// this would crash or throw an maximum string size error. | ||
let last = {}; | ||
const obj = last; | ||
|
||
for (let i = 0; i < 1000; i++) { | ||
last.next = { circular: obj, last, obj: { a: 1, b: 2, c: true } }; | ||
last = last.next; | ||
obj[i] = last; | ||
} | ||
|
||
util.inspect(obj, { depth: Infinity }); |
Uh oh!
There was an error while loading. Please reload this page.