Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/jsc/bindings/BunObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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()));
Expand Down
42 changes: 42 additions & 0 deletions test/js/sql/sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});