Skip to content

Commit 08819f0

Browse files
proggeramlugRalph Küpper
andauthored
test(http): guard against POST-body-consume segfault (#5131) (#5168)
The repro from #5131 — a node:http server consuming the request body via req.on('data')/'end' on a POST — no longer segfaults on main (the crash was fixed by the http/stream work that landed after the issue was filed against v0.5.1167). This adds a regression test that locks in the fixed behavior, covering both the small-body repro and a larger multi-chunk Buffer.concat body. Uses the default auto-optimize build (node:http is a no-op stub under PERRY_NO_AUTO_OPTIMIZE=1). Co-authored-by: Ralph Küpper <ralph2@skelpo.com>
1 parent 28159ae commit 08819f0

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! Regression test for #5131: a `node:http` server that consumes the request
2+
//! body (`req.on('data')` + `req.on('end')`) on a request that actually carries
3+
//! a body segfaulted (SIGSEGV / exit 139).
4+
//!
5+
//! The crash was fixed on `main` by the http/stream work that landed after the
6+
//! issue was filed (v0.5.1167); this test guards against a regression. It needs
7+
//! the default (auto-optimize) build — `PERRY_NO_AUTO_OPTIMIZE=1` produces a
8+
//! runtime-only binary where `node:http` dispatch is a no-op stub.
9+
10+
use std::path::PathBuf;
11+
use std::process::Command;
12+
13+
fn perry_bin() -> PathBuf {
14+
PathBuf::from(env!("CARGO_BIN_EXE_perry"))
15+
}
16+
17+
fn compile_and_run(dir: &std::path::Path, source: &str) -> String {
18+
let entry = dir.join("main.ts");
19+
let output = dir.join("main_bin");
20+
std::fs::write(&entry, source).expect("write entry");
21+
22+
let compile = Command::new(perry_bin())
23+
.current_dir(dir)
24+
.arg("compile")
25+
.arg(&entry)
26+
.arg("-o")
27+
.arg(&output)
28+
.output()
29+
.expect("run perry compile");
30+
assert!(
31+
compile.status.success(),
32+
"perry compile failed\nstdout:\n{}\nstderr:\n{}",
33+
String::from_utf8_lossy(&compile.stdout),
34+
String::from_utf8_lossy(&compile.stderr)
35+
);
36+
37+
let run = Command::new(&output)
38+
.current_dir(dir)
39+
.output()
40+
.expect("run compiled binary");
41+
assert!(
42+
run.status.success(),
43+
"compiled binary failed (pre-fix: SIGSEGV / exit 139 on body consume)\n\
44+
status: {:?}\nstdout:\n{}\nstderr:\n{}",
45+
run.status,
46+
String::from_utf8_lossy(&run.stdout),
47+
String::from_utf8_lossy(&run.stderr)
48+
);
49+
String::from_utf8_lossy(&run.stdout).into_owned()
50+
}
51+
52+
/// The issue's repro: a POST whose body is consumed via `req.on('data')` /
53+
/// `req.on('end')` must round-trip without crashing.
54+
#[test]
55+
fn http_server_consuming_post_body_does_not_segfault() {
56+
let dir = tempfile::tempdir().expect("tempdir");
57+
let stdout = compile_and_run(
58+
dir.path(),
59+
r#"
60+
import http from "node:http";
61+
const server = http.createServer((req, res) => {
62+
let body = "";
63+
req.on("data", (c) => (body += c));
64+
req.on("end", () => { res.writeHead(200); res.end("len:" + body.length); });
65+
});
66+
server.listen(0, () => {
67+
const port = (server.address() as { port: number }).port;
68+
const r = http.request({ port, method: "POST" }, (res) => {
69+
let d = ""; res.on("data", (c) => (d += c));
70+
res.on("end", () => { console.log(d); server.close(); });
71+
});
72+
r.write("payload-bytes");
73+
r.end();
74+
});
75+
"#,
76+
);
77+
assert_eq!(stdout, "len:13\n");
78+
}
79+
80+
/// A larger, chunk-collected body (`Buffer.concat`) must also round-trip — this
81+
/// exercises the multi-chunk data path and more allocation pressure.
82+
#[test]
83+
fn http_server_consuming_large_post_body() {
84+
let dir = tempfile::tempdir().expect("tempdir");
85+
let stdout = compile_and_run(
86+
dir.path(),
87+
r#"
88+
import http from "node:http";
89+
const big = "x".repeat(100000);
90+
const server = http.createServer((req, res) => {
91+
const chunks: Buffer[] = [];
92+
req.on("data", (c) => chunks.push(c as Buffer));
93+
req.on("end", () => {
94+
const body = Buffer.concat(chunks).toString();
95+
res.writeHead(200);
96+
res.end("len:" + body.length);
97+
});
98+
});
99+
server.listen(0, () => {
100+
const port = (server.address() as { port: number }).port;
101+
const r = http.request({ port, method: "POST" }, (res) => {
102+
let d = ""; res.on("data", (c) => (d += c));
103+
res.on("end", () => { console.log(d); server.close(); });
104+
});
105+
r.write(big);
106+
r.end();
107+
});
108+
"#,
109+
);
110+
assert_eq!(stdout, "len:100000\n");
111+
}

0 commit comments

Comments
 (0)