diff --git a/src/jsc/bindings/BunObject.cpp b/src/jsc/bindings/BunObject.cpp index 8a022228eb4c..e4d4ab407ba5 100644 --- a/src/jsc/bindings/BunObject.cpp +++ b/src/jsc/bindings/BunObject.cpp @@ -319,9 +319,6 @@ static JSValue defaultBunSQLObject(VM& vm, JSObject* bunObject) auto scope = DECLARE_THROW_SCOPE(vm); auto* globalObject = defaultGlobalObject(bunObject->globalObject()); JSValue sqlValue = globalObject->internalModuleRegistry()->requireId(globalObject, vm, InternalModuleRegistry::BunSql); -#if BUN_DEBUG - if (scope.exception()) globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); -#endif RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, sqlValue.getObject()->get(globalObject, vm.propertyNames->defaultKeyword)); } @@ -331,9 +328,6 @@ static JSValue constructBunSQLObject(VM& vm, JSObject* bunObject) auto scope = DECLARE_THROW_SCOPE(vm); auto* globalObject = defaultGlobalObject(bunObject->globalObject()); JSValue sqlValue = globalObject->internalModuleRegistry()->requireId(globalObject, vm, InternalModuleRegistry::BunSql); -#if BUN_DEBUG - if (scope.exception()) globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); -#endif RETURN_IF_EXCEPTION(scope, {}); auto clientData = WebCore::clientData(vm); RELEASE_AND_RETURN(scope, sqlValue.getObject()->get(globalObject, clientData->builtinNames().SQLPublicName())); diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index c084a885ee86..39f92984dcee 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -5596,10 +5596,12 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: } JSC::PropertySlot slot(object, PropertySlot::InternalMethodType::Get); - if (!object->getPropertySlot(globalObject, property, slot)) - continue; - // Ignore exceptions from "Get" proxy traps. + bool found = object->getPropertySlot(globalObject, property, slot); + // Ignore exceptions from "Get" proxy traps and throwing lazy + // property initializers (which report the slot as not found). CLEAR_IF_EXCEPTION(scope); + if (!found) + continue; if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { if (property == propertyNames->underscoreProto diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js index 5c91f3dbe05f..b527a8dc0874 100644 --- a/test/js/bun/util/inspect.test.js +++ b/test/js/bun/util/inspect.test.js @@ -928,3 +928,54 @@ describe.skipIf(!isASAN)("object mutated while being formatted", () => { expect(exitCode).toBe(0); }); }); + +it("skips properties whose lazy initializer throws instead of leaving the exception pending", async () => { + // Overwriting the global Symbol breaks the lazily-initialized Bun.$ and + // Bun.sql properties (their module-scope code calls Symbol). Inspecting the + // Bun object reifies every property; a throwing initializer has to be + // skipped, not left as a pending exception, which aborts assert-enabled + // builds. + const code = ` + Symbol++; + const out = Bun.inspect(Bun); + if (typeof out !== "string" || out.length === 0) throw new Error("empty inspect output"); + console.log("ok"); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", code], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, , exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe("ok\n"); + expect(exitCode).toBe(0); +}); + +it("building an invalid-argument error message survives lazy initializers that throw", async () => { + // Same walk reached through ERR_INVALID_ARG_VALUE rendering the received + // value: new CompressionStream(globalThis) inspects globalThis for its + // error message while the global Symbol is broken. Getters that report + // their failure can set a nonzero exit code, so only assert that execution + // gets past the constructor instead of aborting. + const code = ` + Symbol++; + let caught; + try { + new CompressionStream(globalThis); + } catch (e) { + caught = e; + } + if (!caught) throw new Error("expected CompressionStream to throw"); + console.log("ok"); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", code], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe("ok\n"); + expect(proc.signalCode).toBeNull(); +});