-
-
Notifications
You must be signed in to change notification settings - Fork 34.6k
Labels
assertIssues and PRs related to the assert subsystem.Issues and PRs related to the assert subsystem.confirmed-bugIssues with confirmed bugs.Issues with confirmed bugs.
Description
Version
v22.16.0+ (and v24.x)
Platform
All platforms
Subsystem
assert
What steps will reproduce the bug?
const assert = require('assert');
assert.deepEqual([null], [undefined]);How often does it reproduce? Is there a required condition?
Always reproduces on Node >= 22.16.0. Passes on Node <= 22.15.0.
What is the expected behavior? Why is that the expected behavior?
The assertion should pass because:
- The documentation states: "Primitive values are compared with the
==operator" - In JavaScript,
null == undefinedistrue - This worked in all Node versions prior to 22.16.0
What do you see instead?
AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:
[ null ]
should loosely deep-equal
[ undefined ]
Root Cause
PR #57619 (labeled as "performance improvement") introduced this change. The modification in objEquiv now checks a[i] !== undefined with strict equality (===) before calling innerDeepEqual, which would have used loose equality (==):
// Before: called innerDeepEqual() which respects loose equality mode
if (!innerDeepEqual(a[i], b[i], mode, memos)) { return false; }
// After: checks with strict equality BEFORE calling innerDeepEqual
if (b[i] === undefined) {
if (a[i] !== undefined || !hasOwn(a, i)) // <-- uses === not ==
return false;
}When comparing [null] vs [undefined]:
b[0]isundefined→ enters first brancha[0] !== undefined→null !== undefinedistrue(strict equality)- Returns
falseimmediately, never callsinnerDeepEqualwhich would use==
Additional information
- This is an undocumented breaking change in an LTS release
- The PR was not labeled as semver-major
- Affects anyone comparing arrays that may contain
nullvalues against expectedundefinedvalues (common with MongoDB/BSON which storesundefinedasnull)
I understand deepEqual is marked as "Legacy" and docs warn it "can have surprising results", but this specific change contradicts the documented behavior and broke existing code without notice.
Metadata
Metadata
Assignees
Labels
assertIssues and PRs related to the assert subsystem.Issues and PRs related to the assert subsystem.confirmed-bugIssues with confirmed bugs.Issues with confirmed bugs.