Version
v24.13.1, v26.4.0 (reproduced on both)
Platform
Darwin 24.x arm64 (also reproduces on Linux x64)
Subsystem
assert, util
What steps will reproduce the bug?
const { isDeepStrictEqual } = require('node:util');
isDeepStrictEqual(
new Map([[null, { v: 1 }], [{ a: 1 }, 1]]),
new Map([[{ b: 2 }, { v: 1 }], [{ a: 1 }, 1]])
);
// TypeError: Cannot read properties of null (reading 'constructor')
The same input crashes assert.deepStrictEqual and assert.notDeepStrictEqual
with the same TypeError (instead of an AssertionError / clean pass).
The triggering shape: one Map has a null key whose value is an object, the
other Map lacks a null key but contains an object key with a deeply-equal
value, plus at least one additional entry. Simpler null-key cases (primitive
values, or no other entries) return correctly, so the crash is in the
second-pass unordered key matching.
How often does it reproduce? Is there a required condition?
Deterministic.
What is the expected behavior? Why is that the expected behavior?
isDeepStrictEqual returns false; assert.deepStrictEqual throws
AssertionError; assert.notDeepStrictEqual passes. A comparison predicate
should never throw a TypeError on valid inputs.
What do you see instead?
TypeError: Cannot read properties of null (reading 'constructor')
Additional information
Likely cause: in the fallback pass that pairs non-identical Map keys by deep
equality, keys with typeof key === 'object' are collected for pairwise
comparison, and null (also typeof 'object') reaches a code path that reads
key.constructor without a null guard.
Found by differential fuzzing of deep-equality implementations; happy to
provide more failing inputs (they all share the shape above).
Version
v24.13.1, v26.4.0 (reproduced on both)
Platform
Subsystem
assert, util
What steps will reproduce the bug?
The same input crashes
assert.deepStrictEqualandassert.notDeepStrictEqualwith the same TypeError (instead of an
AssertionError/ clean pass).The triggering shape: one Map has a
nullkey whose value is an object, theother Map lacks a
nullkey but contains an object key with a deeply-equalvalue, plus at least one additional entry. Simpler null-key cases (primitive
values, or no other entries) return correctly, so the crash is in the
second-pass unordered key matching.
How often does it reproduce? Is there a required condition?
Deterministic.
What is the expected behavior? Why is that the expected behavior?
isDeepStrictEqualreturnsfalse;assert.deepStrictEqualthrowsAssertionError;assert.notDeepStrictEqualpasses. A comparison predicateshould never throw a
TypeErroron valid inputs.What do you see instead?
Additional information
Likely cause: in the fallback pass that pairs non-identical Map keys by deep
equality, keys with
typeof key === 'object'are collected for pairwisecomparison, and
null(alsotypeof 'object') reaches a code path that readskey.constructorwithout a null guard.Found by differential fuzzing of deep-equality implementations; happy to
provide more failing inputs (they all share the shape above).