Skip to content

Commit

Permalink
lib: faster type checks for some types
Browse files Browse the repository at this point in the history
Backport-PR-URL: #16073
PR-URL: #15663
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Benedikt Meurer <benedikt.meurer@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
TimothyGu authored and evanlucas committed Oct 23, 2017
1 parent b431a49 commit 67aed79
Show file tree
Hide file tree
Showing 18 changed files with 141 additions and 24 deletions.
5 changes: 3 additions & 2 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
const { compare } = process.binding('buffer');
const { isSet, isMap, isDate, isRegExp } = process.binding('util');
const { objectToString } = require('internal/util');
const { isArrayBufferView } = require('internal/util/types');
const errors = require('internal/errors');

// The assert module provides functions that throw
Expand Down Expand Up @@ -198,7 +199,7 @@ function strictDeepEqual(actual, expected) {
if (actual.message !== expected.message) {
return false;
}
} else if (!isFloatTypedArrayTag(actualTag) && ArrayBuffer.isView(actual)) {
} else if (!isFloatTypedArrayTag(actualTag) && isArrayBufferView(actual)) {
if (!areSimilarTypedArrays(actual, expected)) {
return false;
}
Expand Down Expand Up @@ -254,7 +255,7 @@ function looseDeepEqual(actual, expected) {
const expectedTag = objectToString(expected);
if (actualTag === expectedTag) {
if (!isObjectOrArrayTag(actualTag) && !isFloatTypedArrayTag(actualTag) &&
ArrayBuffer.isView(actual)) {
isArrayBufferView(actual)) {
return areSimilarTypedArrays(actual, expected);
}
// Ensure reflexivity of deepEqual with `arguments` objects.
Expand Down
8 changes: 6 additions & 2 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
const binding = process.binding('buffer');
const config = process.binding('config');
const { compare: compare_, compareOffset } = binding;
const { isAnyArrayBuffer, isUint8Array } = process.binding('util');
const { isAnyArrayBuffer } = process.binding('util');
const {
isArrayBufferView,
isUint8Array
} = require('internal/util/types');
const bindingObj = {};
const internalUtil = require('internal/util');
const pendingDeprecation = !!config.pendingDeprecation;
Expand Down Expand Up @@ -468,7 +472,7 @@ function base64ByteLength(str, bytes) {

function byteLength(string, encoding) {
if (typeof string !== 'string') {
if (ArrayBuffer.isView(string) || isAnyArrayBuffer(string)) {
if (isArrayBufferView(string) || isAnyArrayBuffer(string)) {
return string.byteLength;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

const util = require('util');
const { deprecate, convertToValidSignal } = require('internal/util');
const { isUint8Array } = require('internal/util/types');
const { createPromise,
promiseResolve, promiseReject } = process.binding('util');
const debug = util.debuglog('child_process');
Expand All @@ -31,7 +32,6 @@ const uv = process.binding('uv');
const spawn_sync = process.binding('spawn_sync');
const Buffer = require('buffer').Buffer;
const Pipe = process.binding('pipe_wrap').Pipe;
const { isUint8Array } = process.binding('util');
const child_process = require('internal/child_process');

const errnoException = util._errnoException;
Expand Down
7 changes: 5 additions & 2 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ const Buffer = require('buffer').Buffer;
const kBufferMaxLength = require('buffer').kMaxLength;
const stream = require('stream');
const util = require('util');
const { isUint8Array } = process.binding('util');
const {
isArrayBufferView,
isUint8Array
} = require('internal/util/types');
const LazyTransform = require('internal/streams/lazy_transform');

const DH_GENERATOR = 2;
Expand Down Expand Up @@ -416,7 +419,7 @@ function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {

if (typeof sizeOrKey !== 'number' &&
typeof sizeOrKey !== 'string' &&
!ArrayBuffer.isView(sizeOrKey)) {
!isArrayBufferView(sizeOrKey)) {
throw new TypeError('First argument should be number, string, ' +
'Buffer, TypedArray, or DataView');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const errors = require('internal/errors');
const Buffer = require('buffer').Buffer;
const dns = require('dns');
const util = require('util');
const { isUint8Array } = require('internal/util/types');
const EventEmitter = require('events');
const setInitTriggerId = require('async_hooks').setInitTriggerId;
const UV_UDP_REUSEADDR = process.binding('constants').os.UV_UDP_REUSEADDR;
Expand All @@ -34,7 +35,6 @@ const nextTick = require('internal/process/next_tick').nextTick;

const UDP = process.binding('udp_wrap').UDP;
const SendWrap = process.binding('udp_wrap').SendWrap;
const { isUint8Array } = process.binding('util');

const BIND_STATE_UNBOUND = 0;
const BIND_STATE_BINDING = 1;
Expand Down
3 changes: 2 additions & 1 deletion lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const constants = process.binding('constants').fs;
const { S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } = constants;
const util = require('util');
const pathModule = require('path');
const { isUint8Array, createPromise, promiseResolve } = process.binding('util');
const { isUint8Array } = require('internal/util/types');
const { createPromise, promiseResolve } = process.binding('util');

const binding = process.binding('fs');
const fs = exports;
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const normalizeEncoding = require('internal/util').normalizeEncoding;
const Buffer = require('buffer').Buffer;

const icu = process.binding('icu');
const { isUint8Array } = process.binding('util');
const { isUint8Array } = require('internal/util/types');

// Transcodes the Buffer from one encoding to another, returning a new
// Buffer instance.
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const TTY = process.binding('tty_wrap').TTY;
const TCP = process.binding('tcp_wrap').TCP;
const UDP = process.binding('udp_wrap').UDP;
const SocketList = require('internal/socket_list');
const { isUint8Array } = process.binding('util');
const { convertToValidSignal } = require('internal/util');
const { isUint8Array } = require('internal/util/types');

const errnoException = util._errnoException;
const SocketListSend = SocketList.SocketListSend;
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const {
customInspectSymbol: inspect
} = require('internal/util');

const { isArrayBufferView } = require('internal/util/types');

const {
isArrayBuffer
} = process.binding('util');
Expand Down Expand Up @@ -386,7 +388,7 @@ function makeTextDecoderICU() {
throw new errors.TypeError('ERR_INVALID_THIS', 'TextDecoder');
if (isArrayBuffer(input)) {
input = lazyBuffer().from(input);
} else if (!ArrayBuffer.isView(input)) {
} else if (!isArrayBufferView(input)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'input',
['ArrayBuffer', 'ArrayBufferView']);
}
Expand Down Expand Up @@ -462,7 +464,7 @@ function makeTextDecoderJS() {
throw new errors.TypeError('ERR_INVALID_THIS', 'TextDecoder');
if (isArrayBuffer(input)) {
input = lazyBuffer().from(input);
} else if (ArrayBuffer.isView(input)) {
} else if (isArrayBufferView(input)) {
input = lazyBuffer().from(input.buffer, input.byteOffset,
input.byteLength);
} else {
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ const { onServerStream,
} = require('internal/http2/compat');
const { utcDate } = require('internal/http');
const { promisify } = require('internal/util');
const { isUint8Array } = require('internal/util/types');
const { _connectionListener: httpConnectionListener } = require('http');
const { isUint8Array, createPromise, promiseResolve } = process.binding('util');
const { createPromise, promiseResolve } = process.binding('util');
const debug = util.debuglog('http2');


Expand Down
36 changes: 36 additions & 0 deletions lib/internal/util/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const ReflectApply = Reflect.apply;

// This function is borrowed from the function with the same name on V8 Extras'
// `utils` object. V8 implements Reflect.apply very efficiently in conjunction
// with the spread syntax, such that no additional special case is needed for
// function calls w/o arguments.
// Refs: https://github.com/v8/v8/blob/d6ead37d265d7215cf9c5f768f279e21bd170212/src/js/prologue.js#L152-L156
function uncurryThis(func) {
return (thisArg, ...args) => ReflectApply(func, thisArg, args);
}

const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);

const TypedArrayProto_toStringTag =
uncurryThis(
Object.getOwnPropertyDescriptor(TypedArrayPrototype,
Symbol.toStringTag).get);

// Cached to make sure no userland code can tamper with it.
const isArrayBufferView = ArrayBuffer.isView;

function isTypedArray(value) {
return TypedArrayProto_toStringTag(value) !== undefined;
}

function isUint8Array(value) {
return TypedArrayProto_toStringTag(value) === 'Uint8Array';
}

module.exports = {
isArrayBufferView,
isTypedArray,
isUint8Array
};
3 changes: 2 additions & 1 deletion lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

const internalModule = require('internal/module');
const internalUtil = require('internal/util');
const { isTypedArray } = require('internal/util/types');
const util = require('util');
const utilBinding = process.binding('util');
const inherits = util.inherits;
Expand Down Expand Up @@ -700,7 +701,7 @@ const ARRAY_LENGTH_THRESHOLD = 1e6;
function mayBeLargeObject(obj) {
if (Array.isArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? ['length'] : null;
} else if (utilBinding.isTypedArray(obj)) {
} else if (isTypedArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? [] : null;
}

Expand Down
12 changes: 9 additions & 3 deletions lib/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ Stream.Stream = Stream;

// Internal utilities
try {
Stream._isUint8Array = process.binding('util').isUint8Array;
Stream._isUint8Array = require('internal/util/types').isUint8Array;
} catch (e) {
// This throws for Node < 4.2.0 because there’s no util binding and
// returns undefined for Node < 7.4.0.
// Throws for code outside of Node.js core.

try {
Stream._isUint8Array = process.binding('util').isUint8Array;
} catch (e) {
// This throws for Node < 4.2.0 because there’s no util binding and
// returns undefined for Node < 7.4.0.
}
}

if (!Stream._isUint8Array) {
Expand Down
2 changes: 1 addition & 1 deletion lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@

const internalUtil = require('internal/util');
internalUtil.assertCrypto();
const { isUint8Array } = require('internal/util/types');

const net = require('net');
const url = require('url');
const binding = process.binding('crypto');
const Buffer = require('buffer').Buffer;
const { isUint8Array } = process.binding('util');

// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
Expand Down
5 changes: 4 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ const {
isPromise,
isSet,
isSetIterator,
isTypedArray,
isRegExp,
isDate,
kPending,
kRejected,
} = process.binding('util');

const {
isTypedArray
} = require('internal/util/types');

const {
customInspectSymbol,
deprecate,
Expand Down
9 changes: 5 additions & 4 deletions lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const kRangeErrorMessage = 'Cannot create final Buffer. It would be larger ' +

const constants = process.binding('constants').zlib;
const { inherits } = require('util');
const { isArrayBufferView } = require('internal/util/types');

// translation table for return codes.
const codes = {
Expand Down Expand Up @@ -86,7 +87,7 @@ function responseData(engine, buffer) {
function zlibBuffer(engine, buffer, callback) {
// Streams do not support non-Buffer ArrayBufferViews yet. Convert it to a
// Buffer without copying.
if (ArrayBuffer.isView(buffer) &&
if (isArrayBufferView(buffer) &&
Object.getPrototypeOf(buffer) !== Buffer.prototype) {
buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
Expand Down Expand Up @@ -134,7 +135,7 @@ function zlibBuffer(engine, buffer, callback) {
function zlibBufferSync(engine, buffer) {
if (typeof buffer === 'string')
buffer = Buffer.from(buffer);
else if (!ArrayBuffer.isView(buffer))
else if (!isArrayBufferView(buffer))
throw new TypeError('"buffer" argument must be a string, Buffer, ' +
'TypedArray, or DataView');

Expand Down Expand Up @@ -221,7 +222,7 @@ function Zlib(opts, mode) {
throw new TypeError('Invalid strategy: ' + opts.strategy);

if (opts.dictionary !== undefined) {
if (!ArrayBuffer.isView(opts.dictionary)) {
if (!isArrayBufferView(opts.dictionary)) {
throw new TypeError(
'Invalid dictionary: it should be a Buffer, TypedArray, or DataView');
}
Expand Down Expand Up @@ -362,7 +363,7 @@ Zlib.prototype._transform = function _transform(chunk, encoding, cb) {
var ending = ws.ending || ws.ended;
var last = ending && (!chunk || ws.length === chunk.byteLength);

if (chunk !== null && !ArrayBuffer.isView(chunk))
if (chunk !== null && !isArrayBufferView(chunk))
return cb(new TypeError('invalid input'));

if (!this._handle)
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
'lib/internal/test/unicode.js',
'lib/internal/url.js',
'lib/internal/util.js',
'lib/internal/util/types.js',
'lib/internal/http2/core.js',
'lib/internal/http2/compat.js',
'lib/internal/http2/util.js',
Expand Down
57 changes: 57 additions & 0 deletions test/parallel/test-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

// Flags: --expose-internals

require('../common');
const assert = require('assert');
const types = require('internal/util/types');

const primitive = true;
const arrayBuffer = new ArrayBuffer();
const dataView = new DataView(arrayBuffer);
const int32Array = new Int32Array(arrayBuffer);
const uint8Array = new Uint8Array(arrayBuffer);
const buffer = Buffer.from(arrayBuffer);

const fakeDataView = Object.create(DataView.prototype);
const fakeInt32Array = Object.create(Int32Array.prototype);
const fakeUint8Array = Object.create(Uint8Array.prototype);
const fakeBuffer = Object.create(Buffer.prototype);

const stealthyDataView =
Object.setPrototypeOf(new DataView(arrayBuffer), Uint8Array.prototype);
const stealthyInt32Array =
Object.setPrototypeOf(new Int32Array(arrayBuffer), uint8Array);
const stealthyUint8Array =
Object.setPrototypeOf(new Uint8Array(arrayBuffer), ArrayBuffer.prototype);

const all = [
primitive, arrayBuffer, dataView, int32Array, uint8Array, buffer,
fakeDataView, fakeInt32Array, fakeUint8Array, fakeBuffer,
stealthyDataView, stealthyInt32Array, stealthyUint8Array
];

const expected = {
isArrayBufferView: [
dataView, int32Array, uint8Array, buffer,
stealthyDataView, stealthyInt32Array, stealthyUint8Array
],
isTypedArray: [
int32Array, uint8Array, buffer, stealthyInt32Array, stealthyUint8Array
],
isUint8Array: [
uint8Array, buffer, stealthyUint8Array
]
};

for (const testedFunc of Object.keys(expected)) {
const func = types[testedFunc];
const yup = [];
for (const value of all) {
if (func(value)) {
yup.push(value);
}
}
console.log('Testing', testedFunc);
assert.deepStrictEqual(yup, expected[testedFunc]);
}

0 comments on commit 67aed79

Please sign in to comment.