Skip to content

Commit 6b28d88

Browse files
authored
assert: fix TypeError on deepStrictEqual with null Map key or Set member
deepStrictEqual() and util.isDeepStrictEqual() threw "Cannot read properties of null (reading 'constructor')" instead of comparing when a Map key or Set member was null/undefined (or another primitive) and lined up against object-only keys/members in the other collection with an equal count. The primitive/null handling was gated behind an optimization that is skipped when the counts match, letting such keys reach objectComparisonStart, which dereferences `.constructor`. Resolve primitive and null keys/members directly in every case. Signed-off-by: semx <7532921+semx@users.noreply.github.com> PR-URL: #64449 Reviewed-By: Jordan Harband <ljharb@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 7cb3631 commit 6b28d88

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

lib/internal/util/comparisons.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,16 +699,18 @@ function setObjectEquiv(array, a, b, mode, memo) {
699699
const comparator = mode !== kLoose ? objectComparisonStart : innerDeepEqual;
700700
const extraChecks = mode === kLoose || array.length !== a.size;
701701
for (const val1 of a) {
702-
if (extraChecks) {
703-
if (typeof val1 === 'object') {
704-
if (b.has(val1)) {
705-
continue;
706-
}
707-
} else if (b.has(val1)) {
702+
// Primitive and null members can only match by identity, and must never
703+
// reach objectComparisonStart (which throws on `val.constructor` for
704+
// null/undefined). Resolve them directly for every such member.
705+
if (typeof val1 !== 'object' || val1 === null) {
706+
if (b.has(val1)) {
708707
continue;
709-
} else if (mode !== kLoose) {
708+
}
709+
if (mode !== kLoose) {
710710
return false;
711711
}
712+
} else if (extraChecks && b.has(val1)) {
713+
continue;
712714
}
713715

714716
let innerStart = start;

test/parallel/test-assert-deep.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ test('es6 Maps and Sets', () => {
278278
assertDeepAndStrictEqual(new Set([[1, 2], [3, 4]]), new Set([[3, 4], [1, 2]]));
279279
assertNotDeepOrStrict(new Set([{ a: 0 }]), new Set([{ a: 1 }]));
280280
assertNotDeepOrStrict(new Set([Symbol()]), new Set([Symbol()]));
281+
// A null/primitive member lined up against object-only members in the other
282+
// set must report inequality, not throw on `member.constructor`.
283+
assertNotDeepOrStrict(new Set([null, {}, {}]), new Set([{}, {}, {}]));
284+
assertNotDeepOrStrict(new Set([undefined, {}, {}]), new Set([{}, {}, {}]));
281285

282286
{
283287
const a = [ 1, 2 ];
@@ -298,6 +302,17 @@ test('es6 Maps and Sets', () => {
298302
new Map([[[1], 1], [{}, 2]]),
299303
new Map([[[1], 2], [{}, 1]])
300304
);
305+
// A null/primitive key that lines up with object-only keys in the other map
306+
// must report inequality, not throw on `key.constructor`. Refs: object keys
307+
// of `b` equal in count to `a.size` used to skip the primitive-key handling.
308+
assertNotDeepOrStrict(
309+
new Map([[null, 1], [{}, 2]]),
310+
new Map([[{}, 9], [{}, 9]])
311+
);
312+
assertNotDeepOrStrict(
313+
new Map([[undefined, 1], [{}, 2]]),
314+
new Map([[{}, 9], [{}, 9]])
315+
);
301316

302317
assertNotDeepOrStrict(new Set([1]), [1]);
303318
assertNotDeepOrStrict(new Set(), []);

0 commit comments

Comments
 (0)