diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index 22ac38063376..69904d3fa51f 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -5254,6 +5254,8 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: prototypeCount = 1; } } + // Ignore exceptions from Proxy "getPrototypeOf" trap. + CLEAR_IF_EXCEPTION(scope); } } auto* propertyNames = vm.propertyNames; @@ -5369,10 +5371,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 +5447,10 @@ 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. + CLEAR_IF_EXCEPTION(scope); + iterating = proto ? proto.getObject() : nullptr; } } diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 32a70af30183..acdbdaf2f1a1 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -451,6 +451,36 @@ const fixture = [ }, }, ), + () => { + // Proxy in the prototype chain where a getter on the underlying + // prototype throws. This used to leave a pending exception that + // caused a null dereference when walking to the next prototype. + const proto = Object.create(Object.prototype, { + foo: { get: () => 1, enumerable: true, configurable: true }, + bar: { + get() { + throw new Error("boom"); + }, + enumerable: true, + configurable: true, + }, + baz: { get: () => 2, enumerable: true, configurable: true }, + }); + return Object.create(new Proxy(proto, {})); + }, + () => { + // Proxy in the prototype chain whose getPrototypeOf trap throws. + const proto = Object.create(Object.prototype, { + foo: { get: () => 1, enumerable: true, configurable: true }, + }); + return Object.create( + new Proxy(proto, { + getPrototypeOf() { + throw new Error("getPrototypeOf trap"); + }, + }), + ); + }, ]; describe("crash testing", () => {