Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 3 additions & 3 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -415,22 +415,22 @@
if (!httpContext) {
return std::move(*this);
}

/* Terminate on misleading idleTimeout values */
if (behavior.idleTimeout && behavior.idleTimeout < 8) {
std::cerr << "Error: idleTimeout must be either 0 or greater than 8!" << std::endl;
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) {
std::cerr << "Error: idleTimeout must not be greater than 960 seconds!" << std::endl;
fputs("Error: idleTimeout must not be greater than 960 seconds!\n", stderr);
std::terminate();
}

/* Maximum maxLifetime is 4 hours */
if (behavior.maxLifetime > 240) {
std::cerr << "Error: maxLifetime must not be greater than 240 minutes!" << std::endl;
fputs("Error: maxLifetime must not be greater than 240 minutes!\n", stderr);

Check warning on line 433 in packages/bun-uws/src/App.h

View check run for this annotation

Claude / Claude Code Review

App.h uses fputs/stderr without including <cstdio>

App.h now calls `fputs(..., stderr)` at three sites but doesn't get its own `#include <cstdio>` — it compiles only via the transitive include from `HttpContext.h`. The other two files that gained `fputs` calls in this PR (`HttpContext.h`, `TopicTree.h`) each got an explicit `<cstdio>`; adding one here too would keep the three edits consistent and follow include-what-you-use.
Comment thread
robobun marked this conversation as resolved.
Outdated
std::terminate();
}

Expand Down
1 change: 0 additions & 1 deletion packages/bun-uws/src/AsyncSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
* to signal error with -1 (which is how the entire UNIX syscalling is built). */

#include <cstring>
#include <iostream>

#include "libusockets.h"
#include "bun-usockets/src/internal/internal.h"
Expand Down
4 changes: 2 additions & 2 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include <string>
#include <map>
#include <string_view>
#include <iostream>
#include <cstdio>
#include "MoveOnlyFunction.h"
#include "HttpParser.h"
#include <span>
Expand Down Expand Up @@ -466,7 +466,7 @@ struct HttpContext {
/* Returning from a request handler without responding or attaching an onAborted handler is ill-use */
if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted && !httpResponseData->socketData) {
/* Throw exception here? */
std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl;
fputs("Error: Returning from a request handler without responding or attaching an abort handler is forbidden!\n", stderr);
std::terminate();
}

Expand Down
1 change: 0 additions & 1 deletion packages/bun-uws/src/HttpRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#include <memory>
#include <utility>
#include <span>
#include <iostream>

#include "MoveOnlyFunction.h"

Expand Down
1 change: 0 additions & 1 deletion packages/bun-uws/src/Loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

#include "LoopData.h"
#include <libusockets.h>
#include <iostream>
#include "AsyncSocket.h"

