Skip to content

Commit 1f68004

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 <jasnell@gmail.com> Reviewed-By: David Carlier <devnexen@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 219c8e9 commit 1f68004

2 files changed

Lines changed: 134 additions & 22 deletions

File tree

‎lib/internal/util/comparisons.js‎

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ const {
1919
Set,
2020
StringPrototypeValueOf,
2121
SymbolPrototypeValueOf,
22+
SymbolToStringTag,
2223
}=primordials;
2324

2425
const{ compare }=internalBinding('buffer');
26+
consttypes=require('internal/util/types');
2527
const{
2628
isAnyArrayBuffer,
2729
isArrayBufferView,
@@ -37,8 +39,17 @@ const {
3739
isBigIntObject,
3840
isSymbolObject,
3941
isFloat32Array,
40-
isFloat64Array
41-
}=require('internal/util/types');
42+
isFloat64Array,
43+
isUint8Array,
44+
isUint8ClampedArray,
45+
isUint16Array,
46+
isUint32Array,
47+
isInt8Array,
48+
isInt16Array,
49+
isInt32Array,
50+
isBigInt64Array,
51+
isBigUint64Array
52+
}=types;
4253
const{
4354
getOwnNonIndexProperties,
4455
propertyFilter: {
@@ -110,6 +121,33 @@ function isEqualBoxedPrimitive(val1, val2) {
110121
returnfalse;
111122
}
112123

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

132163
functioninnerDeepEqual(val1,val2,strict,memos){
@@ -167,9 +198,10 @@ function innerDeepEqual(val1, val2, strict, memos) {
167198
if(val1Tag!==val2Tag){
168199
returnfalse;
169200
}
201+
170202
if(ArrayIsArray(val1)){
171203
// Check for sparse arrays and general fast path
172-
if(val1.length!==val2.length){
204+
if(!ArrayIsArray(val2)||val1.length!==val2.length){
173205
returnfalse;
174206
}
175207
constfilter=strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE|SKIP_SYMBOLS;
@@ -179,25 +211,28 @@ function innerDeepEqual(val1, val2, strict, memos) {
179211
returnfalse;
180212
}
181213
returnkeyCheck(val1,val2,strict,memos,kIsArray,keys1);
182-
}
183-
if(val1Tag==='[object Object]'){
214+
}elseif(val1Tag==='[object Object]'){
184215
returnkeyCheck(val1,val2,strict,memos,kNoIterator);
185-
}
186-
if(isDate(val1)){
187-
if(DatePrototypeGetTime(val1)!==DatePrototypeGetTime(val2)){
216+
}elseif(isDate(val1)){
217+
if(!isDate(val2)||
218+
DatePrototypeGetTime(val1)!==DatePrototypeGetTime(val2)){
188219
returnfalse;
189220
}
190221
}elseif(isRegExp(val1)){
191-
if(!areSimilarRegExps(val1,val2)){
222+
if(!isRegExp(val2)||!areSimilarRegExps(val1,val2)){
192223
returnfalse;
193224
}
194225
}elseif(isNativeError(val1)||val1instanceofError){
195226
// Do not compare the stack as it might differ even though the error itself
196227
// is otherwise identical.
197-
if(val1.message!==val2.message||val1.name!==val2.name){
228+
if((!isNativeError(val2)&&!(val2instanceofError))||
229+
val1.message!==val2.message||
230+
val1.name!==val2.name){
198231
returnfalse;
199232
}
200233
}elseif(isArrayBufferView(val1)){
234+
if(!isIdenticalTypedArrayType(val1,val2))
235+
returnfalse;
201236
if(!strict&&(isFloat32Array(val1)||isFloat64Array(val1))){
202237
if(!areSimilarFloatArrays(val1,val2)){
203238
returnfalse;
@@ -226,12 +261,23 @@ function innerDeepEqual(val1, val2, strict, memos) {
226261
}
227262
returnkeyCheck(val1,val2,strict,memos,kIsMap);
228263
}elseif(isAnyArrayBuffer(val1)){
229-
if(!areEqualArrayBuffers(val1,val2)){
264+
if(!isAnyArrayBuffer(val2)||!areEqualArrayBuffers(val1,val2)){
230265
returnfalse;
231266
}
232-
}
233-
if((isBoxedPrimitive(val1)||isBoxedPrimitive(val2))&&
234-
!isEqualBoxedPrimitive(val1,val2)){
267+
}elseif(isBoxedPrimitive(val1)){
268+
if(!isEqualBoxedPrimitive(val1,val2)){
269+
returnfalse;
270+
}
271+
}elseif(ArrayIsArray(val2)||
272+
isArrayBufferView(val2)||
273+
isSet(val2)||
274+
isMap(val2)||
275+
isDate(val2)||
276+
isRegExp(val2)||
277+
isAnyArrayBuffer(val2)||
278+
isBoxedPrimitive(val2)||
279+
isNativeError(val2)||
280+
val2instanceofError){
235281
returnfalse;
236282
}
237283
returnkeyCheck(val1,val2,strict,memos,kNoIterator);

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,3 +1138,69 @@ assert.throws(
11381138
// The descriptor is not compared.
11391139
assertDeepAndStrictEqual(a,{a: 5});
11401140
}
1141+
1142+
// Verify object types being identical on both sides.
1143+
{
1144+
leta=Buffer.from('test');
1145+
letb=Object.create(
1146+
Object.getPrototypeOf(a),
1147+
Object.getOwnPropertyDescriptors(a)
1148+
);
1149+
Object.defineProperty(b,Symbol.toStringTag,{
1150+
value: 'Uint8Array'
1151+
});
1152+
assertNotDeepOrStrict(a,b);
1153+
1154+
a=newUint8Array(10);
1155+
b=newInt8Array(10);
1156+
Object.defineProperty(b,Symbol.toStringTag,{
1157+
value: 'Uint8Array'
1158+
});
1159+
Object.setPrototypeOf(b,Uint8Array.prototype);
1160+
assertNotDeepOrStrict(a,b);
1161+
1162+
a=[1,2,3];
1163+
b={0: 1,1: 2,2: 3};
1164+
Object.setPrototypeOf(b,Array.prototype);
1165+
Object.defineProperty(b,'length',{value: 3,enumerable: false});
1166+
Object.defineProperty(b,Symbol.toStringTag,{
1167+
value: 'Array'
1168+
});
1169+
assertNotDeepOrStrict(a,b);
1170+
1171+
a=newDate(2000);
1172+
b=Object.create(
1173+
Object.getPrototypeOf(a),
1174+
Object.getOwnPropertyDescriptors(a)
1175+
);
1176+
Object.defineProperty(b,Symbol.toStringTag,{
1177+
value: 'Date'
1178+
});
1179+
assertNotDeepOrStrict(a,b);
1180+
1181+
a=/abc/g;
1182+
b=Object.create(
1183+
Object.getPrototypeOf(a),
1184+
Object.getOwnPropertyDescriptors(a)
1185+
);
1186+
Object.defineProperty(b,Symbol.toStringTag,{
1187+
value: 'RegExp'
1188+
});
1189+
assertNotDeepOrStrict(a,b);
1190+
1191+
a=[];
1192+
b=/abc/;
1193+
Object.setPrototypeOf(b,Array.prototype);
1194+
Object.defineProperty(b,Symbol.toStringTag,{
1195+
value: 'Array'
1196+
});
1197+
assertNotDeepOrStrict(a,b);
1198+
1199+
a=Object.create(null);
1200+
b=newRangeError('abc');
1201+
Object.defineProperty(a,Symbol.toStringTag,{
1202+
value: 'Error'
1203+
});
1204+
Object.setPrototypeOf(b,null);
1205+
assertNotDeepOrStrict(a,b,assert.AssertionError);
1206+
}

0 commit comments

Comments
 (0)