Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/js/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,20 @@ function isURL(value) {

const SymbolToPrimitive = Symbol.toPrimitive;

const builtInObjects = new SafeSet(
ArrayPrototypeFilter(
ObjectGetOwnPropertyNames(globalThis),
e => RegExpPrototypeExec(/^[A-Z][a-zA-Z0-9]+$/, e) !== null,
),
);
// Node.js computes this from `globalThis` at bootstrap, before any host globals
// (Buffer, URL, ...) are installed. Bun's globalThis already has them when this
// module loads, so hardcode the names Node observes instead of scraping.
// prettier-ignore
Comment thread
robobun marked this conversation as resolved.
const builtInObjects = new SafeSet([
"AggregateError", "Array", "ArrayBuffer", "Atomics", "BigInt", "BigInt64Array",
"BigUint64Array", "Boolean", "DataView", "Date", "Error", "EvalError",
"FinalizationRegistry", "Float32Array", "Float64Array", "Function", "Infinity",
"Int16Array", "Int32Array", "Int8Array", "Intl", "Iterator", "JSON", "Map",
"Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError",
"ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "SyntaxError",
"TypeError", "URIError", "Uint16Array", "Uint32Array", "Uint8Array",
"Uint8ClampedArray", "WeakMap", "WeakRef", "WeakSet",
]);

// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
const isUndetectableObject = v => typeof v === "undefined" && v !== undefined;
Expand Down
19 changes: 19 additions & 0 deletions test/js/node/util/node-inspect-tests/parallel/util-format.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,25 @@ test("no assertion failures", () => {
assert.strictEqual(util.format("%s", { __proto__: null }), "[Object: null prototype] {}");
}

// `%s` with values whose inherited `toString` comes from a non-ECMAScript
// built-in (Buffer, URL, ...) must call `String(value)`, not inspect it.
assert.strictEqual(util.format("%s", Buffer.from("ab")), "ab");
assert.strictEqual(util.format("%s", Buffer.from([0xe2, 0x82, 0xac])), "\u20ac");
assert.strictEqual(util.format("%s", new URL("http://a/b")), "http://a/b");
assert.strictEqual(util.format("%s", new URLSearchParams("a=1&b=2")), "a=1&b=2");
{
// Subclass path: toString is inherited through an extra prototype hop.
class MyURL extends URL {}
assert.strictEqual(util.format("%s", new MyURL("http://a/b")), "http://a/b");
class MyBuffer extends Buffer {}
const sub = Object.setPrototypeOf(Buffer.from([0x68, 0x69]), MyBuffer.prototype);
assert.strictEqual(util.format("%s", sub), "hi");
}
Comment thread
robobun marked this conversation as resolved.
// Uint8Array inherits toString from %TypedArray%.prototype, not Buffer.
assert.strictEqual(util.format("%s", new Uint8Array([65])), "65");
// ECMAScript built-ins with their own toString still take the inspect path.
assert.strictEqual(util.format("%s", [1, 2]), "[ 1, 2 ]");

// JSON format specifier
assert.strictEqual(util.format("%j"), "%j");
assert.strictEqual(util.format("%j", 42), "42");
Expand Down
79 changes: 43 additions & 36 deletions test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

import assert from "assert";
import { isWindows } from "harness";
import { isDebug, isWindows } from "harness";
import util, { inspect } from "util";
import vm from "vm";
import { MessageChannel } from "worker_threads";
Expand Down Expand Up @@ -238,7 +238,6 @@ test("inspect from a different context", () => {

test("no assertion failures 2", () => {
[
Float16Array,
Float32Array,
Float64Array,
Int16Array,
Expand Down Expand Up @@ -268,9 +267,17 @@ test("no assertion failures 2", () => {
assert.strictEqual(util.inspect(array, false), `${constructor.name}(${length}) [ 65, 97 ]`);
});

// Float16Array is absent from Node's bootstrap-time `builtInObjects`, so
// showHidden walks into its prototype and the output diverges from the
// other typed arrays. Lock that in so it is not silently re-added.
assert.match(
util.inspect(new Float16Array(2), { showHidden: true }),
/\[Symbol\(Symbol\.toStringTag\)\]: \[Getter\]/,
);
assert.strictEqual(util.inspect(new Float16Array([65, 97]), false), "Float16Array(2) [ 65, 97 ]");

// Now check that declaring a TypedArray in a different context works the same.
[
Float16Array,
Float32Array,
Float64Array,
Int16Array,
Expand Down Expand Up @@ -775,39 +782,6 @@ test("no assertion failures 2", () => {
assert.strictEqual(util.inspect(y), "[ 'a', 'b', 'c', '\\\\\\\\': 'd', " + "'\\n': 'e', '\\r': 'f' ]");
}

// Escape unpaired surrogate pairs.
{
const edgeChar = String.fromCharCode(0xd799);

for (let charCode = 0xd800; charCode < 0xdfff; charCode++) {
const surrogate = String.fromCharCode(charCode);

assert.strictEqual(util.inspect(surrogate), `'\\u${charCode.toString(16)}'`);
assert.strictEqual(
util.inspect(`${"a".repeat(200)}${surrogate}`),
`'${"a".repeat(200)}\\u${charCode.toString(16)}'`,
);
assert.strictEqual(
util.inspect(`${surrogate}${"a".repeat(200)}`),
`'\\u${charCode.toString(16)}${"a".repeat(200)}'`,
);
if (charCode < 0xdc00) {
const highSurrogate = surrogate;
const lowSurrogate = String.fromCharCode(charCode + 1024);
assert(!util.inspect(`${edgeChar}${highSurrogate}${lowSurrogate}${edgeChar}`).includes("\\u"));
assert.strictEqual(
(util.inspect(`${highSurrogate}${highSurrogate}${lowSurrogate}`).match(/\\u/g) ?? []).length,
1,
);
} else {
assert.strictEqual(
util.inspect(`${edgeChar}${surrogate}${edgeChar}`),
`'${edgeChar}\\u${charCode.toString(16)}${edgeChar}'`,
);
}
}
}

// Test util.inspect.styles and util.inspect.colors.
{
function testColorStyle(style, input) {
Expand Down Expand Up @@ -1813,6 +1787,39 @@ test("no assertion failures 2", () => {
}
});

test("escape unpaired surrogate pairs", () => {
const edgeChar = String.fromCharCode(0xd799);
const step = isDebug ? 17 : 1;

for (let charCode = 0xd800; charCode < 0xdfff; charCode += step) {
const surrogate = String.fromCharCode(charCode);

assert.strictEqual(util.inspect(surrogate), `'\\u${charCode.toString(16)}'`);
assert.strictEqual(
util.inspect(`${"a".repeat(200)}${surrogate}`),
`'${"a".repeat(200)}\\u${charCode.toString(16)}'`,
);
assert.strictEqual(
util.inspect(`${surrogate}${"a".repeat(200)}`),
`'\\u${charCode.toString(16)}${"a".repeat(200)}'`,
);
if (charCode < 0xdc00) {
const highSurrogate = surrogate;
const lowSurrogate = String.fromCharCode(charCode + 1024);
assert(!util.inspect(`${edgeChar}${highSurrogate}${lowSurrogate}${edgeChar}`).includes("\\u"));
assert.strictEqual(
(util.inspect(`${highSurrogate}${highSurrogate}${lowSurrogate}`).match(/\\u/g) ?? []).length,
1,
);
} else {
assert.strictEqual(
util.inspect(`${edgeChar}${surrogate}${edgeChar}`),
`'${edgeChar}\\u${charCode.toString(16)}${edgeChar}'`,
);
}
}
});

test("util.inspect stack overflow handling", () => {
// Test that a long linked list can be inspected without throwing an error.
const list = {};
Expand Down
Loading