Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
db87af9
Support JSC's immutableGlobals option (BUN_JSC_immutableGlobals)
dylan-conway Oct 13, 2026
833ee8f
isInsideNodeModules() looks at 10 frames when it is given no limit
dylan-conway Oct 13, 2026
ce7a4d8
The dropped FinalizationRegistry job test counts only the contexts th…
dylan-conway Oct 13, 2026
b48857f
immutableGlobals covers what Bun creates on first use, and BUN_JSC_lo…
dylan-conway Oct 13, 2026
5fd9a94
Merge remote-tracking branch 'origin/main' into claude/jsc-immutable-…
dylan-conway Oct 16, 2026
5ed3c9b
immutableGlobals covers a class whose prototype is a plain object, wh…
dylan-conway Oct 16, 2026
8157484
Merge remote-tracking branch 'origin/main' into claude/jsc-immutable-…
dylan-conway Oct 16, 2026
ddbd5df
Merge remote-tracking branch 'origin/main' into claude/jsc-immutable-…
dylan-conway Oct 19, 2026
21f3ae0
BUN_JSC_immutableGlobals is one switch that covers every built-in, wi…
dylan-conway Oct 19, 2026
bd334e8
Built-in modules finish native classes through native define, delete …
dylan-conway Oct 19, 2026
4aca684
A write to an immutable built-in goes only to a target obtained nativ…
dylan-conway Oct 19, 2026
90798ca
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 19, 2026
c50afa4
The built-in module helpers are defined once in internal/shared, --tr…
dylan-conway Oct 19, 2026
245e18c
Merge remote-tracking branch 'origin/claude/jsc-immutable-globals-050…
dylan-conway Oct 19, 2026
28ad297
isInsideNodeModules() defaults to 10 frames again, Module.prototype.r…
dylan-conway Oct 19, 2026
8a00a7a
The test of invalid BUN_JSC_immutableGlobals values leaves no core files
dylan-conway Oct 19, 2026
7cd686b
inspector.open() reports its port in process.debugPort when the proce…
dylan-conway Oct 19, 2026
4b5b5e2
WEBKIT_VERSION points at the preview build of 7d394492
dylan-conway Oct 19, 2026
fc866dc
jsFunctionSetDebugPort checks for an exception before it returns
dylan-conway Oct 19, 2026
45efa45
The exports of abort-controller, process._exiting, a shared process.e…
dylan-conway Oct 19, 2026
6511389
The descriptor for a cluster worker's process.channel inherits nothin…
dylan-conway Oct 20, 2026
8d04d27
BUN_JSC_immutableGlobals takes only *
dylan-conway Oct 20, 2026
b14a05d
WEBKIT_VERSION points at the preview build of 9bfc8055
dylan-conway Oct 20, 2026
6144443
Error's default prepareStackTrace and console.Console are covered by …
dylan-conway Oct 20, 2026
b56e5d4
Merge branch 'main' of github.com:oven-sh/bun into claude/jsc-immutab…
dylan-conway Oct 20, 2026
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
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "274ebb3cf6008f627197d818e3939d1e2bfc514d";
export const WEBKIT_VERSION = "autobuild-preview-pr-797-8b82f3fc";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
3 changes: 2 additions & 1 deletion src/codegen/bindgen-emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ ${namespace.functions
` object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, ${asciiLiteral(fn.name)}), ${fn.length}, ${hostFunctionName(fn)}, JSC::ImplementationVisibility::Public, JSC::NoIntrinsic, 0);`,
)
.join("\n")}
return object;
return Bun::namespaceObjectOf(vm, owner, object);
}
}
`;
Expand Down Expand Up @@ -951,6 +951,7 @@ export function cppSource(file: SchemaFile): string {
headers.delete(`Generated${file.origin.namespace}.h`);
const accepted = file.types.filter(type => type instanceof DictionaryType && type.accepted) as DictionaryType[];
if (accepted.length > 0) headers.add("Bindgen/DictionaryMemberReader.h");
if (file.namespaces.length > 0) headers.add("BunClientData.h");
const systemHeaders = ["JavaScriptCore/JSFunction.h"];
if (accepted.some(type => !type.allowArray)) systemHeaders.unshift("JavaScriptCore/ArrayConstructor.h");
if (file.namespaces.length > 0) systemHeaders.push("JavaScriptCore/ObjectConstructor.h");
Expand Down
1 change: 1 addition & 0 deletions src/js/builtins/BunBuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ using namespace JSC;
macro(close) \
macro(cmd) \
macro(code) \
macro(consoleAsyncIterator) \
macro(createCommonJSModule) \
macro(createFIFO) \
macro(createInternalModuleById) \
Expand Down
18 changes: 11 additions & 7 deletions src/js/builtins/ConsoleObject.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
$overriddenName = "[Symbol.asyncIterator]";
export function asyncIterator(this: Console) {
var stream = Bun.stdin.stream();
const stdin = Bun.stdin as typeof Bun.stdin & { $consoleAsyncIterator: (() => AsyncGenerator<string>) | undefined };
const existing = $getByIdDirectPrivate(stdin, "consoleAsyncIterator");
if (existing) return existing();

var stream = stdin.stream();

var decoder = new TextDecoder("utf-8", { fatal: false });
var indexOf = Bun.indexOfLine;
Expand Down Expand Up @@ -110,23 +114,23 @@ export function asyncIterator(this: Console) {
}
}

const symbol = globalThis.Symbol.asyncIterator;
this[symbol] = ConsoleAsyncIterator;
$putByIdDirectPrivate(stdin, "consoleAsyncIterator", ConsoleAsyncIterator);
return ConsoleAsyncIterator();
}

interface ConsoleWriter extends Bun.FileSink {
flush(wait?: boolean): number | Promise<number>;
}

export function write(this: Console & { $writer: ConsoleWriter | undefined }, input) {
export function write(this: Console, input) {
if (!$isObject(this)) throw $ERR_INVALID_THIS("Console");

var writer = $getByIdDirectPrivate(this, "writer");
const stdout = Bun.stdout as typeof Bun.stdout & { $writer: ConsoleWriter | undefined };
var writer = $getByIdDirectPrivate(stdout, "writer");
if (!writer) {
var length = $toLength(input?.length ?? 0);
writer = Bun.stdout.writer({ highWaterMark: length > 65536 ? length : 65536 });
$putByIdDirectPrivate(this, "writer", writer);
writer = stdout.writer({ highWaterMark: length > 65536 ? length : 65536 });
$putByIdDirectPrivate(stdout, "writer", writer);
}

var wrote = writer.write(input) as number;
Expand Down
7 changes: 4 additions & 3 deletions src/js/bun/ffi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ const nativeDLOpen = ffi.dlopen;
const nativeCallback = ffi.callback;
const closeCallback = ffi.closeCallback;
const nativeCFunction = ffi.cfunction;
delete ffi.callback;
delete ffi.closeCallback;
delete ffi.cfunction;
const { deleteBuiltinProperty } = require("internal/shared");
deleteBuiltinProperty(ffi, "callback");
deleteBuiltinProperty(ffi, "closeCallback");
deleteBuiltinProperty(ffi, "cfunction");

class JSCallback {
declare readonly ptr: number | null;
Expand Down
161 changes: 81 additions & 80 deletions src/js/internal/assert/assertion_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { SafeSet } = require("internal/primordials");
const { inspect } = require("internal/util/inspect");
const colors = require("internal/util/colors");
const { validateObject } = require("internal/validators");
const { setStackTraceLimitOverride } = require("internal/shared");
const { printMyersDiff, printSimpleMyersDiff } = require("internal/assert/myers_diff") as typeof Internal;

const ErrorCaptureStackTrace = Error.captureStackTrace;
Expand Down Expand Up @@ -274,103 +275,103 @@ class AssertionError extends Error {
} = options;
let { actual, expected } = options;

// NOTE: stack trace is always writable.
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;

if (message != null) {
if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
// No frames for this error, which captures its own below, or for one made while the message is rendered.
const previousOverride = setStackTraceLimitOverride(0);
try {
if (message != null) {
if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else {
super(String(message));
}
} else {
super(String(message));
}
} else {
// Reset colors on each call to make sure we handle dynamically set environment
// variables correct.
colors.refresh();
// Prevent the error stack from being visible by duplicating the error
// in a very close way to the original in case both sides are actually
// instances of Error.
if (
typeof actual === "object" &&
actual !== null &&
typeof expected === "object" &&
expected !== null &&
"stack" in actual &&
actual instanceof Error &&
"stack" in expected &&
expected instanceof Error
) {
actual = copyError(actual);
expected = copyError(expected);
}

if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else if (operator === "notDeepStrictEqual" || operator === "notStrictEqual") {
// In case the objects are equal but the operator requires unequal, show
// the first object and say A equals B
let base = kReadableOperator[operator];
const res = StringPrototypeSplit.$call(inspectValue(actual), "\n");

// In case "actual" is an object or a function, it should not be
// reference equal.
// Reset colors on each call to make sure we handle dynamically set environment
// variables correct.
colors.refresh();
// Prevent the error stack from being visible by duplicating the error
// in a very close way to the original in case both sides are actually
// instances of Error.
if (
operator === "notStrictEqual" &&
((typeof actual === "object" && actual !== null) || typeof actual === "function")
typeof actual === "object" &&
actual !== null &&
typeof expected === "object" &&
expected !== null &&
"stack" in actual &&
actual instanceof Error &&
"stack" in expected &&
expected instanceof Error
) {
base = kReadableOperator.notStrictEqualObject;
actual = copyError(actual);
expected = copyError(expected);
}

// Only remove lines in case it makes sense to collapse those.
const resLength = res.length;
if (resLength > 50 && diff !== "full") {
res[46] = `${colors.blue}...${colors.white}`;
while (res.length > 47) {
ArrayPrototypePop.$call(res);
if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else if (operator === "notDeepStrictEqual" || operator === "notStrictEqual") {
// In case the objects are equal but the operator requires unequal, show
// the first object and say A equals B
let base = kReadableOperator[operator];
const res = StringPrototypeSplit.$call(inspectValue(actual), "\n");

// In case "actual" is an object or a function, it should not be
// reference equal.
if (
operator === "notStrictEqual" &&
((typeof actual === "object" && actual !== null) || typeof actual === "function")
) {
base = kReadableOperator.notStrictEqualObject;
}
}

// Only print a single input.
if (res.length === 1) {
super(`${base}${res[0].length > 5 ? "\n\n" : " "}${res[0]}`);
} else {
super(`${base}\n\n${ArrayPrototypeJoin.$call(res, "\n")}\n`);
}
} else {
let res = inspectValue(actual);
let other = inspectValue(expected);
const knownOperator = kReadableOperator[operator];
if (operator === "notDeepEqual" && res === other) {
res = `${knownOperator}\n\n${res}`;
if (res.length > 1024 && diff !== "full") {
res = `${StringPrototypeSlice.$call(res, 0, 1021)}...`;
}
super(res);
} else {
if (res.length > kMaxLongStringLength && diff !== "full") {
res = `${StringPrototypeSlice.$call(res, 0, 509)}...`;
// Only remove lines in case it makes sense to collapse those.
const resLength = res.length;
if (resLength > 50 && diff !== "full") {
res[46] = `${colors.blue}...${colors.white}`;
while (res.length > 47) {
ArrayPrototypePop.$call(res);
}
}
if (other.length > kMaxLongStringLength && diff !== "full") {
other = `${StringPrototypeSlice.$call(other, 0, 509)}...`;

// Only print a single input.
if (res.length === 1) {
super(`${base}${res[0].length > 5 ? "\n\n" : " "}${res[0]}`);
} else {
super(`${base}\n\n${ArrayPrototypeJoin.$call(res, "\n")}\n`);
}
if (operator === "deepEqual") {
res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`;
} else {
let res = inspectValue(actual);
let other = inspectValue(expected);
const knownOperator = kReadableOperator[operator];
if (operator === "notDeepEqual" && res === other) {
res = `${knownOperator}\n\n${res}`;
if (res.length > 1024 && diff !== "full") {
res = `${StringPrototypeSlice.$call(res, 0, 1021)}...`;
}
super(res);
} else {
const newOp = kReadableOperator[`${operator}Unequal`];
if (newOp) {
res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`;
if (res.length > kMaxLongStringLength && diff !== "full") {
res = `${StringPrototypeSlice.$call(res, 0, 509)}...`;
}
if (other.length > kMaxLongStringLength && diff !== "full") {
other = `${StringPrototypeSlice.$call(other, 0, 509)}...`;
}
if (operator === "deepEqual") {
res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`;
} else {
other = ` ${operator} ${other}`;
const newOp = kReadableOperator[`${operator}Unequal`];
if (newOp) {
res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`;
} else {
other = ` ${operator} ${other}`;
}
}
super(`${res}${other}`);
}
super(`${res}${other}`);
}
}
} finally {
setStackTraceLimitOverride(previousOverride);
}

Error.stackTraceLimit = limit;

this.generatedMessage = !message;
ObjectDefineProperty(this, "name", {
__proto__: null,
Expand Down
10 changes: 8 additions & 2 deletions src/js/internal/cluster/child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ClusterWorker } from "internal/cluster/Worker";
const EventEmitter = require("node:events");
const Worker = require("internal/cluster/Worker");
const path = require("node:path");
const { kClusterOwner: owner_symbol, kInternalSendOptions } = require("internal/shared");
const { kClusterOwner: owner_symbol, kInternalSendOptions, defineBuiltinProperty } = require("internal/shared");

const onInternalMessage = $bindgen("node_cluster_binding.bind.ts", "onInternalMessageChild");
const closeRawHandle = $bindgen("node_cluster_binding.bind.ts", "clusterCloseHandle");
Expand Down Expand Up @@ -74,7 +74,13 @@ cluster._setupWorker = function () {
// before calling, check if the channel is refd. if it isn't, then unref it after calling process.once();
$bindgen("node_cluster_binding.bind.ts", "channelIgnoreOneDisconnectEventListener")();
process.once("disconnect", () => {
(process as { channel: unknown }).channel = null;
defineBuiltinProperty(process, "channel", {
__proto__: null,
value: null,
writable: true,
enumerable: true,
configurable: true,
});
worker.emit("disconnect");

if (!worker.exitedAfterDisconnect) {
Expand Down
19 changes: 12 additions & 7 deletions src/js/internal/process/pre_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// file, worker, or `-e` eval — is evaluated. Must stay cheap: one execArgv
// scan, bail early when nothing applies.

const { defineBuiltinProperty, setStackTraceLimitOverride } = require("internal/shared");

let writeSync: typeof import("node:fs").writeSync;
let envTracePrintMessage = false;
let envTracePrintJsStack = false;
Expand Down Expand Up @@ -126,10 +128,13 @@ function printEnvTrace(kind: EnvOpKind, key: string | null): void {
// The capture burns 3 frames on trace machinery (printEnvTrace, the
// proxy trap, and the Error line); widen the limit so the user still
// sees `Error.stackTraceLimit` real frames.
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = limit + 3;
const stack = new Error().stack!.split("\n");
Error.stackTraceLimit = limit;
const previousOverride = setStackTraceLimitOverride(Error.stackTraceLimit + 3);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let stack: string[];
try {
stack = new Error().stack!.split("\n");
} finally {
setStackTraceLimitOverride(previousOverride);
}
// stack[0] = "Error", [1] = printEnvTrace, [2] = the proxy trap.
let corrected = false;
for (let i = 3; i < stack.length; i++) {
Expand Down Expand Up @@ -196,7 +201,7 @@ function installEnvTracing(): void {
return ReflectOwnKeys(target);
},
});
Object.defineProperty(process, "env", {
defineBuiltinProperty(process, "env", {
value: proxy,
writable: true,
enumerable: true,
Expand Down Expand Up @@ -250,7 +255,7 @@ function installExitTracing(): void {
writeStderr(out);
return realExit.$call(process, code);
}
process.exit = exit as typeof process.exit;
defineBuiltinProperty(process, "exit", { __proto__: null, value: exit });
Comment thread
dylan-conway marked this conversation as resolved.
}

{
Expand Down Expand Up @@ -297,7 +302,7 @@ function installExitTracing(): void {

if (stackTraceLimit !== null) {
const limit = Number(stackTraceLimit);
if (Number.isFinite(limit)) Error.stackTraceLimit = limit;
if (Number.isFinite(limit)) defineBuiltinProperty(Error, "stackTraceLimit", { __proto__: null, value: limit });
}

// CLI-driven tracing runs in worker VMs too (workers inherit the parent's
Expand Down
10 changes: 6 additions & 4 deletions src/js/internal/quic/quic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const {
isSharedArrayBuffer,
isKeyObject,
} = require("node:util/types");
const { SocketAddress, BlockList } = require("node:net");
const SocketAddress = $rust("node_net_binding.rs", "SocketAddress");
const BlockList = $rust("node_net_binding.rs", "BlockList");

// The native binding hands certificates over as DER bytes; expose them as
// X509Certificate objects like Node does.
Expand Down Expand Up @@ -147,16 +148,17 @@ function ERR_QUIC_VERSION_NEGOTIATION_ERROR() {
const kClientHttp = Symbol("kClientHttp");
const kNoteClientHttp = Symbol("kNoteClientHttp");

const { defineBuiltinProperty } = require("internal/shared");
const kSocketAddressHandle = Symbol("kSocketAddressHandle");
Object.defineProperty(SocketAddress.prototype, kSocketAddressHandle, {
defineBuiltinProperty(SocketAddress.prototype, kSocketAddressHandle, {
__proto__: null,
get() {
return this;
},
configurable: true,
});
const kBlockListHandle = Symbol("kBlockListHandle");
Object.defineProperty(BlockList.prototype, kBlockListHandle, {
defineBuiltinProperty(BlockList.prototype, kBlockListHandle, {
__proto__: null,
get() {
return this;
Expand All @@ -168,7 +170,7 @@ function InternalSocketAddress(handle): void {
}

const kBlobHandle = Symbol("kBlobHandle");
Object.defineProperty(Blob.prototype, kBlobHandle, {
defineBuiltinProperty(Blob.prototype, kBlobHandle, {
__proto__: null,
get() {
return this;
Expand Down
Loading
Loading