summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Bridgewater <ruben@bridgewater.de>2018-05-16 18:10:08 +0200
committerRuben Bridgewater <ruben@bridgewater.de>2018-09-13 10:49:04 +0200
commiteb61127c48ba59e52d7cea293ad80fa54000939c (patch)
treebac34ae84a86c310684a0148a42b6a8d71047759
parent1cee08536794b6d7bc8d3b9ace2b494c74985f7d (diff)
downloadandroid-node-v8-eb61127c48ba59e52d7cea293ad80fa54000939c.tar.gz
android-node-v8-eb61127c48ba59e52d7cea293ad80fa54000939c.tar.bz2
android-node-v8-eb61127c48ba59e52d7cea293ad80fa54000939c.zip
util: limit inspection output size to 128 MB
The maximum hard limit that `util.inspect()` could theoretically handle is the maximum string size. That is ~2 ** 28 on 32 bit systems and ~2 ** 30 on 64 bit systems. Due to the recursive algorithm a complex object could easily exceed that limit without throwing an error right away and therefore crashing the application by exceeding the heap limit. `util.inspect()` is fast enough to compute 128 MB of data below one second on an Intel(R) Core(TM) i7-5600U CPU. This hard limit allows to inspect arbitrary big objects from now on without crashing the application or blocking the event loop significantly. PR-URL: https://github.com/nodejs/node/pull/22756 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
-rw-r--r--doc/api/util.md15
-rw-r--r--lib/util.js69
-rw-r--r--test/parallel/test-util-inspect-long-running.js20
3 files changed, 76 insertions, 28 deletions
diff --git a/doc/api/util.md b/doc/api/util.md
index 44495e977d..d67f46c043 100644
--- a/doc/api/util.md
+++ b/doc/api/util.md
@@ -360,6 +360,10 @@ stream.write('With ES6');
<!-- YAML
added: v0.3.0
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/22756
+ description: The inspection output is now limited to about 128 MB. Data
+ above that size will not be fully inspected.
- version: v10.6.0
pr-url: https://github.com/nodejs/node/pull/20725
description: Inspecting linked lists and similar objects is now possible
@@ -408,11 +412,11 @@ changes:
TODO(BridgeAR): Deprecate `maxArrayLength` and replace it with
`maxEntries`.
-->
- * `maxArrayLength` {number} Specifies the maximum number of `Array`,
+ * `maxArrayLength` {integer} 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
negative to show no elements. **Default:** `100`.
- * `breakLength` {number} The length at which an object's keys are split
+ * `breakLength` {integer} The length at which an object's keys are split
across multiple lines. Set to `Infinity` to format an object as a single
line. **Default:** `60` for legacy compatibility.
* `compact` {boolean} Setting this to `false` changes the default indentation
@@ -532,9 +536,10 @@ console.log(inspect(weakSet, { showHidden: true }));
```
Please note that `util.inspect()` is a synchronous method that is mainly
-intended as a debugging tool. Some input values can have a significant
-performance overhead that can block the event loop. Use this function
-with care and never in a hot code path.
+intended as a debugging tool. Its maximum output length is limited to
+approximately 128 MB and input values that result in output bigger than that
+will not be inspected fully. Such values can have a significant performance
+overhead that can block the event loop for a significant amount of time.
### Customizing `util.inspect` colors
diff --git a/lib/util.js b/lib/util.js
index 35bd9ec46f..985f455e5d 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -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,7 +626,7 @@ 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);
}
@@ -631,6 +634,11 @@ function formatValue(ctx, value, recurseTimes) {
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;
}
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;
diff --git a/test/parallel/test-util-inspect-long-running.js b/test/parallel/test-util-inspect-long-running.js
new file mode 100644
index 0000000000..167f72ba64
--- /dev/null
+++ b/test/parallel/test-util-inspect-long-running.js
@@ -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 });