Skip to content

Commit bf4faf3

Browse 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

2 files changed

Lines changed: 102 additions & 13 deletions

File tree

‎lib/internal/util/comparisons.js‎

Lines 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
constobjectToString=uncurryThis(Object.prototype.toString);
3441
consthasOwnProperty=uncurryThis(Object.prototype.hasOwnProperty);
3542
constpropertyIsEnumerable=uncurryThis(Object.prototype.propertyIsEnumerable);
43+
constdateGetTime=uncurryThis(Date.prototype.getTime);
44+
45+
constbigIntValueOf=uncurryThis(BigInt.prototype.valueOf);
46+
constbooleanValueOf=uncurryThis(Boolean.prototype.valueOf);
47+
constnumberValueOf=uncurryThis(Number.prototype.valueOf);
48+
constsymbolValueOf=uncurryThis(Symbol.prototype.valueOf);
49+
conststringValueOf=uncurryThis(String.prototype.valueOf);
3650

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

99+
functionisEqualBoxedPrimitive(val1,val2){
100+
if(isNumberObject(val1)){
101+
returnisNumberObject(val2)&&
102+
objectIs(numberValueOf(val1),numberValueOf(val2));
103+
}
104+
if(isStringObject(val1)){
105+
returnisStringObject(val2)&&stringValueOf(val1)===stringValueOf(val2);
106+
}
107+
if(isBooleanObject(val1)){
108+
returnisBooleanObject(val2)&&
109+
booleanValueOf(val1)===booleanValueOf(val2);
110+
}
111+
if(isBigIntObject(val1)){
112+
returnisBigIntObject(val2)&&bigIntValueOf(val1)===bigIntValueOf(val2);
113+
}
114+
returnisSymbolObject(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
returnfalse;
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
returnfalse;
@@ -133,15 +165,14 @@ function strictDeepEqual(val1, val2, memos) {
133165
returnkeyCheck(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
returnfalse;
139170
}
140171
}elseif(isRegExp(val1)){
141172
if(!areSimilarRegExps(val1,val2)){
142173
returnfalse;
143174
}
144-
}elseif(val1Tag==='[object Error]'){
175+
}elseif(isNativeError(val1)||val1instanceofError){
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
returnfalse;
177208
}
178-
// TODO: Make the valueOf checks safe.
179-
}elseif(typeofval1.valueOf==='function'){
180-
constval1Value=val1.valueOf();
181-
if(val1Value!==val1&&
182-
(typeofval2.valueOf!=='function'||
183-
!innerDeepEqual(val1Value,val2.valueOf(),kStrict))){
184-
returnfalse;
185-
}
209+
}elseif(isBoxedPrimitive(val1)&&!isEqualBoxedPrimitive(val1,val2)){
210+
returnfalse;
186211
}
187212
returnkeyCheck(val1,val2,kStrict,memos,kNoIterator);
188213
}

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

Lines 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+
consta=newDate('2000');
993+
constb=newDate('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+
consta={
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+
consterr=newError('foo');
1020+
err[Symbol.toStringTag]='Foobar';
1021+
consterr2=newError('bar');
1022+
err2[Symbol.toStringTag]='Foobar';
1023+
assertNotDeepOrStrict(err,err2,AssertionError);
1024+
}
1025+
1026+
// Check for non-native errors.
1027+
{
1028+
constsource=newError('abc');
1029+
consterr=Object.create(
1030+
Object.getPrototypeOf(source),Object.getOwnPropertyDescriptors(source));
1031+
Object.defineProperty(err,'message',{value: 'foo'});
1032+
consterr2=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+
consta=newNumber(5);
1043+
constb=newNumber(5);
1044+
Object.defineProperty(a,'valueOf',{
1045+
value: ()=>{thrownewError('failed');}
1046+
});
1047+
Object.defineProperty(b,'valueOf',{
1048+
value: ()=>{thrownewError('failed');}
1049+
});
1050+
assertDeepAndStrictEqual(a,b);
1051+
}

0 commit comments

Comments
 (0)