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: https://github.com/nodejs/node/issues/62422
PR-URL: https://github.com/nodejs/node/pull/62509
Fixes: https://github.com/nodejs/node/issues/62422
Reviewed-By: Jordan Harband <ljharb@gmail.com>
This commit is contained in:
Ruben Bridgewater authored and GitHub committed 2026-04-09 16:48:25 +00:00
1 parent b52102f11d
commit 8db9d43c45
2 files changed
+42

No files matched your search

+4
View File
@@ -529,6 +529,10 @@ function handleCycles(val1, val2, mode, keys1, keys2, memos, iterationType) {
memos.deep = true;
const result = objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
memos.deep = false;
if (memos.set !== undefined) {
memos.set.delete(memos.c);
memos.set.delete(memos.d);
}
return result;
}
memos.set = new SafeSet();
+38
View File
@@ -260,6 +260,14 @@ function assertOnlyDeepEqual(a, b, err) {
assert.throws(partial, err || { code: 'ERR_ASSERTION' });
}
function activateMemoizedCycleDetection() {
const circA = {};
circA.self = circA;
const circB = {};
circB.self = circB;
assert.deepStrictEqual(circA, circB);
}
test('es6 Maps and Sets', () => {
assertDeepAndStrictEqual(new Set(), new Set());
assertDeepAndStrictEqual(new Map(), new Map());
@@ -609,6 +617,36 @@ test('GH-14441. Circular structures should be consistent', () => {
}
});
test('deepStrictEqual handles shared expected array elements after cycle detection', () => {
const sharedExpected = { outer: { inner: 0 } };
const actualValues = [{ outer: { inner: 0 } }, { outer: { inner: 0 } }];
const expectedValues = [sharedExpected, sharedExpected];
activateMemoizedCycleDetection();
assertDeepAndStrictEqual(actualValues[0], expectedValues[0]);
assertDeepAndStrictEqual(actualValues[1], expectedValues[1]);
assertDeepAndStrictEqual(actualValues, expectedValues);
});
test('deepStrictEqual handles cross-root aliases after cycle detection', () => {
activateMemoizedCycleDetection();
const nestedExpected = {};
nestedExpected.loop = nestedExpected;
nestedExpected.payload = { value: 1 };
const expected = {};
expected.loop = nestedExpected;
expected.payload = { value: 1 };
const actual = {};
actual.loop = expected;
actual.payload = { value: 1 };
assertDeepAndStrictEqual(actual, expected);
});
// https://github.com/nodejs/node-v0.x-archive/pull/7178
test('Ensure reflexivity of deepEqual with `arguments` objects.', () => {
const args = (function() { return arguments; })();