Skip to content

Commit efec14a

Browse files
joyeecheungaddaleax
authored andcommitted
assert: enforce type check in deepStrictEqual
Add checks for the built-in type tags to catch objects with faked prototypes. See https://tc39.github.io/ecma262/#sec-object.prototype.tostring for a partial list of built-in tags. Fixes: #10258 PR-URL: #10282 Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 055482c commit efec14a

File tree

3 files changed

+88
-2
lines changed

3 files changed

+88
-2
lines changed

doc/api/assert.md

+23-2
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,13 @@ changes:
125125
* `expected` {any}
126126
* `message` {any}
127127

128-
Generally identical to `assert.deepEqual()` with two exceptions:
128+
Generally identical to `assert.deepEqual()` with three exceptions:
129129

130130
1. Primitive values are compared using the [Strict Equality Comparison][]
131131
( `===` ).
132132
2. [`[[Prototype]]`][prototype-spec] of objects are compared using
133133
the [Strict Equality Comparison][] too.
134+
3. [Type tags][Object.prototype.toString()] of objects should be the same.
134135

135136
```js
136137
const assert = require('assert');
@@ -141,6 +142,25 @@ assert.deepEqual({a: 1}, {a: '1'});
141142
assert.deepStrictEqual({a: 1}, {a: '1'});
142143
// AssertionError: { a: 1 } deepStrictEqual { a: '1' }
143144
// because 1 !== '1' using strict equality
145+
146+
// The following objects don't have own properties
147+
const date = new Date();
148+
const object = {};
149+
const fakeDate = {};
150+
151+
Object.setPrototypeOf(fakeDate, Date.prototype);
152+
153+
assert.deepEqual(object, fakeDate);
154+
// OK, doesn't check [[Prototype]]
155+
assert.deepStrictEqual(object, fakeDate);
156+
// AssertionError: {} deepStrictEqual Date {}
157+
// Different [[Prototype]]
158+
159+
assert.deepEqual(date, fakeDate);
160+
// OK, doesn't check type tags
161+
assert.deepStrictEqual(date, fakeDate);
162+
// AssertionError: 2017-03-11T14:25:31.849Z deepStrictEqual Date {}
163+
// Different type tags
144164
```
145165

146166
If the values are not equal, an `AssertionError` is thrown with a `message`
@@ -579,4 +599,5 @@ For more information, see
579599
[SameValueZero]: https://tc39.github.io/ecma262/#sec-samevaluezero
580600
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
581601
[mdn-equality-guide]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
582-
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
602+
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
603+
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring

lib/assert.js

+4
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ function _deepEqual(actual, expected, strict, memos) {
200200
if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
201201
return false;
202202
}
203+
204+
if (actualTag !== expectedTag) {
205+
return false;
206+
}
203207
}
204208

205209
// Do fast checks for builtin types.

test/parallel/test-assert-checktag.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const util = require('util');
5+
6+
// Template tag function turning an error message into a RegExp
7+
// for assert.throws()
8+
function re(literals, ...values) {
9+
let result = literals[0];
10+
for (const [i, value] of values.entries()) {
11+
const str = util.inspect(value);
12+
// Need to escape special characters.
13+
result += str.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&');
14+
result += literals[i + 1];
15+
}
16+
return new RegExp('^AssertionError: ' + result + '$');
17+
}
18+
19+
// Turn off no-restricted-properties because we are testing deepEqual!
20+
/* eslint-disable no-restricted-properties */
21+
22+
// See https://github.com/nodejs/node/issues/10258
23+
{
24+
const date = new Date('2016');
25+
function FakeDate() {}
26+
FakeDate.prototype = Date.prototype;
27+
const fake = new FakeDate();
28+
29+
assert.doesNotThrow(() => assert.deepEqual(date, fake));
30+
assert.doesNotThrow(() => assert.deepEqual(fake, date));
31+
32+
// For deepStrictEqual we check the runtime type,
33+
// then reveal the fakeness of the fake date
34+
assert.throws(() => assert.deepStrictEqual(date, fake),
35+
re`${date} deepStrictEqual Date {}`);
36+
assert.throws(() => assert.deepStrictEqual(fake, date),
37+
re`Date {} deepStrictEqual ${date}`);
38+
}
39+
40+
{ // At the moment global has its own type tag
41+
const fakeGlobal = {};
42+
Object.setPrototypeOf(fakeGlobal, Object.getPrototypeOf(global));
43+
for (const prop of Object.keys(global)) {
44+
fakeGlobal[prop] = global[prop];
45+
}
46+
assert.doesNotThrow(() => assert.deepEqual(fakeGlobal, global));
47+
// Message will be truncated anyway, don't validate
48+
assert.throws(() => assert.deepStrictEqual(fakeGlobal, global));
49+
}
50+
51+
{ // At the moment process has its own type tag
52+
const fakeProcess = {};
53+
Object.setPrototypeOf(fakeProcess, Object.getPrototypeOf(process));
54+
for (const prop of Object.keys(process)) {
55+
fakeProcess[prop] = process[prop];
56+
}
57+
assert.doesNotThrow(() => assert.deepEqual(fakeProcess, process));
58+
// Message will be truncated anyway, don't validate
59+
assert.throws(() => assert.deepStrictEqual(fakeProcess, process));
60+
}
61+
/* eslint-enable */

0 commit comments

Comments
 (0)