-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
assert: fix deepEqual similar sets and maps bug #13426
Conversation
This fixes a bug where deepEqual and deepStrictEqual would have incorrect behaviour in sets and maps containing multiple equivalent keys. Fixes: nodejs#13347 Refs: nodejs#12142
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM % nits & CI
test/parallel/test-assert-deep.js
Outdated
@@ -187,6 +187,20 @@ assertOnlyDeepEqual(new Map([['a', '1']]), new Map([['a', 1]])); | |||
|
|||
assertDeepAndStrictEqual(new Set([{}]), new Set([{}])); | |||
|
|||
// Discussion of these test cases here - https://github.com/nodejs/node/issues/13347 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just put Ref: https://github.com/nodejs/node/issues/13347
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
lib/assert.js
Outdated
// This is a set of the entries in b which have been consumed in our pairwise | ||
// comparison. Initialized lazily so sets which only have value types can | ||
// skip an extra allocation. | ||
let bEntriesUsed = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd rather you name it something like usedEntries
, since bEntriesUsed
sounds like a boolean predicating a need to use .entries()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you choose to accept this nit, rename everywhere else as well...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure - I don't have a strong opinion so I've renamed it.
My concern with usedEntries
is that it doesn't make it clear that we're marking entries in set b
.
lib/assert.js
Outdated
for (const val1 of a) { | ||
// If the value doesn't exist in the second set by reference, and its an | ||
// object or an array we'll need to go hunting for something thats | ||
// deep-equal to it. Note that this is O(n^2) complexity, and will get | ||
// slower if large, very similar sets / maps are nested inside. | ||
// Unfortunately there's no real way around this. | ||
if (!setHasSimilarElement(b, val1, strict, memo)) { | ||
if (bEntriesUsed == null && typeof val1 === 'object') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it important that a current val1 is an object? And why it should prevent initializing bEntriesUsed "for now" if it is not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug doesn't affect value types because its impossible for two strings, numbers, bools, etc to be deepEqual to one another without being reference-equal. So in the case where the set or map only contains value typed entries, there's no need to allocate an extra set for bEntriesUsed
.
Lazily allocating the set when needed is a microoptimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then maybe it worth to continue that optimization and checking type of val2 before adding it to set of usedEntries? Because presence of even single object in a set with large number of typed entries would lead to adding them to usedEntries without any need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe.. but in practice its very rare to mix types in set / map keys. And it still won't matter in strict mode because there's an early return for value types anyway.
Uhhhh but not in non-strict mode. Huh - looks like this also doesn't throw:
assert.deepEqual(new Set([3, '3']), new Set([3, 4]));
fixes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... Fixed. PR updated so this behaves correctly too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
Lint errors:
|
Oops! Sorry about that. Fixed!
|
Linux CI fail was infrastructure related, but still rerunning: https://ci.nodejs.org/job/node-test-commit-linux/10349/ |
Unless there's any more comments, I think this is ready to merge 👓 |
I'll land this later today (let PDT people a last chance to chime in) |
This fixes a bug where deepEqual and deepStrictEqual would have incorrect behaviour in sets and maps containing multiple equivalent keys. PR-URL: nodejs#13426 Fixes: nodejs#13347 Refs: nodejs#12142 Reviewed-By: Refael Ackermann <refack@gmail.com>
landed in 7cddcc9 |
Extra quick sanity of |
This fixes a bug where deepEqual and deepStrictEqual would have
incorrect behaviour in sets and maps containing multiple equivalent
keys.
Fixes: #13347
Refs: #12142
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
assert