Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bf4faf3

Browse filesBrowse files
BridgeARBethGriggs
authored andcommitted
assert,util: harden comparison
The former algorithm used checks which were unsafe. Most of these have been replaced with alternatives that can not be manipulated or fooled that easily. PR-URL: #24831 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
1 parent 25dae6c commit bf4faf3
Copy full SHA for bf4faf3

File tree

Expand file treeCollapse file tree

2 files changed

+102
-13
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+102
-13
lines changed
Open diff view settings
Collapse file

‎lib/internal/util/comparisons.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/comparisons.js
+38-13Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ const {
77
isDate,
88
isMap,
99
isRegExp,
10-
isSet
10+
isSet,
11+
isNativeError,
12+
isBoxedPrimitive,
13+
isNumberObject,
14+
isStringObject,
15+
isBooleanObject,
16+
isBigIntObject,
17+
isSymbolObject
1118
} = internalBinding('types');
1219
const {
1320
getOwnNonIndexProperties,
@@ -33,6 +40,13 @@ const kIsMap = 3;
3340
const objectToString = uncurryThis(Object.prototype.toString);
3441
const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
3542
const propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable);
43+
const dateGetTime = uncurryThis(Date.prototype.getTime);
44+
45+
const bigIntValueOf = uncurryThis(BigInt.prototype.valueOf);
46+
const booleanValueOf = uncurryThis(Boolean.prototype.valueOf);
47+
const numberValueOf = uncurryThis(Number.prototype.valueOf);
48+
const symbolValueOf = uncurryThis(Symbol.prototype.valueOf);
49+
const stringValueOf = uncurryThis(String.prototype.valueOf);
3650

3751
const objectKeys = Object.keys;
3852
const getPrototypeOf = Object.getPrototypeOf;
@@ -82,6 +96,24 @@ function isObjectOrArrayTag(tag) {
8296
return tag === '[object Array]' || tag === '[object Object]';
8397
}
8498

99+
function isEqualBoxedPrimitive(val1, val2) {
100+
if (isNumberObject(val1)) {
101+
return isNumberObject(val2) &&
102+
objectIs(numberValueOf(val1), numberValueOf(val2));
103+
}
104+
if (isStringObject(val1)) {
105+
return isStringObject(val2) && stringValueOf(val1) === stringValueOf(val2);
106+
}
107+
if (isBooleanObject(val1)) {
108+
return isBooleanObject(val2) &&
109+
booleanValueOf(val1) === booleanValueOf(val2);
110+
}
111+
if (isBigIntObject(val1)) {
112+
return isBigIntObject(val2) && bigIntValueOf(val1) === bigIntValueOf(val2);
113+
}
114+
return isSymbolObject(val2) && symbolValueOf(val1) === symbolValueOf(val2);
115+
}
116+
85117
// Notes: Type tags are historical [[Class]] properties that can be set by
86118
// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
87119
// and retrieved using Object.prototype.toString.call(obj) in JS
@@ -117,7 +149,7 @@ function strictDeepEqual(val1, val2, memos) {
117149
if (getPrototypeOf(val1) !== getPrototypeOf(val2)) {
118150
return false;
119151
}
120-
if (val1Tag === '[object Array]') {
152+
if (Array.isArray(val1)) {
121153
// Check for sparse arrays and general fast path
122154
if (val1.length !== val2.length) {
123155
return false;
@@ -133,15 +165,14 @@ function strictDeepEqual(val1, val2, memos) {
133165
return keyCheck(val1, val2, kStrict, memos, kNoIterator);
134166
}
135167
if (isDate(val1)) {
136-
// TODO: Make these safe.
137-
if (val1.getTime() !== val2.getTime()) {
168+
if (dateGetTime(val1) !== dateGetTime(val2)) {
138169
return false;
139170
}
140171
} else if (isRegExp(val1)) {
141172
if (!areSimilarRegExps(val1, val2)) {
142173
return false;
143174
}
144-
} else if (val1Tag === '[object Error]') {
175+
} else if (isNativeError(val1) || val1 instanceof Error) {
145176
// Do not compare the stack as it might differ even though the error itself
146177
// is otherwise identical. The non-enumerable name should be identical as
147178
// the prototype is also identical. Otherwise this is caught later on.
@@ -175,14 +206,8 @@ function strictDeepEqual(val1, val2, memos) {
175206
if (!areEqualArrayBuffers(val1, val2)) {
176207
return false;
177208
}
178-
// TODO: Make the valueOf checks safe.
179-
} else if (typeof val1.valueOf === 'function') {
180-
const val1Value = val1.valueOf();
181-
if (val1Value !== val1 &&
182-
(typeof val2.valueOf !== 'function' ||
183-
!innerDeepEqual(val1Value, val2.valueOf(), kStrict))) {
184-
return false;
185-
}
209+
} else if (isBoxedPrimitive(val1) && !isEqualBoxedPrimitive(val1, val2)) {
210+
return false;
186211
}
187212
return keyCheck(val1, val2, kStrict, memos, kNoIterator);
188213
}
Collapse file

‎test/parallel/test-assert-deep.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-assert-deep.js
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,3 +985,67 @@ assert.throws(
985985
' ]'
986986
}
987987
);
988+
989+
// Verify that manipulating the `getTime()` function has no impact on the time
990+
// verification.
991+
{
992+
const a = new Date('2000');
993+
const b = new Date('2000');
994+
Object.defineProperty(a, 'getTime', {
995+
value: () => 5
996+
});
997+
assert.deepStrictEqual(a, b);
998+
}
999+
1000+
// Verify that extra keys will be tested for when using fake arrays.
1001+
{
1002+
const a = {
1003+
0: 1,
1004+
1: 1,
1005+
2: 'broken'
1006+
};
1007+
Object.setPrototypeOf(a, Object.getPrototypeOf([]));
1008+
Object.defineProperty(a, Symbol.toStringTag, {
1009+
value: 'Array',
1010+
});
1011+
Object.defineProperty(a, 'length', {
1012+
value: 2
1013+
});
1014+
assert.notDeepStrictEqual(a, [1, 1]);
1015+
}
1016+
1017+
// Verify that changed tags will still check for the error message.
1018+
{
1019+
const err = new Error('foo');
1020+
err[Symbol.toStringTag] = 'Foobar';
1021+
const err2 = new Error('bar');
1022+
err2[Symbol.toStringTag] = 'Foobar';
1023+
assertNotDeepOrStrict(err, err2, AssertionError);
1024+
}
1025+
1026+
// Check for non-native errors.
1027+
{
1028+
const source = new Error('abc');
1029+
const err = Object.create(
1030+
Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source));
1031+
Object.defineProperty(err, 'message', { value: 'foo' });
1032+
const err2 = Object.create(
1033+
Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source));
1034+
Object.defineProperty(err2, 'message', { value: 'bar' });
1035+
err[Symbol.toStringTag] = 'Foo';
1036+
err2[Symbol.toStringTag] = 'Foo';
1037+
assert.notDeepStrictEqual(err, err2);
1038+
}
1039+
1040+
// Verify that `valueOf` is not called for boxed primitives.
1041+
{
1042+
const a = new Number(5);
1043+
const b = new Number(5);
1044+
Object.defineProperty(a, 'valueOf', {
1045+
value: () => { throw new Error('failed'); }
1046+
});
1047+
Object.defineProperty(b, 'valueOf', {
1048+
value: () => { throw new Error('failed'); }
1049+
});
1050+
assertDeepAndStrictEqual(a, b);
1051+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.