diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index ee42eaa398e0..3374583cc243 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -5285,10 +5285,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 @@ -5360,7 +5361,13 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: break; if (iterating == globalObject) break; - iterating = iterating->getPrototype(globalObject).getObject(); + JSValue proto = iterating->getPrototype(globalObject); + // Ignore exceptions from Proxy "getPrototypeOf" trap. + if (scope.exception()) [[unlikely]] { + (void)scope.tryClearException(); + break; + } + iterating = proto.getObject(); } } diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..b42834b1a6f0 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -453,6 +453,44 @@ const fixture = [ ), ]; +describe("Proxy prototype with throwing traps", () => { + it("getPrototypeOf trap that throws", () => { + const obj = {}; + Object.setPrototypeOf( + obj, + new Proxy( + {}, + { + getPrototypeOf() { + throw new Error("nope"); + }, + }, + ), + ); + expect(Bun.inspect(obj)).toBe("{}"); + }); + + it("get trap that throws", () => { + const obj = {}; + const proto = {}; + Object.defineProperty(proto, "x", { + get() { + throw new Error("getter throw"); + }, + enumerable: true, + }); + Object.setPrototypeOf( + obj, + new Proxy(proto, { + get(t, k, r) { + return Reflect.get(t, k, r); + }, + }), + ); + expect(Bun.inspect(obj)).toBe("{}"); + }); +}); + describe("crash testing", () => { for (let input of fixture) { it(`inspecting "${input.toString().slice(0, 20).replaceAll("\n", "\\n")}" doesn't crash`, async () => {