Skip to content

Commit e785a12

Browse files
committed
assert,util: fix constructor lookup in deep equal comparison
The latest performance optimization did not take into account that an object may have a property called constructor. This is addressed in this PR by adding a new fast path and using fallbacks.
1 parent f89baf2 commit e785a12

File tree

3 files changed

+173
-40
lines changed

3 files changed

+173
-40
lines changed

benchmark/assert/deepequal-prims-and-objs-big-loop.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const notCircular = {};
1010
notCircular.circular = {};
1111

1212
const primValues = {
13+
'null_prototype': { __proto__: null },
1314
'string': 'abcdef',
1415
'number': 1_000,
1516
'boolean': true,
@@ -24,6 +25,7 @@ const primValues = {
2425
};
2526

2627
const primValues2 = {
28+
'null_prototype': { __proto__: null },
2729
'object': { property: 'abcdef' },
2830
'array': [1, 2, 3],
2931
'set_object': new Set([[1]]),
@@ -35,6 +37,7 @@ const primValues2 = {
3537
};
3638

3739
const primValuesUnequal = {
40+
'null_prototype': { __proto__: { __proto__: null } },
3841
'string': 'abcdez',
3942
'number': 1_001,
4043
'boolean': false,

lib/internal/util/comparisons.js

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
'use strict';
22

33
const {
4+
Array,
5+
ArrayBuffer,
46
ArrayIsArray,
57
ArrayPrototypeFilter,
68
ArrayPrototypePush,
9+
BigInt,
10+
BigInt64Array,
711
BigIntPrototypeValueOf,
12+
BigUint64Array,
13+
Boolean,
814
BooleanPrototypeValueOf,
15+
DataView,
16+
Date,
917
DatePrototypeGetTime,
1018
Error,
19+
Float16Array,
20+
Float32Array,
21+
Float64Array,
22+
Function,
23+
Int16Array,
24+
Int32Array,
25+
Int8Array,
26+
Map,
27+
Number,
1128
NumberPrototypeValueOf,
29+
Object,
1230
ObjectGetOwnPropertyDescriptor,
1331
ObjectGetOwnPropertySymbols: getOwnSymbols,
1432
ObjectGetPrototypeOf,
@@ -17,18 +35,65 @@ const {
1735
ObjectPrototypeHasOwnProperty: hasOwn,
1836
ObjectPrototypePropertyIsEnumerable: hasEnumerable,
1937
ObjectPrototypeToString,
38+
Promise,
39+
RegExp,
2040
SafeSet,
41+
Set,
42+
String,
2143
StringPrototypeValueOf,
44+
Symbol,
2245
SymbolPrototypeValueOf,
2346
TypedArrayPrototypeGetByteLength: getByteLength,
2447
TypedArrayPrototypeGetSymbolToStringTag,
48+
Uint16Array,
49+
Uint32Array,
2550
Uint8Array,
51+
Uint8ClampedArray,
52+
WeakMap,
53+
WeakSet,
2654
} = primordials;
2755

2856
const { compare } = internalBinding('buffer');
2957
const assert = require('internal/assert');
3058
const { isURL } = require('internal/url');
3159
const { isError } = require('internal/util');
60+
const { Buffer } = require('buffer');
61+
62+
const wellKnownConstructors = new SafeSet();
63+
wellKnownConstructors.add(Array);
64+
wellKnownConstructors.add(ArrayBuffer);
65+
wellKnownConstructors.add(BigInt);
66+
wellKnownConstructors.add(BigInt64Array);
67+
wellKnownConstructors.add(BigUint64Array);
68+
wellKnownConstructors.add(Boolean);
69+
wellKnownConstructors.add(Buffer);
70+
wellKnownConstructors.add(DataView);
71+
wellKnownConstructors.add(Date);
72+
wellKnownConstructors.add(Error);
73+
if (Float16Array) { // TODO(BridgeAR): Remove when regularly supported
74+
wellKnownConstructors.add(Float16Array);
75+
}
76+
wellKnownConstructors.add(Float32Array);
77+
wellKnownConstructors.add(Float64Array);
78+
wellKnownConstructors.add(Function);
79+
wellKnownConstructors.add(Int16Array);
80+
wellKnownConstructors.add(Int32Array);
81+
wellKnownConstructors.add(Int8Array);
82+
wellKnownConstructors.add(Map);
83+
wellKnownConstructors.add(Number);
84+
wellKnownConstructors.add(Object);
85+
wellKnownConstructors.add(Promise);
86+
wellKnownConstructors.add(RegExp);
87+
wellKnownConstructors.add(Set);
88+
wellKnownConstructors.add(String);
89+
wellKnownConstructors.add(Symbol);
90+
wellKnownConstructors.add(Uint16Array);
91+
wellKnownConstructors.add(Uint32Array);
92+
wellKnownConstructors.add(Uint8Array);
93+
wellKnownConstructors.add(Uint8ClampedArray);
94+
wellKnownConstructors.add(WeakMap);
95+
wellKnownConstructors.add(WeakSet);
96+
3297
const types = require('internal/util/types');
3398
const {
3499
isAnyArrayBuffer,
@@ -198,11 +263,15 @@ function innerDeepEqual(val1, val2, mode, memos) {
198263
}
199264

200265
function objectComparisonStart(val1, val2, mode, memos) {
201-
if (mode === kStrict &&
202-
(val1.constructor !== val2.constructor ||
203-
(val1.constructor === undefined &&
204-
ObjectGetPrototypeOf(val1) !== ObjectGetPrototypeOf(val2)))) {
205-
return false;
266+
if (mode === kStrict) {
267+
if (wellKnownConstructors.has(val1.constructor) ||
268+
(val1.constructor !== undefined && !hasOwn(val1, 'constructor'))) {
269+
if (val1.constructor !== val2.constructor) {
270+
return false;
271+
}
272+
} else if (ObjectGetPrototypeOf(val1) !== ObjectGetPrototypeOf(val2)) {
273+
return false;
274+
}
206275
}
207276

208277
const val1Tag = ObjectPrototypeToString(val1);

test/parallel/test-assert-deep.js

Lines changed: 96 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,46 +1527,107 @@ test('Detects differences in deeply nested arrays instead of seeing a new object
15271527
);
15281528
});
15291529

1530-
// check URL
1531-
{
1532-
const a = new URL('http://foo');
1533-
const b = new URL('http://bar');
1530+
test('URLs', () => {
1531+
// check URL
1532+
{
1533+
const a = new URL('http://foo');
1534+
const b = new URL('http://bar');
15341535

1535-
assertNotDeepOrStrict(a, b);
1536-
}
1536+
assertNotDeepOrStrict(a, b);
1537+
}
15371538

1538-
{
1539-
const a = new URL('http://foo');
1540-
const b = new URL('http://foo');
1539+
{
1540+
const a = new URL('http://foo');
1541+
const b = new URL('http://foo');
15411542

1543+
assertDeepAndStrictEqual(a, b);
1544+
}
1545+
1546+
{
1547+
const a = new URL('http://foo');
1548+
const b = new URL('http://foo');
1549+
a.bar = 1;
1550+
b.bar = 2;
1551+
assertNotDeepOrStrict(a, b);
1552+
}
1553+
1554+
{
1555+
const a = new URL('http://foo');
1556+
const b = new URL('http://foo');
1557+
a.bar = 1;
1558+
b.bar = 1;
1559+
assertDeepAndStrictEqual(a, b);
1560+
}
1561+
1562+
{
1563+
const a = new URL('http://foo');
1564+
const b = new URL('http://bar');
1565+
assert.throws(
1566+
() => assert.deepStrictEqual(a, b),
1567+
{
1568+
code: 'ERR_ASSERTION',
1569+
name: 'AssertionError',
1570+
message: /http:\/\/bar/
1571+
}
1572+
);
1573+
}
1574+
});
1575+
1576+
test('Own property constructor properties should check against the original prototype', () => {
1577+
const a = { constructor: { name: 'Foo' } };
1578+
const b = { constructor: { name: 'Foo' } };
15421579
assertDeepAndStrictEqual(a, b);
1543-
}
15441580

1545-
{
1546-
const a = new URL('http://foo');
1547-
const b = new URL('http://foo');
1548-
a.bar = 1;
1549-
b.bar = 2;
1550-
assertNotDeepOrStrict(a, b);
1551-
}
1581+
let prototype = {};
1582+
Object.setPrototypeOf(a, prototype);
1583+
Object.setPrototypeOf(b, prototype);
1584+
assertDeepAndStrictEqual(a, b);
15521585

1553-
{
1554-
const a = new URL('http://foo');
1555-
const b = new URL('http://foo');
1556-
a.bar = 1;
1557-
b.bar = 1;
1586+
Object.setPrototypeOf(b, {});
1587+
assertNotDeepOrStrict(a, {});
1588+
1589+
prototype = { __proto__: null };
1590+
Object.setPrototypeOf(a, prototype);
1591+
Object.setPrototypeOf(b, prototype);
15581592
assertDeepAndStrictEqual(a, b);
1559-
}
15601593

1561-
{
1562-
const a = new URL('http://foo');
1563-
const b = new URL('http://bar');
1564-
assert.throws(
1565-
() => assert.deepStrictEqual(a, b),
1566-
{
1567-
code: 'ERR_ASSERTION',
1568-
name: 'AssertionError',
1569-
message: /http:\/\/bar/
1570-
}
1571-
);
1572-
}
1594+
Object.setPrototypeOf(b, { __proto__: null });
1595+
assert.notDeepStrictEqual(a, b);
1596+
assert.notDeepStrictEqual(b, a);
1597+
1598+
// Turn off no-restricted-properties because we are testing deepEqual!
1599+
/* eslint-disable no-restricted-properties */
1600+
assert.deepEqual(a, b);
1601+
assert.deepEqual(b, a);
1602+
});
1603+
1604+
test('Inherited null prototype without own constructor properties should check the correct prototype', () => {
1605+
const a = { foo: { name: 'Foo' } };
1606+
const b = { foo: { name: 'Foo' } };
1607+
assertDeepAndStrictEqual(a, b);
1608+
1609+
let prototype = {};
1610+
Object.setPrototypeOf(a, prototype);
1611+
Object.setPrototypeOf(b, prototype);
1612+
assertDeepAndStrictEqual(a, b);
1613+
1614+
Object.setPrototypeOf(b, {});
1615+
assertNotDeepOrStrict(a, {});
1616+
1617+
prototype = { __proto__: null };
1618+
Object.setPrototypeOf(a, prototype);
1619+
Object.setPrototypeOf(b, prototype);
1620+
assertDeepAndStrictEqual(a, b);
1621+
1622+
Object.setPrototypeOf(b, { __proto__: null });
1623+
assert.notDeepStrictEqual(a, b);
1624+
assert.notDeepStrictEqual(b, a);
1625+
1626+
assert.notDeepStrictEqual({ __proto__: null }, { __proto__: { __proto__: null } });
1627+
assert.notDeepStrictEqual({ __proto__: { __proto__: null } }, { __proto__: null });
1628+
1629+
// Turn off no-restricted-properties because we are testing deepEqual!
1630+
/* eslint-disable no-restricted-properties */
1631+
assert.deepEqual(a, b);
1632+
assert.deepEqual(b, a);
1633+
});

0 commit comments

Comments
 (0)