Skip to content

Commit 1c4b2f1

Browse files
BridgeARtargos
authored andcommitted
assert,util: stricter type comparison using deep equal comparisons
This veryfies that both input arguments are always of the identical type. It was possible to miss a few cases before. This change applies to all deep equal assert functions (e.g., `assert.deepStrictEqual()`) and to `util.isDeepStrictEqual()`. PR-URL: #30764 Refs: #30743 Reviewed-By: James M Snell <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 3898b23 commit 1c4b2f1

File tree

2 files changed

+133
-22
lines changed

2 files changed

+133
-22
lines changed

lib/internal/util/comparisons.js

+67-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const {
1919
} = primordials;
2020

2121
const { compare } = internalBinding('buffer');
22+
const types = require('internal/util/types');
2223
const {
2324
isAnyArrayBuffer,
2425
isArrayBufferView,
@@ -34,8 +35,17 @@ const {
3435
isBigIntObject,
3536
isSymbolObject,
3637
isFloat32Array,
37-
isFloat64Array
38-
} = require('internal/util/types');
38+
isFloat64Array,
39+
isUint8Array,
40+
isUint8ClampedArray,
41+
isUint16Array,
42+
isUint32Array,
43+
isInt8Array,
44+
isInt16Array,
45+
isInt32Array,
46+
isBigInt64Array,
47+
isBigUint64Array
48+
} = types;
3949
const {
4050
getOwnNonIndexProperties,
4151
propertyFilter: {
@@ -107,6 +117,33 @@ function isEqualBoxedPrimitive(val1, val2) {
107117
return false;
108118
}
109119

120+
function isIdenticalTypedArrayType(a, b) {
121+
// Fast path to reduce type checks in the common case.
122+
const check = types[`is${a[Symbol.toStringTag]}`];
123+
if (check !== undefined && check(a)) {
124+
return check(b);
125+
}
126+
// Manipulated Symbol.toStringTag.
127+
for (const check of [
128+
isUint16Array,
129+
isUint32Array,
130+
isInt8Array,
131+
isInt16Array,
132+
isInt32Array,
133+
isFloat32Array,
134+
isFloat64Array,
135+
isBigInt64Array,
136+
isBigUint64Array,
137+
isUint8ClampedArray,
138+
isUint8Array
139+
]) {
140+
if (check(a)) {
141+
return check(b);
142+
}
143+
}
144+
return false;
145+
}
146+
110147
// Notes: Type tags are historical [[Class]] properties that can be set by
111148
// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
112149
// and retrieved using Object.prototype.toString.call(obj) in JS
@@ -115,15 +152,8 @@ function isEqualBoxedPrimitive(val1, val2) {
115152
// There are some unspecified tags in the wild too (e.g. typed array tags).
116153
// Since tags can be altered, they only serve fast failures
117154
//
118-
// Typed arrays and buffers are checked by comparing the content in their
119-
// underlying ArrayBuffer. This optimization requires that it's
120-
// reasonable to interpret their underlying memory in the same way,
121-
// which is checked by comparing their type tags.
122-
// (e.g. a Uint8Array and a Uint16Array with the same memory content
123-
// could still be different because they will be interpreted differently).
124-
//
125155
// For strict comparison, objects should have
126-
// a) The same built-in type tags
156+
// a) The same built-in type tag.
127157
// b) The same prototypes.
128158

129159
function innerDeepEqual(val1, val2, strict, memos) {
@@ -164,9 +194,10 @@ function innerDeepEqual(val1, val2, strict, memos) {
164194
if (val1Tag !== val2Tag) {
165195
return false;
166196
}
197+
167198
if (ArrayIsArray(val1)) {
168199
// Check for sparse arrays and general fast path
169-
if (val1.length !== val2.length) {
200+
if (!ArrayIsArray(val2) || val1.length !== val2.length) {
170201
return false;
171202
}
172203
const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS;
@@ -176,25 +207,28 @@ function innerDeepEqual(val1, val2, strict, memos) {
176207
return false;
177208
}
178209
return keyCheck(val1, val2, strict, memos, kIsArray, keys1);
179-
}
180-
if (val1Tag === '[object Object]') {
210+
} else if (val1Tag === '[object Object]') {
181211
return keyCheck(val1, val2, strict, memos, kNoIterator);
182-
}
183-
if (isDate(val1)) {
184-
if (DatePrototypeGetTime(val1) !== DatePrototypeGetTime(val2)) {
212+
} else if (isDate(val1)) {
213+
if (!isDate(val2) ||
214+
DatePrototypeGetTime(val1) !== DatePrototypeGetTime(val2)) {
185215
return false;
186216
}
187217
} else if (isRegExp(val1)) {
188-
if (!areSimilarRegExps(val1, val2)) {
218+
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
189219
return false;
190220
}
191221
} else if (isNativeError(val1) || val1 instanceof Error) {
192222
// Do not compare the stack as it might differ even though the error itself
193223
// is otherwise identical.
194-
if (val1.message !== val2.message || val1.name !== val2.name) {
224+
if ((!isNativeError(val2) && !(val2 instanceof Error)) ||
225+
val1.message !== val2.message ||
226+
val1.name !== val2.name) {
195227
return false;
196228
}
197229
} else if (isArrayBufferView(val1)) {
230+
if (!isIdenticalTypedArrayType(val1, val2))
231+
return false;
198232
if (!strict && (isFloat32Array(val1) || isFloat64Array(val1))) {
199233
if (!areSimilarFloatArrays(val1, val2)) {
200234
return false;
@@ -223,12 +257,23 @@ function innerDeepEqual(val1, val2, strict, memos) {
223257
}
224258
return keyCheck(val1, val2, strict, memos, kIsMap);
225259
} else if (isAnyArrayBuffer(val1)) {
226-
if (!areEqualArrayBuffers(val1, val2)) {
260+
if (!isAnyArrayBuffer(val2) || !areEqualArrayBuffers(val1, val2)) {
227261
return false;
228262
}
229-
}
230-
if ((isBoxedPrimitive(val1) || isBoxedPrimitive(val2)) &&
231-
!isEqualBoxedPrimitive(val1, val2)) {
263+
} else if (isBoxedPrimitive(val1)) {
264+
if (!isEqualBoxedPrimitive(val1, val2)) {
265+
return false;
266+
}
267+
} else if (ArrayIsArray(val2) ||
268+
isArrayBufferView(val2) ||
269+
isSet(val2) ||
270+
isMap(val2) ||
271+
isDate(val2) ||
272+
isRegExp(val2) ||
273+
isAnyArrayBuffer(val2) ||
274+
isBoxedPrimitive(val2) ||
275+
isNativeError(val2) ||
276+
val2 instanceof Error) {
232277
return false;
233278
}
234279
return keyCheck(val1, val2, strict, memos, kNoIterator);

test/parallel/test-assert-deep.js

+66
Original file line numberDiff line numberDiff line change
@@ -1123,3 +1123,69 @@ assert.throws(
11231123
// The descriptor is not compared.
11241124
assertDeepAndStrictEqual(a, { a: 5 });
11251125
}
1126+
1127+
// Verify object types being identical on both sides.
1128+
{
1129+
let a = Buffer.from('test');
1130+
let b = Object.create(
1131+
Object.getPrototypeOf(a),
1132+
Object.getOwnPropertyDescriptors(a)
1133+
);
1134+
Object.defineProperty(b, Symbol.toStringTag, {
1135+
value: 'Uint8Array'
1136+
});
1137+
assertNotDeepOrStrict(a, b);
1138+
1139+
a = new Uint8Array(10);
1140+
b = new Int8Array(10);
1141+
Object.defineProperty(b, Symbol.toStringTag, {
1142+
value: 'Uint8Array'
1143+
});
1144+
Object.setPrototypeOf(b, Uint8Array.prototype);
1145+
assertNotDeepOrStrict(a, b);
1146+
1147+
a = [1, 2, 3];
1148+
b = { 0: 1, 1: 2, 2: 3 };
1149+
Object.setPrototypeOf(b, Array.prototype);
1150+
Object.defineProperty(b, 'length', { value: 3, enumerable: false });
1151+
Object.defineProperty(b, Symbol.toStringTag, {
1152+
value: 'Array'
1153+
});
1154+
assertNotDeepOrStrict(a, b);
1155+
1156+
a = new Date(2000);
1157+
b = Object.create(
1158+
Object.getPrototypeOf(a),
1159+
Object.getOwnPropertyDescriptors(a)
1160+
);
1161+
Object.defineProperty(b, Symbol.toStringTag, {
1162+
value: 'Date'
1163+
});
1164+
assertNotDeepOrStrict(a, b);
1165+
1166+
a = /abc/g;
1167+
b = Object.create(
1168+
Object.getPrototypeOf(a),
1169+
Object.getOwnPropertyDescriptors(a)
1170+
);
1171+
Object.defineProperty(b, Symbol.toStringTag, {
1172+
value: 'RegExp'
1173+
});
1174+
assertNotDeepOrStrict(a, b);
1175+
1176+
a = [];
1177+
b = /abc/;
1178+
Object.setPrototypeOf(b, Array.prototype);
1179+
Object.defineProperty(b, Symbol.toStringTag, {
1180+
value: 'Array'
1181+
});
1182+
assertNotDeepOrStrict(a, b);
1183+
1184+
a = Object.create(null);
1185+
b = new RangeError('abc');
1186+
Object.defineProperty(a, Symbol.toStringTag, {
1187+
value: 'Error'
1188+
});
1189+
Object.setPrototypeOf(b, null);
1190+
assertNotDeepOrStrict(a, b, assert.AssertionError);
1191+
}

0 commit comments

Comments
 (0)