Files
bun.sh/test/regression/issue/postgres-stringbuilder-assertion-aggressive.test.ts
robobun 09c56c8ba8 Fix PostgreSQL StringBuilder assertion failure with empty error messages (#22558)
## Summary
- Fixed a debug build assertion failure in PostgreSQL error handling
when all error message fields are empty
- Added safety check before calling `StringBuilder.allocatedSlice()` to
handle zero-length messages
- Added regression test to prevent future occurrences

## The Problem
When PostgreSQL sends an error response with completely empty message
fields, the `ErrorResponse.toJS` function would:
1. Calculate `b.cap` but end up with `b.len = 0` (no actual content)
2. Call `b.allocatedSlice()[0..b.len]` unconditionally
3. Trigger an assertion in `StringBuilder.allocatedSlice()` that
requires `cap > 0`

This only affected debug builds since the assertion is compiled out in
release builds.

## The Fix
Check if `b.len > 0` before calling `allocatedSlice()`. If there's no
content, use an empty string instead.

## Test Plan
- [x] Added regression test that triggers the exact crash scenario
- [x] Verified test crashes without the fix (debug build)
- [x] Verified test passes with the fix
- [x] Confirmed release builds were not affected

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-10 18:47:50 -07:00

47 lines
1.3 KiB
TypeScript

import { SQL } from "bun";
import { expect, test } from "bun:test";
import net from "net";
test("PostgreSQL StringBuilder assertion - aggressive empty error test", async () => {
const server = net.createServer(socket => {
// Immediately send an error response with completely empty fields
// This is more likely to trigger the assertion
socket.write(createPostgresPacket("E", Buffer.from("\0"))); // Terminator only
socket.destroy();
});
await new Promise<void>(r => server.listen(0, "127.0.0.1", () => r()));
const port = (server.address() as net.AddressInfo).port;
const sql = new SQL({
url: `postgres://test@127.0.0.1:${port}/test`,
max: 10,
connection_timeout: 0.1,
});
const promises = [];
for (let i = 0; i < 20; i++) {
promises.push(
sql`SELECT ${i}`.catch(err => {
// We expect errors, just checking for crashes
return null;
}),
);
}
await Promise.all(promises);
await sql.close();
server.close();
// If we get here without crashing, the test passes
expect(true).toBe(true);
});
function createPostgresPacket(type: string, data: Buffer): Buffer {
const len = data.length + 4;
const header = Buffer.alloc(5);
header.write(type, 0);
header.writeInt32BE(len, 1);
return Buffer.concat([header, data]);
}