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: https://github.com/nodejs/node/issues/10258 PR-URL: https://github.com/nodejs/node/pull/10282 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
055482c21e
commit
efec14a7d1
@ -125,12 +125,13 @@ changes:
|
|||||||
* `expected` {any}
|
* `expected` {any}
|
||||||
* `message` {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][]
|
1. Primitive values are compared using the [Strict Equality Comparison][]
|
||||||
( `===` ).
|
( `===` ).
|
||||||
2. [`[[Prototype]]`][prototype-spec] of objects are compared using
|
2. [`[[Prototype]]`][prototype-spec] of objects are compared using
|
||||||
the [Strict Equality Comparison][] too.
|
the [Strict Equality Comparison][] too.
|
||||||
|
3. [Type tags][Object.prototype.toString()] of objects should be the same.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
@ -141,6 +142,25 @@ assert.deepEqual({a: 1}, {a: '1'});
|
|||||||
assert.deepStrictEqual({a: 1}, {a: '1'});
|
assert.deepStrictEqual({a: 1}, {a: '1'});
|
||||||
// AssertionError: { a: 1 } deepStrictEqual { a: '1' }
|
// AssertionError: { a: 1 } deepStrictEqual { a: '1' }
|
||||||
// because 1 !== '1' using strict equality
|
// 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`
|
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
|
[SameValueZero]: https://tc39.github.io/ecma262/#sec-samevaluezero
|
||||||
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
[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
|
[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
|
||||||
|
@ -200,6 +200,10 @@ function _deepEqual(actual, expected, strict, memos) {
|
|||||||
if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
|
if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actualTag !== expectedTag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do fast checks for builtin types.
|
// Do fast checks for builtin types.
|
||||||
|
61
test/parallel/test-assert-checktag.js
Normal file
61
test/parallel/test-assert-checktag.js
Normal file
@ -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 */
|
Loading…
x
Reference in New Issue
Block a user