Skip to content

Commit 3995b72

Browse files
committed
assert: fix deepEqual similar sets and maps bug
This fixes a bug where deepEqual and deepStrictEqual would have incorrect behaviour in sets and maps containing multiple equivalent keys. Fixes: nodejs#13347 Refs: nodejs#12142
1 parent e0f4310 commit 3995b72

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

lib/assert.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function _deepEqual(actual, expected, strict, memos) {
285285
return areEq;
286286
}
287287

288-
function setHasSimilarElement(set, val1, strict, memo) {
288+
function setHasSimilarElement(set, val1, bEntriesUsed, strict, memo) {
289289
if (set.has(val1))
290290
return true;
291291

@@ -296,8 +296,14 @@ function setHasSimilarElement(set, val1, strict, memo) {
296296

297297
// Otherwise go looking.
298298
for (const val2 of set) {
299-
if (_deepEqual(val1, val2, strict, memo))
299+
if (bEntriesUsed && bEntriesUsed.has(val2))
300+
continue;
301+
302+
if (_deepEqual(val1, val2, strict, memo)) {
303+
if (bEntriesUsed)
304+
bEntriesUsed.add(val2);
300305
return true;
306+
}
301307
}
302308

303309
return false;
@@ -314,21 +320,29 @@ function setEquiv(a, b, strict, memo) {
314320
if (a.size !== b.size)
315321
return false;
316322

323+
// This is a set of the entries in b which have been consumed in our pairwise
324+
// comparison. Initialized lazily so sets which only have value types can
325+
// skip an extra allocation.
326+
let bEntriesUsed = null;
327+
317328
for (const val1 of a) {
318329
// If the value doesn't exist in the second set by reference, and its an
319330
// object or an array we'll need to go hunting for something thats
320331
// deep-equal to it. Note that this is O(n^2) complexity, and will get
321332
// slower if large, very similar sets / maps are nested inside.
322333
// Unfortunately there's no real way around this.
323-
if (!setHasSimilarElement(b, val1, strict, memo)) {
334+
if (bEntriesUsed == null && typeof val1 === 'object')
335+
bEntriesUsed = new Set();
336+
337+
if (!setHasSimilarElement(b, val1, bEntriesUsed, strict, memo)) {
324338
return false;
325339
}
326340
}
327341

328342
return true;
329343
}
330344

331-
function mapHasSimilarEntry(map, key1, item1, strict, memo) {
345+
function mapHasSimilarEntry(map, key1, item1, bEntriesUsed, strict, memo) {
332346
// To be able to handle cases like:
333347
// Map([[1, 'a'], ['1', 'b']]) vs Map([['1', 'a'], [1, 'b']])
334348
// or:
@@ -349,8 +363,13 @@ function mapHasSimilarEntry(map, key1, item1, strict, memo) {
349363
if (key2 === key1)
350364
continue;
351365

366+
if (bEntriesUsed && bEntriesUsed.has(key2))
367+
continue;
368+
352369
if (_deepEqual(key1, key2, strict, memo) &&
353370
_deepEqual(item1, item2, strict, memo)) {
371+
if (bEntriesUsed)
372+
bEntriesUsed.add(key2);
354373
return true;
355374
}
356375
}
@@ -366,10 +385,15 @@ function mapEquiv(a, b, strict, memo) {
366385
if (a.size !== b.size)
367386
return false;
368387

388+
let bEntriesUsed = null;
389+
369390
for (const [key1, item1] of a) {
391+
if (bEntriesUsed == null && typeof key1 === 'object')
392+
bEntriesUsed = new Set();
393+
370394
// Just like setEquiv above, this hunt makes this function O(n^2) when
371395
// using objects and lists as keys
372-
if (!mapHasSimilarEntry(b, key1, item1, strict, memo))
396+
if (!mapHasSimilarEntry(b, key1, item1, bEntriesUsed, strict, memo))
373397
return false;
374398
}
375399

test/parallel/test-assert-deep.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,20 @@ assertOnlyDeepEqual(new Map([['a', '1']]), new Map([['a', 1]]));
187187

188188
assertDeepAndStrictEqual(new Set([{}]), new Set([{}]));
189189

190+
// Discussion of these test cases here - https://github.com/nodejs/node/issues/13347
191+
assertNotDeepOrStrict(
192+
new Set([{a: 1}, {a: 1}]),
193+
new Set([{a: 1}, {a: 2}])
194+
);
195+
assertNotDeepOrStrict(
196+
new Set([{a: 1}, {a: 1}, {a: 2}]),
197+
new Set([{a: 1}, {a: 2}, {a: 2}])
198+
);
199+
assertNotDeepOrStrict(
200+
new Map([[{x: 1}, 5], [{x: 1}, 5]]),
201+
new Map([[{x: 1}, 5], [{x: 2}, 5]])
202+
);
203+
190204
// This is an awful case, where a map contains multiple equivalent keys:
191205
assertOnlyDeepEqual(
192206
new Map([[1, 'a'], ['1', 'b']]),

0 commit comments

Comments
 (0)