Skip to content

Commit 9b6d52a

Browse files
addaleaxdurran
authored andcommitted
fix: clean up instanceof usage
Similarly to 99722f6, instanceof is not a great brand check in JS in general, and using `Object.prototype.toString.call()` is better at detecting built-in types. This particular change is needed for mongosh, because the user input it not evaluated in the same `vm.Context` as the bson package that we’re using. NODE-3208
1 parent 99722f6 commit 9b6d52a

6 files changed

+32
-24
lines changed

src/ensure_buffer.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Buffer } from 'buffer';
2-
import { isBuffer } from './parser/utils';
2+
import { isBuffer, isAnyArrayBuffer } from './parser/utils';
33

44
/**
55
* Makes sure that, if a Uint8Array is passed in, it is wrapped in a Buffer.
@@ -18,11 +18,7 @@ export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBu
1818
return Buffer.from(potentialBuffer.buffer);
1919
}
2020

21-
if (
22-
['[object ArrayBuffer]', '[object SharedArrayBuffer]'].includes(
23-
Object.prototype.toString.call(potentialBuffer)
24-
)
25-
) {
21+
if (isAnyArrayBuffer(potentialBuffer)) {
2622
return Buffer.from(potentialBuffer);
2723
}
2824

src/extended_json.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Long } from './long';
99
import { MaxKey } from './max_key';
1010
import { MinKey } from './min_key';
1111
import { ObjectId } from './objectid';
12-
import { isObjectLike } from './parser/utils';
12+
import { isDate, isObjectLike, isRegExp } from './parser/utils';
1313
import { BSONRegExp } from './regexp';
1414
import { BSONSymbol } from './symbol';
1515
import { Timestamp } from './timestamp';
@@ -157,7 +157,7 @@ function serializeValue(value: any, options: EJSON.Options): any {
157157

158158
if (value === undefined) return null;
159159

160-
if (value instanceof Date) {
160+
if (value instanceof Date || isDate(value)) {
161161
const dateNum = value.getTime(),
162162
// is it in year range 1970-9999?
163163
inRange = dateNum > -1 && dateNum < 253402318800000;
@@ -185,7 +185,7 @@ function serializeValue(value: any, options: EJSON.Options): any {
185185
return { $numberDouble: value.toString() };
186186
}
187187

188-
if (value instanceof RegExp) {
188+
if (value instanceof RegExp || isRegExp(value)) {
189189
let flags = value.flags;
190190
if (flags === undefined) {
191191
const match = value.toString().match(/[gimuy]*$/);

src/parser/calculate_size.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Buffer } from 'buffer';
22
import { Binary } from '../binary';
33
import type { Document } from '../bson';
44
import * as constants from '../constants';
5-
import { isDate, normalizedFunctionString } from './utils';
5+
import { isAnyArrayBuffer, isDate, isRegExp, normalizedFunctionString } from './utils';
66

77
export function calculateObjectSize(
88
object: Document,
@@ -83,7 +83,7 @@ function calculateElement(
8383
return (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (12 + 1);
8484
} else if (value instanceof Date || isDate(value)) {
8585
return (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (8 + 1);
86-
} else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
86+
} else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || isAnyArrayBuffer(value)) {
8787
return (
8888
(name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (1 + 4 + 1) + value.byteLength
8989
);
@@ -156,10 +156,7 @@ function calculateElement(
156156
1 +
157157
calculateObjectSize(ordered_values, serializeFunctions, ignoreUndefined)
158158
);
159-
} else if (
160-
value instanceof RegExp ||
161-
Object.prototype.toString.call(value) === '[object RegExp]'
162-
) {
159+
} else if (value instanceof RegExp || isRegExp(value)) {
163160
return (
164161
(name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) +
165162
1 +
@@ -189,8 +186,7 @@ function calculateElement(
189186
case 'function':
190187
// WTF for 0.4.X where typeof /someregexp/ === 'function'
191188
if (
192-
value instanceof RegExp ||
193-
Object.prototype.toString.call(value) === '[object RegExp]' ||
189+
value instanceof RegExp || isRegExp(value) ||
194190
String.call(value) === '[object RegExp]'
195191
) {
196192
return (

src/parser/serializer.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
isBigUInt64Array,
2121
isBuffer,
2222
isDate,
23+
isMap,
24+
isRegExp,
2325
isUint8Array,
2426
normalizedFunctionString
2527
} from './utils';
@@ -41,10 +43,6 @@ export interface SerializeOptions {
4143
const regexp = /\x00/; // eslint-disable-line no-control-regex
4244
const ignoreKeys = new Set(['$db', '$ref', '$id', '$clusterTime']);
4345

44-
function isRegExp(d: unknown): d is RegExp {
45-
return Object.prototype.toString.call(d) === '[object RegExp]';
46-
}
47-
4846
/*
4947
* isArray indicates if we are writing to a BSON array (type 0x04)
5048
* which forces the "key" which really an array index as a string to be written as ascii
@@ -843,7 +841,7 @@ export function serializeInto(
843841
throw new TypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']);
844842
}
845843
}
846-
} else if (object instanceof Map) {
844+
} else if (object instanceof Map || isMap(object)) {
847845
const iterator = object.entries();
848846
let done = false;
849847

src/parser/utils.ts

+13
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ const detectRandomBytes = (): RandomBytesFunction => {
6262

6363
export const randomBytes = detectRandomBytes();
6464

65+
export function isAnyArrayBuffer(value: unknown): value is ArrayBuffer {
66+
return ['[object ArrayBuffer]', '[object SharedArrayBuffer]'].includes(
67+
Object.prototype.toString.call(value));
68+
}
69+
6570
export function isUint8Array(value: unknown): value is Uint8Array {
6671
return Object.prototype.toString.call(value) === '[object Uint8Array]';
6772
}
@@ -74,6 +79,14 @@ export function isBigUInt64Array(value: unknown): value is BigUint64Array {
7479
return Object.prototype.toString.call(value) === '[object BigUint64Array]';
7580
}
7681

82+
export function isRegExp(d: unknown): d is RegExp {
83+
return Object.prototype.toString.call(d) === '[object RegExp]';
84+
}
85+
86+
export function isMap(d: unknown): d is Map<unknown, unknown> {
87+
return Object.prototype.toString.call(d) === '[object Map]';
88+
}
89+
7790
/** Call to check if your environment has `Buffer` */
7891
export function haveBuffer(): boolean {
7992
return typeof global !== 'undefined' && typeof global.Buffer !== 'undefined';

test/node/extended_json_tests.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const BSON = require('../register-bson');
44
const EJSON = BSON.EJSON;
5+
const vm = require('vm');
56

67
// BSON types
78
const Binary = BSON.Binary;
@@ -228,7 +229,9 @@ describe('Extended JSON', function () {
228229
oldObjectID: OldObjectID.createFromHexString('111111111111111111111111'),
229230
bsonRegExp: new BSONRegExp('hello world', 'i'),
230231
symbol: new BSONSymbol('symbol'),
231-
timestamp: new Timestamp()
232+
timestamp: new Timestamp(),
233+
foreignRegExp: vm.runInNewContext('/abc/'),
234+
foreignDate: vm.runInNewContext('new Date(0)')
232235
};
233236

234237
const result = EJSON.serialize(doc, { relaxed: false });
@@ -247,7 +250,9 @@ describe('Extended JSON', function () {
247250
oldObjectID: { $oid: '111111111111111111111111' },
248251
bsonRegExp: { $regularExpression: { pattern: 'hello world', options: 'i' } },
249252
symbol: { $symbol: 'symbol' },
250-
timestamp: { $timestamp: { t: 0, i: 0 } }
253+
timestamp: { $timestamp: { t: 0, i: 0 } },
254+
foreignDate: { $date: { $numberLong: '0' } },
255+
foreignRegExp: { $regularExpression: { pattern: 'abc', options: '' } }
251256
});
252257
});
253258

0 commit comments

Comments
 (0)