util: improve inspect performance

This improves a slow code part in `util.inspect` by directly
retrieving the `Symbol.toStringTag` and by optimizing some code
paths.

PR-URL: https://github.com/nodejs/node/pull/20009
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Yuta Hiroto <hello@hiroppy.me>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
This commit is contained in:
Ruben Bridgewater 2018-04-13 15:56:37 +02:00
parent 94596560c2
commit ad1d1057f9
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
2 changed files with 53 additions and 59 deletions

View File

@ -252,43 +252,6 @@ function getSystemErrorName(err) {
return entry ? entry[0] : `Unknown system error ${err}`;
}
// getConstructorOf is wrapped into this to save iterations
function getIdentificationOf(obj) {
const original = obj;
let constructor;
let tag;
while (obj) {
if (constructor === undefined) {
const desc = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (desc !== undefined &&
typeof desc.value === 'function' &&
desc.value.name !== '')
constructor = desc.value.name;
}
if (tag === undefined) {
const desc = Object.getOwnPropertyDescriptor(obj, Symbol.toStringTag);
if (desc !== undefined) {
if (typeof desc.value === 'string') {
tag = desc.value;
} else if (desc.get !== undefined) {
tag = desc.get.call(original);
if (typeof tag !== 'string')
tag = undefined;
}
}
}
if (constructor !== undefined && tag !== undefined)
break;
obj = Object.getPrototypeOf(obj);
}
return { constructor, tag };
}
const kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
@ -431,7 +394,6 @@ module.exports = {
filterDuplicateStrings,
getConstructorOf,
getSystemErrorName,
getIdentificationOf,
isError,
isInsideNodeModules,
join,

View File

@ -72,7 +72,6 @@ const {
customInspectSymbol,
deprecate,
getSystemErrorName: internalErrorName,
getIdentificationOf,
isError,
promisify,
join,
@ -396,6 +395,35 @@ function stylizeNoColor(str, styleType) {
return str;
}
function getConstructorName(obj) {
while (obj) {
const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
descriptor.value.name !== '') {
return descriptor.value.name;
}
obj = Object.getPrototypeOf(obj);
}
return '';
}
function getPrefix(constructor, tag) {
if (constructor !== '') {
if (tag !== '' && constructor !== tag) {
return `${constructor} [${tag}] `;
}
return `${constructor} `;
}
if (tag !== '')
return `[${tag}] `;
return '';
}
function formatValue(ctx, value, recurseTimes, ln) {
// Primitive types cannot have properties
if (typeof value !== 'object' && typeof value !== 'function') {
@ -475,15 +503,10 @@ function formatValue(ctx, value, recurseTimes, ln) {
const keyLength = keys.length + symbols.length;
const { constructor, tag } = getIdentificationOf(value);
let prefix = '';
if (constructor && tag && constructor !== tag)
prefix = `${constructor} [${tag}] `;
else if (constructor)
prefix = `${constructor} `;
else if (tag)
prefix = `[${tag}] `;
const constructor = getConstructorName(value);
let tag = value[Symbol.toStringTag];
if (typeof tag !== 'string')
tag = '';
let base = '';
let formatter = formatObject;
let braces;
@ -496,22 +519,25 @@ function formatValue(ctx, value, recurseTimes, ln) {
noIterator = false;
if (Array.isArray(value)) {
// Only set the constructor for non ordinary ("Array [...]") arrays.
const prefix = getPrefix(constructor, tag);
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
if (value.length === 0 && keyLength === 0)
return `${braces[0]}]`;
formatter = formatArray;
} else if (isSet(value)) {
const prefix = getPrefix(constructor, tag);
if (value.size === 0 && keyLength === 0)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatSet;
} else if (isMap(value)) {
const prefix = getPrefix(constructor, tag);
if (value.size === 0 && keyLength === 0)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatMap;
} else if (isTypedArray(value)) {
braces = [`${prefix}[`, ']'];
braces = [`${getPrefix(constructor, tag)}[`, ']'];
formatter = formatTypedArray;
} else if (isMapIterator(value)) {
braces = [`[${tag}] {`, '}'];
@ -543,11 +569,16 @@ function formatValue(ctx, value, recurseTimes, ln) {
}
if (noIterator) {
braces = ['{', '}'];
if (prefix === 'Object ') {
if (constructor === 'Object') {
if (isArgumentsObject(value)) {
braces[0] = '[Arguments] {';
if (keyLength === 0)
return '[Arguments] {}';
} else if (tag !== '') {
braces[0] = `${getPrefix(constructor, tag)}{`;
if (keyLength === 0) {
return `${braces[0]}}`;
}
} else if (keyLength === 0) {
return '{}';
}
@ -579,27 +610,28 @@ function formatValue(ctx, value, recurseTimes, ln) {
// Fast path for ArrayBuffer and SharedArrayBuffer.
// Can't do the same for DataView because it has a non-primitive
// .buffer property that we need to recurse for.
const prefix = getPrefix(constructor, tag);
if (keyLength === 0)
return prefix +
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
braces[0] = `${prefix}{`;
keys.unshift('byteLength');
} else if (isDataView(value)) {
braces[0] = `${prefix}{`;
braces[0] = `${getPrefix(constructor, tag)}{`;
// .buffer goes last, it's not a primitive like the others.
keys.unshift('byteLength', 'byteOffset', 'buffer');
} else if (isPromise(value)) {
braces[0] = `${prefix}{`;
braces[0] = `${getPrefix(constructor, tag)}{`;
formatter = formatPromise;
} else if (isWeakSet(value)) {
braces[0] = `${prefix}{`;
braces[0] = `${getPrefix(constructor, tag)}{`;
if (ctx.showHidden) {
formatter = formatWeakSet;
} else {
extra = '[items unknown]';
}
} else if (isWeakMap(value)) {
braces[0] = `${prefix}{`;
braces[0] = `${getPrefix(constructor, tag)}{`;
if (ctx.showHidden) {
formatter = formatWeakMap;
} else {
@ -638,9 +670,9 @@ function formatValue(ctx, value, recurseTimes, ln) {
} else if (keyLength === 0) {
if (isExternal(value))
return ctx.stylize('[External]', 'special');
return `${prefix}{}`;
return `${getPrefix(constructor, tag)}{}`;
} else {
braces[0] = `${prefix}{`;
braces[0] = `${getPrefix(constructor, tag)}{`;
}
}
}
@ -675,8 +707,8 @@ function formatNumber(fn, value) {
function formatPrimitive(fn, value, ctx) {
if (typeof value === 'string') {
if (ctx.compact === false &&
value.length > MIN_LINE_LENGTH &&
ctx.indentationLvl + value.length > ctx.breakLength) {
ctx.indentationLvl + value.length > ctx.breakLength &&
value.length > MIN_LINE_LENGTH) {
// eslint-disable-next-line max-len
const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH);
// eslint-disable-next-line max-len
@ -695,9 +727,9 @@ function formatPrimitive(fn, value, ctx) {
// eslint-disable-next-line max-len, node-core/no-unescaped-regexp-dot
readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm');
}
const indent = ' '.repeat(ctx.indentationLvl);
const matches = value.match(readableRegExps[divisor]);
if (matches.length > 1) {
const indent = ' '.repeat(ctx.indentationLvl);
res += `${fn(strEscape(matches[0]), 'string')} +\n`;
for (var i = 1; i < matches.length - 1; i++) {
res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`;