diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index 22ac38063376..97bed8776624 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/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..b165cb011ae1 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -772,3 +772,53 @@ it("CustomEvent", () => { }" `); }); + +describe("Proxy in prototype chain", () => { + it("does not crash when a Proxy prototype has a throwing getter", () => { + const obj = {}; + const proto = { + get ok() { + return 1; + }, + get bad() { + throw new Error("nope"); + }, + get ok2() { + return 2; + }, + }; + Object.setPrototypeOf(obj, new Proxy(proto, {})); + const out = Bun.inspect(obj); + expect(out).toContain("ok: 1"); + expect(out).toContain("ok2: 2"); + }); + + it("does not crash when a Proxy prototype has a throwing getPrototypeOf trap", () => { + class Foo { + bar() {} + baz() {} + } + const obj = new Foo(); + const originalPrototype = Object.getPrototypeOf(obj); + const newPrototype = new Proxy(originalPrototype, { + getPrototypeOf() { + throw new Error("boom"); + }, + }); + Object.setPrototypeOf(obj, newPrototype); + const out = Bun.inspect(obj); + expect(out).toContain("bar"); + expect(out).toContain("baz"); + }); + + it("does not crash when formatting an expect() object with a Proxy prototype", () => { + const received = expect({}); + const originalPrototype = Object.getPrototypeOf(received); + Object.setPrototypeOf(received, new Proxy(originalPrototype, {})); + try { + expect(typeof Bun.inspect(received)).toBe("string"); + } finally { + Object.setPrototypeOf(received, originalPrototype); + } + }); +});