Skip to content

assert: fix TypeError on deepStrictEqual with null Map key or Set member#64449

Open
semx wants to merge 1 commit into
nodejs:mainfrom
semx:fix-deepstrictequal-map-null-key
Open

assert: fix TypeError on deepStrictEqual with null Map key or Set member#64449
semx wants to merge 1 commit into
nodejs:mainfrom
semx:fix-deepstrictequal-map-null-key

Conversation

@semx

@semx semx commented Jul 12, 2026

Copy link
Copy Markdown

assert.deepStrictEqual() (and util.isDeepStrictEqual()) throw a TypeError instead of comparing when the first Map has a null (or other primitive) key that lines up against object-only keys in the other map:

const assert = require('node:assert');

const a = new Map([[null, 1], [{}, 2]]);
const b = new Map([[{}, 9], [{}, 9]]);

assert.deepStrictEqual(a, b);
// TypeError: Cannot read properties of null (reading 'constructor')
// expected: an AssertionError (the maps are not deeply equal)

The same happens for an undefined key. Set has the identical problem for a null/undefined member (once the set is large enough to skip the small-set fast path):

assert.deepStrictEqual(new Set([null, {}, {}]), new Set([{}, {}, {}]));
// TypeError: Cannot read properties of null (reading 'constructor')

It only triggers in strict mode when the other collection's keys/members are all objects and their count equals the first collection's size.

Cause

In mapObjectEquiv and setObjectEquiv (lib/internal/util/comparisons.js), primitive/null keys and members are resolved directly via b.has() / b.get(), but that handling was gated behind extraChecks (array.length !== a.size). When the counts match, the gate is skipped and the primitive/null key/member falls through to objectComparisonStart, which dereferences .constructor and throws on null/undefined.

Fix

Handle primitive/null keys and members unconditionally — they can only match by identity and can never match through the object comparator — so they are always resolved by direct lookup and never reach objectComparisonStart. The collections above now compare as unequal (throwing an AssertionError, as expected) instead of throwing a TypeError. Object comparison is unchanged.

Added regression cases (null and undefined keys/members, for both Map and Set) to test/parallel/test-assert-deep.js.

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.
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module. labels Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants