Permalink
Browse files

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 <rtrott@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
joyeecheung authored and addaleax committed Jan 31, 2017
1 parent 055482c commit efec14a7d12d9e07e4efcc8e5c0ad7f80c47f83f
Showing with 88 additions and 2 deletions.
  1. +23 −2 doc/api/assert.md
  2. +4 −0 lib/assert.js
  3. +61 −0 test/parallel/test-assert-checktag.js
View
@@ -125,12 +125,13 @@ changes:
* `expected` {any}
* `message` {any}
Generally identical to `assert.deepEqual()` with two exceptions:
Generally identical to `assert.deepEqual()` with three exceptions:
1. Primitive values are compared using the [Strict Equality Comparison][]
( `===` ).
2. [`[[Prototype]]`][prototype-spec] of objects are compared using
the [Strict Equality Comparison][] too.
3. [Type tags][Object.prototype.toString()] of objects should be the same.
```js
const assert = require('assert');
@@ -141,6 +142,25 @@ assert.deepEqual({a: 1}, {a: '1'});
assert.deepStrictEqual({a: 1}, {a: '1'});
// AssertionError: { a: 1 } deepStrictEqual { a: '1' }
// because 1 !== '1' using strict equality
// The following objects don't have own properties
const date = new Date();
const object = {};
const fakeDate = {};
Object.setPrototypeOf(fakeDate, Date.prototype);
assert.deepEqual(object, fakeDate);
// OK, doesn't check [[Prototype]]
assert.deepStrictEqual(object, fakeDate);
// AssertionError: {} deepStrictEqual Date {}
// Different [[Prototype]]
assert.deepEqual(date, fakeDate);
// OK, doesn't check type tags
assert.deepStrictEqual(date, fakeDate);
// AssertionError: 2017-03-11T14:25:31.849Z deepStrictEqual Date {}
// Different type tags
```
If the values are not equal, an `AssertionError` is thrown with a `message`
@@ -579,4 +599,5 @@ For more information, see
[SameValueZero]: https://tc39.github.io/ecma262/#sec-samevaluezero
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
[mdn-equality-guide]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
View
@@ -200,6 +200,10 @@ function _deepEqual(actual, expected, strict, memos) {
if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
return false;
}
if (actualTag !== expectedTag) {
return false;
}
}
// Do fast checks for builtin types.
@@ -0,0 +1,61 @@
'use strict';
require('../common');
const assert = require('assert');
const util = require('util');
// Template tag function turning an error message into a RegExp
// for assert.throws()
function re(literals, ...values) {
let result = literals[0];
for (const [i, value] of values.entries()) {
const str = util.inspect(value);
// Need to escape special characters.
result += str.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&');
result += literals[i + 1];
}
return new RegExp('^AssertionError: ' + result + '$');
}
// Turn off no-restricted-properties because we are testing deepEqual!
/* eslint-disable no-restricted-properties */
// See https://github.com/nodejs/node/issues/10258
{
const date = new Date('2016');
function FakeDate() {}
FakeDate.prototype = Date.prototype;
const fake = new FakeDate();
assert.doesNotThrow(() => assert.deepEqual(date, fake));
assert.doesNotThrow(() => assert.deepEqual(fake, date));
// For deepStrictEqual we check the runtime type,
// then reveal the fakeness of the fake date
assert.throws(() => assert.deepStrictEqual(date, fake),
re`${date} deepStrictEqual Date {}`);
assert.throws(() => assert.deepStrictEqual(fake, date),
re`Date {} deepStrictEqual ${date}`);
}
{ // At the moment global has its own type tag
const fakeGlobal = {};
Object.setPrototypeOf(fakeGlobal, Object.getPrototypeOf(global));
for (const prop of Object.keys(global)) {
fakeGlobal[prop] = global[prop];
}
assert.doesNotThrow(() => assert.deepEqual(fakeGlobal, global));
// Message will be truncated anyway, don't validate
assert.throws(() => assert.deepStrictEqual(fakeGlobal, global));
}
{ // At the moment process has its own type tag
const fakeProcess = {};
Object.setPrototypeOf(fakeProcess, Object.getPrototypeOf(process));
for (const prop of Object.keys(process)) {
fakeProcess[prop] = process[prop];
}
assert.doesNotThrow(() => assert.deepEqual(fakeProcess, process));
// Message will be truncated anyway, don't validate
assert.throws(() => assert.deepStrictEqual(fakeProcess, process));
}
/* eslint-enable */

0 comments on commit efec14a

Please sign in to comment.