diff --git a/.github/workflows/source-lints.yml b/.github/workflows/source-lints.yml index dffeac1724ec..c05563442f2a 100644 --- a/.github/workflows/source-lints.yml +++ b/.github/workflows/source-lints.yml @@ -14,6 +14,8 @@ on: paths: - "src/**/*.rs" - "src/jsc/bindings/**" + - "packages/bun-uws/**" + - "packages/bun-usockets/**" - "scripts/build/**" - "scripts/glob-sources.ts" - "test/harness.ts" @@ -26,6 +28,8 @@ on: paths: - "src/**/*.rs" - "src/jsc/bindings/**" + - "packages/bun-uws/**" + - "packages/bun-usockets/**" - "scripts/build/**" - "scripts/glob-sources.ts" - "test/harness.ts" diff --git a/packages/bun-uws/src/App.h b/packages/bun-uws/src/App.h index 8b3181eb1d99..6bafc3263804 100644 --- a/packages/bun-uws/src/App.h +++ b/packages/bun-uws/src/App.h @@ -18,6 +18,7 @@ // clang-format off +#include #include #include #include @@ -418,16 +419,19 @@ struct TemplatedApp { /* Terminate on misleading idleTimeout values */ if (behavior.idleTimeout && behavior.idleTimeout < 8) { + fputs("Error: idleTimeout must be either 0 or greater than 8!\n", stderr); std::terminate(); } /* Maximum idleTimeout is 16 minutes */ if (behavior.idleTimeout > 240 * 4) { + fputs("Error: idleTimeout must not be greater than 960 seconds!\n", stderr); std::terminate(); } /* Maximum maxLifetime is 4 hours */ if (behavior.maxLifetime > 240) { + fputs("Error: maxLifetime must not be greater than 240 minutes!\n", stderr); std::terminate(); } diff --git a/packages/bun-uws/src/HttpContext.h b/packages/bun-uws/src/HttpContext.h index 6e3bd96fe15d..c6b5b303a29e 100644 --- a/packages/bun-uws/src/HttpContext.h +++ b/packages/bun-uws/src/HttpContext.h @@ -27,6 +27,7 @@ #include "WebSocketData.h" #include "SocketKinds.h" +#include #include #include #include @@ -464,6 +465,7 @@ struct HttpContext { /* Returning from a request handler without responding or attaching an onAborted handler is ill-use */ if (!((HttpResponse *) s)->hasResponded() && !httpResponseData->onAborted && !httpResponseData->socketData) { + fputs("Error: Returning from a request handler without responding or attaching an abort handler is forbidden!\n", stderr); std::terminate(); } diff --git a/packages/bun-uws/src/TopicTree.h b/packages/bun-uws/src/TopicTree.h index ec3a61d1671a..e4a7e888ade7 100644 --- a/packages/bun-uws/src/TopicTree.h +++ b/packages/bun-uws/src/TopicTree.h @@ -16,6 +16,7 @@ */ #pragma once +#include #include #include #include @@ -106,6 +107,7 @@ struct TopicTree { void checkIteratingSubscriber(Subscriber *s) { if (iteratingSubscriber == s) { + fputs("Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!\n", stderr); std::terminate(); } } diff --git a/test/internal/source-lints/uws-terminate-diagnostic.test.ts b/test/internal/source-lints/uws-terminate-diagnostic.test.ts new file mode 100644 index 000000000000..3d860fe4a421 --- /dev/null +++ b/test/internal/source-lints/uws-terminate-diagnostic.test.ts @@ -0,0 +1,48 @@ +import { Glob } from "bun"; +import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import path from "node:path"; + +// #35256 banned from Bun's release build and stripped the +// `std::cerr << "Error: ..."` lines ahead of each std::terminate() in bun-uws. +// Bun routes std::terminate through its own crash handler (JSCInitialize calls +// std::set_terminate), so without these writes every guard dies with the same +// generic "panic: A C++ exception occurred" and nothing names which uWS +// invariant fired. Keep every std::terminate() in bun-uws paired with a +// preceding fputs/fprintf to stderr, the replacement the shim prescribes. +test("bun-uws std::terminate() sites write a diagnostic to stderr first", async () => { + const uwsSrc = path.resolve(import.meta.dir, "..", "..", "..", "packages", "bun-uws", "src"); + + let terminates = 0; + const violations: string[] = []; + const glob = new Glob("**/*.{h,hpp,cpp}"); + for await (const rel of glob.scan({ cwd: uwsSrc })) { + const source = readFileSync(path.join(uwsSrc, rel), "utf8"); + const lines = source.split("\n"); + for (let i = 0; i < lines.length; i++) { + if (!/\bstd::terminate\s*\(\s*\)/.test(lines[i])) continue; + terminates++; + // Look back over the immediately preceding non-empty lines inside the + // same block for a stderr write. + let j = i - 1; + let ok = false; + while (j >= 0) { + const prev = lines[j].trim(); + if (prev === "" || prev.startsWith("//") || prev.startsWith("/*") || prev.startsWith("*")) { + j--; + continue; + } + if (prev.endsWith("{")) break; + if (/\b(fputs|fprintf)\b.*\bstderr\b/.test(prev)) ok = true; + break; + } + if (!ok) { + violations.push(`packages/bun-uws/src/${rel}:${i + 1}`); + } + } + } + + expect(terminates).toBeGreaterThan(0); + violations.sort(); + expect(violations).toEqual([]); +});