From 484a3ff04425df11cf45232f989df3f93290fb75 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Sat, 11 Jul 2026 21:24:07 -0400 Subject: [PATCH] assert,util: fix TypeError on Maps with null keys `assert.deepStrictEqual`, `assert.notDeepStrictEqual`, and `util.isDeepStrictEqual` threw a `TypeError` instead of returning a result when one Map had a `null` key with an object value, the other had an object key with a deeply-equal value but no `null` key, and both maps were the same size. In that case `mapObjectEquiv` skipped its primitive and `null` key handling, which was gated on `array.length !== a.size`, and passed the `null` key (which is `typeof 'object'`) to the object comparator. That comparator reads `key.constructor` without a null guard, so it threw. Handle primitive and `null` keys unconditionally, resolving them directly against the other map. Fixes: https://github.com/nodejs/node/issues/64433 Signed-off-by: Paul Bouchon --- lib/internal/util/comparisons.js | 7 +++++-- test/parallel/test-assert-deep.js | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 80d39756cf155a..2582217febc622 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -837,10 +837,13 @@ function mapObjectEquiv(array, a, b, mode, memo) { let start = 0; let end = array.length - 1; const comparator = mode !== kLoose ? objectComparisonStart : innerDeepEqual; - const extraChecks = mode === kLoose || array.length !== a.size; for (const { 0: key1, 1: item1 } of a) { - if (extraChecks && (typeof key1 !== 'object' || key1 === null)) { + // Primitive and `null` keys can never match an object key collected in + // `array`, so resolve them directly against `b`. `null` is `typeof + // 'object'`, so without this it would reach the object comparator, which + // reads `key1.constructor` and throws a `TypeError`. + if (typeof key1 !== 'object' || key1 === null) { if (b.has(key1)) { if (mode !== kLoose || innerDeepEqual(item1, b.get(key1), mode, memo)) { continue; diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index 5d856bf1f378b9..16db17d171d40a 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -421,6 +421,17 @@ test('es6 Maps and Sets', () => { new Map([[undefined, null], ['+000', 2n]]), new Map([[null, undefined], [false, '2']]), ); + // A null key whose value is an object, alongside another object key and a + // matching map size, must not crash the unordered object-key matching (null + // is `typeof 'object'`). Refs: https://github.com/nodejs/node/issues/64433 + assertNotDeepOrStrict( + new Map([[null, { v: 1 }], [{ a: 1 }, 1]]), + new Map([[{ b: 2 }, { v: 1 }], [{ a: 1 }, 1]]) + ); + assertDeepAndStrictEqual( + new Map([[null, { v: 1 }], [{ a: 1 }, 1]]), + new Map([[null, { v: 1 }], [{ a: 1 }, 1]]) + ); const xarray = ['x']; assertDeepAndStrictEqual( new Set([xarray, ['y']]),