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/sql/sql.test.ts b/test/js/sql/sql.test.ts index 75ce11d3b130..6e435ac4ed81 100644 --- a/test/js/sql/sql.test.ts +++ b/test/js/sql/sql.test.ts @@ -12781,3 +12781,45 @@ test("data row that omits columns declared in the row description yields nulls f expect(filteredStderr).toBe(""); expect(exitCode).toBe(0); }, 30_000); + +// Bun.sql / Bun.postgres / Bun.SQL are lazy properties whose getter loads the +// bun:sql module on first access. If that load throws (here the stack is +// already exhausted, so evaluating the module overflows it), the throw has to +// reach the caller like any other exception and the property must stay lazy so +// a later access works. Debug builds used to additionally report the pending +// exception as uncaught from inside the getter, which aborted the process. +// Runs in a subprocess because the property must not have been reified yet. +test.concurrent.each(["sql", "postgres", "SQL"])("Bun.%s throwing under stack exhaustion is catchable", async name => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` + let first; + function recurse() { + try { + recurse(); + } catch {} + if (first === undefined) { + try { + Bun.${name}; + } catch (e) { + first = e; + } + } + } + recurse(); + console.log(String(first)); + console.log(typeof Bun.${name}); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe("RangeError: Maximum call stack size exceeded.\nfunction\n"); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +});