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
4 changes: 2 additions & 2 deletions src/bun.js/bindings/UtilInspect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ extern "C" JSC::EncodedJSValue JSC__JSValue__callCustomInspectFunction(
auto callData = JSC::getCallData(functionToCall);
MarkedArgumentBuffer arguments;
arguments.append(jsNumber(depth));
arguments.append(options);
arguments.append(inspectFn);
arguments.append(options ? JSValue(options) : jsUndefined());
arguments.append(inspectFn ? JSValue(inspectFn) : jsUndefined());

auto inspectRet = JSC::profiledCall(globalObject, ProfilingReason::API, functionToCall, callData, thisValue, arguments);
RETURN_IF_EXCEPTION(scope, {});
Expand Down
17 changes: 10 additions & 7 deletions src/bun.js/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1901,10 +1901,10 @@ void GlobalObject::finishCreation(VM& vm)
[](const Initializer<JSFunction>& init) {
auto scope = DECLARE_THROW_SCOPE(init.vm);
JSValue nodeUtilValue = jsCast<Zig::GlobalObject*>(init.owner)->internalModuleRegistry()->requireId(init.owner, init.vm, Bun::InternalModuleRegistry::Field::NodeUtil);
RETURN_IF_EXCEPTION(scope, );
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));
RELEASE_ASSERT(nodeUtilValue.isObject());
auto prop = nodeUtilValue.getObject()->getIfPropertyExists(init.owner, Identifier::fromString(init.vm, "inspect"_s));
RETURN_IF_EXCEPTION(scope, );
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));
ASSERT(prop);
init.set(jsCast<JSFunction*>(prop));
});
Expand All @@ -1926,22 +1926,25 @@ void GlobalObject::finishCreation(VM& vm)
m_utilInspectStylizeColorFunction.initLater(
[](const Initializer<JSFunction>& init) {
auto scope = DECLARE_THROW_SCOPE(init.vm);
JSFunction* inspectFn = jsCast<Zig::GlobalObject*>(init.owner)->utilInspectFunction();
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));
if (!inspectFn) [[unlikely]]
return init.property.setMayBeNull(init.vm, init.owner, nullptr);
JSC::MarkedArgumentBuffer args;
args.append(jsCast<Zig::GlobalObject*>(init.owner)->utilInspectFunction());
RETURN_IF_EXCEPTION(scope, );
args.append(inspectFn);

JSC::JSFunction* getStylize = JSC::JSFunction::create(init.vm, init.owner, utilInspectGetStylizeWithColorCodeGenerator(init.vm), init.owner);
RETURN_IF_EXCEPTION(scope, );
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));

JSC::CallData callData = JSC::getCallData(getStylize);
NakedPtr<JSC::Exception> returnedException = nullptr;
auto result = JSC::profiledCall(init.owner, ProfilingReason::API, getStylize, callData, jsNull(), args, returnedException);
RETURN_IF_EXCEPTION(scope, );
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));

if (returnedException) {
throwException(init.owner, scope, returnedException.get());
}
RETURN_IF_EXCEPTION(scope, );
RETURN_IF_EXCEPTION(scope, init.property.setMayBeNull(init.vm, init.owner, nullptr));
init.set(jsCast<JSFunction*>(result));
});

Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6421,6 +6421,7 @@ extern "C" JSC::EncodedJSValue Bun__REPL__formatValue(
// Get the util.inspect function from the global object
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
JSC::JSValue inspectFn = bunGlobal->utilInspectFunction();
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));

if (!inspectFn || !inspectFn.isCallable()) {
// Fallback to toString if util.inspect is not available
Expand Down
7 changes: 5 additions & 2 deletions src/bun.js/bindings/webcore/JSBroadcastChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBroadcastChannelPrototype_inspectCustom, (JSC::JSGlob
depthValue = jsNumber(depth - 1);
}

JSFunction* utilInspect = globalObject->utilInspectFunction();
RETURN_IF_EXCEPTION(throwScope, {});
if (!utilInspect) [[unlikely]]
return JSValue::encode(jsNontrivialString(vm, "BroadcastChannel"_s));

JSObject* options = optionsValue.toObject(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
PropertyNameArrayBuilder optionsArray(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
Expand All @@ -211,8 +216,6 @@ JSC_DEFINE_HOST_FUNCTION(jsBroadcastChannelPrototype_inspectCustom, (JSC::JSGlob
inputObj->putDirect(vm, vm.propertyNames->name, jsString(vm, channel->name()), 0);
inputObj->putDirect(vm, Identifier::fromString(vm, "active"_s), jsBoolean(!channel->isClosed()), 0);

JSFunction* utilInspect = globalObject->utilInspectFunction();
RETURN_IF_EXCEPTION(throwScope, {});
auto callData = JSC::getCallData(utilInspect);
MarkedArgumentBuffer arguments;
arguments.append(inputObj);
Expand Down
80 changes: 80 additions & 0 deletions test/js/bun/util/inspect-custom-lazy-init-exception.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

test("Bun.inspect on object with custom inspect does not crash when node:util fails to load", async () => {
const src = `
const { writeSync } = require("node:fs");
const print = (s) => writeSync(1, s + "\\n");
Object.defineProperty(Array.prototype, "forEach", {
get() { throw new Error("poisoned forEach"); },
});
const obj = { [Symbol.for("nodejs.util.inspect.custom")]() { return "custom-result"; } };
for (const colors of [true, false, true, false]) {
try {
Bun.inspect(obj, { colors });
print("ok");
} catch (e) {
print("caught: " + e.message);
}
}
const bc = new BroadcastChannel("test");
try {
Bun.inspect(bc);
print("bc ok");
} catch (e) {
print("bc caught: " + e.message);
}
bc.close();
process.exit(0);
`;

await using proc = Bun.spawn({
cmd: [bunExe(), "-e", src],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("caught: poisoned forEach\nok\nok\nok\nbc ok\n");
expect(proc.signalCode).toBeNull();
expect(exitCode).toBe(0);
});

test("Bun.inspect with colors does not crash when utilInspectFunction was previously nulled", async () => {
const src = `
const { writeSync } = require("node:fs");
const print = (s) => writeSync(1, s + "\\n");
Object.defineProperty(Array.prototype, "forEach", {
get() { throw new Error("poisoned forEach"); },
});
const obj = { [Symbol.for("nodejs.util.inspect.custom")]() { return "custom-result"; } };
try {
Bun.inspect(obj, { colors: false });
print("nocolors ok");
} catch (e) {
print("nocolors caught: " + e.message);
}
try {
Bun.inspect(obj, { colors: true });
print("colors ok");
} catch (e) {
print("colors caught: " + e.message);
}
process.exit(0);
`;

await using proc = Bun.spawn({
cmd: [bunExe(), "-e", src],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("nocolors caught: poisoned forEach\ncolors ok\n");
expect(proc.signalCode).toBeNull();
expect(exitCode).toBe(0);
});
Loading