extern "C" int bun_is_exiting();
Expand Down
4 changes: 2 additions & 2 deletions packages/bun-uws/src/TopicTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#pragma once
#include <map>
#include <list>
#include <iostream>
#include <cstdio>
#include <unordered_set>
#include <utility>
#include <memory>
Expand Down Expand Up @@ -108,7 +108,7 @@ struct TopicTree {
void checkIteratingSubscriber(Subscriber *s) {
/* Notify user that they are doing something wrong here */
if (iteratingSubscriber == s) {
std::cerr << "Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!" << std::endl;
fputs("Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!\n", stderr);
std::terminate();
}
}
Comment thread
robobun marked this conversation as resolved.
Expand Down
9 changes: 6 additions & 3 deletions scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
// oven-sh/WebKit main: macOS + Windows artifacts cross-compiled on Linux,
// -lto variants built with ThinLTO (per-module summaries for cross-language
// importing), every x64 at the nehalem floor (no separate -baseline variant),
// typed-array constructor ClassInfo kept address-unique under LTO, and the
// Windows ICU data table filtered + per-item zstd compressed.
export const WEBKIT_VERSION = "c9296e353e365ecf0de82f273bb0a88a3df465be";
// typed-array constructor ClassInfo kept address-unique under LTO, the
// Windows ICU data table filtered + per-item zstd compressed, eager timezone
// prewarm skipped under USE_BUN_JSC_ADDITIONS, the stray simdutf scalar/base64
// <iostream> include dropped, and <iostream> banned at compile time for
// non-Debug USE_BUN_JSC_ADDITIONS builds (see src/banned-includes/iostream).
export const WEBKIT_VERSION = "2f7e89c84f818b07b27d4702813a37db543c298a";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
6 changes: 6 additions & 0 deletions scripts/build/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,12 @@ export const stripFlags: Flag[] = [
export function bunIncludes(cfg: Config): string[] {
const { cwd, codegenDir, vendorDir } = cfg;
const includes: string[] = [
// Release builds shadow <iostream> with a #error shim. A single <iostream>
// include anywhere (our headers, bun-uws, or a WebKit header pulled into a
// Bun TU) drags libstdc++'s globals_io.o into the link and runs
// std::ios_base::Init + the full locale facet set before main. Debug builds
// keep the real header available for ad-hoc printf-debugging.
...(cfg.release ? [join(cwd, "src/banned-includes")] : []),
join(cwd, "packages"),
join(cwd, "packages/bun-usockets"),
join(cwd, "packages/bun-usockets/src"),
Expand Down
27 changes: 27 additions & 0 deletions src/banned-includes/iostream
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// This directory is placed first on the -I search path for Bun's C++ compile
// (release profile), so `#include <iostream>` resolves here instead of the
// toolchain header.
//
// <iostream> is unlike <ostream>/<istream>/<sstream>/<fstream>: on libstdc++
// it emits a reference to std::ios_base_library_init (or, on configurations
// without the init-priority attribute, a static `std::ios_base::Init __ioinit`
// object) in every translation unit that includes it. One such reference
// anywhere in the link pulls libstdc++'s globals_io.o in, whose
// _GLOBAL__sub_I.00090_globals_io.cc static initializer constructs
// cin/cout/cerr/clog and their wchar_t siblings before main. That in turn
// references the full std::locale facet set (ctype / numpunct / moneypunct /
// timepunct / messages, for both char and wchar_t), so roughly fifty libstdc++
// functions run on every Bun process start.
//
// Bun never touches C++ iostreams at runtime. Use fputs/fprintf for error
// output, or WTF's dataLog()/PrintStream in JSC-adjacent code.
//
// Because Bun's own headers include wtf/SIMDUTF.h (and others) from the
// WebKit prebuilt, this shim also catches a WebKit header that starts
// including <iostream>: it will fail Bun's release build rather than silently
// regressing startup.
#ifndef BUN_ALLOW_IOSTREAM
#error "<iostream> is banned in Bun release builds: it drags std::ios_base::Init and the full std::locale facet set into pre-main startup. Use <cstdio> fputs/fprintf for stderr output. See src/banned-includes/iostream."
#else
#include_next <iostream>
#endif
46 changes: 46 additions & 0 deletions test/internal/source-lints/no-iostream-include.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Glob } from "bun";
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import path from "node:path";

// <iostream> is unique among the C++ stream headers: on libstdc++ it emits a
// reference to std::ios_base_library_init in every TU that includes it, which
// forces libstdc++'s globals_io.o into the link. That object's
// _GLOBAL__sub_I.00090_globals_io.cc initializer constructs cin/cout/cerr/clog
// (and the wchar_t variants) before main, dragging the full std::locale facet
// set (ctype/numpunct/moneypunct/timepunct/messages for char and wchar_t) into
// every Bun process startup. Bun never touches C++ iostreams at runtime.
//
// <ostream>, <istream>, <sstream> and <fstream> are fine: they declare the
// stream types but do not emit the static Init object. If you need to print to
// stderr from C++, use fputs/fprintf.
//
// The upstream source of the original leak was the vendored simdutf header
// inside WebKit (Source/WTF/wtf/simdutf/simdutf_impl.h); that is handled by
// the WebKit pin. This test guards Bun's own compiled C++ so the initializer
// cannot creep back in through packages/ or src/.
test("C++ sources compiled into Bun do not include <iostream>", async () => {
const repoRoot = path.resolve(import.meta.dir, "..", "..", "..");

const roots = ["src", "packages/bun-uws", "packages/bun-usockets"];
// sizegen.cpp is a build-time code generator, not linked into the bun binary.
const allowlist = new Set(["src/jsc/headergen/sizegen.cpp"]);

const iostreamInclude = /^\s*#\s*include\s*<iostream>/m;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const violations: string[] = [];

for (const root of roots) {
const glob = new Glob("**/*.{h,hpp,hxx,cpp,cc,cxx}");
for await (const rel of glob.scan({ cwd: path.join(repoRoot, root) })) {
const relFromRepo = path.join(root, rel).replaceAll("\\", "/");
if (allowlist.has(relFromRepo)) continue;
const source = readFileSync(path.join(repoRoot, root, rel), "utf8");
if (iostreamInclude.test(source)) {
violations.push(relFromRepo);
}
}
}

violations.sort();
expect(violations).toEqual([]);
Comment thread
robobun marked this conversation as resolved.
});
Loading