Skip to content

Commit 3fb03d9

Browse files
committed
assert,util: fix stale nested cycle memo entries
Temporary nested cycle-tracking entries could remain in the memory set after a successful comparison. If a later sibling comparison reused one of those objects, deepStrictEqual could incorrectly fail for equivalent structures. This cleans up the temporary nested entries after the nested comparison returns. Fixes: #62422
1 parent 784ca7b commit 3fb03d9

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

lib/internal/util/comparisons.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@ function handleCycles(val1, val2, mode, keys1, keys2, memos, iterationType) {
529529
memos.deep = true;
530530
const result = objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
531531
memos.deep = false;
532+
if (memos.set !== undefined) {
533+
memos.set.delete(memos.c);
534+
memos.set.delete(memos.d);
535+
}
532536
return result;
533537
}
534538
memos.set = new SafeSet();

test/parallel/test-assert-deep.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ function assertOnlyDeepEqual(a, b, err) {
248248
);
249249
}
250250

251+
function activateMemoizedCycleDetection() {
252+
const circA = {};
253+
circA.self = circA;
254+
const circB = {};
255+
circB.self = circB;
256+
assert.deepStrictEqual(circA, circB);
257+
}
258+
251259
test('es6 Maps and Sets', () => {
252260
assertDeepAndStrictEqual(new Set(), new Set());
253261
assertDeepAndStrictEqual(new Map(), new Map());
@@ -597,6 +605,36 @@ test('GH-14441. Circular structures should be consistent', () => {
597605
}
598606
});
599607

608+
test('deepStrictEqual handles shared expected array elements after cycle detection', () => {
609+
const sharedExpected = { outer: { inner: 0 } };
610+
const actualValues = [{ outer: { inner: 0 } }, { outer: { inner: 0 } }];
611+
const expectedValues = [sharedExpected, sharedExpected];
612+
613+
activateMemoizedCycleDetection();
614+
615+
assertDeepAndStrictEqual(actualValues[0], expectedValues[0]);
616+
assertDeepAndStrictEqual(actualValues[1], expectedValues[1]);
617+
assertDeepAndStrictEqual(actualValues, expectedValues);
618+
});
619+
620+
test('deepStrictEqual handles cross-root aliases after cycle detection', () => {
621+
activateMemoizedCycleDetection();
622+
623+
const nestedExpected = {};
624+
nestedExpected.loop = nestedExpected;
625+
nestedExpected.payload = { value: 1 };
626+
627+
const expected = {};
628+
expected.loop = nestedExpected;
629+
expected.payload = { value: 1 };
630+
631+
const actual = {};
632+
actual.loop = expected;
633+
actual.payload = { value: 1 };
634+
635+
assertDeepAndStrictEqual(actual, expected);
636+
});
637+
600638
// https://github.com/nodejs/node-v0.x-archive/pull/7178
601639
test('Ensure reflexivity of deepEqual with `arguments` objects.', () => {
602640
const args = (function() { return arguments; })();

0 commit comments

Comments
 (0)