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/test/js/bun/bun-object/lazy-getter-module-failure.test.ts b/test/js/bun/bun-object/lazy-getter-module-failure.test.ts new file mode 100644 index 000000000000..a166394d2483 --- /dev/null +++ b/test/js/bun/bun-object/lazy-getter-module-failure.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +// Clobbering a global that an internal module needs makes that module fail to +// evaluate. The Bun.sql and Bun.SQL lazy getters used to report the failure +// while the exception was still pending on the VM, which aborted debug builds +// inside the uncaught exception handler (and in the process.emit path when +// process._fatalException had not been reified yet). The access must instead +// throw the evaluation error to the caller and leave the process healthy. +describe.concurrent("Bun object lazy getters", () => { + for (const reifyFatalException of [false, true]) { + test(`sql getter module failure propagates cleanly (fatalException reified: ${reifyFatalException})`, async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + ${reifyFatalException ? "process._fatalException;" : ""} + globalThis.Object = undefined; + let caught = ""; + try { + Bun.sql; + } catch (e) { + caught = e.constructor.name; + } + try { + Bun.SQL; + } catch (e) {} + console.log("caught " + caught); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]); + expect(stdout.trim()).toBe("caught TypeError"); + expect(exitCode).toBe(0); + }); + } +});