From 1d048ad459077f2f04e6e249f5918ca9cb6aaa3c Mon Sep 17 00:00:00 2001 From: robobun Date: Sat, 2 May 2026 07:31:46 +0000 Subject: [PATCH 1/4] fix(inspect): handle exceptions from Proxy traps in forEachProperty When walking the prototype chain for property enumeration, exceptions thrown by Proxy traps could leak past the continue on a false getPropertySlot result, and getPrototype could return an empty JSValue which was then passed to getObject() causing a null dereference. --- src/jsc/bindings/bindings.cpp | 12 +++++++++--- test/js/bun/util/inspect.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) 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", () => { From fb85f699a4620e19d4c71e02a190ce551af3916c Mon Sep 17 00:00:00 2001 From: robobun Date: Sat, 2 May 2026 07:41:41 +0000 Subject: [PATCH 2/4] ci: retrigger From cc70adf28fb4bfecb9c04fc86b906fff25d24b53 Mon Sep 17 00:00:00 2001 From: robobun Date: Mon, 4 May 2026 12:25:22 +0000 Subject: [PATCH 3/4] ci: retrigger after expired jobs From c4eb9e3ff3d6ebd71b87e574a9fc6d70e91fcd1a Mon Sep 17 00:00:00 2001 From: robobun Date: Mon, 4 May 2026 12:55:54 +0000 Subject: [PATCH 4/4] ci: retrigger