78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
// When a TLS connection is wrapped around a user-supplied Duplex transport and
|
|
// that Duplex's `write()` throws synchronously, the thrown value must reach the
|
|
// TLSSocket's `'error'` listener as a plain Error, not the engine-internal
|
|
// JSC::Exception wrapper cell. The wrapper cell has no prototype:
|
|
// `Object.prototype.toString.call` on it aborts the process, property loads on
|
|
// it trip a debug assertion inside `synthesizePrototype`, and the user's own
|
|
// error message is lost.
|
|
|
|
import { expect, test } from "bun:test";
|
|
import { bunEnv, bunExe, tls as certs, tempDir } from "harness";
|
|
|
|
test("tls over Duplex: throwing transport write surfaces the thrown Error, not an engine-internal cell", async () => {
|
|
using dir = tempDir("tls-duplex-throw", {
|
|
"fixture.cjs": `
|
|
const tls = require("node:tls");
|
|
const { Duplex } = require("node:stream");
|
|
const key = ${JSON.stringify(certs.key)};
|
|
const cert = ${JSON.stringify(certs.cert)};
|
|
|
|
let armed = false;
|
|
let fired = 0;
|
|
class Wire extends Duplex {
|
|
_write(chunk, enc, cb) {
|
|
if (armed && this.hook && fired++ === 0) throw new Error("boom-in-transport-_write");
|
|
this.peer.push(Buffer.from(chunk));
|
|
cb();
|
|
}
|
|
_read() {}
|
|
}
|
|
const c = new Wire();
|
|
const s = new Wire();
|
|
c.peer = s;
|
|
s.peer = c;
|
|
c.hook = true;
|
|
|
|
process.on("uncaughtException", e => console.log("uncaught:" + (e && e.message)));
|
|
|
|
const srv = new tls.TLSSocket(s, { isServer: true, key, cert });
|
|
srv.on("error", () => {});
|
|
srv.resume();
|
|
|
|
const cli = tls.connect({ socket: c, rejectUnauthorized: false });
|
|
cli.on("error", v => {
|
|
// The crash under test fires on the first property load / toString of
|
|
// the value: in debug builds even node:events' own \`err.code\` read
|
|
// trips a JSC assertion before this listener runs at all.
|
|
const tag = Object.prototype.toString.call(v);
|
|
console.log("error:" + (v instanceof Error) + ":" + tag + ":" + (v && v.message));
|
|
});
|
|
cli.on("secureConnect", () => {
|
|
armed = true;
|
|
cli.write(Buffer.alloc(64 * 1024, 0x41));
|
|
setImmediate(() => setImmediate(() => process.exit(0)));
|
|
});
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "fixture.cjs"],
|
|
env: { ...bunEnv, ASAN_OPTIONS: "symbolize=0:abort_on_error=1:allow_user_segv_handler=1" },
|
|
cwd: String(dir),
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
// The server side sees the truncated handshake bytes and may report its own
|
|
// protocol error as an uncaughtException; the assertion below only cares
|
|
// that the client's `'error'` listener received the user's thrown Error.
|
|
const lines = stdout.trim().split(/\r?\n/);
|
|
expect({ lines, stderr, exitCode }).toEqual({
|
|
lines: expect.arrayContaining(["error:true:[object Error]:boom-in-transport-_write"]),
|
|
stderr: "",
|
|
exitCode: 0,
|
|
});
|
|
});
|