Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 0 additions & 3 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,19 +418,16 @@ struct TemplatedApp {

/* 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;
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;
std::terminate();
}

/* Maximum maxLifetime is 4 hours */
if (behavior.maxLifetime > 240) {
std::cerr << "Error: maxLifetime must not be greater than 240 minutes!" << std::endl;
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
3 changes: 0 additions & 3 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include <string>
#include <map>
#include <string_view>
#include <iostream>
#include "MoveOnlyFunction.h"
#include "HttpParser.h"
#include <span>
Expand Down Expand Up @@ -465,8 +464,6 @@ 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;
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
5 changes: 0 additions & 5 deletions packages/bun-uws/src/TopicTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#pragma once
#include <map>
#include <list>
#include <iostream>
#include <unordered_set>
#include <utility>
#include <memory>
Expand Down Expand Up @@ -106,9 +105,7 @@ struct TopicTree {
std::vector<T> outgoingMessages;

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;
std::terminate();
}
}
Comment thread
robobun marked this conversation as resolved.
Expand Down Expand Up @@ -163,7 +160,6 @@ struct TopicTree {

/* Subscribe fails if we already are subscribed */
Topic *subscribe(Subscriber *s, std::string_view topic) {
/* Notify user that they are doing something wrong here */
checkIteratingSubscriber(s);

/* Lookup or create new topic */
Expand All @@ -187,7 +183,6 @@ struct TopicTree {

/* Returns ok, last, newCount */
std::tuple<bool, bool, int> unsubscribe(Subscriber *s, std::string_view topic) {
/* Notify user that they are doing something wrong here */
checkIteratingSubscriber(s);

/* Lookup topic */
Expand Down
10 changes: 7 additions & 3 deletions scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
// 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, <iostream> banned at compile time for non-Debug
// USE_BUN_JSC_ADDITIONS builds (see src/banned-includes/iostream), and the
// WebAssembly namespace object created lazily.
export const WEBKIT_VERSION = "af2e8dc6393ce244eb1865e34e7636225744cfba";

/**
* 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([]);

Check warning on line 45 in test/internal/source-lints/no-iostream-include.test.ts

View check run for this annotation

Claude / Claude Code Review

no-iostream lint test can pass vacuously

The test's only assertion is `expect(violations).toEqual([])`, which passes vacuously if `glob.scan()` yields nothing (e.g. `repoRoot` resolves wrong after a file move, or a scanned root goes away — Bun's `Glob.scan` on a non-existent `cwd` yields nothing rather than throwing). The four sibling tests in this directory (`shim-stdint-includes.test.ts`, `frozen-nonnull-reborrow.test.ts`, `unsound-erased-box.test.ts`, `expect-call-counter.test.ts`) each guard this with `expect(scanned).toBeGreaterTh
Comment thread
robobun marked this conversation as resolved.
});
Loading