util: refactor inspect for performance and more

The main optimizations are
- Removed visibleKeys
- Removed proxy cache
- Removed Object.assign
- No key concatenating anymore
- No key recalculating anymore
- Improved indentation logic
- Improved string escape logic
- Added many fast paths
- Optimized code branches a lot
- Optimized (boxed) primitive handling
- Inline code if possible
- Only check extra keys if necessary
- Guard against unnecessary more expensive calls

This also fixes a bug with special array number keys as e.g. "00".

Besides that there were lots of smaller optimizations, the
code got a bit cleaned up and a few more tests got in.

PR-URL: https://github.com/nodejs/node/pull/14881
Fixes: https://github.com/nodejs/node/issues/15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
Ruben Bridgewater 2017-08-16 21:36:52 -03:00
parent 01652ccc68
commit f9ad23dc91
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
4 changed files with 567 additions and 514 deletions

File diff suppressed because it is too large Load Diff

View File

@ -114,6 +114,11 @@ const nestedObj = {
func: function() {}
}
};
const nestedObj2 = {
foo: 'bar',
foobar: 1,
func: [{ a: function() {} }]
};
assert.strictEqual(util.format('%o'), '%o');
assert.strictEqual(util.format('%o', 42), '42');
assert.strictEqual(util.format('%o', 'foo'), '\'foo\'');
@ -126,6 +131,17 @@ assert.strictEqual(
' [length]: 0,\n' +
' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular] } } }');
assert.strictEqual(
util.format('%o', nestedObj2),
'{ foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' [ { a: \n' +
' { [Function: a]\n' +
' [length]: 0,\n' +
' [name]: \'a\',\n' +
' [prototype]: a { [constructor]: [Circular] } } },\n' +
' [length]: 1 ] }');
assert.strictEqual(
util.format('%o', nestedObj),
'{ foo: \'bar\',\n' +

View File

@ -55,7 +55,9 @@ const expected6 = 'Proxy [ Proxy [ Proxy [ Proxy [Array], Proxy [Array]' +
' ],\n Proxy [ Proxy [Array], Proxy [Array] ] ],\n' +
' Proxy [ Proxy [ Proxy [Array], Proxy [Array] ],\n' +
' Proxy [ Proxy [Array], Proxy [Array] ] ] ]';
assert.strictEqual(util.inspect(proxy1, opts), expected1);
assert.strictEqual(
util.inspect(proxy1, { showProxy: true, depth: null }),
expected1);
assert.strictEqual(util.inspect(proxy2, opts), expected2);
assert.strictEqual(util.inspect(proxy3, opts), expected3);
assert.strictEqual(util.inspect(proxy4, opts), expected4);

View File

@ -43,14 +43,15 @@ assert.strictEqual(
new Date('2010-02-14T12:48:40+01:00').toISOString()
);
assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString());
assert.strictEqual(util.inspect('\n\u0001'), "'\\n\\u0001'");
assert.strictEqual(
util.inspect(`${Array(75).fill(1)}'\n\u001d\n\u0003`),
`'${Array(75).fill(1)}\\'\\n\\u001d\\n\\u0003'`
);
assert.strictEqual(util.inspect([]), '[]');
assert.strictEqual(util.inspect(Object.create([])), 'Array {}');
assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');
assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');
assert.strictEqual(util.inspect({}), '{}');
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
@ -76,6 +77,7 @@ assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1),
'{ a: { b: [Object] } }');
assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1),
'{ a: { b: [Array] } }');
assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array [ ]');
assert.strictEqual(
util.inspect(
Object.create(
@ -327,6 +329,38 @@ assert.strictEqual(
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
}
// Array with extra properties
{
const arr = [1, 2, 3, , ];
arr.foo = 'bar';
assert.strictEqual(util.inspect(arr),
"[ 1, 2, 3, <1 empty item>, foo: 'bar' ]");
const arr2 = [];
assert.strictEqual(util.inspect([], { showHidden: true }), '[ [length]: 0 ]');
arr2['00'] = 1;
assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ [length]: 0, '00': 1 ]");
arr2[1] = 0;
assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <1 empty item>, 0, [length]: 2, '00': 1 ]");
delete arr2[1];
assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1 ]");
arr2['01'] = 2;
assert.strictEqual(util.inspect(arr2),
"[ <2 empty items>, '00': 1, '01': 2 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]");
const arr3 = [];
arr3[-1] = -1;
assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]");
}
// Function with properties
{
const value = () => {};
@ -387,6 +421,11 @@ assert.strictEqual(util.inspect(-0), '-0');
util.inspect(a, { breakLength: Infinity }),
'[ \'foo\', <1 empty item>, \'baz\', \'bar\', <96 empty items>, \'qux\' ]'
);
delete a[3];
assert.strictEqual(
util.inspect(a, { maxArrayLength: 4 }),
'[ \'foo\', <1 empty item>, \'baz\', <97 empty items>, ... 1 more item ]'
);
}
// test for Array constructor in different context
@ -604,6 +643,10 @@ assert.doesNotThrow(() => {
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'),
true
);
}
{
@ -714,6 +757,7 @@ assert.doesNotThrow(() => {
testLines([1, 2, 3, 4, 5, 6, 7]);
testLines(bigArray);
testLines({ foo: 'bar', baz: 35, b: { a: 35 } });
testLines({ a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 }, b: 1 });
testLines({
foo: 'bar',
baz: 35,
@ -941,6 +985,20 @@ if (typeof Symbol !== 'undefined') {
'PromiseSubclass { <pending> }');
}
// Empty and circular before depth
{
const arr = [[[[]]]];
assert.strictEqual(util.inspect(arr), '[ [ [ [] ] ] ]');
arr[0][0][0][0] = [];
assert.strictEqual(util.inspect(arr), '[ [ [ [Array] ] ] ]');
arr[0][0][0] = {};
assert.strictEqual(util.inspect(arr), '[ [ [ {} ] ] ]');
arr[0][0][0] = { a: 2 };
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
arr[0][0][0] = arr;
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
}
// Corner cases.
{
const x = { constructor: 42 };