Compare commits

...

3 Commits

Author SHA1 Message Date
Zack Radisic
516253e128 Update panic message 2025-07-17 22:30:52 -07:00
Zack Radisic
f2494f8718 rename and fix case sus place where we were setting undefined memory 2025-07-17 21:02:47 -07:00
Zack Radisic
9dd74598f0 checked uninit 2025-07-17 20:36:16 -07:00
13 changed files with 144 additions and 36 deletions

View File

@@ -319,6 +319,7 @@ src/bundler/ServerComponentParseTask.zig
src/bundler/ThreadPool.zig
src/bunfig.zig
src/cache.zig
src/CheckedUninit.zig
src/ci_info.zig
src/cli.zig
src/cli/add_command.zig

88
src/CheckedField.zig Normal file
View File

@@ -0,0 +1,88 @@
/// Q: When to use this type?
/// A: When you have to default initialize a field to `undefined` because you
/// can't initialize it right away. (For example the `jsc: *VM` field in
/// `VirtualMachine.zig`)
///
/// This wrapper type inserts checks in debug builds that ensure we're not
/// accidentally forgetting to set it and causing subtle and time-wasting
/// bugs!
///
/// Q: Why though, can't I just remember to initialize it?
/// A: *You* might remember to initialize it, but someone else using the API may
/// not, or a refactoring may forget it, and as we all know `undefined` in Zig
/// causes subtle and extremely time-consuming btle bugs.
///
/// Fun fact: I wasted 30 minutes fixing a bug that turned out to be a field
/// defaulted to `undefined` that didn't get set! So please, use this wrapper
/// type to save everyone's time and patience :)
///
/// Q: Why not just use an optional? (e.g. `my_field: ?T = null`)
/// A: A lot of the fields that get default set to undefined are only
/// *temporarily* unset during initialization. It is annoying to have to unwrap
/// optionals when 99% of the program will have the field initialized.
///
/// Q: Okay, how do I use it?
/// A: Read on:
///
/// # Example
///
/// Take a field that was previously default initialized to `undefined`:
/// ```zig
/// const VirtualMachine = struct {
/// jsc: *VM = undefined,
/// };
/// ```
///
/// And use `CheckedField(T)` instead!
/// ```zig
/// const VirtualMachine = struct {
/// jsc: CheckedField(*VM) = .{},
/// };
/// ```
///
/// You can then call `this.jsc.set(value)` to initialize it and
/// `this.jsc.get()` to get the value.
///
/// Congratulations! You've just saved everyone's time!
pub fn CheckedField(comptime T: type) type {
const enabled = bun.Environment.isDebug;
return struct {
__value: T = undefined,
__is_init: if (enabled) bool else void = if (enabled) false else {},
const This = @This();
pub inline fn get(this: *const This) T {
this.assertInitialized();
return this.__value;
}
pub inline fn getPtr(this: *const This) *const T {
this.assertInitialized();
return &this.__value;
}
pub inline fn mut(this: *This) *T {
this.assertInitialized();
return &this.__value;
}
pub inline fn set(this: *This, value: T) void {
this.__value = value;
if (comptime enabled) {
this.__is_init = true;
}
}
pub inline fn assertInitialized(this: *const This) void {
if (comptime enabled) {
if (!this.__is_init) {
@panic("CheckedField: Not initialized");
}
}
}
};
}
const std = @import("std");
const bun = @import("bun");

View File

