diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index fd48e2af0ce3..b777a65e7ebd 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -5369,10 +5369,11 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: } JSC::PropertySlot slot(object, PropertySlot::InternalMethodType::Get); - if (!object->getPropertySlot(globalObject, property, slot)) - continue; + bool hasProperty = object->getPropertySlot(globalObject, property, slot); // Ignore exceptions from "Get" proxy traps. CLEAR_IF_EXCEPTION(scope); + if (!hasProperty) + continue; if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { if (property == propertyNames->underscoreProto @@ -5444,7 +5445,12 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: break; if (iterating == globalObject) break; - iterating = iterating->getPrototype(globalObject).getObject(); + JSValue nextProto = iterating->getPrototype(globalObject); + // Ignore exceptions from Proxy "getPrototypeOf" trap. + CLEAR_IF_EXCEPTION(scope); + if (!nextProto) + break; + iterating = nextProto.getObject(); } } diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..2ab65e5cf4e5 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { normalizeBunSnapshot, tmpdirSync } from "harness"; +import { bunEnv, bunExe, normalizeBunSnapshot, tmpdirSync } from "harness"; import { join } from "path"; import util from "util"; it("prototype", () => { @@ -465,6 +465,49 @@ describe("crash testing", () => { } }); } + + it.concurrent.each([ + [ + "throwing get trap", + ` + const o = {}; + Object.setPrototypeOf(o, new Proxy({ foo: 1 }, { + get(t, k) { if (typeof k === "symbol") return undefined; throw new Error("nope"); }, + })); + Bun.inspect(o); + `, + ], + [ + "throwing getPrototypeOf trap", + ` + const o = {}; + Object.setPrototypeOf(o, new Proxy({ foo: 1 }, { + getPrototypeOf() { throw new Error("nope"); }, + })); + Bun.inspect(o); + `, + ], + [ + "getter throws after side-effect from prior getter", + ` + const { expect } = Bun.jest(""); + const e = expect(1); + Object.setPrototypeOf(e, new Proxy(Object.getPrototypeOf(e), {})); + Bun.inspect(e); + `, + ], + ])("Proxy prototype with %s doesn't crash", async (_, code) => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", code + '\nconsole.log("OK");'], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stderr).toBe(""); + expect(stdout).toBe("OK\n"); + expect(exitCode).toBe(0); + }); }); it("possibly formatted emojis log", () => {