From 3c7be5790630eda7e52cbc78ee8f9ec194d3d14d Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:30:29 +0000 Subject: [PATCH 1/2] fix(sql): don't crash in debug builds when bun:sql module fails to load The Bun.sql / Bun.SQL lazy property callbacks call requireId() to load the bun:sql internal module. If that evaluation throws (for example because user code clobbered a global the module reads at top level), the debug-only reportUncaughtExceptionAtEventLoop() call would run the full uncaught-exception machinery. That path can clear the pending exception (via Bun__handleUncaughtException -> tryClearException), so the following RETURN_IF_EXCEPTION doesn't fire and we dereference a null getObject() result. Drop the debug block so the exception propagates to the caller the same way it does in release builds. --- src/jsc/bindings/BunObject.cpp | 6 ----- test/js/sql/sql-lazy-load-throw.test.ts | 31 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 test/js/sql/sql-lazy-load-throw.test.ts diff --git a/src/jsc/bindings/BunObject.cpp b/src/jsc/bindings/BunObject.cpp index 02a1127e00df..d3d19a4399f4 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/sql/sql-lazy-load-throw.test.ts b/test/js/sql/sql-lazy-load-throw.test.ts new file mode 100644 index 000000000000..94af9ed307e3 --- /dev/null +++ b/test/js/sql/sql-lazy-load-throw.test.ts @@ -0,0 +1,31 @@ +import { test, expect } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("Bun.SQL lazy getter propagates module load errors without crashing", async () => { + // The bun:sql internal module reads global `Symbol` at top level. If user + // code has clobbered the global before the first access, evaluating the + // module throws. That exception must propagate back to the caller as a + // normal JS error rather than crashing the process. + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + globalThis.Symbol = "i"; + let caught = 0; + try { Bun.sql; } catch { caught++; } + try { Bun.SQL; } catch { caught++; } + console.log("caught=" + caught); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout.trim()).toBe("caught=2"); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +}); From b9f302cdd100b56704f5be5c383c2f7bdb4e35db Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:32:52 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- test/js/sql/sql-lazy-load-throw.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/sql/sql-lazy-load-throw.test.ts b/test/js/sql/sql-lazy-load-throw.test.ts index 94af9ed307e3..96aebf65e9f9 100644 --- a/test/js/sql/sql-lazy-load-throw.test.ts +++ b/test/js/sql/sql-lazy-load-throw.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test"; +import { expect, test } from "bun:test"; import { bunEnv, bunExe } from "harness"; test("Bun.SQL lazy getter propagates module load errors without crashing", async () => {