@@ -198,7 +198,7 @@ pub fn init(
const loaded_result = try vm.loadMacroEntryPoint(input_specifier, function_name, specifier, hash);
switch (loaded_result.unwrap(vm.jsc, .leave_unhandled)) {
switch (loaded_result.unwrap(vm.jsc.get(), .leave_unhandled)) {
.rejected => |result| {
vm.unhandledRejection(vm.global, result, loaded_result.asValue());
vm.disableMacroMode();
@@ -502,8 +502,8 @@ pub const Runner = struct {
this.macro.vm.waitForPromise(promise);
const promise_result = promise.result(this.macro.vm.jsc);
const rejected = promise.status(this.macro.vm.jsc) == .rejected;
const promise_result = promise.result(this.macro.vm.jsc.get());
const rejected = promise.status(this.macro.vm.jsc.get()) == .rejected;
if (promise_result.isUndefined() and this.is_top_level) {
this.is_top_level = false;

View File

@@ -40,7 +40,7 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
// that bypass Bun's normal module resolver and plugin system.
vm.global = BakeCreateProdGlobal(vm.console);
vm.regular_event_loop.global = vm.global;
vm.jsc = vm.global.vm();
vm.jsc.set(vm.global.vm());
vm.event_loop.ensureWaker();
const b = &vm.transpiler;
vm.preload = ctx.preloads;
@@ -80,7 +80,7 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
vm.is_main_thread = true;
JSC.VirtualMachine.is_main_thread_vm = true;
const api_lock = vm.jsc.getAPILock();
const api_lock = vm.jsc.get().getAPILock();
defer api_lock.release();
buildWithVm(ctx, cwd, vm) catch |err| switch (err) {
error.JSError => |e| {
@@ -136,9 +136,9 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
return error.JSError;
};
config_promise.setHandled(vm.jsc);
config_promise.setHandled(vm.jsc.get());
vm.waitForPromise(.{ .internal = config_promise });
var options = switch (config_promise.unwrap(vm.jsc, .mark_handled)) {
var options = switch (config_promise.unwrap(vm.jsc.get(), .mark_handled)) {
.pending => unreachable,
.fulfilled => |resolved| config: {
bun.assert(resolved.isUndefined());
@@ -549,9 +549,9 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
route_param_info,
route_style_references,
);
render_promise.setHandled(vm.jsc);
render_promise.setHandled(vm.jsc.get());
vm.waitForPromise(.{ .normal = render_promise });
switch (render_promise.unwrap(vm.jsc, .mark_handled)) {
switch (render_promise.unwrap(vm.jsc.get(), .mark_handled)) {
.pending => unreachable,
.fulfilled => {
Output.prettyln("done", .{});
@@ -568,9 +568,9 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
/// quits the process on exception
fn loadModule(vm: *VirtualMachine, global: *JSC.JSGlobalObject, key: JSValue) !JSValue {
const promise = BakeLoadModuleByKey(global, key).asAnyPromise().?.internal;
promise.setHandled(vm.jsc);
promise.setHandled(vm.jsc.get());
vm.waitForPromise(.{ .internal = promise });
switch (promise.unwrap(vm.jsc, .mark_handled)) {
switch (promise.unwrap(vm.jsc.get(), .mark_handled)) {
.pending => unreachable,
.fulfilled => |val| {
bun.assert(val.isUndefined());

View File

@@ -2144,7 +2144,7 @@ pub const RuntimeTranspilerStore = struct {
pub fn runFromJSThread(this: *RuntimeTranspilerStore, event_loop: *JSC.EventLoop, global: *JSC.JSGlobalObject, vm: *JSC.VirtualMachine) void {
var batch = this.queue.popBatch();
const jsc_vm = vm.jsc;
const jsc_vm = vm.jsc.get();
var iter = batch.iterator();
if (iter.next()) |job| {
// we run just one job first to see if there are more

View File

@@ -46,7 +46,7 @@ dns_result_order: DNSResolver.Order = .verbatim,
counters: Counters = .{},
hot_reload: bun.CLI.Command.HotReload = .none,
jsc: *VM = undefined,
jsc: CheckedField(*VM) = .{},
/// hide bun:wrap from stack traces
/// bun:wrap is very noisy
@@ -1018,8 +1018,8 @@ pub fn initWithModuleGraph(
null,
);
vm.regular_event_loop.global = vm.global;
vm.jsc = vm.global.vm();
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc;
vm.jsc.set(vm.global.vm());
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc.get();
vm.configureDebugger(opts.debugger);
vm.body_value_hive_allocator = Body.Value.HiveAllocator.init(bun.typedAllocator(JSC.WebCore.Body.Value));
@@ -1127,17 +1127,22 @@ pub fn init(opts: Options) !*VirtualMachine {
vm.transpiler.macro_context = js_ast.Macro.MacroContext.init(&vm.transpiler);
vm.global = JSGlobalObject.create(
vm.global = JSGlobalObject.createEnsureWaker(
vm,
vm.console,
if (opts.is_main_thread) 1 else std.math.maxInt(i32),
opts.smol,
opts.eval,
null,
// DO NOT call `.ensureWaker()` as this accesses `vm.jsc` which is not
// initialized yet.
false,
);
vm.regular_event_loop.global = vm.global;
vm.jsc = vm.global.vm();
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc;
vm.jsc.set(vm.global.vm());
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc.get();
// NOW call ensure waker since everything is set
vm.eventLoop().ensureWaker();
vm.smol = opts.smol;
vm.dns_result_order = opts.dns_result_order;
@@ -1299,8 +1304,8 @@ pub fn initWorker(
worker.cpp_worker,
);
vm.regular_event_loop.global = vm.global;
vm.jsc = vm.global.vm();
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc;
vm.jsc.set(vm.global.vm());
uws.Loop.get().internal_loop_data.jsc_vm = vm.jsc.get();
vm.transpiler.setAllocator(allocator);
vm.body_value_hive_allocator = Body.Value.HiveAllocator.init(bun.typedAllocator(JSC.WebCore.Body.Value));
@@ -1945,7 +1950,7 @@ pub noinline fn runErrorHandler(this: *VirtualMachine, result: JSValue, exceptio
const writer = buffered_writer.writer();
if (result.asException(this.jsc)) |exception| {
if (result.asException(this.jsc.get())) |exception| {
this.printException(
exception,
exception_list,
@@ -3680,3 +3685,4 @@ const DotEnv = bun.DotEnv;
const HotReloader = JSC.hot_reloader.HotReloader;
const Body = webcore.Body;
const Counters = @import("./Counters.zig");
const CheckedField = bun.CheckedField;

View File

@@ -2250,7 +2250,7 @@ pub fn spawnMaybeSync(
!stdio[2].isPiped() and
extra_fds.items.len == 0 and
!jsc_vm.auto_killer.enabled and
!jsc_vm.jsc.hasExecutionTimeLimit() and
!jsc_vm.jsc.get().hasExecutionTimeLimit() and
!jsc_vm.isInspectorEnabled() and
!bun.getRuntimeFeatureFlag(.BUN_FEATURE_FLAG_DISABLE_SPAWNSYNC_FAST_PATH);

View File

@@ -2239,7 +2239,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
const ctx = this.request_pool_allocator.tryGet() catch bun.outOfMemory();
ctx.create(this, req, resp, should_deinit_context, method);
this.vm.jsc.reportExtraMemory(@sizeOf(RequestContext));
this.vm.jsc.get().reportExtraMemory(@sizeOf(RequestContext));
const body = this.vm.initRequestBodyValue(.{ .Null = {} }) catch unreachable;
ctx.request_body = body;

View File

@@ -773,11 +773,22 @@ pub const JSGlobalObject = opaque {
mini_mode: bool,
eval_mode: bool,
worker_ptr: ?*anyopaque,
) *JSGlobalObject {
return createEnsureWaker(v, console, context_id, mini_mode, eval_mode, worker_ptr, true);
}
pub fn createEnsureWaker(
v: *JSC.VirtualMachine,
console: *anyopaque,
context_id: i32,
mini_mode: bool,
eval_mode: bool,
worker_ptr: ?*anyopaque,
ensure_waker: bool,
) *JSGlobalObject {
const trace = bun.perf.trace("JSGlobalObject.create");
defer trace.end();
v.eventLoop().ensureWaker();
if (ensure_waker) v.eventLoop().ensureWaker();
const global = Zig__GlobalObject__create(console, context_id, mini_mode, eval_mode, worker_ptr);
// JSC might mess with the stack size.

View File

@@ -70,7 +70,7 @@ pub fn exit(this: *EventLoop) void {
defer this.debug.exit();
if (count == 1 and !this.virtual_machine.is_inside_deferred_task_queue) {
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc) catch {};
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc.get()) catch {};
}
this.entered_event_loop_count -= 1;
@@ -83,7 +83,7 @@ pub fn exitMaybeDrainMicrotasks(this: *EventLoop, allow_drain_microtask: bool) b
defer this.debug.exit();
if (allow_drain_microtask and count == 1 and !this.virtual_machine.is_inside_deferred_task_queue) {
try this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc);
try this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc.get());
}
this.entered_event_loop_count -= 1;
@@ -127,13 +127,13 @@ pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalOb
}
pub fn drainMicrotasks(this: *EventLoop) bun.JSExecutionTerminated!void {
try this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc);
try this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc.get());
}
// should be called after exit()
pub fn maybeDrainMicrotasks(this: *EventLoop) void {
if (this.entered_event_loop_count == 0 and !this.virtual_machine.is_inside_deferred_task_queue) {
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc) catch {};
this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc.get()) catch {};
}
}
@@ -463,7 +463,7 @@ pub fn tick(this: *EventLoop) void {
this.processGCTimer();
const global = ctx.global;
const global_vm = ctx.jsc;
const global_vm = ctx.jsc.get();
while (true) {
while (this.tickWithCount(ctx) > 0) : (this.global.handleRejectedPromises()) {
@@ -485,7 +485,7 @@ pub fn tick(this: *EventLoop) void {
}
pub fn waitForPromise(this: *EventLoop, promise: JSC.AnyPromise) void {
const jsc_vm = this.virtual_machine.jsc;
const jsc_vm = this.virtual_machine.jsc.get();
switch (promise.status(jsc_vm)) {
.pending => {
while (promise.status(jsc_vm) == .pending) {
@@ -502,7 +502,7 @@ pub fn waitForPromise(this: *EventLoop, promise: JSC.AnyPromise) void {
pub fn waitForPromiseWithTermination(this: *EventLoop, promise: JSC.AnyPromise) void {
const worker = this.virtual_machine.worker orelse @panic("EventLoop.waitForPromiseWithTermination: worker is not initialized");
const jsc_vm = this.virtual_machine.jsc;
const jsc_vm = this.virtual_machine.jsc.get();
switch (promise.status(jsc_vm)) {
.pending => {
while (!worker.hasRequestedTerminate() and promise.status(jsc_vm) == .pending) {

View File

@@ -33,7 +33,7 @@ pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
const actual = uws.Loop.get();
this.gc_timer = uws.Timer.createFallthrough(actual, this);
this.gc_repeating_timer = uws.Timer.createFallthrough(actual, this);
actual.internal_loop_data.jsc_vm = vm.jsc;
actual.internal_loop_data.jsc_vm = vm.jsc.get();
if (comptime Environment.isDebug) {
if (bun.getenvZ("BUN_TRACK_LAST_FN_NAME") != null) {
@@ -114,7 +114,7 @@ pub fn onGCRepeatingTimer(timer: *uws.Timer) callconv(.C) void {
pub fn processGCTimer(this: *GarbageCollectionController) void {
if (this.disabled) return;
var vm = this.bunVM().jsc;
var vm = this.bunVM().jsc.get();
this.processGCTimerWithHeapSize(vm, vm.blockBytesAllocated());
}
@@ -155,7 +155,7 @@ fn processGCTimerWithHeapSize(this: *GarbageCollectionController, vm: *JSC.VM, t
pub fn performGC(this: *GarbageCollectionController) void {
if (this.disabled) return;
var vm = this.bunVM().jsc;
var vm = this.bunVM().jsc.get();
vm.collectAsync();
this.gc_last_heap_size = vm.blockBytesAllocated();
}

View File

@@ -3767,3 +3767,5 @@ pub fn contains(item: anytype, list: *const std.ArrayListUnmanaged(@TypeOf(item)
else => std.mem.indexOfScalar(T, list.items, item) != null,
};
}
pub const CheckedField = @import("./CheckedField.zig").CheckedField;

View File

@@ -437,7 +437,7 @@ pub const Run = struct {
const to_print = brk: {
const result: JSC.JSValue = vm.entry_point_result.value.get() orelse .js_undefined;
if (result.asAnyPromise()) |promise| {
switch (promise.status(vm.jsc)) {
switch (promise.status(vm.jsc.get())) {
.pending => {
result._then2(vm.global, .js_undefined, Bun__onResolveEntryPointResult, Bun__onRejectEntryPointResult);
@@ -451,7 +451,7 @@ pub const Run = struct {
break :brk result;
},
else => break :brk promise.result(vm.jsc),
else => break :brk promise.result(vm.jsc.get()),
}
}