node/lib/internal/util.js
Yagiz Nizipli f51c152f60
util: reduce javascript call for ToUSVString
PR-URL: https://github.com/nodejs/node/pull/47192
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
2023-03-23 15:01:31 +00:00

808 lines
22 KiB
JavaScript

'use strict';
const {
ArrayBufferPrototypeGetByteLength,
ArrayFrom,
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
Error,
FunctionPrototypeCall,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptors,
ObjectGetPrototypeOf,
ObjectFreeze,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
ObjectValues,
Promise,
ReflectApply,
ReflectConstruct,
RegExpPrototypeExec,
RegExpPrototypeGetDotAll,
RegExpPrototypeGetGlobal,
RegExpPrototypeGetHasIndices,
RegExpPrototypeGetIgnoreCase,
RegExpPrototypeGetMultiline,
RegExpPrototypeGetSticky,
RegExpPrototypeGetUnicode,
RegExpPrototypeGetSource,
SafeMap,
SafeSet,
SafeWeakMap,
StringPrototypeReplace,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Symbol,
SymbolFor,
SymbolReplace,
SymbolSplit,
} = primordials;
const {
hideStackFrames,
codes: {
ERR_NO_CRYPTO,
ERR_UNKNOWN_SIGNAL,
},
uvErrmapGet,
overrideStackTrace,
} = require('internal/errors');
const { signals } = internalBinding('constants').os;
const {
isArrayBufferDetached: _isArrayBufferDetached,
privateSymbols: {
arrow_message_private_symbol,
decorated_private_symbol,
},
sleep: _sleep,
toUSVString: _toUSVString,
} = internalBinding('util');
const { isNativeError } = internalBinding('types');
const noCrypto = !process.versions.openssl;
const experimentalWarnings = new SafeSet();
const colorRegExp = /\u001b\[\d\d?m/g; // eslint-disable-line no-control-regex
function toUSVString(val) {
return _toUSVString(`${val}`);
}
let uvBinding;
function lazyUv() {
uvBinding ??= internalBinding('uv');
return uvBinding;
}
function removeColors(str) {
return StringPrototypeReplace(str, colorRegExp, '');
}
function isError(e) {
// An error could be an instance of Error while not being a native error
// or could be from a different realm and not be instance of Error but still
// be a native error.
return isNativeError(e) || e instanceof Error;
}
// Keep a list of deprecation codes that have been warned on so we only warn on
// each one once.
const codesWarned = new SafeSet();
let validateString;
// Mark that a method should not be used.
// Returns a modified function which warns once by default.
// If --no-deprecation is set, then it is a no-op.
function deprecate(fn, msg, code, useEmitSync) {
if (process.noDeprecation === true) {
return fn;
}
// Lazy-load to avoid a circular dependency.
if (validateString === undefined)
({ validateString } = require('internal/validators'));
if (code !== undefined)
validateString(code, 'code');
let warned = false;
function deprecated(...args) {
if (!warned) {
warned = true;
if (code !== undefined) {
if (!codesWarned.has(code)) {
const emitWarning = useEmitSync ?
require('internal/process/warning').emitWarningSync :
process.emitWarning;
emitWarning(msg, 'DeprecationWarning', code, deprecated);
codesWarned.add(code);
}
} else {
process.emitWarning(msg, 'DeprecationWarning', deprecated);
}
}
if (new.target) {
return ReflectConstruct(fn, args, new.target);
}
return ReflectApply(fn, this, args);
}
// The wrapper will keep the same prototype as fn to maintain prototype chain
ObjectSetPrototypeOf(deprecated, fn);
if (fn.prototype) {
// Setting this (rather than using Object.setPrototype, as above) ensures
// that calling the unwrapped constructor gives an instanceof the wrapped
// constructor.
deprecated.prototype = fn.prototype;
}
return deprecated;
}
function decorateErrorStack(err) {
if (!(isError(err) && err.stack) || err[decorated_private_symbol])
return;
const arrow = err[arrow_message_private_symbol];
if (arrow) {
err.stack = arrow + err.stack;
err[decorated_private_symbol] = true;
}
}
function assertCrypto() {
if (noCrypto)
throw new ERR_NO_CRYPTO();
}
// Return undefined if there is no match.
// Move the "slow cases" to a separate function to make sure this function gets
// inlined properly. That prioritizes the common case.
function normalizeEncoding(enc) {
if (enc == null || enc === 'utf8' || enc === 'utf-8') return 'utf8';
return slowCases(enc);
}
function slowCases(enc) {
switch (enc.length) {
case 4:
if (enc === 'UTF8') return 'utf8';
if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le';
enc = `${enc}`.toLowerCase();
if (enc === 'utf8') return 'utf8';
if (enc === 'ucs2') return 'utf16le';
break;
case 3:
if (enc === 'hex' || enc === 'HEX' ||
`${enc}`.toLowerCase() === 'hex')
return 'hex';
break;
case 5:
if (enc === 'ascii') return 'ascii';
if (enc === 'ucs-2') return 'utf16le';
if (enc === 'UTF-8') return 'utf8';
if (enc === 'ASCII') return 'ascii';
if (enc === 'UCS-2') return 'utf16le';
enc = `${enc}`.toLowerCase();
if (enc === 'utf-8') return 'utf8';
if (enc === 'ascii') return 'ascii';
if (enc === 'ucs-2') return 'utf16le';
break;
case 6:
if (enc === 'base64') return 'base64';
if (enc === 'latin1' || enc === 'binary') return 'latin1';
if (enc === 'BASE64') return 'base64';
if (enc === 'LATIN1' || enc === 'BINARY') return 'latin1';
enc = `${enc}`.toLowerCase();
if (enc === 'base64') return 'base64';
if (enc === 'latin1' || enc === 'binary') return 'latin1';
break;
case 7:
if (enc === 'utf16le' || enc === 'UTF16LE' ||
`${enc}`.toLowerCase() === 'utf16le')
return 'utf16le';
break;
case 8:
if (enc === 'utf-16le' || enc === 'UTF-16LE' ||
`${enc}`.toLowerCase() === 'utf-16le')
return 'utf16le';
break;
case 9:
if (enc === 'base64url' || enc === 'BASE64URL' ||
`${enc}`.toLowerCase() === 'base64url')
return 'base64url';
break;
default:
if (enc === '') return 'utf8';
}
}
function emitExperimentalWarning(feature) {
if (experimentalWarnings.has(feature)) return;
const msg = `${feature} is an experimental feature and might change at any time`;
experimentalWarnings.add(feature);
process.emitWarning(msg, 'ExperimentalWarning');
}
function filterDuplicateStrings(items, low) {
const map = new SafeMap();
for (let i = 0; i < items.length; i++) {
const item = items[i];
const key = StringPrototypeToLowerCase(item);
if (low) {
map.set(key, key);
} else {
map.set(key, item);
}
}
return ArrayPrototypeSort(ArrayFrom(map.values()));
}
function cachedResult(fn) {
let result;
return () => {
if (result === undefined)
result = fn();
return ArrayPrototypeSlice(result);
};
}
// Useful for Wrapping an ES6 Class with a constructor Function that
// does not require the new keyword. For instance:
// class A { constructor(x) {this.x = x;}}
// const B = createClassWrapper(A);
// B() instanceof A // true
// B() instanceof B // true
function createClassWrapper(type) {
function fn(...args) {
return ReflectConstruct(type, args, new.target || type);
}
// Mask the wrapper function name and length values
ObjectDefineProperties(fn, {
name: { __proto__: null, value: type.name },
length: { __proto__: null, value: type.length },
});
ObjectSetPrototypeOf(fn, type);
fn.prototype = type.prototype;
return fn;
}
let signalsToNamesMapping;
function getSignalsToNamesMapping() {
if (signalsToNamesMapping !== undefined)
return signalsToNamesMapping;
signalsToNamesMapping = { __proto__: null };
for (const key in signals) {
signalsToNamesMapping[signals[key]] = key;
}
return signalsToNamesMapping;
}
function convertToValidSignal(signal) {
if (typeof signal === 'number' && getSignalsToNamesMapping()[signal])
return signal;
if (typeof signal === 'string') {
const signalName = signals[StringPrototypeToUpperCase(signal)];
if (signalName) return signalName;
}
throw new ERR_UNKNOWN_SIGNAL(signal);
}
function getConstructorOf(obj) {
while (obj) {
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
descriptor.value.name !== '') {
return descriptor.value;
}
obj = ObjectGetPrototypeOf(obj);
}
return null;
}
function getSystemErrorName(err) {
const entry = uvErrmapGet(err);
return entry ? entry[0] : `Unknown system error ${err}`;
}
function getSystemErrorMap() {
return lazyUv().getErrorMap();
}
const kCustomPromisifiedSymbol = SymbolFor('nodejs.util.promisify.custom');
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
let validateFunction;
function promisify(original) {
// Lazy-load to avoid a circular dependency.
if (validateFunction === undefined)
({ validateFunction } = require('internal/validators'));
validateFunction(original, 'original');
if (original[kCustomPromisifiedSymbol]) {
const fn = original[kCustomPromisifiedSymbol];
validateFunction(fn, 'util.promisify.custom');
return ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
__proto__: null,
value: fn, enumerable: false, writable: false, configurable: true,
});
}
// Names to create an object from in case the callback receives multiple
// arguments, e.g. ['bytesRead', 'buffer'] for fs.read.
const argumentNames = original[kCustomPromisifyArgsSymbol];
function fn(...args) {
return new Promise((resolve, reject) => {
ArrayPrototypePush(args, (err, ...values) => {
if (err) {
return reject(err);
}
if (argumentNames !== undefined && values.length > 1) {
const obj = {};
for (let i = 0; i < argumentNames.length; i++)
obj[argumentNames[i]] = values[i];
resolve(obj);
} else {
resolve(values[0]);
}
});
ReflectApply(original, this, args);
});
}
ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));
ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
__proto__: null,
value: fn, enumerable: false, writable: false, configurable: true,
});
const descriptors = ObjectGetOwnPropertyDescriptors(original);
const propertiesValues = ObjectValues(descriptors);
for (let i = 0; i < propertiesValues.length; i++) {
// We want to use null-prototype objects to not rely on globally mutable
// %Object.prototype%.
ObjectSetPrototypeOf(propertiesValues[i], null);
}
return ObjectDefineProperties(fn, descriptors);
}
promisify.custom = kCustomPromisifiedSymbol;
// The built-in Array#join is slower in v8 6.0
function join(output, separator) {
let str = '';
if (output.length !== 0) {
const lastIndex = output.length - 1;
for (let i = 0; i < lastIndex; i++) {
// It is faster not to use a template string here
str += output[i];
str += separator;
}
str += output[lastIndex];
}
return str;
}
// As of V8 6.6, depending on the size of the array, this is anywhere
// between 1.5-10x faster than the two-arg version of Array#splice()
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
const kNodeModulesRE = /^(.*)[\\/]node_modules[\\/]/;
let getStructuredStack;
function isInsideNodeModules() {
if (getStructuredStack === undefined) {
// Lazy-load to avoid a circular dependency.
const { runInNewContext } = require('vm');
// Use `runInNewContext()` to get something tamper-proof and
// side-effect-free. Since this is currently only used for a deprecated API,
// the perf implications should be okay.
getStructuredStack = runInNewContext(`(function() {
try { Error.stackTraceLimit = Infinity; } catch {}
return function structuredStack() {
const e = new Error();
overrideStackTrace.set(e, (err, trace) => trace);
return e.stack;
};
})()`, { overrideStackTrace }, { filename: 'structured-stack' });
}
const stack = getStructuredStack();
// Iterate over all stack frames and look for the first one not coming
// from inside Node.js itself:
if (ArrayIsArray(stack)) {
for (const frame of stack) {
const filename = frame.getFileName();
// If a filename does not start with / or contain \,
// it's likely from Node.js core.
if (RegExpPrototypeExec(/^\/|\\/, filename) === null)
continue;
return RegExpPrototypeExec(kNodeModulesRE, filename) !== null;
}
}
return false;
}
function once(callback) {
let called = false;
return function(...args) {
if (called) return;
called = true;
return ReflectApply(callback, this, args);
};
}
let validateUint32;
function sleep(msec) {
// Lazy-load to avoid a circular dependency.
if (validateUint32 === undefined)
({ validateUint32 } = require('internal/validators'));
validateUint32(msec, 'msec');
_sleep(msec);
}
function createDeferredPromise() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
// https://heycam.github.io/webidl/#define-the-operations
function defineOperation(target, name, method) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: true,
configurable: true,
value: method,
});
}
// https://heycam.github.io/webidl/#es-interfaces
function exposeInterface(target, name, interfaceObject) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: interfaceObject,
});
}
// https://heycam.github.io/webidl/#es-namespaces
function exposeNamespace(target, name, namespaceObject) {
ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: namespaceObject,
});
}
function exposeGetterAndSetter(target, name, getter, setter = undefined) {
ObjectDefineProperty(target, name, {
__proto__: null,
enumerable: false,
configurable: true,
get: getter,
set: setter,
});
}
function defineLazyProperties(target, id, keys, enumerable = true) {
const descriptors = { __proto__: null };
let mod;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
let lazyLoadedValue;
function set(value) {
ObjectDefineProperty(target, key, {
__proto__: null,
writable: true,
value,
});
}
ObjectDefineProperty(set, 'name', {
__proto__: null,
value: `set ${key}`,
});
function get() {
mod ??= require(id);
if (lazyLoadedValue === undefined) {
lazyLoadedValue = mod[key];
set(lazyLoadedValue);
}
return lazyLoadedValue;
}
ObjectDefineProperty(get, 'name', {
__proto__: null,
value: `get ${key}`,
});
descriptors[key] = {
__proto__: null,
configurable: true,
enumerable,
get,
set,
};
}
ObjectDefineProperties(target, descriptors);
}
function defineReplaceableLazyAttribute(target, id, keys, writable = true, check) {
let mod;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
let value;
let setterCalled = false;
function get() {
if (check !== undefined) {
FunctionPrototypeCall(check, this);
}
if (setterCalled) {
return value;
}
mod ??= require(id);
value ??= mod[key];
return value;
}
ObjectDefineProperty(get, 'name', {
__proto__: null,
value: `get ${key}`,
});
function set(val) {
setterCalled = true;
value = val;
}
ObjectDefineProperty(set, 'name', {
__proto__: null,
value: `set ${key}`,
});
ObjectDefineProperty(target, key, {
__proto__: null,
enumerable: true,
configurable: true,
get,
set: writable ? set : undefined,
});
}
}
function exposeLazyInterfaces(target, id, keys) {
defineLazyProperties(target, id, keys, false);
}
let _DOMException;
const lazyDOMExceptionClass = () => {
_DOMException ??= internalBinding('messaging').DOMException;
return _DOMException;
};
const lazyDOMException = hideStackFrames((message, name) => {
_DOMException ??= internalBinding('messaging').DOMException;
return new _DOMException(message, name);
});
const kEnumerableProperty = { __proto__: null };
kEnumerableProperty.enumerable = true;
ObjectFreeze(kEnumerableProperty);
const kEmptyObject = ObjectFreeze({ __proto__: null });
function filterOwnProperties(source, keys) {
const filtered = { __proto__: null };
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (ObjectPrototypeHasOwnProperty(source, key)) {
filtered[key] = source[key];
}
}
return filtered;
}
/**
* Mimics `obj[key] = value` but ignoring potential prototype inheritance.
* @param {any} obj
* @param {string} key
* @param {any} value
* @returns {any}
*/
function setOwnProperty(obj, key, value) {
return ObjectDefineProperty(obj, key, {
__proto__: null,
configurable: true,
enumerable: true,
value,
writable: true,
});
}
let internalGlobal;
function getInternalGlobal() {
if (internalGlobal == null) {
// Lazy-load to avoid a circular dependency.
const { runInNewContext } = require('vm');
internalGlobal = runInNewContext('this', undefined, { contextName: 'internal' });
}
return internalGlobal;
}
function SideEffectFreeRegExpPrototypeExec(regex, string) {
const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
return FunctionPrototypeCall(RegExpFromAnotherRealm.prototype.exec, regex, string);
}
const crossRelmRegexes = new SafeWeakMap();
function getCrossRelmRegex(regex) {
const cached = crossRelmRegexes.get(regex);
if (cached) return cached;
let flagString = '';
if (RegExpPrototypeGetHasIndices(regex)) flagString += 'd';
if (RegExpPrototypeGetGlobal(regex)) flagString += 'g';
if (RegExpPrototypeGetIgnoreCase(regex)) flagString += 'i';
if (RegExpPrototypeGetMultiline(regex)) flagString += 'm';
if (RegExpPrototypeGetDotAll(regex)) flagString += 's';
if (RegExpPrototypeGetUnicode(regex)) flagString += 'u';
if (RegExpPrototypeGetSticky(regex)) flagString += 'y';
const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
const crossRelmRegex = new RegExpFromAnotherRealm(RegExpPrototypeGetSource(regex), flagString);
crossRelmRegexes.set(regex, crossRelmRegex);
return crossRelmRegex;
}
function SideEffectFreeRegExpPrototypeSymbolReplace(regex, string, replacement) {
return getCrossRelmRegex(regex)[SymbolReplace](string, replacement);
}
function SideEffectFreeRegExpPrototypeSymbolSplit(regex, string, limit = undefined) {
return getCrossRelmRegex(regex)[SymbolSplit](string, limit);
}
function isArrayBufferDetached(value) {
if (ArrayBufferPrototypeGetByteLength(value) === 0) {
return _isArrayBufferDetached(value);
}
return false;
}
/**
* Helper function to lazy-load an initialize-once value.
* @template T Return value of initializer
* @param {()=>T} initializer Initializer of the lazily loaded value.
* @returns {()=>T}
*/
function getLazy(initializer) {
let value;
let initialized = false;
return function() {
if (initialized === false) {
value = initializer();
initialized = true;
}
return value;
};
}
// Setup user-facing NODE_V8_COVERAGE environment variable that writes
// ScriptCoverage objects to a specified directory.
function setupCoverageHooks(dir) {
const cwd = require('internal/process/execution').tryGetCwd();
const { resolve } = require('path');
const coverageDirectory = resolve(cwd, dir);
const { sourceMapCacheToObject } =
require('internal/source_map/source_map_cache');
if (process.features.inspector) {
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
internalBinding('profiler').setSourceMapCacheGetter(sourceMapCacheToObject);
} else {
process.emitWarning('The inspector is disabled, ' +
'coverage could not be collected',
'Warning');
return '';
}
return coverageDirectory;
}
module.exports = {
getLazy,
assertCrypto,
cachedResult,
convertToValidSignal,
createClassWrapper,
createDeferredPromise,
decorateErrorStack,
defineOperation,
defineLazyProperties,
defineReplaceableLazyAttribute,
deprecate,
emitExperimentalWarning,
exposeInterface,
exposeLazyInterfaces,
exposeNamespace,
exposeGetterAndSetter,
filterDuplicateStrings,
filterOwnProperties,
getConstructorOf,
getInternalGlobal,
getSystemErrorMap,
getSystemErrorName,
isArrayBufferDetached,
isError,
isInsideNodeModules,
join,
lazyDOMException,
lazyDOMExceptionClass,
normalizeEncoding,
once,
promisify,
SideEffectFreeRegExpPrototypeExec,
SideEffectFreeRegExpPrototypeSymbolReplace,
SideEffectFreeRegExpPrototypeSymbolSplit,
sleep,
spliceOne,
setupCoverageHooks,
toUSVString,
removeColors,
// Symbol used to customize promisify conversion
customPromisifyArgs: kCustomPromisifyArgsSymbol,
// Symbol used to provide a custom inspect function for an object as an
// alternative to using 'inspect'
customInspectSymbol: SymbolFor('nodejs.util.inspect.custom'),
// Used by the buffer module to capture an internal reference to the
// default isEncoding implementation, just in case userland overrides it.
kIsEncodingSymbol: Symbol('kIsEncodingSymbol'),
kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol'),
kEmptyObject,
kEnumerableProperty,
setOwnProperty,
};