From b20af8088a4d5cccb1901d42107f6d9ca82d306f Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 27 Jan 2018 22:01:32 +0100 Subject: [PATCH] =?UTF-8?q?util:=20introduce=20`util.types.is[=E2=80=A6]`?= =?UTF-8?q?=20type=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide public APIs for native typechecking that is actually useful. The motivation for this is providing alternatives to userland modules that would currently rely on `process.binding('util')`. PR-URL: https://github.com/nodejs/node/pull/18415 Reviewed-By: Evan Lucas Reviewed-By: Joyee Cheung Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Сковорода Никита Андреевич --- doc/api/deprecations.md | 9 + doc/api/util.md | 587 ++++++++++++++++++++++++++++++- lib/buffer.js | 9 +- lib/internal/bootstrap_node.js | 22 ++ lib/internal/encoding.js | 2 +- lib/internal/util/comparisons.js | 2 +- lib/internal/util/types.js | 42 ++- lib/util.js | 15 +- node.gyp | 1 + src/node_internals.h | 1 + src/node_types.cc | 69 ++++ src/node_util.cc | 40 --- test/parallel/test-types.js | 57 --- test/parallel/test-util-types.js | 162 +++++++++ test/parallel/test-util.js | 26 +- 15 files changed, 919 insertions(+), 125 deletions(-) create mode 100644 src/node_types.cc delete mode 100644 test/parallel/test-types.js create mode 100644 test/parallel/test-util-types.js diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 097fdfd0791ff0..b1cad2d701fbde 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -932,6 +932,14 @@ Using the `noAssert` argument has no functionality anymore. All input is going to be verified, no matter if it is set to true or not. Skipping the verification could lead to hard to find errors and crashes. + +### DEP0XXX: process.binding('util').is[...] typechecks + +Type: Documentation-only (supports [`--pending-deprecation`][]) + +Using `process.binding()` in general should be avoided. The type checking +methods in particular can be replaced by using [`util.types`][]. + [`--pending-deprecation`]: cli.html#cli_pending_deprecation [`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size [`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array @@ -1000,6 +1008,7 @@ could lead to hard to find errors and crashes. [`util.log()`]: util.html#util_util_log_string [`util.print()`]: util.html#util_util_print_strings [`util.puts()`]: util.html#util_util_puts_strings +[`util.types`]: util.html#util_util_types [`util`]: util.html [`worker.exitedAfterDisconnect`]: cluster.html#cluster_worker_exitedafterdisconnect [alloc]: buffer.html#buffer_class_method_buffer_alloc_size_fill_encoding diff --git a/doc/api/util.md b/doc/api/util.md index 03ef10ba1aac2e..524372a6fe29c9 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -891,6 +891,555 @@ encoded bytes. The encoding supported by the `TextEncoder` instance. Always set to `'utf-8'`. +## util.types + + +`util.types` provides a number of type checks for different kinds of built-in +objects. Unlike `instanceof` or `Object.prototype.toString.call(value)`, +these checks do not inspect properties of the object that are accessible from +JavaScript (like their prototype), and usually have the overhead of +calling into C++. + +The result generally does not make any guarantees about what kinds of +properties or behavior a value exposes in JavaScript. They are primarily +useful for addon developers who prefer to do type checking in JavaScript. + +### util.types.isAnyArrayBuffer(value) + + +Returns `true` if the value is a built-in [`ArrayBuffer`][] or +[`SharedArrayBuffer`][] instance. + +See also [`util.types.isArrayBuffer()`][] and +[`util.types.isSharedArrayBuffer()`][]. + +For example: + +```js +util.types.isAnyArrayBuffer(new ArrayBuffer()); // Returns true +util.types.isAnyArrayBuffer(new SharedArrayBuffer()); // Returns true +``` + +### util.types.isArgumentsObject(value) + + +Returns `true` if the value is an `arguments` object. + +For example: + + +```js +function foo() { + util.types.isArgumentsObject(arguments); // Returns true +} +``` + +### util.types.isArrayBuffer(value) + + +Returns `true` if the value is a built-in [`ArrayBuffer`][] instance. +This does *not* include [`SharedArrayBuffer`][] instances. Usually, it is +desirable to test for both; See [`util.types.isAnyArrayBuffer()`][] for that. + +For example: + +```js +util.types.isArrayBuffer(new ArrayBuffer()); // Returns true +util.types.isArrayBuffer(new SharedArrayBuffer()); // Returns false +``` + +### util.types.isAsyncFunction(value) + + +Returns `true` if the value is an [async function][]. +Note that this only reports back what the JavaScript engine is seeing; +in particular, the return value may not match the original source code if +a transpilation tool was used. + +For example: + +```js +util.types.isAsyncFunction(function foo() {}); // Returns false +util.types.isAsyncFunction(async function foo() {}); // Returns true +``` + +### util.types.isBooleanObject(value) + + +Returns `true` if the value is a boolean object, e.g. created +by `new Boolean()`. + +For example: + +```js +util.types.isBooleanObject(false); // Returns false +util.types.isBooleanObject(true); // Returns false +util.types.isBooleanObject(new Boolean(false)); // Returns true +util.types.isBooleanObject(new Boolean(true)); // Returns true +util.types.isBooleanObject(Boolean(false)); // Returns false +util.types.isBooleanObject(Boolean(true)); // Returns false +``` + +### util.types.isDataView(value) + + +Returns `true` if the value is a built-in [`DataView`][] instance. + +For example: + +```js +const ab = new ArrayBuffer(20); +util.types.isDataView(new DataView(ab)); // Returns true +util.types.isDataView(new Float64Array()); // Returns false +``` + +See also [`ArrayBuffer.isView()`][]. + +### util.types.isDate(value) + + +Returns `true` if the value is a built-in [`Date`][] instance. + +For example: + +```js +util.types.isDate(new Date()); // Returns true +``` + +### util.types.isExternal(value) + + +Returns `true` if the value is a native `External` value. + +### util.types.isFloat32Array(value) + + +Returns `true` if the value is a built-in [`Float32Array`][] instance. + +For example: + +```js +util.types.isFloat32Array(new ArrayBuffer()); // Returns false +util.types.isFloat32Array(new Float32Array()); // Returns true +util.types.isFloat32Array(new Float64Array()); // Returns false +``` + +### util.types.isFloat64Array(value) + + +Returns `true` if the value is a built-in [`Float64Array`][] instance. + +For example: + +```js +util.types.isFloat64Array(new ArrayBuffer()); // Returns false +util.types.isFloat64Array(new Uint8Array()); // Returns false +util.types.isFloat64Array(new Float64Array()); // Returns true +``` + +### util.types.isGeneratorFunction(value) + + +Returns `true` if the value is a generator function. +Note that this only reports back what the JavaScript engine is seeing; +in particular, the return value may not match the original source code if +a transpilation tool was used. + +For example: + +```js +util.types.isGeneratorFunction(function foo() {}); // Returns false +util.types.isGeneratorFunction(function* foo() {}); // Returns true +``` + +### util.types.isGeneratorObject(value) + + +Returns `true` if the value is a generator object as returned from a +built-in generator function. +Note that this only reports back what the JavaScript engine is seeing; +in particular, the return value may not match the original source code if +a transpilation tool was used. + +For example: + +```js +function* foo() {} +const generator = foo(); +util.types.isGeneratorObject(generator); // Returns true +``` + +### util.types.isInt8Array(value) + + +Returns `true` if the value is a built-in [`Int8Array`][] instance. + +For example: + +```js +util.types.isInt8Array(new ArrayBuffer()); // Returns false +util.types.isInt8Array(new Int8Array()); // Returns true +util.types.isInt8Array(new Float64Array()); // Returns false +``` + +### util.types.isInt16Array(value) + + +Returns `true` if the value is a built-in [`Int16Array`][] instance. + +For example: + +```js +util.types.isInt16Array(new ArrayBuffer()); // Returns false +util.types.isInt16Array(new Int16Array()); // Returns true +util.types.isInt16Array(new Float64Array()); // Returns false +``` + +### util.types.isInt32Array(value) + + +Returns `true` if the value is a built-in [`Int32Array`][] instance. + +For example: + +```js +util.types.isInt32Array(new ArrayBuffer()); // Returns false +util.types.isInt32Array(new Int32Array()); // Returns true +util.types.isInt32Array(new Float64Array()); // Returns false +``` + +### util.types.isMap(value) + + +Returns `true` if the value is a built-in [`Map`][] instance. + +For example: + +```js +util.types.isMap(new Map()); // Returns true +``` + +### util.types.isMapIterator(value) + + +Returns `true` if the value is an iterator returned for a built-in +[`Map`][] instance. + +For example: + +```js +const map = new Map(); +util.types.isMapIterator(map.keys()); // Returns true +util.types.isMapIterator(map.values()); // Returns true +util.types.isMapIterator(map.entries()); // Returns true +util.types.isMapIterator(map[Symbol.iterator]()); // Returns true +``` + +### util.types.isNativeError(value) + + +Returns `true` if the value is an instance of a built-in [`Error`][] type. + +For example: + +```js +util.types.isNativeError(new Error()); // Returns true +util.types.isNativeError(new TypeError()); // Returns true +util.types.isNativeError(new RangeError()); // Returns true +``` + +### util.types.isNumberObject(value) + + +Returns `true` if the value is a number object, e.g. created +by `new Number()`. + +For example: + +```js +util.types.isNumberObject(0); // Returns false +util.types.isNumberObject(new Number(0)); // Returns true +``` + +### util.types.isPromise(value) + + +Returns `true` if the value is a built-in [`Promise`][]. + +For example: + +```js +util.types.isPromise(Promise.resolve(42)); // Returns true +``` + +### util.types.isProxy(value) + + +Returns `true` if the value is a [`Proxy`][] instance. + +For example: + +```js +const target = {}; +const proxy = new Proxy(target, {}); +util.types.isProxy(target); // Returns false +util.types.isProxy(proxy); // Returns true +``` + +### util.types.isRegExp(value) + + +Returns `true` if the value is a regular expression object. + +For example: + +```js +util.types.isRegExp(/abc/); // Returns true +util.types.isRegExp(new RegExp('abc')); // Returns true +``` + +### util.types.isSet(value) + + +Returns `true` if the value is a built-in [`Set`][] instance. + +For example: + +```js +util.types.isSet(new Set()); // Returns true +``` + +### util.types.isSetIterator(value) + + +Returns `true` if the value is an iterator returned for a built-in +[`Set`][] instance. + +For example: + +```js +const set = new Set(); +util.types.isSetIterator(set.keys()); // Returns true +util.types.isSetIterator(set.values()); // Returns true +util.types.isSetIterator(set.entries()); // Returns true +util.types.isSetIterator(set[Symbol.iterator]()); // Returns true +``` + +### util.types.isSharedArrayBuffer(value) + + +Returns `true` if the value is a built-in [`SharedArrayBuffer`][] instance. +This does *not* include [`ArrayBuffer`][] instances. Usually, it is +desirable to test for both; See [`util.types.isAnyArrayBuffer()`][] for that. + +For example: + +```js +util.types.isSharedArrayBuffer(new ArrayBuffer()); // Returns false +util.types.isSharedArrayBuffer(new SharedArrayBuffer()); // Returns true +``` + +### util.types.isStringObject(value) + + +Returns `true` if the value is a string object, e.g. created +by `new String()`. + +For example: + +```js +util.types.isStringObject('foo'); // Returns false +util.types.isStringObject(new String('foo')); // Returns true +``` + +### util.types.isSymbolObject(value) + + +Returns `true` if the value is a symbol object, created +by calling `Object()` on a `Symbol` primitive. + +For example: + +```js +const symbol = Symbol('foo'); +util.types.isSymbolObject(symbol); // Returns false +util.types.isSymbolObject(Object(symbol)); // Returns true +``` + +### util.types.isTypedArray(value) + + +Returns `true` if the value is a built-in [`TypedArray`][] instance. + +For example: + +```js +util.types.isTypedArray(new ArrayBuffer()); // Returns false +util.types.isTypedArray(new Uint8Array()); // Returns true +util.types.isTypedArray(new Float64Array()); // Returns true +``` + +See also [`ArrayBuffer.isView()`][]. + +### util.types.isUint8Array(value) + + +Returns `true` if the value is a built-in [`Uint8Array`][] instance. + +For example: + +```js +util.types.isUint8Array(new ArrayBuffer()); // Returns false +util.types.isUint8Array(new Uint8Array()); // Returns true +util.types.isUint8Array(new Float64Array()); // Returns false +``` + +### util.types.isUint8ClampedArray(value) + + +Returns `true` if the value is a built-in [`Uint8ClampedArray`][] instance. + +For example: + +```js +util.types.isUint8ClampedArray(new ArrayBuffer()); // Returns false +util.types.isUint8ClampedArray(new Uint8ClampedArray()); // Returns true +util.types.isUint8ClampedArray(new Float64Array()); // Returns false +``` + +### util.types.isUint16Array(value) + + +Returns `true` if the value is a built-in [`Uint16Array`][] instance. + +For example: + +```js +util.types.isUint16Array(new ArrayBuffer()); // Returns false +util.types.isUint16Array(new Uint16Array()); // Returns true +util.types.isUint16Array(new Float64Array()); // Returns false +``` + +### util.types.isUint32Array(value) + + +Returns `true` if the value is a built-in [`Uint32Array`][] instance. + +For example: + +```js +util.types.isUint32Array(new ArrayBuffer()); // Returns false +util.types.isUint32Array(new Uint32Array()); // Returns true +util.types.isUint32Array(new Float64Array()); // Returns false +``` + +### util.types.isWeakMap(value) + + +Returns `true` if the value is a built-in [`WeakMap`][] instance. + +For example: + +```js +util.types.isWeakMap(new WeakMap()); // Returns true +``` + +### util.types.isWeakSet(value) + + +Returns `true` if the value is a built-in [`WeakSet`][] instance. + +For example: + +```js +util.types.isWeakSet(new WeakSet()); // Returns true +``` + +### util.types.isWebAssemblyCompiledModule(value) + + +Returns `true` if the value is a built-in [`WebAssembly.Module`][] instance. + +For example: + +```js +const module = new WebAssembly.Module(wasmBuffer); +util.types.isWebAssemblyCompiledModule(module); // Returns true +``` + + ## Deprecated APIs The following APIs have been deprecated and should no longer be used. Existing @@ -1014,7 +1563,7 @@ added: v0.6.0 deprecated: v4.0.0 --> -> Stability: 0 - Deprecated +> Stability: 0 - Deprecated: Use [`util.types.isDate()`][] instead. * `object` {any} * Returns: {boolean} @@ -1038,7 +1587,7 @@ added: v0.6.0 deprecated: v4.0.0 --> -> Stability: 0 - Deprecated +> Stability: 0 - Deprecated: Use [`util.types.isNativeError()`][] instead. * `object` {any} * Returns: {boolean} @@ -1385,14 +1934,42 @@ Deprecated predecessor of `console.log`. [`'uncaughtException'`]: process.html#process_event_uncaughtexception [`Array.isArray()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray -[`Buffer.isBuffer()`]: buffer.html#buffer_class_method_buffer_isbuffer_obj -[`Error`]: errors.html#errors_class_error -[`Object.assign()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign +[`ArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer +[`ArrayBuffer.isView()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView +[async function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function [`assert.deepStrictEqual()`]: assert.html#assert_assert_deepstrictequal_actual_expected_message +[`Buffer.isBuffer()`]: buffer.html#buffer_class_method_buffer_isbuffer_obj [`console.error()`]: console.html#console_console_error_data_args [`console.log()`]: console.html#console_console_log_data_args +[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView +[`Date`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date +[`Error`]: errors.html#errors_class_error +[`Float32Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array +[`Float64Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array +[`Int8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array +[`Int16Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array +[`Int32Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array +[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map +[`Object.assign()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign +[`Promise`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise +[`Proxy`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +[`Set`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set +[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer +[`TypedArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray [`util.inspect()`]: #util_util_inspect_object_options [`util.promisify()`]: #util_util_promisify_original +[`util.types.isAnyArrayBuffer()`]: #util_util_types_isanyarraybuffer_value +[`util.types.isArrayBuffer()`]: #util_util_types_isarraybuffer_value +[`util.types.isDate()`]: #util_util_types_isdate_value +[`util.types.isNativeError()`]: #util_util_types_isnativeerror_value +[`util.types.isSharedArrayBuffer()`]: #util_util_types_issharedarraybuffer_value +[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array +[`Uint8ClampedArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray +[`Uint16Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array +[`Uint32Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array +[`WeakMap`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap +[`WeakSet`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet +[`WebAssembly.Module`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module [Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects [Custom promisified functions]: #util_custom_promisified_functions [Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors diff --git a/lib/buffer.js b/lib/buffer.js index 593e10d74147bd..add3303824b687 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -37,7 +37,14 @@ const { kMaxLength, kStringMaxLength } = process.binding('buffer'); -const { isAnyArrayBuffer } = process.binding('util'); +// We cannot use internalBinding unconditionally here because of the way +// that test/parallel/test-buffer-bindingobj-no-zerofill.js is written. +let isAnyArrayBuffer; +try { + isAnyArrayBuffer = internalBinding('types').isAnyArrayBuffer; +} catch (e) { + isAnyArrayBuffer = require('util').types.isAnyArrayBuffer; +} const { customInspectSymbol, normalizeEncoding, diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index a0d508c5b135a3..2444c117816ed4 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -110,6 +110,28 @@ NativeModule.require('internal/process/modules').setup(); } + { + // Install legacy getters on the `util` binding for typechecking. + // TODO(addaleax): Turn into a full runtime deprecation. + const { pendingDeprecation } = process.binding('config'); + const { deprecate } = NativeModule.require('internal/util'); + const utilBinding = process.binding('util'); + const types = internalBinding('types'); + for (const name of [ + 'isArrayBuffer', 'isArrayBufferView', 'isAsyncFunction', + 'isDataView', 'isDate', 'isExternal', 'isMap', 'isMapIterator', + 'isNativeError', 'isPromise', 'isRegExp', 'isSet', 'isSetIterator', + 'isTypedArray', 'isUint8Array', 'isAnyArrayBuffer' + ]) { + utilBinding[name] = pendingDeprecation ? + deprecate(types[name], + 'Accessing native typechecking bindings of Node ' + + 'directly is deprecated. ' + + `Please use \`util.types.${name}\` instead.`, + 'DEP0XXX') : + types[name]; + } + } // There are various modes that Node can run in. The most common two // are running from a script and running the REPL - but there are a few diff --git a/lib/internal/encoding.js b/lib/internal/encoding.js index fa178f3a8c7caa..f80bdcdf2b49df 100644 --- a/lib/internal/encoding.js +++ b/lib/internal/encoding.js @@ -19,7 +19,7 @@ const { isArrayBufferView } = require('internal/util/types'); const { isArrayBuffer -} = process.binding('util'); +} = internalBinding('types'); const { encodeUtf8String diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 1145bc7d9971ba..23d8488fbdbb2c 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -2,7 +2,7 @@ const { compare } = process.binding('buffer'); const { isArrayBufferView } = require('internal/util/types'); -const { isDate, isMap, isRegExp, isSet } = process.binding('util'); +const { isDate, isMap, isRegExp, isSet } = internalBinding('types'); function objectToString(o) { return Object.prototype.toString.call(o); diff --git a/lib/internal/util/types.js b/lib/internal/util/types.js index c990bea6db4605..4df62554d134ed 100644 --- a/lib/internal/util/types.js +++ b/lib/internal/util/types.js @@ -29,8 +29,48 @@ function isUint8Array(value) { return TypedArrayProto_toStringTag(value) === 'Uint8Array'; } +function isUint8ClampedArray(value) { + return TypedArrayProto_toStringTag(value) === 'Uint8ClampedArray'; +} + +function isUint16Array(value) { + return TypedArrayProto_toStringTag(value) === 'Uint16Array'; +} + +function isUint32Array(value) { + return TypedArrayProto_toStringTag(value) === 'Uint32Array'; +} + +function isInt8Array(value) { + return TypedArrayProto_toStringTag(value) === 'Int8Array'; +} + +function isInt16Array(value) { + return TypedArrayProto_toStringTag(value) === 'Int16Array'; +} + +function isInt32Array(value) { + return TypedArrayProto_toStringTag(value) === 'Int32Array'; +} + +function isFloat32Array(value) { + return TypedArrayProto_toStringTag(value) === 'Float32Array'; +} + +function isFloat64Array(value) { + return TypedArrayProto_toStringTag(value) === 'Float64Array'; +} + module.exports = { isArrayBufferView, isTypedArray, - isUint8Array + isUint8Array, + isUint8ClampedArray, + isUint16Array, + isUint32Array, + isInt8Array, + isInt16Array, + isInt32Array, + isFloat32Array, + isFloat64Array }; diff --git a/lib/util.js b/lib/util.js index df117782ca962c..888c005f2f60ef 100644 --- a/lib/util.js +++ b/lib/util.js @@ -30,6 +30,13 @@ const { previewMapIterator, previewSetIterator } = require('internal/v8'); const { getPromiseDetails, getProxyDetails, + kPending, + kRejected, +} = process.binding('util'); + +const types = internalBinding('types'); +Object.assign(types, require('internal/util/types')); +const { isAnyArrayBuffer, isDataView, isExternal, @@ -40,13 +47,8 @@ const { isSetIterator, isRegExp, isDate, - kPending, - kRejected, -} = process.binding('util'); - -const { isTypedArray -} = require('internal/util/types'); +} = types; const { isDeepStrictEqual @@ -1149,6 +1151,7 @@ module.exports = exports = { promisify, TextDecoder, TextEncoder, + types, // Deprecated Old Stuff debug: deprecate(debug, diff --git a/node.gyp b/node.gyp index 827c81692a67e4..977cef33ffbbbb 100644 --- a/node.gyp +++ b/node.gyp @@ -320,6 +320,7 @@ 'src/node_postmortem_metadata.cc', 'src/node_serdes.cc', 'src/node_trace_events.cc', + 'src/node_types.cc', 'src/node_url.cc', 'src/node_util.cc', 'src/node_v8.cc', diff --git a/src/node_internals.h b/src/node_internals.h index 140826bb7c5ebd..43c2b5f112bac7 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -126,6 +126,7 @@ struct sockaddr; V(timer_wrap) \ V(trace_events) \ V(tty_wrap) \ + V(types) \ V(udp_wrap) \ V(url) \ V(util) \ diff --git a/src/node_types.cc b/src/node_types.cc new file mode 100644 index 00000000000000..4b9f86f0d54d8c --- /dev/null +++ b/src/node_types.cc @@ -0,0 +1,69 @@ +#include "node_internals.h" + +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Local; +using v8::Object; +using v8::Value; + +namespace node { +namespace { + +#define VALUE_METHOD_MAP(V) \ + V(External) \ + V(Date) \ + V(ArgumentsObject) \ + V(BooleanObject) \ + V(NumberObject) \ + V(StringObject) \ + V(SymbolObject) \ + V(NativeError) \ + V(RegExp) \ + V(AsyncFunction) \ + V(GeneratorFunction) \ + V(GeneratorObject) \ + V(Promise) \ + V(Map) \ + V(Set) \ + V(MapIterator) \ + V(SetIterator) \ + V(WeakMap) \ + V(WeakSet) \ + V(ArrayBuffer) \ + V(DataView) \ + V(SharedArrayBuffer) \ + V(Proxy) \ + V(WebAssemblyCompiledModule) \ + + +#define V(type) \ + static void Is##type(const FunctionCallbackInfo& args) { \ + args.GetReturnValue().Set(args[0]->Is##type()); \ + } + + VALUE_METHOD_MAP(V) +#undef V + +static void IsAnyArrayBuffer(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set( + args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); +} + +void InitializeTypes(Local target, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + +#define V(type) env->SetMethod(target, \ + "is" #type, \ + Is##type); + VALUE_METHOD_MAP(V) +#undef V + + env->SetMethod(target, "isAnyArrayBuffer", IsAnyArrayBuffer); +} + +} // anonymous namespace +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(types, node::InitializeTypes) diff --git a/src/node_util.cc b/src/node_util.cc index 1542b533f3484d..662809fb851a98 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -17,40 +17,6 @@ using v8::Proxy; using v8::String; using v8::Value; - -#define VALUE_METHOD_MAP(V) \ - V(isArrayBuffer, IsArrayBuffer) \ - V(isArrayBufferView, IsArrayBufferView) \ - V(isAsyncFunction, IsAsyncFunction) \ - V(isDataView, IsDataView) \ - V(isDate, IsDate) \ - V(isExternal, IsExternal) \ - V(isMap, IsMap) \ - V(isMapIterator, IsMapIterator) \ - V(isNativeError, IsNativeError) \ - V(isPromise, IsPromise) \ - V(isRegExp, IsRegExp) \ - V(isSet, IsSet) \ - V(isSetIterator, IsSetIterator) \ - V(isTypedArray, IsTypedArray) \ - V(isUint8Array, IsUint8Array) - - -#define V(_, ucname) \ - static void ucname(const FunctionCallbackInfo& args) { \ - CHECK_EQ(1, args.Length()); \ - args.GetReturnValue().Set(args[0]->ucname()); \ - } - - VALUE_METHOD_MAP(V) -#undef V - -static void IsAnyArrayBuffer(const FunctionCallbackInfo& args) { - CHECK_EQ(1, args.Length()); - args.GetReturnValue().Set( - args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); -} - static void GetPromiseDetails(const FunctionCallbackInfo& args) { // Return undefined if it's not a Promise. if (!args[0]->IsPromise()) @@ -191,12 +157,6 @@ void Initialize(Local target, Local context) { Environment* env = Environment::GetCurrent(context); -#define V(lcname, ucname) env->SetMethod(target, #lcname, ucname); - VALUE_METHOD_MAP(V) -#undef V - - env->SetMethod(target, "isAnyArrayBuffer", IsAnyArrayBuffer); - #define V(name, _) \ target->Set(context, \ FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ diff --git a/test/parallel/test-types.js b/test/parallel/test-types.js deleted file mode 100644 index ea8adc6cb17d3a..00000000000000 --- a/test/parallel/test-types.js +++ /dev/null @@ -1,57 +0,0 @@ -'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]); -} diff --git a/test/parallel/test-util-types.js b/test/parallel/test-util-types.js new file mode 100644 index 00000000000000..f9211ddf4209b4 --- /dev/null +++ b/test/parallel/test-util-types.js @@ -0,0 +1,162 @@ +/*global SharedArrayBuffer*/ +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const { types, inspect } = require('util'); +const path = require('path'); +const fs = require('fs'); +const vm = require('vm'); +const { JSStream } = process.binding('js_stream'); + +common.crashOnUnhandledRejection(); + +const external = (new JSStream())._externalStream; +const wasmBuffer = fixtures.readSync('test.wasm'); + +for (const [ value, _method ] of [ + [ external, 'isExternal' ], + [ new Date() ], + [ (function() { return arguments; })(), 'isArgumentsObject' ], + [ new Boolean(), 'isBooleanObject' ], + [ new Number(), 'isNumberObject' ], + [ new String(), 'isStringObject' ], + [ Object(Symbol()), 'isSymbolObject' ], + [ new Error(), 'isNativeError' ], + [ new RegExp() ], + [ async function() {}, 'isAsyncFunction' ], + [ function*() {}, 'isGeneratorFunction' ], + [ (function*() {})(), 'isGeneratorObject' ], + [ Promise.resolve() ], + [ new Map() ], + [ new Set() ], + [ (new Map())[Symbol.iterator](), 'isMapIterator' ], + [ (new Set())[Symbol.iterator](), 'isSetIterator' ], + [ new WeakMap() ], + [ new WeakSet() ], + [ new ArrayBuffer() ], + [ new Uint8Array() ], + [ new Uint8ClampedArray() ], + [ new Uint16Array() ], + [ new Uint32Array() ], + [ new Int8Array() ], + [ new Int16Array() ], + [ new Int32Array() ], + [ new Float32Array() ], + [ new Float64Array() ], + [ Object.defineProperty(new Uint8Array(), + Symbol.toStringTag, + { value: 'foo' }) ], + [ new DataView(new ArrayBuffer()) ], + [ new SharedArrayBuffer() ], + [ new Proxy({}, {}), 'isProxy' ], + [ new WebAssembly.Module(wasmBuffer), 'isWebAssemblyCompiledModule' ], +]) { + const method = _method || `is${value.constructor.name}`; + assert(method in types, `Missing ${method} for ${inspect(value)}`); + assert(types[method](value), `Want ${inspect(value)} to match ${method}`); + + for (const key of Object.keys(types)) { + if ((types.isArrayBufferView(value) || + types.isAnyArrayBuffer(value)) && key.includes('Array')) { + continue; + } + + assert.strictEqual(types[key](value), + key === method, + `${inspect(value)}: ${key}, ` + + `${method}, ${types[key](value)}`); + } +} + +{ + assert(!types.isUint8Array({ [Symbol.toStringTag]: 'Uint8Array' })); + assert(types.isUint8Array(vm.runInNewContext('new Uint8Array'))); +} + +{ + 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]); + } +} + + +// Try reading the v8.h header to verify completeness. + +let v8_h; +try { + v8_h = fs.readFileSync(path.resolve( + __dirname, '..', '..', 'deps', 'v8', 'include', 'v8.h'), 'utf8'); +} catch (e) { + // If loading the header fails, it should fail because we did not find it. + assert.strictEqual(e.code, 'ENOENT'); + common.skip('Could not read v8.h'); + return; +} + +// Exclude a number of checks that make sense on the C++ side but have +// much faster/better JS equivalents, so they should not be exposed. +const exclude = [ + 'Undefined', 'Null', 'NullOrUndefined', 'True', 'False', 'Name', 'String', + 'Symbol', 'Function', 'Array', 'Object', 'Boolean', 'Number', 'Int32', + 'Uint32' +]; + +const start = v8_h.indexOf('Value : public Data'); +const end = v8_h.indexOf('};', start); +const valueDefinition = v8_h.substr(start, end - start); + +const re = /bool Is(\w+)\(\)/g; +let match; +while (match = re.exec(valueDefinition)) { + if (exclude.includes(match[1])) + continue; + assert(`is${match[1]}` in types, + `util.types should provide check for Is${match[1]}`); +} diff --git a/test/parallel/test-util.js b/test/parallel/test-util.js index 3b2729c107b4b1..72d4b16b353102 100644 --- a/test/parallel/test-util.js +++ b/test/parallel/test-util.js @@ -25,7 +25,6 @@ const common = require('../common'); const assert = require('assert'); const util = require('util'); const errors = require('internal/errors'); -const binding = process.binding('util'); const context = require('vm').runInNewContext; // isArray @@ -155,22 +154,23 @@ util.debug('test'); util.error('test'); { - // binding.isNativeError() - assert.strictEqual(binding.isNativeError(new Error()), true); - assert.strictEqual(binding.isNativeError(new TypeError()), true); - assert.strictEqual(binding.isNativeError(new SyntaxError()), true); - assert.strictEqual(binding.isNativeError(new (context('Error'))()), true); - assert.strictEqual(binding.isNativeError(new (context('TypeError'))()), true); - assert.strictEqual(binding.isNativeError(new (context('SyntaxError'))()), + assert.strictEqual(util.types.isNativeError(new Error()), true); + assert.strictEqual(util.types.isNativeError(new TypeError()), true); + assert.strictEqual(util.types.isNativeError(new SyntaxError()), true); + assert.strictEqual(util.types.isNativeError(new (context('Error'))()), true); - assert.strictEqual(binding.isNativeError({}), false); - assert.strictEqual(binding.isNativeError({ name: 'Error', message: '' }), + assert.strictEqual(util.types.isNativeError(new (context('TypeError'))()), + true); + assert.strictEqual(util.types.isNativeError(new (context('SyntaxError'))()), + true); + assert.strictEqual(util.types.isNativeError({}), false); + assert.strictEqual(util.types.isNativeError({ name: 'Error', message: '' }), false); - assert.strictEqual(binding.isNativeError([]), false); - assert.strictEqual(binding.isNativeError(Object.create(Error.prototype)), + assert.strictEqual(util.types.isNativeError([]), false); + assert.strictEqual(util.types.isNativeError(Object.create(Error.prototype)), false); assert.strictEqual( - binding.isNativeError(new errors.Error('ERR_IPC_CHANNEL_CLOSED')), + util.types.isNativeError(new errors.Error('ERR_IPC_CHANNEL_CLOSED')), true ); }