Skip to content

Commit 5bb2cdc

Browse files
assert: prevent OOM when generating diff for large objects
Objects with many converging paths to shared objects can cause exponential growth in util.inspect output. When assert.strictEqual fails on such objects, the error message generation would OOM while trying to create a diff of the 100+ MB inspect strings. Add a 2MB limit to inspectValue() output. When truncation occurs, a marker is added and the error message indicates lines were skipped. The comparison itself is unaffected; only the error output is truncated.
1 parent 9132d16 commit 5bb2cdc

File tree

1 file changed

+23
-2
lines changed

1 file changed

+23
-2
lines changed

lib/internal/assert/assertion_error.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
ObjectPrototypeHasOwnProperty,
1313
SafeSet,
1414
String,
15+
StringPrototypeEndsWith,
1516
StringPrototypeRepeat,
1617
StringPrototypeSlice,
1718
StringPrototypeSplit,
@@ -42,6 +43,10 @@ const kReadableOperator = {
4243

4344
const kMaxShortStringLength = 12;
4445
const kMaxLongStringLength = 512;
46+
// Maximum size for inspect output before truncation to prevent OOM.
47+
// Objects with many converging paths can produce exponential growth in
48+
// util.inspect output at high depths, leading to OOM during diff generation.
49+
const kMaxInspectOutputLength = 2 * 1024 * 1024; // 2MB
4550

4651
const kMethodsWithCustomMessageDiff = new SafeSet()
4752
.add('deepStrictEqual')
@@ -69,10 +74,10 @@ function copyError(source) {
6974
return target;
7075
}
7176

72-
function inspectValue(val) {
77+
function inspectValue(val, maxLength = kMaxInspectOutputLength) {
7378
// The util.inspect default values could be changed. This makes sure the
7479
// error messages contain the necessary information nevertheless.
75-
return inspect(val, {
80+
const result = inspect(val, {
7681
compact: false,
7782
customInspect: false,
7883
depth: 1000,
@@ -85,6 +90,15 @@ function inspectValue(val) {
8590
// Inspect getters as we also check them when comparing entries.
8691
getters: true,
8792
});
93+
94+
// Truncate if the output is too large to prevent OOM during diff generation.
95+
// Objects with deeply nested structures can produce exponentially large
96+
// inspect output that causes memory exhaustion when passed to the diff
97+
// algorithm.
98+
if (result.length > maxLength) {
99+
return StringPrototypeSlice(result, 0, maxLength) + '\n... [truncated]';
100+
}
101+
return result;
88102
}
89103

90104
function getErrorMessage(operator, message) {
@@ -189,6 +203,13 @@ function createErrDiff(actual, expected, operator, customMessage, diffType = 'si
189203
let message = '';
190204
const inspectedActual = inspectValue(actual);
191205
const inspectedExpected = inspectValue(expected);
206+
207+
// Check if either value was truncated due to size limits
208+
const truncationMarker = '\n... [truncated]';
209+
if (StringPrototypeEndsWith(inspectedActual, truncationMarker) ||
210+
StringPrototypeEndsWith(inspectedExpected, truncationMarker)) {
211+
skipped = true;
212+
}
192213
const inspectedSplitActual = StringPrototypeSplit(inspectedActual, '\n');
193214
const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, '\n');
194215
const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected);

0 commit comments

Comments
 (0)