Skip to content

Commit

Permalink
util: inspect ArrayBuffers contents as well
Browse files Browse the repository at this point in the history
Inspecting an ArrayBuffer now also shows their binary contents.

PR-URL: #25006
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
BridgeAR authored and addaleax committed Jan 14, 2019
1 parent 4794cf6 commit 901d3d0
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 30 deletions.
3 changes: 3 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ stream.write('With ES6');
<!-- YAML
added: v0.3.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/25006
description: ArrayBuffers now also show their binary contents.
- version: v11.5.0
pr-url: https://github.com/nodejs/node/pull/24852
description: The `getters` option is supported now.
Expand Down
26 changes: 21 additions & 5 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const setValues = uncurryThis(Set.prototype.values);
const mapEntries = uncurryThis(Map.prototype.entries);
const dateGetTime = uncurryThis(Date.prototype.getTime);
const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
let hexSlice;

const inspectDefaultOptions = Object.seal({
showHidden: false,
Expand Down Expand Up @@ -494,7 +495,7 @@ function noPrototypeIterator(ctx, value, recurseTimes) {
// Note: using `formatValue` directly requires the indentation level to be
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
// value afterwards again.
function formatValue(ctx, value, recurseTimes) {
function formatValue(ctx, value, recurseTimes, typedArray) {
// Primitive types cannot have properties.
if (typeof value !== 'object' && typeof value !== 'function') {
return formatPrimitive(ctx.stylize, value, ctx);
Expand Down Expand Up @@ -542,10 +543,10 @@ function formatValue(ctx, value, recurseTimes) {
if (ctx.seen.indexOf(value) !== -1)
return ctx.stylize('[Circular]', 'special');

return formatRaw(ctx, value, recurseTimes);
return formatRaw(ctx, value, recurseTimes, typedArray);
}

function formatRaw(ctx, value, recurseTimes) {
function formatRaw(ctx, value, recurseTimes, typedArray) {
let keys;

const constructor = getConstructorName(value, ctx);
Expand Down Expand Up @@ -678,9 +679,12 @@ function formatRaw(ctx, value, recurseTimes) {
const arrayType = isArrayBuffer(value) ? 'ArrayBuffer' :
'SharedArrayBuffer';
const prefix = getPrefix(constructor, tag, arrayType);
if (keys.length === 0)
if (typedArray === undefined) {
formatter = formatArrayBuffer;
} else if (keys.length === 0) {
return prefix +
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
}
braces[0] = `${prefix}{`;
keys.unshift('byteLength');
} else if (isDataView(value)) {
Expand Down Expand Up @@ -941,6 +945,18 @@ function formatSpecialArray(ctx, value, recurseTimes, maxLength, output, i) {
return output;
}

function formatArrayBuffer(ctx, value) {
const buffer = new Uint8Array(value);
if (hexSlice === undefined)
hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice);
let str = hexSlice(buffer, 0, Math.min(ctx.maxArrayLength, buffer.length))
.replace(/(.{2})/g, '$1 ').trim();
const remaining = buffer.length - ctx.maxArrayLength;
if (remaining > 0)
str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`];
}

function formatArray(ctx, value, recurseTimes) {
const valLen = value.length;
const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen);
Expand Down Expand Up @@ -981,7 +997,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
'byteOffset',
'buffer'
]) {
const str = formatValue(ctx, value[key], recurseTimes);
const str = formatValue(ctx, value[key], recurseTimes, true);
output.push(`[${key}]: ${str}`);
}
ctx.indentationLvl -= 2;
Expand Down
6 changes: 0 additions & 6 deletions test/parallel/test-util-format-shared-arraybuffer.js

This file was deleted.

5 changes: 5 additions & 0 deletions test/parallel/test-util-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,8 @@ Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);
Object.setPrototypeOf(BadCustomError, Error);
assert.strictEqual(util.format(new BadCustomError('foo')),
'[BadCustomError: foo]');

assert.strictEqual(
util.format(new SharedArrayBuffer(4)),
'SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'
);
55 changes: 36 additions & 19 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,70 +115,86 @@ assert(!/Object/.test(
util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true)
));

for (const showHidden of [true, false]) {
const ab = new ArrayBuffer(4);
{
const showHidden = true;
const ab = new Uint8Array([1, 2, 3, 4]).buffer;
const dv = new DataView(ab, 1, 2);
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: ' +
'<01 02 03 04>, byteLength: 4 } }');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: ' +
'<01 02 03 04>, byteLength: 4 } }');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4, x: 42 }');
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, ' +
'byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: <01 02 03 04>, ' +
'byteLength: 4, x: 42 },\n' +
' y: 1337 }');
}

// Now do the same checks but from a different context.
for (const showHidden of [true, false]) {
{
const showHidden = false;
const ab = vm.runInNewContext('new ArrayBuffer(4)');
const dv = vm.runInNewContext('new DataView(ab, 1, 2)', { ab });
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: <00 00 00 00>, ' +
'byteLength: 4 } }');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: <00 00 00 00>, ' +
'byteLength: 4 } }');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4, x: 42 }');
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, ' +
'byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
' buffer:\n' +
' ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' +
' byteLength: 4, x: 42 },\n' +
' y: 1337 }');
}

Expand Down Expand Up @@ -1639,13 +1655,14 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
[new Float64Array(2), '[Float64Array: null prototype] [ 0, 0 ]'],
[new BigInt64Array(2), '[BigInt64Array: null prototype] [ 0n, 0n ]'],
[new BigUint64Array(2), '[BigUint64Array: null prototype] [ 0n, 0n ]'],
[new ArrayBuffer(16), '[ArrayBuffer: null prototype] ' +
'{ byteLength: undefined }'],
[new ArrayBuffer(16), '[ArrayBuffer: null prototype] {\n' +
' [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,\n' +
' byteLength: undefined }'],
[new DataView(new ArrayBuffer(16)),
'[DataView: null prototype] {\n byteLength: undefined,\n ' +
'byteOffset: undefined,\n buffer: undefined }'],
'byteOffset: undefined,\n buffer: undefined }'],
[new SharedArrayBuffer(2), '[SharedArrayBuffer: null prototype] ' +
'{ byteLength: undefined }'],
'{ [Uint8Contents]: <00 00>, byteLength: undefined }'],
[/foobar/, '[RegExp: null prototype] /foobar/']
].forEach(([value, expected]) => {
assert.strictEqual(
Expand Down

0 comments on commit 901d3d0

Please sign in to comment.