From a199b85f2b1c8c7d2eda1acbb4c4f11a26095107 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Mon, 31 Mar 2025 17:17:38 -0700 Subject: [PATCH] remove many `usingnamespace`, introduce new ref count and ref leak debugging tools (#18353) --- build.zig | 7 - src/Mutex.zig | 10 +- src/StandaloneModuleGraph.zig | 2 +- src/allocators/AllocationScope.zig | 98 ++- src/allocators/linux_memfd_allocator.zig | 34 +- src/async/posix_event_loop.zig | 6 +- src/async/windows_event_loop.zig | 11 +- src/bake/DevServer.zig | 8 +- src/brotli.zig | 4 +- src/bun.js/api/JSTranspiler.zig | 7 +- src/bun.js/api/Timer.zig | 40 +- src/bun.js/api/bun/dns_resolver.zig | 30 +- src/bun.js/api/bun/h2_frame_parser.zig | 19 +- src/bun.js/api/bun/process.zig | 43 +- src/bun.js/api/bun/socket.zig | 42 +- src/bun.js/api/bun/socket/SocketAddress.zig | 9 +- src/bun.js/api/bun/subprocess.zig | 48 +- src/bun.js/api/bun/udp_socket.zig | 4 +- src/bun.js/api/crypto/CryptoHasher.zig | 23 +- src/bun.js/api/crypto/HMAC.zig | 4 +- src/bun.js/api/crypto/PBKDF2.zig | 4 +- src/bun.js/api/crypto/PasswordObject.zig | 20 +- src/bun.js/api/ffi.zig | 2 +- src/bun.js/api/html_rewriter.zig | 157 +++-- src/bun.js/api/server.zig | 32 +- src/bun.js/api/server/HTMLBundle.zig | 84 ++- src/bun.js/api/server/NodeHTTPResponse.zig | 31 +- src/bun.js/api/server/ServerWebSocket.zig | 4 +- src/bun.js/api/server/StaticRoute.zig | 19 +- src/bun.js/event_loop.zig | 37 +- src/bun.js/javascript.zig | 13 +- src/bun.js/node/Stat.zig | 6 +- src/bun.js/node/node_fs.zig | 16 +- src/bun.js/node/node_fs_binding.zig | 4 +- src/bun.js/node/node_fs_stat_watcher.zig | 10 +- src/bun.js/node/node_fs_watcher.zig | 43 +- src/bun.js/node/node_zlib_binding.zig | 28 +- src/bun.js/node/win_watcher.zig | 12 +- src/bun.js/webcore/ObjectURLRegistry.zig | 4 +- src/bun.js/webcore/S3Client.zig | 4 +- src/bun.js/webcore/S3File.zig | 4 +- src/bun.js/webcore/S3Stat.zig | 4 +- src/bun.js/webcore/TextDecoder.zig | 5 +- .../webcore/TextEncoderStreamEncoder.zig | 4 +- src/bun.js/webcore/blob.zig | 48 +- src/bun.js/webcore/blob/WriteFile.zig | 4 +- src/bun.js/webcore/fetch.zig | 4 +- src/bun.js/webcore/request.zig | 8 +- src/bun.js/webcore/streams.zig | 48 +- src/bun.zig | 137 +--- src/bundler/bundle_v2.zig | 15 +- src/cli/run_command.zig | 10 +- src/cli/test_command.zig | 2 +- src/cli/upgrade_command.zig | 16 +- src/codegen/class-definitions.ts | 29 +- src/crash_handler.zig | 281 +++++++- src/css/css_parser.zig | 29 +- src/css/properties/custom.zig | 7 +- src/css/properties/font.zig | 11 +- src/css/rules/rules.zig | 2 +- src/css/rules/style.zig | 26 +- src/css/selectors/selector.zig | 2 +- src/css/values/easing.zig | 10 +- src/css/values/ident.zig | 6 +- src/css/values/image.zig | 9 +- src/deps/c_ares.zig | 4 +- src/deps/diffz/DiffMatchPatch.zig | 12 +- src/deps/libuv.zig | 2 +- src/env_loader.zig | 1 + src/http.zig | 35 +- src/http/websocket_http_client.zig | 33 +- src/install/install.zig | 13 +- src/install/lifecycle_script_runner.zig | 4 +- src/install/npm.zig | 6 +- src/install/windows-shim/BinLinkingShim.zig | 2 +- src/io/PipeReader.zig | 58 +- src/js_lexer.zig | 6 +- src/macho.zig | 12 +- src/napi/napi.zig | 4 +- src/ptr.zig | 11 +- src/ptr/ref_count.zig | 620 ++++++++++++++---- src/ptr/weak_ptr.zig | 70 ++ src/resolver/data_url.zig | 4 +- src/resolver/package_json.zig | 2 +- src/resolver/tsconfig_json.zig | 19 +- src/s3/client.zig | 26 +- src/s3/credentials.zig | 14 +- src/s3/download_stream.zig | 6 +- src/s3/multipart.zig | 9 +- src/s3/simple_request.zig | 5 +- src/semver/SemverRange.zig | 14 +- src/semver/SemverString.zig | 2 +- src/shell/interpreter.zig | 52 +- src/shell/shell.zig | 79 +-- src/shell/subproc.zig | 15 +- src/sourcemap/sourcemap.zig | 23 +- src/sql/postgres/postgres_protocol.zig | 6 +- src/windows.zig | 4 +- test/internal/ban-words.test.ts | 2 +- 99 files changed, 1857 insertions(+), 1018 deletions(-) create mode 100644 src/ptr/weak_ptr.zig diff --git a/build.zig b/build.zig index 5bb2804182..e5f4ca7276 100644 --- a/build.zig +++ b/build.zig @@ -153,13 +153,6 @@ pub fn build(b: *Build) !void { std.log.info("zig compiler v{s}", .{builtin.zig_version_string}); checked_file_exists = std.AutoHashMap(u64, void).init(b.allocator); - // TODO: Upgrade path for 0.14.0 - // b.graph.zig_lib_directory = brk: { - // const sub_path = "vendor/zig/lib"; - // const dir = try b.build_root.handle.openDir(sub_path, .{}); - // break :brk .{ .handle = dir, .path = try b.build_root.join(b.graph.arena, &.{sub_path}) }; - // }; - var target_query = b.standardTargetOptionsQueryOnly(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/src/Mutex.zig b/src/Mutex.zig index 0aaaee366d..7ca748a64a 100644 --- a/src/Mutex.zig +++ b/src/Mutex.zig @@ -59,11 +59,11 @@ else pub const ReleaseImpl = if (builtin.os.tag == .windows) - WindowsImpl -else if (builtin.os.tag.isDarwin()) - DarwinImpl -else - FutexImpl; + WindowsImpl + else if (builtin.os.tag.isDarwin()) + DarwinImpl + else + FutexImpl; pub const ExternImpl = ReleaseImpl.Type; diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 807ecf520b..779e48a288 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -254,7 +254,7 @@ pub const StandaloneModuleGraph = struct { stored.underlying_provider = .{ .data = @truncate(@intFromPtr(data)), .load_hint = .none }; stored.is_standalone_module_graph = true; - const parsed = stored.new(); // allocate this on the heap + const parsed = bun.new(SourceMap.ParsedSourceMap, stored); parsed.ref(); // never free this.* = .{ .parsed = parsed }; return parsed; diff --git a/src/allocators/AllocationScope.zig b/src/allocators/AllocationScope.zig index 5e2f7e3c8b..272f34eb70 100644 --- a/src/allocators/AllocationScope.zig +++ b/src/allocators/AllocationScope.zig @@ -19,6 +19,7 @@ pub const max_free_tracking = 2048 - 1; pub const Allocation = struct { allocated_at: StoredTrace, len: usize, + extra: Extra, }; pub const Free = struct { @@ -26,6 +27,14 @@ pub const Free = struct { freed_at: StoredTrace, }; +pub const Extra = union(enum) { + none, + ref_count: *RefCountDebugData(false), + ref_count_threadsafe: *RefCountDebugData(true), + + const RefCountDebugData = @import("../ptr/ref_count.zig").DebugData; +}; + pub fn init(parent: Allocator) AllocationScope { return if (enabled) .{ @@ -56,7 +65,13 @@ pub fn deinit(scope: *AllocationScope) void { var n: usize = 0; while (it.next()) |entry| { Output.prettyErrorln("- {any}, len {d}, at:", .{ entry.key_ptr.*, entry.value_ptr.len }); - bun.crash_handler.dumpStackTrace(entry.value_ptr.allocated_at.trace()); + bun.crash_handler.dumpStackTrace(entry.value_ptr.allocated_at.trace(), trace_limits); + + switch (entry.value_ptr.extra) { + .none => {}, + inline else => |t| t.onAllocationLeak(@constCast(entry.key_ptr.*[0..entry.value_ptr.len])), + } + n += 1; if (n >= 8) { Output.prettyErrorln("(only showing first 10 leaks)", .{}); @@ -78,6 +93,16 @@ const vtable: Allocator.VTable = .{ .free = free, }; +// Smaller traces since AllocationScope prints so many +pub const trace_limits: bun.crash_handler.WriteStackTraceLimits = .{ + .frame_count = 6, + .stop_at_jsc_llint = true, +}; +pub const free_trace_limits: bun.crash_handler.WriteStackTraceLimits = .{ + .frame_count = 3, + .stop_at_jsc_llint = true, +}; + fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 { const scope: *AllocationScope = @ptrCast(@alignCast(ctx)); scope.state.mutex.lock(); @@ -86,15 +111,20 @@ fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: us return null; const result = scope.parent.vtable.alloc(scope.parent.ptr, len, alignment, ret_addr) orelse return null; - const trace = StoredTrace.capture(ret_addr); - scope.state.allocations.putAssumeCapacityNoClobber(result, .{ - .allocated_at = trace, - .len = len, - }); - scope.state.total_memory_allocated += len; + scope.trackAllocationAssumeCapacity(result[0..len], ret_addr, .none); return result; } +fn trackAllocationAssumeCapacity(scope: *AllocationScope, buf: []const u8, ret_addr: usize, extra: Extra) void { + const trace = StoredTrace.capture(ret_addr); + scope.state.allocations.putAssumeCapacityNoClobber(buf.ptr, .{ + .allocated_at = trace, + .len = buf.len, + .extra = extra, + }); + scope.state.total_memory_allocated += buf.len; +} + fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { const scope: *AllocationScope = @ptrCast(@alignCast(ctx)); return scope.parent.vtable.resize(scope.parent.ptr, buf, alignment, new_len, ret_addr); @@ -109,7 +139,15 @@ fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usiz const scope: *AllocationScope = @ptrCast(@alignCast(ctx)); scope.state.mutex.lock(); defer scope.state.mutex.unlock(); - var invalid = false; + const invalid = scope.trackFreeAssumeLocked(buf, ret_addr); + + scope.parent.vtable.free(scope.parent.ptr, buf, alignment, ret_addr); + + // If asan did not catch the free, panic now. + if (invalid) @panic("Invalid free"); +} + +fn trackFreeAssumeLocked(scope: *AllocationScope, buf: []const u8, ret_addr: usize) bool { if (scope.state.allocations.fetchRemove(buf.ptr)) |entry| { scope.state.total_memory_allocated -= entry.value.len; @@ -125,28 +163,24 @@ fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usiz scope.state.frees.swapRemoveAt(i); } } + return false; } else { - invalid = true; - bun.Output.errGeneric("Invalid free, pointer {any}, len {d}", .{ buf.ptr, buf.len }); if (scope.state.frees.get(buf.ptr)) |free_entry_const| { var free_entry = free_entry_const; bun.Output.printErrorln("Pointer allocated here:", .{}); - bun.crash_handler.dumpStackTrace(free_entry.allocated_at.trace()); + bun.crash_handler.dumpStackTrace(free_entry.allocated_at.trace(), trace_limits); bun.Output.printErrorln("Pointer first freed here:", .{}); - bun.crash_handler.dumpStackTrace(free_entry.freed_at.trace()); + bun.crash_handler.dumpStackTrace(free_entry.freed_at.trace(), free_trace_limits); } // do not panic because address sanitizer will catch this case better. // the log message is in case there is a situation where address // sanitizer does not catch the invalid free. + + return true; } - - scope.parent.vtable.free(scope.parent.ptr, buf, alignment, ret_addr); - - // If asan did not catch the free, panic now. - if (invalid) @panic("Invalid free"); } pub fn assertOwned(scope: *AllocationScope, ptr: anytype) void { @@ -171,11 +205,39 @@ pub fn assertUnowned(scope: *AllocationScope, ptr: anytype) void { defer scope.state.mutex.unlock(); if (scope.state.allocations.getPtr(cast_ptr)) |owned| { Output.debugWarn("Pointer allocated here:"); - bun.crash_handler.dumpStackTrace(owned.allocated_at.trace()); + bun.crash_handler.dumpStackTrace(owned.allocated_at.trace(), trace_limits, trace_limits); } @panic("this pointer was owned by the allocation scope when it was not supposed to be"); } +/// Track an arbitrary pointer. Extra data can be stored in the allocation, +/// which will be printed when a leak is detected. +pub fn trackExternalAllocation(scope: *AllocationScope, ptr: []const u8, ret_addr: ?usize, extra: Extra) void { + if (!enabled) return; + scope.state.mutex.lock(); + defer scope.state.mutex.unlock(); + scope.state.allocations.ensureUnusedCapacity(scope.parent, 1) catch bun.outOfMemory(); + trackAllocationAssumeCapacity(scope, ptr, ptr.len, ret_addr orelse @returnAddress(), extra); +} + +/// Call when the pointer from `trackExternalAllocation` is freed. +/// Returns true if the free was invalid. +pub fn trackExternalFree(scope: *AllocationScope, ptr: []const u8, ret_addr: ?usize) bool { + if (!enabled) return; + scope.state.mutex.lock(); + defer scope.state.mutex.unlock(); + return trackFreeAssumeLocked(scope, ptr, ret_addr orelse @returnAddress()); +} + +pub fn setPointerExtra(scope: *AllocationScope, ptr: *anyopaque, extra: Extra) void { + if (!enabled) return; + scope.state.mutex.lock(); + defer scope.state.mutex.unlock(); + const allocation = scope.state.allocations.getPtr(ptr) orelse + @panic("Pointer not owned by allocation scope"); + allocation.extra = extra; +} + pub inline fn downcast(a: Allocator) ?*AllocationScope { return if (enabled and a.vtable == &vtable) @ptrCast(@alignCast(a.ptr)) diff --git a/src/allocators/linux_memfd_allocator.zig b/src/allocators/linux_memfd_allocator.zig index 25ae9ed14f..dfff46427f 100644 --- a/src/allocators/linux_memfd_allocator.zig +++ b/src/allocators/linux_memfd_allocator.zig @@ -18,32 +18,20 @@ const std = @import("std"); /// the virtual memory. So we should only really use this for large blobs of /// data that we expect to be cloned multiple times. Such as Blob in FormData. pub const LinuxMemFdAllocator = struct { + const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{}); + pub const new = bun.TrivialNew(@This()); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + fd: bun.FileDescriptor = .zero, - ref_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), + ref_count: RefCount, size: usize = 0, var memfd_counter = std.atomic.Value(usize).init(0); - pub usingnamespace bun.New(LinuxMemFdAllocator); - - pub fn ref(this: *LinuxMemFdAllocator) void { - _ = this.ref_count.fetchAdd(1, .monotonic); - } - - pub fn deref(this: *LinuxMemFdAllocator) void { - switch (this.ref_count.fetchSub(1, .monotonic)) { - 1 => { - _ = bun.sys.close(this.fd); - this.destroy(); - }, - 0 => { - // TODO: @branchHint(.cold) after Zig 0.14 upgrade - if (comptime bun.Environment.isDebug) { - std.debug.panic("LinuxMemFdAllocator ref_count underflow", .{}); - } - }, - else => {}, - } + fn deinit(this: *LinuxMemFdAllocator) void { + _ = bun.sys.close(this.fd); + bun.destroy(this); } pub fn allocator(this: *LinuxMemFdAllocator) std.mem.Allocator { @@ -182,7 +170,7 @@ pub const LinuxMemFdAllocator = struct { var linux_memfd_allocator = LinuxMemFdAllocator.new(.{ .fd = fd, - .ref_count = std.atomic.Value(u32).init(1), + .ref_count = .init(), .size = bytes.len, }); @@ -195,7 +183,5 @@ pub const LinuxMemFdAllocator = struct { return .{ .err = err }; }, } - - unreachable; } }; diff --git a/src/async/posix_event_loop.zig b/src/async/posix_event_loop.zig index 59c6a5d4b5..343843ca63 100644 --- a/src/async/posix_event_loop.zig +++ b/src/async/posix_event_loop.zig @@ -1205,11 +1205,11 @@ pub const Closer = struct { fd: bun.FileDescriptor, task: JSC.WorkPoolTask = .{ .callback = &onClose }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn close( fd: bun.FileDescriptor, - /// for compatibiltiy with windows version + /// for compatibility with windows version _: anytype, ) void { bun.assert(fd != bun.invalid_fd); @@ -1218,7 +1218,7 @@ pub const Closer = struct { fn onClose(task: *JSC.WorkPoolTask) void { const closer: *Closer = @fieldParentPtr("task", task); - defer closer.destroy(); + defer bun.destroy(closer); _ = bun.sys.close(closer.fd); } }; diff --git a/src/async/windows_event_loop.zig b/src/async/windows_event_loop.zig index c5c3d3bd29..ecb3b2c21a 100644 --- a/src/async/windows_event_loop.zig +++ b/src/async/windows_event_loop.zig @@ -392,17 +392,16 @@ pub const Waker = struct { }; pub const Closer = struct { - io_request: uv.fs_t = std.mem.zeroes(uv.fs_t), - pub usingnamespace bun.New(@This()); + io_request: uv.fs_t, pub fn close(fd: uv.uv_file, loop: *uv.Loop) void { - var closer = Closer.new(.{}); + const closer = bun.new(Closer, .{ .io_request = std.mem.zeroes(uv.fs_t) }); + // data is not overridden by libuv when calling uv_fs_close, its ok to set it here closer.io_request.data = closer; if (uv.uv_fs_close(loop, &closer.io_request, fd, onClose).errEnum()) |err| { Output.debugWarn("libuv close() failed = {}", .{err}); - closer.destroy(); - return; + bun.destroy(closer); } } @@ -418,6 +417,6 @@ pub const Closer = struct { } req.deinit(); - closer.destroy(); + bun.destroy(closer); } }; diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index 2a6bb81673..edf70de51e 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -1350,7 +1350,7 @@ fn appendRouteEntryPointsIfNotStale(dev: *DevServer, entry_points: *EntryPointLi } }, .html => |*html| { - try entry_points.append(alloc, html.html_bundle.html_bundle.path, .{ .client = true }); + try entry_points.append(alloc, html.html_bundle.bundle.data.path, .{ .client = true }); }, } @@ -1487,7 +1487,7 @@ fn generateHTMLPayload(dev: *DevServer, route_bundle_index: RouteBundle.Index, r const before_head_end = bundled_html[0..script_injection_offset]; const after_head_end = bundled_html[script_injection_offset..]; - var display_name = bun.strings.withoutSuffixComptime(bun.path.basename(html.html_bundle.html_bundle.path), ".html"); + var display_name = bun.strings.withoutSuffixComptime(bun.path.basename(html.html_bundle.bundle.data.path), ".html"); // TODO: function for URL safe chars if (!bun.strings.isAllASCII(display_name)) display_name = "page"; @@ -2622,7 +2622,7 @@ pub fn finalizeBundle( else null // TODO: How does this happen else switch (dev.routeBundlePtr(current_bundle.requests.first.?.data.route_bundle_index).data) { - .html => |html| dev.relativePath(html.html_bundle.html_bundle.path), + .html => |html| dev.relativePath(html.html_bundle.bundle.data.path), .framework => |fw| file_name: { const route = dev.router.routePtr(fw.route_index); const opaque_id = route.file_page.unwrap() orelse @@ -2872,7 +2872,7 @@ fn getOrPutRouteBundle(dev: *DevServer, route: RouteBundle.UnresolvedIndex) !Rou .cached_css_file_array = .empty, } }, .html => |html| brk: { - const incremental_graph_index = try dev.client_graph.insertStaleExtra(html.html_bundle.path, false, true); + const incremental_graph_index = try dev.client_graph.insertStaleExtra(html.bundle.data.path, false, true); dev.client_graph.source_maps.items[incremental_graph_index.get()].extra.empty.html_bundle_route_index = .init(bundle_index.get()); break :brk .{ .html = .{ .html_bundle = html, diff --git a/src/brotli.zig b/src/brotli.zig index 464d4b55c8..8cd6d0eba6 100644 --- a/src/brotli.zig +++ b/src/brotli.zig @@ -59,7 +59,7 @@ pub const BrotliReaderArrayList = struct { finishFlushOp: BrotliEncoder.Operation, fullFlushOp: BrotliEncoder.Operation, - pub usingnamespace bun.New(BrotliReaderArrayList); + pub const new = bun.TrivialNew(BrotliReaderArrayList); pub fn newWithOptions(input: []const u8, list: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, options: DecoderOptions) !*BrotliReaderArrayList { return BrotliReaderArrayList.new(try initWithOptions(input, list, allocator, options, .process, .finish, .flush)); @@ -180,7 +180,7 @@ pub const BrotliReaderArrayList = struct { pub fn deinit(this: *BrotliReaderArrayList) void { this.brotli.destroyInstance(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index c9e01906cd..2df443c1f4 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -93,7 +93,7 @@ pub const TransformTask = struct { global: *JSGlobalObject, replace_exports: Runtime.Features.ReplaceableExport.Map = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const AsyncTransformTask = JSC.ConcurrentPromiseTask(TransformTask); pub const AsyncTransformEventLoopTask = AsyncTransformTask.EventLoopTask; @@ -245,10 +245,9 @@ pub const TransformTask = struct { this.input_code.deinitAndUnprotect(); this.output_code.deref(); if (this.tsconfig) |tsconfig| { - tsconfig.destroy(); + tsconfig.deinit(); } - - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/api/Timer.zig b/src/bun.js/api/Timer.zig index ddacbcf984..8ed72fe959 100644 --- a/src/bun.js/api/Timer.zig +++ b/src/bun.js/api/Timer.zig @@ -530,15 +530,18 @@ pub const All = struct { const uws = bun.uws; pub const TimeoutObject = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + pub usingnamespace JSC.Codegen.JSTimeout; + + ref_count: RefCount, event_loop_timer: EventLoopTimer = .{ .next = .{}, .tag = .TimeoutObject, }, internals: TimerObjectInternals, - ref_count: u32 = 1, - - pub usingnamespace JSC.Codegen.JSTimeout; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn init( globalThis: *JSGlobalObject, @@ -549,7 +552,7 @@ pub const TimeoutObject = struct { arguments_array_or_zero: JSValue, ) JSValue { // internals are initialized by init() - const timeout = TimeoutObject.new(.{ .internals = undefined }); + const timeout = bun.new(TimeoutObject, .{ .ref_count = .init(), .internals = undefined }); const js = timeout.toJS(globalThis); defer js.ensureStillAlive(); timeout.internals.init( @@ -564,8 +567,9 @@ pub const TimeoutObject = struct { return js; } - pub fn deinit(this: *TimeoutObject) void { + fn deinit(this: *TimeoutObject) void { this.internals.deinit(); + bun.destroy(this); } pub fn constructor(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) !*TimeoutObject { @@ -615,15 +619,18 @@ pub const TimeoutObject = struct { }; pub const ImmediateObject = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + pub usingnamespace JSC.Codegen.JSImmediate; + + ref_count: RefCount, event_loop_timer: EventLoopTimer = .{ .next = .{}, .tag = .ImmediateObject, }, internals: TimerObjectInternals, - ref_count: u32 = 1, - - pub usingnamespace JSC.Codegen.JSImmediate; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn init( globalThis: *JSGlobalObject, @@ -632,7 +639,7 @@ pub const ImmediateObject = struct { arguments_array_or_zero: JSValue, ) JSValue { // internals are initialized by init() - const immediate = ImmediateObject.new(.{ .internals = undefined }); + const immediate = bun.new(ImmediateObject, .{ .ref_count = .init(), .internals = undefined }); const js = immediate.toJS(globalThis); defer js.ensureStillAlive(); immediate.internals.init( @@ -647,8 +654,9 @@ pub const ImmediateObject = struct { return js; } - pub fn deinit(this: *ImmediateObject) void { + fn deinit(this: *ImmediateObject) void { this.internals.deinit(); + bun.destroy(this); } pub fn constructor(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) !*ImmediateObject { @@ -1085,8 +1093,8 @@ const TimerObjectInternals = struct { this.setEnableKeepingEventLoopAlive(vm, false); switch (kind) { - .setImmediate => @as(*ImmediateObject, @fieldParentPtr("internals", this)).destroy(), - .setTimeout, .setInterval => @as(*TimeoutObject, @fieldParentPtr("internals", this)).destroy(), + .setImmediate => (@as(*ImmediateObject, @fieldParentPtr("internals", this))).ref_count.assertNoRefs(), + .setTimeout, .setInterval => (@as(*TimeoutObject, @fieldParentPtr("internals", this))).ref_count.assertNoRefs(), } } }; @@ -1339,7 +1347,7 @@ pub const WTFTimer = struct { repeat: bool, lock: bun.Mutex = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn init(run_loop_timer: *RunLoopTimer, js_vm: *VirtualMachine) *WTFTimer { const this = WTFTimer.new(.{ @@ -1417,7 +1425,7 @@ pub const WTFTimer = struct { pub fn deinit(this: *WTFTimer) void { this.cancel(); - this.destroy(); + bun.destroy(this); } export fn WTFTimer__create(run_loop_timer: *RunLoopTimer) ?*anyopaque { diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index 7f1ba9a46a..fc2d5f6a5c 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -979,7 +979,10 @@ pub fn CAresLookup(comptime cares_type: type, comptime type_name: []const u8) ty next: ?*@This() = null, name: []const u8, - pub usingnamespace bun.New(@This()); + pub fn new(data: @This()) *@This() { + bun.assert(data.allocated); // deinit will not free this otherwise + return bun.new(@This(), data); + } pub fn init(resolver: ?*DNSResolver, globalThis: *JSC.JSGlobalObject, _: std.mem.Allocator, name: []const u8) !*@This() { if (resolver) |resolver_| { @@ -1040,7 +1043,7 @@ pub fn CAresLookup(comptime cares_type: type, comptime type_name: []const u8) ty } if (this.allocated) { - this.destroy(); + bun.destroy(this); } } }; @@ -1182,7 +1185,7 @@ pub const InternalDNS = struct { } pub const Request = struct { - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); const Key = struct { host: ?[:0]const u8, hash: u64, @@ -1271,7 +1274,7 @@ pub const InternalDNS = struct { bun.default_allocator.free(host); } - this.destroy(); + bun.destroy(this); } }; @@ -1765,14 +1768,18 @@ comptime { } pub const DNSResolver = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + const log = Output.scoped(.DNSResolver, false); + ref_count: RefCount, channel: ?*c_ares.Channel = null, vm: *JSC.VirtualMachine, polls: PollsMap, options: c_ares.ChannelOptions = .{}, - ref_count: u32 = 1, event_loop_timer: EventLoopTimer = .{ .next = .{}, .tag = .DNSResolver, @@ -1796,7 +1803,6 @@ pub const DNSResolver = struct { pending_nameinfo_cache_cares: NameInfoPendingCache, pub usingnamespace JSC.Codegen.JSDNSResolver; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); const PollsMap = std.AutoArrayHashMap(c_ares.ares_socket_t, *PollType); @@ -1806,6 +1812,9 @@ pub const DNSResolver = struct { Async.FilePoll; const UvDnsPoll = struct { + pub const new = bun.TrivialNew(@This()); + pub const destroy = bun.TrivialDeinit(@This()); + parent: *DNSResolver, socket: c_ares.ares_socket_t, poll: bun.windows.libuv.uv_poll_t, @@ -1813,12 +1822,11 @@ pub const DNSResolver = struct { pub fn fromPoll(poll: *bun.windows.libuv.uv_poll_t) *UvDnsPoll { return @fieldParentPtr("poll", poll); } - - pub usingnamespace bun.New(@This()); }; pub fn setup(allocator: std.mem.Allocator, vm: *JSC.VirtualMachine) DNSResolver { return .{ + .ref_count = .init(), .vm = vm, .polls = DNSResolver.PollsMap.init(allocator), .pending_host_cache_cares = PendingCache.empty, @@ -1842,19 +1850,19 @@ pub const DNSResolver = struct { pub fn init(allocator: std.mem.Allocator, vm: *JSC.VirtualMachine) *DNSResolver { log("init", .{}); - return DNSResolver.new(.setup(allocator, vm)); + return bun.new(DNSResolver, .setup(allocator, vm)); } pub fn finalize(this: *DNSResolver) void { this.deref(); } - pub fn deinit(this: *DNSResolver) void { + fn deinit(this: *DNSResolver) void { if (this.channel) |channel| { channel.deinit(); } - this.destroy(); + bun.destroy(this); } pub const Order = enum(u8) { diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig index f0a560f346..f9e65dd6b0 100644 --- a/src/bun.js/api/bun/h2_frame_parser.zig +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -653,8 +653,11 @@ const Handlers = struct { pub const H2FrameParser = struct { pub const log = Output.scoped(.H2FrameParser, false); + const Self = @This(); pub usingnamespace JSC.Codegen.JSH2FrameParser; - pub usingnamespace bun.NewRefCounted(@This(), deinit, "H2"); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; const ENABLE_AUTO_CORK = true; // ENABLE CORK OPTIMIZATION const ENABLE_ALLOCATOR_POOL = true; // ENABLE HIVE ALLOCATOR OPTIMIZATION @@ -708,7 +711,7 @@ pub const H2FrameParser = struct { autouncork_registered: bool = false, has_nonnative_backpressure: bool = false, - ref_count: u8 = 1, + ref_count: RefCount, threadlocal var shared_request_buffer: [16384]u8 = undefined; /// The streams hashmap may mutate when growing we use this when we need to make sure its safe to iterate over it @@ -771,7 +774,7 @@ pub const H2FrameParser = struct { parser: *H2FrameParser, stream_id: u32, - usingnamespace bun.New(SignalRef); + pub const new = bun.TrivialNew(SignalRef); pub fn isAborted(this: *SignalRef) bool { return this.signal.aborted(); @@ -790,7 +793,7 @@ pub const H2FrameParser = struct { pub fn deinit(this: *SignalRef) void { this.signal.detach(this); this.parser.deref(); - this.destroy(); + bun.destroy(this); } }; const PendingQueue = struct { @@ -4071,6 +4074,7 @@ pub const H2FrameParser = struct { const self = H2FrameParser.pool.?.tryGet() catch bun.outOfMemory(); self.* = H2FrameParser{ + .ref_count = .init(), .handlers = handlers, .globalThis = globalObject, .allocator = bun.default_allocator, @@ -4085,7 +4089,8 @@ pub const H2FrameParser = struct { }; break :brk self; } else { - break :brk H2FrameParser.new(.{ + break :brk bun.new(H2FrameParser, .{ + .ref_count = .init(), .handlers = handlers, .globalThis = globalObject, .allocator = bun.default_allocator, @@ -4212,14 +4217,14 @@ pub const H2FrameParser = struct { this.streams = bun.U32HashMap(Stream).init(bun.default_allocator); } - pub fn deinit(this: *H2FrameParser) void { + fn deinit(this: *H2FrameParser) void { log("deinit", .{}); defer { if (ENABLE_ALLOCATOR_POOL) { H2FrameParser.pool.?.put(this); } else { - this.destroy(); + bun.destroy(this); } } this.detach(true); diff --git a/src/bun.js/api/bun/process.zig b/src/bun.js/api/bun/process.zig index 5f15e5556c..f955d7f7b5 100644 --- a/src/bun.js/api/bun/process.zig +++ b/src/bun.js/api/bun/process.zig @@ -142,13 +142,18 @@ pub const ProcessExitHandler = struct { pub const PidFDType = if (Environment.isLinux) fd_t else u0; pub const Process = struct { + const Self = @This(); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + pid: pid_t = 0, pidfd: PidFDType = 0, status: Status = Status{ .running = {} }, poller: Poller = Poller{ .detached = {}, }, - ref_count: u32 = 1, + ref_count: RefCount, exit_handler: ProcessExitHandler = ProcessExitHandler{}, sync: bool = false, event_loop: JSC.EventLoopHandle, @@ -157,8 +162,6 @@ pub const Process = struct { return @sizeOf(@This()); } - pub usingnamespace bun.NewRefCounted(Process, deinit, null); - pub fn setExitHandler(this: *Process, handler: anytype) void { this.exit_handler.init(handler); } @@ -176,7 +179,8 @@ pub const Process = struct { event_loop: anytype, sync_: bool, ) *Process { - return Process.new(.{ + return bun.new(Process, .{ + .ref_count = .init(), .pid = posix.pid, .pidfd = posix.pidfd orelse 0, .event_loop = JSC.EventLoopHandle.init(event_loop), @@ -487,7 +491,7 @@ pub const Process = struct { fn deinit(this: *Process) void { this.poller.deinit(); - this.destroy(); + bun.destroy(this); } pub fn kill(this: *Process, signal: u8) Maybe(void) { @@ -745,7 +749,8 @@ const WaiterThreadPosix = struct { process: *T, next: ?*TaskQueueEntry = null, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); }; pub const ConcurrentQueue = bun.UnboundedQueue(TaskQueueEntry, .next); @@ -754,7 +759,7 @@ const WaiterThreadPosix = struct { subprocess: *T, rusage: Rusage, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const runFromJSThread = runFromMainThread; @@ -762,7 +767,7 @@ const WaiterThreadPosix = struct { const result = self.result; const subprocess = self.subprocess; const rusage = self.rusage; - self.destroy(); + bun.destroy(self); subprocess.onWaitPidFromWaiterThread(&result, &rusage); } @@ -776,14 +781,14 @@ const WaiterThreadPosix = struct { subprocess: *T, task: JSC.AnyTaskWithExtraContext = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const runFromJSThread = runFromMainThread; pub fn runFromMainThread(self: *@This()) void { const result = self.result; const subprocess = self.subprocess; - self.destroy(); + bun.destroy(self); subprocess.onWaitPidFromWaiterThread(&result, &std.mem.zeroes(Rusage)); } @@ -807,7 +812,7 @@ const WaiterThreadPosix = struct { this.active.ensureUnusedCapacity(batch.count) catch unreachable; while (iter.next()) |task| { this.active.appendAssumeCapacity(task.process); - task.destroy(); + task.deinit(); } } @@ -1673,7 +1678,8 @@ pub fn spawnProcessWindows( uv_process_options.stdio_count = @intCast(stdio_containers.items.len); uv_process_options.exit_cb = &Process.onExitUV; - const process = Process.new(.{ + const process = bun.new(Process, .{ + .ref_count = .init(), .event_loop = options.windows.loop, .pid = 0, }); @@ -1829,7 +1835,7 @@ pub const sync = struct { onDoneCallback: *const fn (*SyncWindowsProcess, tag: bun.FDTag, chunks: []const []u8, err: bun.C.E) void = &SyncWindowsProcess.onReaderDone, tag: bun.FDTag = .none, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); fn onAlloc(_: *SyncWindowsPipeReader, suggested_size: usize) []u8 { return bun.default_allocator.alloc(u8, suggested_size) catch bun.outOfMemory(); @@ -1864,6 +1870,9 @@ pub const sync = struct { }; const SyncWindowsProcess = struct { + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + stderr: []const []u8 = &.{}, stdout: []const []u8 = &.{}, err: bun.C.E = .SUCCESS, @@ -1871,8 +1880,6 @@ pub const sync = struct { process: *Process, status: ?Status = null, - pub usingnamespace bun.New(@This()); - pub fn onProcessExit(this: *SyncWindowsProcess, status: Status, _: *const Rusage) void { this.status = status; this.waiting_count -= 1; @@ -1951,14 +1958,14 @@ pub const sync = struct { var loop: JSC.EventLoopHandle = options.windows.loop; var spawned = switch (try spawnProcessWindows(&options.toSpawnOptions(), argv, envp)) { .err => |err| return .{ .err = err }, - .result => |proces| proces, + .result => |process| process, }; - var this = SyncWindowsProcess.new(.{ + const this = SyncWindowsProcess.new(.{ .process = spawned.toProcess(undefined, true), }); this.process.ref(); this.process.setExitHandler(this); - defer this.destroy(); + defer this.deinit(); this.process.enableKeepingEventLoopAlive(); inline for (.{ .stdout, .stderr }) |tag| { if (@field(spawned, @tagName(tag)) == .buffer) { diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 8d71cb5b12..46c42e5fa2 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -868,6 +868,7 @@ pub const Listener = struct { bun.assert(ssl == listener.ssl); var this_socket = Socket.new(.{ + .ref_count = .init(), .handlers = &listener.handlers, .this_value = .zero, // here we start with a detached socket and attach it later after accept @@ -893,7 +894,8 @@ pub const Listener = struct { const Socket = NewSocket(ssl); bun.assert(ssl == listener.ssl); - var this_socket = Socket.new(.{ + const this_socket = bun.new(Socket, .{ + .ref_count = .init(), .handlers = &listener.handlers, .this_value = .zero, .socket = socket, @@ -1144,6 +1146,7 @@ pub const Listener = struct { if (ssl_enabled) { var tls = TLSSocket.new(.{ + .ref_count = .init(), .handlers = handlers_ptr, .this_value = .zero, .socket = TLSSocket.Socket.detached, @@ -1169,6 +1172,7 @@ pub const Listener = struct { } } else { var tcp = TCPSocket.new(.{ + .ref_count = .init(), .handlers = handlers_ptr, .this_value = .zero, .socket = TCPSocket.Socket.detached, @@ -1240,7 +1244,8 @@ pub const Listener = struct { switch (ssl_enabled) { inline else => |is_ssl_enabled| { const SocketType = NewSocket(is_ssl_enabled); - var socket = SocketType.new(.{ + const socket = bun.new(SocketType, .{ + .ref_count = .init(), .handlers = handlers_ptr, .this_value = .zero, .socket = SocketType.Socket.detached, @@ -1300,13 +1305,19 @@ fn selectALPNCallback( fn NewSocket(comptime ssl: bool) type { return struct { + const This = @This(); + pub const new = bun.TrivialNew(@This()); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + pub const Socket = uws.NewSocketHandler(ssl); socket: Socket, // if the socket owns a context it will be here socket_context: ?*uws.SocketContext, flags: Flags = .{}, - ref_count: u32 = 1, + ref_count: RefCount, wrapped: WrappedType = .none, handlers: *Handlers, this_value: JSC.JSValue = .zero, @@ -1322,7 +1333,6 @@ fn NewSocket(comptime ssl: bool) type { // This is wasteful because it means we are keeping a JSC::Weak for every single open socket has_pending_activity: std.atomic.Value(bool) = std.atomic.Value(bool).init(true), native_callback: NativeCallbacks = .none, - pub usingnamespace bun.NewRefCounted(@This(), deinit, "Socket"); // We use this direct callbacks on HTTP2 when available pub const NativeCallbacks = union(enum) { @@ -1349,7 +1359,6 @@ fn NewSocket(comptime ssl: bool) type { } }; - const This = @This(); const log = Output.scoped(.Socket, false); const WriteResult = union(enum) { fail: void, @@ -2586,7 +2595,7 @@ fn NewSocket(comptime ssl: bool) type { this.socket_context = null; socket_context.deinit(ssl); } - this.destroy(); + bun.destroy(this); } pub fn finalize(this: *This) void { @@ -3453,7 +3462,8 @@ fn NewSocket(comptime ssl: bool) type { handlers_ptr.* = handlers; handlers_ptr.is_server = is_server; handlers_ptr.protect(); - var tls = TLSSocket.new(.{ + var tls = bun.new(TLSSocket, .{ + .ref_count = .init(), .handlers = handlers_ptr, .this_value = .zero, .socket = TLSSocket.Socket.detached, @@ -3552,7 +3562,8 @@ fn NewSocket(comptime ssl: bool) type { raw_handlers_ptr.protect(); - var raw = TLSSocket.new(.{ + const raw = bun.new(TLSSocket, .{ + .ref_count = .init(), .handlers = raw_handlers_ptr, .this_value = .zero, .socket = new_socket, @@ -3587,7 +3598,7 @@ fn NewSocket(comptime ssl: bool) type { this.poll_ref.disable(); this.flags.is_active = false; // will free handlers when hits 0 active connections - // the connection can be upgraded inside a handler call so we need to garantee that it will be still alive + // the connection can be upgraded inside a handler call so we need to guarantee that it will be still alive this.handlers.markInactive(); this.has_pending_activity.store(false, .release); @@ -3744,7 +3755,7 @@ pub const DuplexUpgradeContext = struct { Close, }; - usingnamespace bun.New(DuplexUpgradeContext); + pub const new = bun.TrivialNew(DuplexUpgradeContext); fn onOpen(this: *DuplexUpgradeContext) void { this.is_open = true; @@ -3872,7 +3883,7 @@ pub const WindowsNamedPipeListeningContext = if (Environment.isWindows) struct { globalThis: *JSC.JSGlobalObject, vm: *JSC.VirtualMachine, ctx: ?*BoringSSL.SSL_CTX = null, // server reuses the same ctx - usingnamespace bun.New(WindowsNamedPipeListeningContext); + pub const new = bun.TrivialNew(WindowsNamedPipeListeningContext); fn onClientConnect(this: *WindowsNamedPipeListeningContext, status: uv.ReturnCode) void { if (status != uv.ReturnCode.zero or this.vm.isShuttingDown() or this.listener == null) { @@ -3972,7 +3983,7 @@ pub const WindowsNamedPipeListeningContext = if (Environment.isWindows) struct { this.ctx = null; BoringSSL.SSL_CTX_free(ctx); } - this.destroy(); + bun.destroy(this); } } else void; pub const WindowsNamedPipeContext = if (Environment.isWindows) struct { @@ -3996,7 +4007,7 @@ pub const WindowsNamedPipeContext = if (Environment.isWindows) struct { none: void, }; - usingnamespace bun.New(WindowsNamedPipeContext); + pub const new = bun.TrivialNew(WindowsNamedPipeContext); const log = Output.scoped(.WindowsNamedPipeContext, false); fn onOpen(this: *WindowsNamedPipeContext) void { @@ -4252,7 +4263,7 @@ pub const WindowsNamedPipeContext = if (Environment.isWindows) struct { } this.named_pipe.deinit(); - this.destroy(); + bun.destroy(this); } } else void; @@ -4327,7 +4338,8 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *JSC.JSGlobalObject, callframe: *JSC.C handlers_ptr.* = handlers; handlers_ptr.is_server = is_server; handlers_ptr.protect(); - var tls = TLSSocket.new(.{ + var tls = bun.new(TLSSocket, .{ + .ref_count = .init(), .handlers = handlers_ptr, .this_value = .zero, .socket = TLSSocket.Socket.detached, diff --git a/src/bun.js/api/bun/socket/SocketAddress.zig b/src/bun.js/api/bun/socket/SocketAddress.zig index 5de8d612f0..cb4ad1fe4c 100644 --- a/src/bun.js/api/bun/socket/SocketAddress.zig +++ b/src/bun.js/api/bun/socket/SocketAddress.zig @@ -5,6 +5,8 @@ //! TODO: add a inspect method (under `Symbol.for("nodejs.util.inspect.custom")`). //! Requires updating bindgen. const SocketAddress = @This(); +pub usingnamespace JSC.Codegen.JSSocketAddress; +pub const new = bun.TrivialNew(SocketAddress); // NOTE: not std.net.Address b/c .un is huge and we don't use it. // NOTE: not C.sockaddr_storage b/c it's _huge_. we need >= 28 bytes for sockaddr_in6, @@ -115,9 +117,6 @@ pub const Options = struct { } }; -pub usingnamespace JSC.Codegen.JSSocketAddress; -pub usingnamespace bun.New(SocketAddress); - // ============================================================================= // ============================== STATIC METHODS =============================== // ============================================================================= @@ -317,15 +316,15 @@ pub fn initIPv6(addr: [16]u8, port_: u16, flowinfo: u32, scope_id: u32) SocketAd // ================================ DESTRUCTORS ================================ // ============================================================================= -pub fn deinit(this: *SocketAddress) void { +fn deinit(this: *SocketAddress) void { // .deref() on dead strings is a no-op. this._presentation.deref(); + bun.destroy(this); } pub fn finalize(this: *SocketAddress) void { JSC.markBinding(@src()); this.deinit(); - this.destroy(); } // ============================================================================= diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index d6edf19a1f..ac13b24cea 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -2,8 +2,11 @@ //! code for `Bun.spawnSync` const Subprocess = @This(); pub usingnamespace JSC.Codegen.JSSubprocess; -pub usingnamespace bun.NewRefCounted(@This(), deinit, null); +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; +ref_count: RefCount, process: *Process, stdin: Writable, stdout: Readable, @@ -32,7 +35,6 @@ ipc_callback: JSC.Strong = .empty, flags: Flags = .{}, weak_file_sink_stdin_ptr: ?*JSC.WebCore.FileSink = null, -ref_count: u32 = 1, abort_signal: ?*JSC.AbortSignal = null, event_loop_timer_refd: bool = false, @@ -913,10 +915,13 @@ pub fn NewStaticPipeWriter(comptime ProcessType: type) type { source: Source = .{ .detached = {} }, process: *ProcessType = undefined, event_loop: JSC.EventLoopHandle, - ref_count: u32 = 1, + ref_count: WriterRefCount, buffer: []const u8 = "", - pub usingnamespace bun.NewRefCounted(@This(), _deinit, null); + // It seems there is a bug in the Zig compiler. We'll get back to this one later + const WriterRefCount = bun.ptr.RefCount(This, "ref_count", _deinit, .{}); + pub usingnamespace bun.ptr.RefCount(This, "ref_count", _deinit, .{}); + const This = @This(); const print = bun.Output.scoped(.StaticPipeWriter, false); @@ -949,7 +954,8 @@ pub fn NewStaticPipeWriter(comptime ProcessType: type) type { } pub fn create(event_loop: anytype, subprocess: *ProcessType, result: StdioResult, source: Source) *This { - const this = This.new(.{ + const this = bun.new(This, .{ + .ref_count = .init(), .event_loop = JSC.EventLoopHandle.init(event_loop), .process = subprocess, .stdio_result = result, @@ -1006,7 +1012,7 @@ pub fn NewStaticPipeWriter(comptime ProcessType: type) type { fn _deinit(this: *This) void { this.writer.end(); this.source.detach(); - this.destroy(); + bun.destroy(this); } pub fn memoryCost(this: *const This) usize { @@ -1030,10 +1036,14 @@ pub fn NewStaticPipeWriter(comptime ProcessType: type) type { } pub const PipeReader = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", PipeReader.deinit, .{}); + pub const ref = PipeReader.RefCount.ref; + pub const deref = PipeReader.RefCount.deref; + reader: IOReader = undefined, process: ?*Subprocess = null, event_loop: *JSC.EventLoop = undefined, - ref_count: u32 = 1, + ref_count: PipeReader.RefCount, state: union(enum) { pending: void, done: []u8, @@ -1044,8 +1054,6 @@ pub const PipeReader = struct { pub const IOReader = bun.io.BufferedReader; pub const Poll = IOReader; - pub usingnamespace bun.NewRefCounted(PipeReader, _deinit, null); - pub fn memoryCost(this: *const PipeReader) usize { return this.reader.memoryCost(); } @@ -1063,7 +1071,8 @@ pub const PipeReader = struct { } pub fn create(event_loop: *JSC.EventLoop, process: *Subprocess, result: StdioResult) *PipeReader { - var this = PipeReader.new(.{ + var this = bun.new(PipeReader, .{ + .ref_count = .init(), .process = process, .reader = IOReader.init(@This()), .event_loop = event_loop, @@ -1211,7 +1220,7 @@ pub const PipeReader = struct { return this.event_loop.virtual_machine.uwsLoop(); } - fn _deinit(this: *PipeReader) void { + fn deinit(this: *PipeReader) void { if (comptime Environment.isPosix) { bun.assert(this.reader.isDone()); } @@ -1225,7 +1234,7 @@ pub const PipeReader = struct { } this.reader.deinit(); - this.destroy(); + bun.destroy(this); } }; @@ -1448,10 +1457,10 @@ const Writable = union(enum) { // So, let's not do that. // https://github.com/oven-sh/bun/pull/14092 bun.debugAssert(!subprocess.flags.deref_on_stdin_destroyed); - const debug_ref_count: if (Environment.isDebug) u32 else u0 = if (Environment.isDebug) subprocess.ref_count else 0; + const debug_ref_count = if (Environment.isDebug) subprocess.ref_count else 0; pipe.onAttachedProcessExit(); - if (comptime Environment.isDebug) { - bun.debugAssert(subprocess.ref_count == debug_ref_count); + if (Environment.isDebug) { + bun.debugAssert(subprocess.ref_count.active_counts == debug_ref_count.active_counts); } return pipe.toJS(globalThis); } else { @@ -1699,9 +1708,9 @@ pub fn finalizeStreams(this: *Subprocess) void { this.on_disconnect_callback.deinit(); } -pub fn deinit(this: *Subprocess) void { +fn deinit(this: *Subprocess) void { log("deinit", .{}); - this.destroy(); + bun.destroy(this); } fn clearAbortSignal(this: *Subprocess) void { @@ -2266,7 +2275,8 @@ pub fn spawnMaybeSync( const process = spawned.toProcess(loop, is_sync); - var subprocess = Subprocess.new(.{ + var subprocess = bun.new(Subprocess, .{ + .ref_count = .init(), .globalThis = globalThis, .process = process, .pid_rusage = null, @@ -2323,7 +2333,7 @@ pub fn spawnMaybeSync( ), // 1. JavaScript. // 2. Process. - .ref_count = 2, + .ref_count = .initExactRefs(2), .stdio_pipes = spawned.extra_pipes.moveToUnmanaged(), .on_exit_callback = JSC.Strong.create(on_exit_callback, globalThis), .on_disconnect_callback = JSC.Strong.create(on_disconnect_callback, globalThis), diff --git a/src/bun.js/api/bun/udp_socket.zig b/src/bun.js/api/bun/udp_socket.zig index d9043c6e17..144d5a30ef 100644 --- a/src/bun.js/api/bun/udp_socket.zig +++ b/src/bun.js/api/bun/udp_socket.zig @@ -296,7 +296,7 @@ pub const UDPSocket = struct { return this.js_refcount.load(.monotonic) > 0; } - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn udpSocket(globalThis: *JSGlobalObject, options: JSValue) bun.JSError!JSValue { log("udpSocket", .{}); @@ -901,7 +901,7 @@ pub const UDPSocket = struct { bun.assert(this.closed); this.poll_ref.disable(); this.config.deinit(); - this.destroy(); + bun.destroy(this); } pub fn jsConnect(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { diff --git a/src/bun.js/api/crypto/CryptoHasher.zig b/src/bun.js/api/crypto/CryptoHasher.zig index e723652ac3..3190217847 100644 --- a/src/bun.js/api/crypto/CryptoHasher.zig +++ b/src/bun.js/api/crypto/CryptoHasher.zig @@ -8,7 +8,7 @@ pub const CryptoHasher = union(enum) { const Digest = EVP.Digest; pub usingnamespace JSC.Codegen.JSCryptoHasher; - usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); // For using only CryptoHasherZig in c++ pub const Extern = struct { @@ -364,16 +364,13 @@ pub const CryptoHasher = union(enum) { globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) bun.JSError!JSC.JSValue { - var new: CryptoHasher = undefined; - switch (this.*) { - .evp => |*inner| { - new = .{ .evp = inner.copy(globalObject.bunVM().rareData().boringEngine()) catch bun.outOfMemory() }; - }, - .hmac => |inner| { + const copied: CryptoHasher = switch (this.*) { + .evp => |*inner| .{ .evp = inner.copy(globalObject.bunVM().rareData().boringEngine()) catch bun.outOfMemory() }, + .hmac => |inner| brk: { const hmac = inner orelse { return throwHmacConsumed(globalObject); }; - new = .{ + break :brk .{ .hmac = hmac.copy() catch { const err = createCryptoError(globalObject, BoringSSL.ERR_get_error()); BoringSSL.ERR_clear_error(); @@ -381,11 +378,9 @@ pub const CryptoHasher = union(enum) { }, }; }, - .zig => |*inner| { - new = .{ .zig = inner.copy() }; - }, - } - return CryptoHasher.new(new).toJS(globalObject); + .zig => |*inner| .{ .zig = inner.copy() }, + }; + return CryptoHasher.new(copied).toJS(globalObject); } pub fn digest_(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.Node.StringOrBuffer) bun.JSError!JSC.JSValue { @@ -477,7 +472,7 @@ pub const CryptoHasher = union(enum) { } }, } - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/api/crypto/HMAC.zig b/src/bun.js/api/crypto/HMAC.zig index 7fe2ebb0fc..e0aae477e0 100644 --- a/src/bun.js/api/crypto/HMAC.zig +++ b/src/bun.js/api/crypto/HMAC.zig @@ -1,7 +1,7 @@ ctx: BoringSSL.HMAC_CTX, algorithm: EVP.Algorithm, -pub usingnamespace bun.New(@This()); +pub const new = bun.TrivialNew(@This()); pub fn init(algorithm: EVP.Algorithm, key: []const u8) ?*HMAC { const md = algorithm.md() orelse return null; @@ -46,7 +46,7 @@ pub fn final(this: *HMAC, out: []u8) []u8 { pub fn deinit(this: *HMAC) void { BoringSSL.HMAC_CTX_cleanup(&this.ctx); - this.destroy(); + bun.destroy(this); } const bun = @import("root").bun; diff --git a/src/bun.js/api/crypto/PBKDF2.zig b/src/bun.js/api/crypto/PBKDF2.zig index 60eef0784c..8c335d9dcb 100644 --- a/src/bun.js/api/crypto/PBKDF2.zig +++ b/src/bun.js/api/crypto/PBKDF2.zig @@ -42,7 +42,7 @@ pub const Job = struct { any_task: JSC.AnyTask = undefined, poll: Async.KeepAlive = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn runTask(task: *JSC.WorkPoolTask) void { const job: *PBKDF2.Job = @fieldParentPtr("task", task); @@ -90,7 +90,7 @@ pub const Job = struct { this.pbkdf2.deinitAndUnprotect(); this.promise.deinit(); bun.default_allocator.free(this.output); - this.destroy(); + bun.destroy(this); } pub fn create(vm: *JSC.VirtualMachine, globalThis: *JSC.JSGlobalObject, data: *const PBKDF2) *Job { diff --git a/src/bun.js/api/crypto/PasswordObject.zig b/src/bun.js/api/crypto/PasswordObject.zig index ac371f3793..0d91767bce 100644 --- a/src/bun.js/api/crypto/PasswordObject.zig +++ b/src/bun.js/api/crypto/PasswordObject.zig @@ -356,7 +356,7 @@ pub const JSPasswordObject = struct { ref: Async.KeepAlive = .{}, task: JSC.WorkPoolTask = .{ .callback = &run }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const Result = struct { value: Value, @@ -366,7 +366,7 @@ pub const JSPasswordObject = struct { promise: JSC.JSPromise.Strong, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const Value = union(enum) { err: PasswordObject.HashError, @@ -390,12 +390,12 @@ pub const JSPasswordObject = struct { switch (this.value) { .err => { const error_instance = this.value.toErrorInstance(global); - this.destroy(); + bun.destroy(this); promise.reject(global, error_instance); }, .hash => |value| { const js_string = JSC.ZigString.init(value).toJS(global); - this.destroy(); + bun.destroy(this); promise.resolve(global, js_string); }, } @@ -405,7 +405,7 @@ pub const JSPasswordObject = struct { pub fn deinit(this: *HashJob) void { this.promise.deinit(); bun.freeSensitive(bun.default_allocator, this.password); - this.destroy(); + bun.destroy(this); } pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value { @@ -568,7 +568,7 @@ pub const JSPasswordObject = struct { ref: Async.KeepAlive = .{}, task: JSC.WorkPoolTask = .{ .callback = &run }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const Result = struct { value: Value, @@ -578,7 +578,7 @@ pub const JSPasswordObject = struct { promise: JSC.JSPromise.Strong, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const Value = union(enum) { err: PasswordObject.HashError, @@ -602,11 +602,11 @@ pub const JSPasswordObject = struct { switch (this.value) { .err => { const error_instance = this.value.toErrorInstance(global); - this.destroy(); + bun.destroy(this); promise.reject(global, error_instance); }, .pass => |pass| { - this.destroy(); + bun.destroy(this); promise.resolve(global, JSC.JSValue.jsBoolean(pass)); }, } @@ -619,7 +619,7 @@ pub const JSPasswordObject = struct { bun.freeSensitive(bun.default_allocator, this.password); bun.freeSensitive(bun.default_allocator, this.prev_hash); - this.destroy(); + bun.destroy(this); } pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value { diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 898c57e5e3..4a08c77d5f 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -790,7 +790,7 @@ pub const FFI = struct { } } - // TODO: pub usingnamespace bun.New(FFI) + // TODO: pub const new = bun.TrivialNew(FFI) var lib = bun.default_allocator.create(FFI) catch bun.outOfMemory(); lib.* = .{ .dylib = null, diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index e22072c620..41810e45ca 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -13,12 +13,14 @@ const LOLHTML = bun.LOLHTML; const SelectorMap = std.ArrayListUnmanaged(*LOLHTML.HTMLSelector); pub const LOLHTMLContext = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, selectors: SelectorMap = .{}, element_handlers: std.ArrayListUnmanaged(*ElementHandler) = .{}, document_handlers: std.ArrayListUnmanaged(*DocumentHandler) = .{}, - ref_count: u32 = 1, - - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); fn deinit(this: *LOLHTMLContext) void { for (this.selectors.items) |selector| { @@ -39,7 +41,7 @@ pub const LOLHTMLContext = struct { this.document_handlers.deinit(bun.default_allocator); this.document_handlers = .{}; - this.destroy(); + bun.destroy(this); } }; pub const HTMLRewriter = struct { @@ -52,7 +54,9 @@ pub const HTMLRewriter = struct { const rewriter = bun.default_allocator.create(HTMLRewriter) catch bun.outOfMemory(); rewriter.* = HTMLRewriter{ .builder = LOLHTML.HTMLRewriter.Builder.init(), - .context = LOLHTMLContext.new(.{}), + .context = bun.new(LOLHTMLContext, .{ + .ref_count = .init(), + }), }; bun.Analytics.Features.html_rewriter += 1; return rewriter; @@ -386,6 +390,11 @@ pub const HTMLRewriter = struct { }; pub const BufferOutputSink = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, global: *JSGlobalObject, bytes: bun.MutableString, rewriter: ?*LOLHTML.HTMLRewriter = null, @@ -394,12 +403,11 @@ pub const HTMLRewriter = struct { response_value: JSC.Strong = .empty, bodyValueBufferer: ?JSC.WebCore.BodyValueBufferer = null, tmp_sync_error: ?*JSC.JSValue = null, - ref_count: u32 = 1, - pub usingnamespace bun.NewRefCounted(BufferOutputSink, deinit, null); // const log = bun.Output.scoped(.BufferOutputSink, false); pub fn init(context: *LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSC.JSValue { - var sink = BufferOutputSink.new(.{ + var sink = bun.new(BufferOutputSink, .{ + .ref_count = .init(), .global = global, .bytes = bun.MutableString.initEmpty(bun.default_allocator), .rewriter = null, @@ -620,7 +628,7 @@ pub const HTMLRewriter = struct { rewriter.deinit(); } - this.destroy(); + bun.destroy(this); } }; @@ -882,10 +890,9 @@ fn HandlerCallback( var wrapper = ZigType.init(value); - // All of these start with a ref_count of 2. - // 1. For this scope. - // 2. For the JS value. - bun.debugAssert(wrapper.ref_count == 2); + // When using RefCount, we don't check the count value directly + // as it's an opaque type now + // The init values are handled by bun.new with .init() defer { @field(wrapper, field_name) = null; @@ -1062,13 +1069,20 @@ fn htmlStringValue(input: LOLHTML.HTMLString, globalObject: *JSGlobalObject) JSV } pub const TextChunk = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, text_chunk: ?*LOLHTML.TextChunk = null, - ref_count: u32 = 1, pub usingnamespace JSC.Codegen.JSTextChunk; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); + pub fn init(text_chunk: *LOLHTML.TextChunk) *TextChunk { - return TextChunk.new(.{ .text_chunk = text_chunk, .ref_count = 2 }); + return bun.new(TextChunk, .{ + .ref_count = .init(), + .text_chunk = text_chunk, + }); } fn contentHandler(this: *TextChunk, comptime Callback: (fn (*LOLHTML.TextChunk, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { @@ -1152,19 +1166,23 @@ pub const TextChunk = struct { this.deref(); } - pub fn deinit(this: *TextChunk) void { + fn deinit(this: *TextChunk) void { this.text_chunk = null; - this.destroy(); + bun.destroy(this); } }; pub const DocType = struct { - doctype: ?*LOLHTML.DocType = null, - ref_count: u32 = 1, + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; - pub fn deinit(this: *DocType) void { + ref_count: RefCount, + doctype: ?*LOLHTML.DocType = null, + + fn deinit(this: *DocType) void { this.doctype = null; - this.destroy(); + bun.destroy(this); } pub fn finalize(this: *DocType) void { @@ -1172,10 +1190,12 @@ pub const DocType = struct { } pub fn init(doctype: *LOLHTML.DocType) *DocType { - return DocType.new(.{ .doctype = doctype, .ref_count = 2 }); + return bun.new(DocType, .{ + .ref_count = .init(), + .doctype = doctype, + }); } - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub usingnamespace JSC.Codegen.JSDocType; /// The doctype name. @@ -1239,14 +1259,20 @@ pub const DocType = struct { }; pub const DocEnd = struct { - doc_end: ?*LOLHTML.DocEnd, - ref_count: u32 = 1, + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, + doc_end: ?*LOLHTML.DocEnd, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub usingnamespace JSC.Codegen.JSDocEnd; pub fn init(doc_end: *LOLHTML.DocEnd) *DocEnd { - return DocEnd.new(.{ .doc_end = doc_end, .ref_count = 2 }); + return bun.new(DocEnd, .{ + .ref_count = .init(), + .doc_end = doc_end, + }); } fn contentHandler(this: *DocEnd, comptime Callback: (fn (*LOLHTML.DocEnd, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { @@ -1281,21 +1307,27 @@ pub const DocEnd = struct { this.deref(); } - pub fn deinit(this: *DocEnd) void { + fn deinit(this: *DocEnd) void { this.doc_end = null; - this.destroy(); + bun.destroy(this); } }; pub const Comment = struct { - comment: ?*LOLHTML.Comment = null, - ref_count: u32 = 1, + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, + comment: ?*LOLHTML.Comment = null, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub usingnamespace JSC.Codegen.JSComment; pub fn init(comment: *LOLHTML.Comment) *Comment { - return Comment.new(.{ .comment = comment, .ref_count = 2 }); + return bun.new(Comment, .{ + .ref_count = .init(), + .comment = comment, + }); } fn contentHandler(this: *Comment, comptime Callback: (fn (*LOLHTML.Comment, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { @@ -1397,27 +1429,34 @@ pub const Comment = struct { this.deref(); } - pub fn deinit(this: *Comment) void { + fn deinit(this: *Comment) void { this.comment = null; - this.destroy(); + bun.destroy(this); } }; pub const EndTag = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, end_tag: ?*LOLHTML.EndTag, - ref_count: u32 = 1, pub fn init(end_tag: *LOLHTML.EndTag) *EndTag { - return EndTag.new(.{ .end_tag = end_tag, .ref_count = 2 }); + return bun.new(EndTag, .{ + .ref_count = .init(), + .end_tag = end_tag, + }); } pub fn finalize(this: *EndTag) void { this.deref(); } - pub fn deinit(this: *EndTag) void { + fn deinit(this: *EndTag) void { this.end_tag = null; - this.destroy(); + bun.destroy(this); } pub const Handler = struct { @@ -1436,7 +1475,6 @@ pub const EndTag = struct { }; pub usingnamespace JSC.Codegen.JSEndTag; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); fn contentHandler(this: *EndTag, comptime Callback: (fn (*LOLHTML.EndTag, []const u8, bool) LOLHTML.Error!void), thisObject: JSValue, globalObject: *JSGlobalObject, content: ZigString, contentOptions: ?ContentOptions) JSValue { if (this.end_tag == null) @@ -1529,11 +1567,18 @@ pub const EndTag = struct { }; pub const AttributeIterator = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, iterator: ?*LOLHTML.Attribute.Iterator = null, - ref_count: u32 = 1, pub fn init(iterator: *LOLHTML.Attribute.Iterator) *AttributeIterator { - return AttributeIterator.new(.{ .iterator = iterator, .ref_count = 2 }); + return bun.new(AttributeIterator, .{ + .ref_count = .init(), + .iterator = iterator, + }); } fn detach(this: *AttributeIterator) void { @@ -1548,13 +1593,12 @@ pub const AttributeIterator = struct { this.deref(); } - pub fn deinit(this: *AttributeIterator) void { + fn deinit(this: *AttributeIterator) void { this.detach(); - this.destroy(); + bun.destroy(this); } pub usingnamespace JSC.Codegen.JSAttributeIterator; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn next(this: *AttributeIterator, globalObject: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue { const done_label = JSC.ZigString.static("done"); @@ -1587,23 +1631,29 @@ pub const AttributeIterator = struct { } }; pub const Element = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, element: ?*LOLHTML.Element = null, - ref_count: u32 = 1, pub usingnamespace JSC.Codegen.JSElement; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn init(element: *LOLHTML.Element) *Element { - return Element.new(.{ .element = element, .ref_count = 2 }); + return bun.new(Element, .{ + .ref_count = .init(), + .element = element, + }); } pub fn finalize(this: *Element) void { this.deref(); } - pub fn deinit(this: *Element) void { + fn deinit(this: *Element) void { this.element = null; - this.destroy(); + bun.destroy(this); } pub fn onEndTag_( @@ -1881,7 +1931,10 @@ pub const Element = struct { return JSValue.jsUndefined(); const iter = this.element.?.attributes() orelse return createLOLHTMLError(globalObject); - var attr_iter = AttributeIterator.new(.{ .iterator = iter, .ref_count = 1 }); + var attr_iter = bun.new(AttributeIterator, .{ + .ref_count = .init(), + .iterator = iter, + }); return attr_iter.toJS(globalObject); } }; diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 6fd2ccbbe5..90b96fe574 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -2201,7 +2201,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp /// this prevents an extra pthread_getspecific() call which shows up in profiling allocator: std.mem.Allocator, req: ?*uws.Request, - request_weakref: Request.WeakRef = .{}, + request_weakref: Request.WeakRef = .empty, signal: ?*JSC.WebCore.AbortSignal = null, method: HTTP.Method, cookies: ?*JSC.WebCore.CookieMap = null, @@ -2758,7 +2758,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } // we can already clean this strong refs request.internal_event_callback.deinit(); - this.request_weakref.deinit(); + this.request_weakref.deref(); } // if signal is not aborted, abort the signal if (this.signal) |signal| { @@ -2834,7 +2834,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp request.request_context = AnyRequestContext.Null; // we can already clean this strong refs request.internal_event_callback.deinit(); - this.request_weakref.deinit(); + this.request_weakref.deref(); } // if signal is not aborted, abort the signal @@ -4911,10 +4911,12 @@ pub const NodeHTTPResponse = @import("./server/NodeHTTPResponse.zig"); /// State machine to handle loading plugins asynchronously. This structure is not thread-safe. const ServePlugins = struct { state: State, - ref_count: u32 = 1, + ref_count: RefCount, /// Reference count is incremented while there are other objects that are waiting on plugin loads. - pub usingnamespace bun.NewRefCounted(ServePlugins, deinit, null); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; pub const State = union(enum) { unqueued: []const []const u8, @@ -4943,17 +4945,17 @@ const ServePlugins = struct { }; pub fn init(plugins: []const []const u8) *ServePlugins { - return ServePlugins.new(.{ .state = .{ .unqueued = plugins } }); + return bun.new(ServePlugins, .{ .ref_count = .init(), .state = .{ .unqueued = plugins } }); } - pub fn deinit(this: *ServePlugins) void { + fn deinit(this: *ServePlugins) void { switch (this.state) { .unqueued => {}, .pending => assert(false), // should have one ref while pending! .loaded => |loaded| loaded.deinit(), .err => {}, } - this.destroy(); + bun.destroy(this); } pub fn getOrStartLoad(this: *ServePlugins, global: *JSC.JSGlobalObject, cb: Callback) bun.OOM!GetOrStartLoadResult { @@ -5138,7 +5140,7 @@ const PluginsResult = union(enum) { pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { return struct { pub usingnamespace NamespaceType; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const ssl_enabled = ssl_enabled_; pub const debug_mode = debug_mode_; @@ -5573,7 +5575,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp upgrader.signal = null; upgrader.resp = null; request.request_context = AnyRequestContext.Null; - upgrader.request_weakref.deinit(); + upgrader.request_weakref.deref(); data_value.ensureStillAlive(); const ws = ServerWebSocket.new(.{ @@ -6184,7 +6186,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp plugins.deref(); } - this.destroy(); + bun.destroy(this); } pub fn init(config: *ServerConfig, global: *JSGlobalObject) bun.JSOOM!*ThisServer { @@ -6768,7 +6770,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp .signal = signal.ref(), .body = body.ref(), }); - ctx.request_weakref = Request.WeakRef.create(request_object); + ctx.request_weakref = .initRef(request_object); if (comptime debug_mode) { ctx.flags.is_web_browser_navigation = brk: { @@ -6891,7 +6893,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp .body = body.ref(), }); ctx.upgrade_context = upgrade_ctx; - ctx.request_weakref = Request.WeakRef.create(request_object); + ctx.request_weakref = .initRef(request_object); // We keep the Request object alive for the duration of the request so that we can remove the pointer to the UWS request object. var args = [_]JSC.JSValue{ request_object.toJS(this.globalThis), @@ -7325,7 +7327,7 @@ pub const ServerAllConnectionsClosedTask = struct { promise: JSC.JSPromise.Strong, tracker: JSC.AsyncTaskTracker, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn runFromJSThread(this: *ServerAllConnectionsClosedTask, vm: *JSC.VirtualMachine) void { httplog("ServerAllConnectionsClosedTask runFromJSThread", .{}); @@ -7337,7 +7339,7 @@ pub const ServerAllConnectionsClosedTask = struct { var promise = this.promise; defer promise.deinit(); - this.destroy(); + bun.destroy(this); if (!vm.isShuttingDown()) { promise.resolve(globalObject, .undefined); diff --git a/src/bun.js/api/server/HTMLBundle.zig b/src/bun.js/api/server/HTMLBundle.zig index ed84c4cc10..d3bc6d45b6 100644 --- a/src/bun.js/api/server/HTMLBundle.zig +++ b/src/bun.js/api/server/HTMLBundle.zig @@ -3,16 +3,19 @@ //! is done lazily (state held in HTMLBundle.Route or DevServer.RouteBundle.HTML). pub const HTMLBundle = @This(); pub usingnamespace JSC.Codegen.JSHTMLBundle; -// HTMLBundle can be owned by JavaScript as well as any number of Server instances. -pub usingnamespace bun.NewRefCounted(HTMLBundle, deinit, null); +/// HTMLBundle can be owned by JavaScript as well as any number of Server instances. +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; -ref_count: u32 = 1, +ref_count: RefCount, global: *JSGlobalObject, path: []const u8, /// Initialize an HTMLBundle given a path. pub fn init(global: *JSGlobalObject, path: []const u8) !*HTMLBundle { - return HTMLBundle.new(.{ + return bun.new(HTMLBundle, .{ + .ref_count = .init(), .global = global, .path = try bun.default_allocator.dupe(u8, path), }); @@ -22,9 +25,9 @@ pub fn finalize(this: *HTMLBundle) void { this.deref(); } -pub fn deinit(this: *HTMLBundle) void { +fn deinit(this: *HTMLBundle) void { bun.default_allocator.free(this.path); - this.destroy(); + bun.destroy(this); } pub fn getIndex(this: *HTMLBundle, globalObject: *JSGlobalObject) JSValue { @@ -40,9 +43,13 @@ pub const HTMLBundleRoute = Route; /// reference-counted because a server can have multiple instances of the same /// html file on multiple endpoints. pub const Route = struct { - /// Rename to `bundle` - html_bundle: *HTMLBundle, - ref_count: u32 = 1, + /// One HTMLBundle.Route can be specified multiple times + const RefCount = bun.ptr.RefCount(@This(), "ref_count", Route.deinit, .{}); + pub const ref = Route.RefCount.ref; + pub const deref = Route.RefCount.deref; + + bundle: RefPtr(HTMLBundle), + ref_count: Route.RefCount, // TODO: attempt to remove the null case. null is only present during server // initialization as only a ServerConfig object is present. server: ?AnyServer = null, @@ -54,9 +61,6 @@ pub const Route = struct { /// When state == .pending, incomplete responses are stored here. pending_responses: std.ArrayListUnmanaged(*PendingResponse) = .{}, - /// One HTMLBundle.Route can be specified multiple times - pub usingnamespace bun.NewRefCounted(@This(), _deinit, null); - pub fn memoryCost(this: *const Route) usize { var cost: usize = 0; cost += @sizeOf(Route); @@ -66,16 +70,23 @@ pub const Route = struct { } pub fn init(html_bundle: *HTMLBundle) *Route { - html_bundle.ref(); - return Route.new(.{ - .html_bundle = html_bundle, + return bun.new(Route, .{ + .bundle = .initRef(html_bundle), .pending_responses = .{}, - .ref_count = 1, + .ref_count = .init(), .server = null, .state = .pending, }); } + fn deinit(this: *Route) void { + bun.assert(this.pending_responses.items.len == 0); // pending responses keep a ref to the route + this.pending_responses.deinit(bun.default_allocator); + this.bundle.deref(); + this.state.deinit(); + bun.destroy(this); + } + pub const State = union(enum) { pending, building: ?*bun.BundleV2.JSBundleCompletionTask, @@ -108,16 +119,6 @@ pub const Route = struct { } }; - fn _deinit(this: *Route) void { - for (this.pending_responses.items) |pending_response| { - pending_response.deref(); - } - this.pending_responses.deinit(bun.default_allocator); - this.html_bundle.deref(); - this.state.deinit(); - this.destroy(); - } - pub fn onRequest(this: *Route, req: *uws.Request, resp: HTTPResponse) void { this.onAnyRequest(req, resp, false); } @@ -162,7 +163,7 @@ pub const Route = struct { debug("onRequest: {s} - building", .{req.url()}); // create the PendingResponse, add it to the list - var pending = PendingResponse.new(.{ + const pending = bun.new(PendingResponse, .{ .method = bun.http.Method.which(req.method()) orelse { resp.writeStatus("405 Method Not Allowed"); resp.endWithoutBody(true); @@ -171,13 +172,11 @@ pub const Route = struct { .resp = resp, .server = this.server, .route = this, - .ref_count = 1, }); this.pending_responses.append(bun.default_allocator, pending) catch bun.outOfMemory(); this.ref(); - pending.ref(); resp.onAborted(*PendingResponse, PendingResponse.onAborted, pending); req.setYield(false); }, @@ -210,14 +209,14 @@ pub const Route = struct { } pub fn onPluginsResolved(this: *Route, plugins: ?*JSC.API.JSBundler.Plugin) !void { - const global = this.html_bundle.global; + const global = this.bundle.data.global; const server = this.server.?; const development = server.config().development; const vm = global.bunVM(); var config: JSBundler.Config = .{}; errdefer config.deinit(bun.default_allocator); - try config.entry_points.insert(this.html_bundle.path); + try config.entry_points.insert(this.bundle.data.path); if (vm.transpiler.options.transform_options.serve_public_path) |public_path| { if (public_path.len > 0) { try config.public_path.appendSlice(public_path); @@ -345,7 +344,7 @@ pub const Route = struct { byte_length += output_file.size_without_sourcemap; } - bun.Output.prettyErrorln(" bundle {s} {d:.2} KB", .{ std.fs.path.basename(this.html_bundle.path), @as(f64, @floatFromInt(byte_length)) / 1000.0 }); + bun.Output.prettyErrorln(" bundle {s} {d:.2} KB", .{ std.fs.path.basename(this.bundle.data.path), @as(f64, @floatFromInt(byte_length)) / 1000.0 }); bun.Output.flush(); } @@ -381,7 +380,8 @@ pub const Route = struct { } } - const static_route = StaticRoute.new(.{ + const static_route = bun.new(StaticRoute, .{ + .ref_count = .init(), .blob = blob, .server = server, .status_code = 200, @@ -426,7 +426,7 @@ pub const Route = struct { defer pending.deinit(bun.default_allocator); this.pending_responses = .{}; for (pending.items) |pending_response| { - defer pending_response.deref(); // First ref for being in the pending items array. + defer pending_response.deinit(); const resp = pending_response.resp; const method = pending_response.method; @@ -434,9 +434,6 @@ pub const Route = struct { // Aborted continue; } - // Second ref for UWS abort callback. - defer pending_response.deref(); - pending_response.is_response_pending = false; resp.clearAborted(); @@ -469,25 +466,21 @@ pub const Route = struct { pub const PendingResponse = struct { method: bun.http.Method, resp: HTTPResponse, - ref_count: u32 = 1, is_response_pending: bool = true, server: ?AnyServer = null, route: *Route, - pub usingnamespace bun.NewRefCounted(@This(), destroyInternal, null); - - fn destroyInternal(this: *PendingResponse) void { + pub fn deinit(this: *PendingResponse) void { if (this.is_response_pending) { this.resp.clearAborted(); this.resp.clearOnWritable(); this.resp.endWithoutBody(true); } this.route.deref(); - this.destroy(); + bun.destroy(this); } - pub fn onAborted(this: *PendingResponse, resp: HTTPResponse) void { - _ = resp; // autofix + pub fn onAborted(this: *PendingResponse, _: HTTPResponse) void { bun.debugAssert(this.is_response_pending == true); this.is_response_pending = false; @@ -499,8 +492,6 @@ pub const Route = struct { _ = this.route.pending_responses.orderedRemove(index); this.route.deref(); } - - this.deref(); } }; }; @@ -517,6 +508,7 @@ const HTTPResponse = bun.uws.AnyResponse; const uws = bun.uws; const AnyServer = JSC.API.AnyServer; const StaticRoute = @import("./StaticRoute.zig"); +const RefPtr = bun.ptr.RefPtr; const debug = bun.Output.scoped(.HTMLBundle, true); const strings = bun.strings; diff --git a/src/bun.js/api/server/NodeHTTPResponse.zig b/src/bun.js/api/server/NodeHTTPResponse.zig index d5f5e57360..e5a9320ebd 100644 --- a/src/bun.js/api/server/NodeHTTPResponse.zig +++ b/src/bun.js/api/server/NodeHTTPResponse.zig @@ -1,6 +1,14 @@ +const NodeHTTPResponse = @This(); +const log = bun.Output.scoped(.NodeHTTPResponse, false); + +pub usingnamespace JSC.Codegen.JSNodeHTTPResponse; +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; + raw_response: uws.AnyResponse, -ref_count: u32 = 1, +ref_count: RefCount, js_ref: JSC.Ref = .{}, flags: Flags = .{}, @@ -17,9 +25,6 @@ buffered_request_body_data_during_pause: bun.ByteList = .{}, upgrade_context: UpgradeCTX = .{}, -const log = bun.Output.scoped(.NodeHTTPResponse, false); -pub usingnamespace JSC.Codegen.JSNodeHTTPResponse; -pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub const Flags = packed struct(u8) { socket_closed: bool = false, request_has_completed: bool = false, @@ -31,6 +36,7 @@ pub const Flags = packed struct(u8) { /// Did we receive the last chunk of data during pause? is_data_buffered_during_pause_last: bool = false, }; + pub const UpgradeCTX = struct { context: ?*uws.uws_socket_context_t = null, // request will be detached when go async @@ -281,7 +287,11 @@ pub fn create( has_body.* = req_len > 0 or request.header("transfer-encoding") != null; } - const response = NodeHTTPResponse.new(.{ + const response = bun.new(NodeHTTPResponse, .{ + // 1 - the HTTP response + // 1 - the JS object + // 1 - the Server handler. + .ref_count = .initExactRefs(3), .upgrade_context = .{ .context = @ptrCast(upgrade_ctx), .request = request, @@ -292,11 +302,6 @@ pub fn create( false => uws.AnyResponse{ .TCP = @ptrCast(response_ptr) }, }, .body_read_state = if (has_body.*) .pending else .none, - // 1 - the HTTP response - // 1 - the JS object - // 1 - the Server handler. - // 1 - the onData callback (request body) - .ref_count = 3, }); if (has_body.*) { response.body_read_ref.ref(vm); @@ -1056,7 +1061,7 @@ pub fn finalize(this: *NodeHTTPResponse) void { this.deref(); } -pub fn deinit(this: *NodeHTTPResponse) void { +fn deinit(this: *NodeHTTPResponse) void { bun.debugAssert(!this.body_read_ref.has); bun.debugAssert(!this.js_ref.has); bun.debugAssert(!this.flags.is_request_pending); @@ -1067,7 +1072,7 @@ pub fn deinit(this: *NodeHTTPResponse) void { this.body_read_ref.unref(JSC.VirtualMachine.get()); this.promise.deinit(); - this.destroy(); + bun.destroy(this); } comptime { @@ -1083,8 +1088,6 @@ pub export fn Bun__NodeHTTPResponse_setClosed(response: *NodeHTTPResponse) void response.flags.socket_closed = true; } -const NodeHTTPResponse = @This(); - const JSGlobalObject = JSC.JSGlobalObject; const JSObject = JSC.JSObject; const JSValue = JSC.JSValue; diff --git a/src/bun.js/api/server/ServerWebSocket.zig b/src/bun.js/api/server/ServerWebSocket.zig index 14996b1ed9..04c9c7664d 100644 --- a/src/bun.js/api/server/ServerWebSocket.zig +++ b/src/bun.js/api/server/ServerWebSocket.zig @@ -28,7 +28,7 @@ inline fn websocket(this: *const ServerWebSocket) uws.AnyWebSocket { } pub usingnamespace JSC.Codegen.JSServerWebSocket; -pub usingnamespace bun.New(ServerWebSocket); +pub const new = bun.TrivialNew(ServerWebSocket); pub fn memoryCost(this: *const ServerWebSocket) usize { if (this.flags.closed) { @@ -351,7 +351,7 @@ pub fn constructor(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSE pub fn finalize(this: *ServerWebSocket) void { log("finalize", .{}); - this.destroy(); + bun.destroy(this); } pub fn publish( diff --git a/src/bun.js/api/server/StaticRoute.zig b/src/bun.js/api/server/StaticRoute.zig index b9dfa1e096..3aae794479 100644 --- a/src/bun.js/api/server/StaticRoute.zig +++ b/src/bun.js/api/server/StaticRoute.zig @@ -2,8 +2,13 @@ //! Response object, or from globally allocated bytes. const StaticRoute = @This(); +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; + // TODO: Remove optional. StaticRoute requires a server object or else it will // not ensure it is alive while sending a large blob. +ref_count: RefCount, server: ?AnyServer = null, status_code: u16, blob: AnyBlob, @@ -12,9 +17,6 @@ has_content_disposition: bool = false, headers: Headers = .{ .allocator = bun.default_allocator, }, -ref_count: u32 = 1, - -pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub const InitFromBytesOptions = struct { server: ?AnyServer, @@ -30,7 +32,8 @@ pub fn initFromAnyBlob(blob: *const AnyBlob, options: InitFromBytesOptions) *Sta headers.append("Content-Type", mime_type.value) catch bun.outOfMemory(); } } - return StaticRoute.new(.{ + return bun.new(StaticRoute, .{ + .ref_count = .init(), .blob = blob.*, .cached_blob_size = blob.size(), .has_content_disposition = false, @@ -51,14 +54,15 @@ fn deinit(this: *StaticRoute) void { this.blob.detach(); this.headers.deinit(); - this.destroy(); + bun.destroy(this); } pub fn clone(this: *StaticRoute, globalThis: *JSC.JSGlobalObject) !*StaticRoute { var blob = this.blob.toBlob(globalThis); this.blob = .{ .Blob = blob }; - return StaticRoute.new(.{ + return bun.new(StaticRoute, .{ + .ref_count = .init(), .blob = .{ .Blob = blob.dupe() }, .cached_blob_size = this.cached_blob_size, .has_content_disposition = this.has_content_disposition, @@ -132,7 +136,8 @@ pub fn fromJS(globalThis: *JSC.JSGlobalObject, argument: JSC.JSValue) bun.JSErro .allocator = bun.default_allocator, }; - return StaticRoute.new(.{ + return bun.new(StaticRoute, .{ + .ref_count = .init(), .blob = blob, .cached_blob_size = blob.size(), .has_content_disposition = has_content_disposition, diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index b418729795..5113219702 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -43,7 +43,7 @@ pub fn ConcurrentPromiseTask(comptime Context: type) type { // This is a poll because we want it to enter the uSockets loop ref: Async.KeepAlive = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, value: *Context) !*This { var this = This.new(.{ @@ -84,7 +84,7 @@ pub fn ConcurrentPromiseTask(comptime Context: type) type { pub fn deinit(this: *This) void { this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; } @@ -105,11 +105,9 @@ pub fn WorkTask(comptime Context: type) type { // This is a poll because we want it to enter the uSockets loop ref: Async.KeepAlive = .{}, - pub usingnamespace bun.New(@This()); - pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, value: *Context) !*This { var vm = globalThis.bunVM(); - var this = This.new(.{ + var this = bun.new(This, .{ .event_loop = vm.eventLoop(), .ctx = value, .allocator = allocator, @@ -121,6 +119,11 @@ pub fn WorkTask(comptime Context: type) type { return this; } + pub fn deinit(this: *This) void { + this.ref.unref(this.event_loop.virtual_machine); + bun.destroy(this); + } + pub fn runFromThreadPool(task: *TaskType) void { JSC.markBinding(@src()); const this: *This = @fieldParentPtr("task", task); @@ -149,11 +152,6 @@ pub fn WorkTask(comptime Context: type) type { pub fn onFinish(this: *This) void { this.event_loop.enqueueTaskConcurrent(this.concurrent_task.from(this, .manual_deinit)); } - - pub fn deinit(this: *This) void { - this.ref.unref(this.event_loop.virtual_machine); - this.destroy(); - } }; } @@ -309,6 +307,8 @@ pub const CppTask = opaque { }; pub const ConcurrentCppTask = struct { + pub const new = bun.TrivialNew(@This()); + cpp_task: *EventLoopTaskNoContext, workpool_task: JSC.WorkPoolTask = .{ .callback = &runFromWorkpool }, @@ -328,20 +328,18 @@ pub const ConcurrentCppTask = struct { }; pub fn runFromWorkpool(task: *JSC.WorkPoolTask) void { - var this: *ConcurrentCppTask = @fieldParentPtr("workpool_task", task); + const this: *ConcurrentCppTask = @fieldParentPtr("workpool_task", task); // Extract all the info we need from `this` and `cpp_task` before we call functions that // free them const cpp_task = this.cpp_task; const maybe_vm = cpp_task.getVM(); - this.destroy(); + bun.destroy(this); cpp_task.run(); if (maybe_vm) |vm| { vm.event_loop.unrefConcurrently(); } } - pub usingnamespace bun.New(@This()); - pub export fn ConcurrentCppTask__createAndRun(cpp_task: *EventLoopTaskNoContext) void { JSC.markBinding(@src()); if (cpp_task.getVM()) |vm| { @@ -561,7 +559,8 @@ pub const ConcurrentTask = struct { auto_delete: bool = false, pub const Queue = UnboundedQueue(ConcurrentTask, .next); - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); pub const AutoDeinit = enum { manual_deinit, @@ -1475,7 +1474,7 @@ pub const EventLoop = struct { while (iter.next()) |task| { if (to_destroy) |dest| { to_destroy = null; - dest.destroy(); + dest.deinit(); } if (task.auto_delete) { @@ -1489,7 +1488,7 @@ pub const EventLoop = struct { } if (to_destroy) |dest| { - dest.destroy(); + dest.deinit(); } return this.tasks.count - start_count; @@ -2469,7 +2468,7 @@ pub const PosixSignalHandle = struct { const log = bun.Output.scoped(.PosixSignalHandle, true); - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); /// Called by the signal handler (single producer). /// Returns `true` if enqueued successfully, or `false` if the ring is full. @@ -2547,7 +2546,7 @@ pub const PosixSignalTask = struct { number: u8, extern "c" fn Bun__onSignalForJS(number: i32, globalObject: *JSC.JSGlobalObject) void; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn runFromJSThread(number: u8, globalObject: *JSC.JSGlobalObject) void { Bun__onSignalForJS(number, globalObject); } diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index d72798803b..b02be9a506 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -320,7 +320,7 @@ pub const SavedSourceMap = struct { defer this.unlock(); var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(Value.from(mapping.value_ptr.*).as(ParsedSourceMap))) }; defer saved.deinit(); - const result = ParsedSourceMap.new(saved.toMapping(default_allocator, path) catch { + const result = bun.new(ParsedSourceMap, saved.toMapping(default_allocator, path) catch { _ = this.map.remove(mapping.key_ptr.*); return .{}; }); @@ -4413,13 +4413,14 @@ pub const VirtualMachine = struct { }; pub const IPCInstance = struct { + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + globalThis: ?*JSGlobalObject, context: if (Environment.isPosix) *uws.SocketContext else void, data: IPC.IPCData, has_disconnect_called: bool = false, - pub usingnamespace bun.New(@This()); - const node_cluster_binding = @import("./node/node_cluster_binding.zig"); pub fn ipc(this: *IPCInstance) ?*IPC.IPCData { @@ -4468,7 +4469,7 @@ pub const VirtualMachine = struct { uws.us_socket_context_free(0, this.context); } vm.channel_ref.disable(); - this.destroy(); + this.deinit(); } export fn Bun__closeChildIPC(global: *JSGlobalObject) void { @@ -4509,7 +4510,7 @@ pub const VirtualMachine = struct { this.ipc = .{ .initialized = instance }; const socket = IPC.Socket.fromFd(context, opts.info, IPCInstance, instance, null) orelse { - instance.destroy(); + instance.deinit(); this.ipc = null; Output.warn("Unable to start IPC socket", .{}); return null; @@ -4530,7 +4531,7 @@ pub const VirtualMachine = struct { this.ipc = .{ .initialized = instance }; instance.data.configureClient(IPCInstance, instance, opts.info) catch { - instance.destroy(); + instance.deinit(); this.ipc = null; Output.warn("Unable to start IPC pipe '{}'", .{opts.info}); return null; diff --git a/src/bun.js/node/Stat.zig b/src/bun.js/node/Stat.zig index a705fdceb1..a08f8c454e 100644 --- a/src/bun.js/node/Stat.zig +++ b/src/bun.js/node/Stat.zig @@ -1,7 +1,9 @@ /// Stats and BigIntStats classes from node:fs pub fn StatType(comptime big: bool) type { return struct { - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + value: bun.Stat, const StatTimespec = if (Environment.isWindows) bun.windows.libuv.uv_timespec_t else std.posix.timespec; @@ -22,7 +24,7 @@ pub fn StatType(comptime big: bool) type { } fn toTimeMS(ts: StatTimespec) Float { - // On windows, Node.js purposefully mis-interprets time values + // On windows, Node.js purposefully misinterprets time values // > On win32, time is stored in uint64_t and starts from 1601-01-01. // > libuv calculates tv_sec and tv_nsec from it and converts to signed long, // > which causes Y2038 overflow. On the other platforms it is safe to treat diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index d0e65e7409..9137bb8409 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -124,7 +124,7 @@ pub const Async = struct { task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn workPoolCallback(task: *JSC.WorkPoolTask) void { var this: *AsyncMkdirp = @fieldParentPtr("task", task); @@ -181,10 +181,8 @@ pub const Async = struct { pub const heap_label = "Async" ++ bun.meta.typeBaseName(@typeName(ArgumentType)) ++ "UvTask"; - pub usingnamespace bun.New(@This()); - pub fn create(globalObject: *JSC.JSGlobalObject, this: *JSC.Node.NodeJSFS, task_args: ArgumentType, vm: *JSC.VirtualMachine) JSC.JSValue { - var task = Task.new(.{ + var task = bun.new(Task, .{ .promise = JSC.JSPromise.Strong.init(globalObject), .args = task_args, .result = undefined, @@ -372,7 +370,7 @@ pub const Async = struct { this.args.deinit(); } this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; } @@ -1007,7 +1005,7 @@ pub const AsyncReaddirRecursiveTask = struct { pending_err: ?Syscall.Error = null, pending_err_mutex: bun.Mutex = .{}, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const ResultListEntry = struct { pub const Value = union(Return.Readdir.Tag) { @@ -1050,13 +1048,13 @@ pub const AsyncReaddirRecursiveTask = struct { basename: bun.PathString = bun.PathString.empty, task: JSC.WorkPoolTask = .{ .callback = call }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn call(task: *JSC.WorkPoolTask) void { var this: *Subtask = @alignCast(@fieldParentPtr("task", task)); defer { bun.default_allocator.free(this.basename.sliceAssumeZ()); - this.destroy(); + bun.destroy(this); } var buf: bun.PathBuffer = undefined; this.readdir_task.performWork(this.basename.sliceAssumeZ(), &buf, false); @@ -1308,7 +1306,7 @@ pub const AsyncReaddirRecursiveTask = struct { bun.default_allocator.free(this.root_path.slice()); this.clearResultList(); this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig index feb633c2e0..66a4b6ddb7 100644 --- a/src/bun.js/node/node_fs_binding.zig +++ b/src/bun.js/node/node_fs_binding.zig @@ -97,7 +97,7 @@ pub const NodeJSFS = struct { node_fs: JSC.Node.NodeFS = .{}, pub usingnamespace JSC.Codegen.JSNodeJSFS; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn finalize(this: *JSC.Node.NodeJSFS) void { if (this.node_fs.vm) |vm| { @@ -106,7 +106,7 @@ pub const NodeJSFS = struct { } } - this.destroy(); + bun.destroy(this); } pub fn getDirent(_: *NodeJSFS, globalThis: *JSC.JSGlobalObject) JSC.JSValue { diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig index 646571c18c..3e979b1207 100644 --- a/src/bun.js/node/node_fs_stat_watcher.zig +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -334,18 +334,14 @@ pub const StatWatcher = struct { watcher: *StatWatcher, task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - pub usingnamespace bun.New(@This()); - - pub fn createAndSchedule( - watcher: *StatWatcher, - ) void { - const task = InitialStatTask.new(.{ .watcher = watcher }); + pub fn createAndSchedule(watcher: *StatWatcher) void { + const task = bun.new(InitialStatTask, .{ .watcher = watcher }); JSC.WorkPool.schedule(&task.task); } fn workPoolCallback(task: *JSC.WorkPoolTask) void { const initial_stat_task: *InitialStatTask = @fieldParentPtr("task", task); - defer initial_stat_task.destroy(); + defer bun.destroy(initial_stat_task); const this = initial_stat_task.watcher; if (this.closed) { diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig index 425b932909..9bfc451873 100644 --- a/src/bun.js/node/node_fs_watcher.zig +++ b/src/bun.js/node/node_fs_watcher.zig @@ -19,6 +19,8 @@ const log = Output.scoped(.@"fs.watch", true); const PathWatcher = if (Environment.isWindows) @import("./win_watcher.zig") else @import("./path_watcher.zig"); pub const FSWatcher = struct { + pub usingnamespace JSC.Codegen.JSFSWatcher; + ctx: *VirtualMachine, verbose: bool = false, @@ -38,9 +40,6 @@ pub const FSWatcher = struct { pending_activity_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), current_task: FSWatchTask = undefined, - pub usingnamespace JSC.Codegen.JSFSWatcher; - pub usingnamespace bun.New(@This()); - pub fn eventLoop(this: FSWatcher) *EventLoop { return this.ctx.eventLoop(); } @@ -52,11 +51,15 @@ pub const FSWatcher = struct { pub fn deinit(this: *FSWatcher) void { // stop all managers and signals this.detach(); - this.destroy(); + bun.destroy(this); } + pub const finalize = deinit; + pub const FSWatchTask = if (Environment.isWindows) FSWatchTaskWindows else FSWatchTaskPosix; pub const FSWatchTaskPosix = struct { + pub const new = bun.TrivialNew(@This()); + ctx: *FSWatcher, count: u8 = 0, @@ -68,6 +71,14 @@ pub const FSWatcher = struct { needs_free: bool, }; + pub fn deinit(this: *FSWatchTask) void { + this.cleanEntries(); + if (comptime Environment.allow_assert) { + bun.assert(&this.ctx.current_task != this); + } + bun.destroy(this); + } + pub fn append(this: *FSWatchTask, event: Event, needs_free: bool) void { if (this.count == 8) { this.enqueue(); @@ -136,16 +147,6 @@ pub const FSWatcher = struct { } this.count = 0; } - - pub usingnamespace bun.New(@This()); - - pub fn deinit(this: *FSWatchTask) void { - this.cleanEntries(); - if (comptime Environment.allow_assert) { - bun.assert(&this.ctx.current_task != this); - } - this.destroy(); - } }; pub const EventPathString = switch (Environment.os) { @@ -202,8 +203,6 @@ pub const FSWatcher = struct { /// Unused: To match the API of the posix version count: u0 = 0, - pub usingnamespace bun.New(@This()); - pub const StringOrBytesToDecode = union(enum) { string: bun.String, bytes_to_free: []const u8, @@ -228,7 +227,7 @@ pub const FSWatcher = struct { pub fn appendAbort(this: *FSWatchTaskWindows) void { const ctx = this.ctx; - const task = FSWatchTaskWindows.new(.{ + const task = bun.new(FSWatchTaskWindows, .{ .ctx = ctx, .event = .abort, }); @@ -267,7 +266,7 @@ pub const FSWatcher = struct { pub fn deinit(this: *FSWatchTaskWindows) void { this.event.deinit(); - this.destroy(); + bun.destroy(this); } }; @@ -311,7 +310,7 @@ pub const FSWatcher = struct { return; } - const task = FSWatchTaskWindows.new(.{ + const task = bun.new(FSWatchTaskWindows, .{ .ctx = this, .event = event, }); @@ -638,10 +637,6 @@ pub const FSWatcher = struct { return .undefined; } - pub fn finalize(this: *FSWatcher) void { - this.deinit(); - } - pub fn init(args: Arguments) bun.JSC.Maybe(*FSWatcher) { var buf: bun.PathBuffer = undefined; var slice = args.path.slice(); @@ -672,7 +667,7 @@ pub const FSWatcher = struct { const vm = args.global_this.bunVM(); - var ctx = FSWatcher.new(.{ + const ctx = bun.new(FSWatcher, .{ .ctx = vm, .current_task = .{ .ctx = undefined, diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index 80d348b05c..8e4ae02afe 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -311,11 +311,14 @@ const CountedKeepAlive = struct { }; pub const SNativeZlib = struct { - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + pub usingnamespace JSC.Codegen.JSNativeZlib; pub usingnamespace CompressionStream(@This()); - ref_count: u32 = 1, + ref_count: RefCount, mode: bun.zlib.NodeMode, globalThis: *JSC.JSGlobalObject, stream: ZlibContext = .{}, @@ -343,7 +346,8 @@ pub const SNativeZlib = struct { return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 1, .max = 7 }); } - const ptr = SNativeZlib.new(.{ + const ptr = bun.new(SNativeZlib, .{ + .ref_count = .init(), .mode = @enumFromInt(mode_int), .globalThis = globalThis, }); @@ -404,11 +408,11 @@ pub const SNativeZlib = struct { return .undefined; } - pub fn deinit(this: *@This()) void { + fn deinit(this: *@This()) void { this.this_value.deinit(); this.poll_ref.deinit(); this.stream.close(); - this.destroy(); + bun.destroy(this); } }; @@ -677,11 +681,14 @@ const ZlibContext = struct { pub const NativeBrotli = JSC.Codegen.JSNativeBrotli.getConstructor; pub const SNativeBrotli = struct { - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + pub usingnamespace JSC.Codegen.JSNativeBrotli; pub usingnamespace CompressionStream(@This()); - ref_count: u32 = 1, + ref_count: RefCount, mode: bun.zlib.NodeMode, globalThis: *JSC.JSGlobalObject, stream: BrotliContext = .{}, @@ -711,7 +718,8 @@ pub const SNativeBrotli = struct { return globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 8, .max = 9 }); } - const ptr = @This().new(.{ + const ptr = bun.new(@This(), .{ + .ref_count = .init(), .mode = @enumFromInt(mode_int), .globalThis = globalThis, }); @@ -775,14 +783,14 @@ pub const SNativeBrotli = struct { return .undefined; } - pub fn deinit(this: *@This()) void { + fn deinit(this: *@This()) void { this.this_value.deinit(); this.poll_ref.deinit(); switch (this.stream.mode) { .BROTLI_ENCODE, .BROTLI_DECODE => this.stream.close(), else => {}, } - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/node/win_watcher.zig b/src/bun.js/node/win_watcher.zig index 8b7f8b7123..2445c07f0c 100644 --- a/src/bun.js/node/win_watcher.zig +++ b/src/bun.js/node/win_watcher.zig @@ -28,7 +28,7 @@ pub const PathWatcherManager = struct { vm: *JSC.VirtualMachine, deinit_on_last_watcher: bool = false, - pub usingnamespace bun.New(PathWatcherManager); + pub const new = bun.TrivialNew(PathWatcherManager); pub fn init(vm: *JSC.VirtualMachine) *PathWatcherManager { return PathWatcherManager.new(.{ @@ -55,6 +55,7 @@ pub const PathWatcherManager = struct { _ = this.watchers.swapRemoveAt(index); } } + fn deinit(this: *PathWatcherManager) void { // enable to create a new manager if (default_manager == this) { @@ -76,7 +77,7 @@ pub const PathWatcherManager = struct { } this.watchers.deinit(bun.default_allocator); - this.destroy(); + bun.destroy(this); } }; @@ -88,7 +89,8 @@ pub const PathWatcher = struct { manager: ?*PathWatcherManager, emit_in_progress: bool = false, handlers: std.AutoArrayHashMapUnmanaged(*anyopaque, ChangeEvent) = .{}, - pub usingnamespace bun.New(PathWatcher); + + pub const new = bun.TrivialNew(PathWatcher); const log = Output.scoped(.@"fs.watch", false); @@ -238,7 +240,7 @@ pub const PathWatcher = struct { log("onClose", .{}); const event = bun.cast(*uv.uv_fs_event_t, handler); const this = bun.cast(*PathWatcher, event.data); - this.destroy(); + bun.destroy(this); } pub fn detach(this: *PathWatcher, handler: *anyopaque) void { @@ -266,7 +268,7 @@ pub const PathWatcher = struct { } } if (uv.uv_is_closed(@ptrCast(&this.handle))) { - this.destroy(); + bun.destroy(this); } else { _ = uv.uv_fs_event_stop(&this.handle); _ = uv.uv_close(@ptrCast(&this.handle), PathWatcher.uvClosedCallback); diff --git a/src/bun.js/webcore/ObjectURLRegistry.zig b/src/bun.js/webcore/ObjectURLRegistry.zig index 47812dc2ea..c0226ca95e 100644 --- a/src/bun.js/webcore/ObjectURLRegistry.zig +++ b/src/bun.js/webcore/ObjectURLRegistry.zig @@ -11,7 +11,7 @@ map: std.AutoHashMap(UUID, *RegistryEntry) = std.AutoHashMap(UUID, *RegistryEntr pub const RegistryEntry = struct { blob: JSC.WebCore.Blob, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn init(blob: *const JSC.WebCore.Blob) *RegistryEntry { return RegistryEntry.new(.{ .blob = blob.dupeWithContentType(true), @@ -20,7 +20,7 @@ pub const RegistryEntry = struct { pub fn deinit(this: *RegistryEntry) void { this.blob.deinit(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/webcore/S3Client.zig b/src/bun.js/webcore/S3Client.zig index 19f4c561fc..6336667039 100644 --- a/src/bun.js/webcore/S3Client.zig +++ b/src/bun.js/webcore/S3Client.zig @@ -90,7 +90,7 @@ pub const S3Client = struct { const log = bun.Output.scoped(.S3Client, false); pub usingnamespace JSC.Codegen.JSS3Client; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); credentials: *S3Credentials, options: bun.S3.MultiPartUploadOptions = .{}, acl: ?bun.S3.ACL = null, @@ -254,7 +254,7 @@ pub const S3Client = struct { pub fn deinit(this: *@This()) void { this.credentials.deref(); - this.destroy(); + bun.destroy(this); } pub fn finalize( diff --git a/src/bun.js/webcore/S3File.zig b/src/bun.js/webcore/S3File.zig index 4cb3e266d6..1936e1046b 100644 --- a/src/bun.js/webcore/S3File.zig +++ b/src/bun.js/webcore/S3File.zig @@ -366,7 +366,7 @@ pub const S3BlobStatTask = struct { global: *JSC.JSGlobalObject, store: *Blob.Store, - usingnamespace bun.New(S3BlobStatTask); + pub const new = bun.TrivialNew(S3BlobStatTask); pub fn onS3ExistsResolved(result: S3.S3StatResult, this: *S3BlobStatTask) void { defer this.deinit(); @@ -469,7 +469,7 @@ pub const S3BlobStatTask = struct { pub fn deinit(this: *S3BlobStatTask) void { this.store.deref(); this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/webcore/S3Stat.zig b/src/bun.js/webcore/S3Stat.zig index 361c639d3f..6c33ac785c 100644 --- a/src/bun.js/webcore/S3Stat.zig +++ b/src/bun.js/webcore/S3Stat.zig @@ -4,7 +4,7 @@ const JSC = bun.JSC; pub const S3Stat = struct { const log = bun.Output.scoped(.S3Stat, false); pub usingnamespace JSC.Codegen.JSS3Stat; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); size: u64, etag: bun.String, @@ -53,6 +53,6 @@ pub const S3Stat = struct { pub fn finalize(this: *@This()) void { this.etag.deref(); this.contentType.deref(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/bun.js/webcore/TextDecoder.zig b/src/bun.js/webcore/TextDecoder.zig index 5d6da1bcc0..6c9222e4d6 100644 --- a/src/bun.js/webcore/TextDecoder.zig +++ b/src/bun.js/webcore/TextDecoder.zig @@ -16,11 +16,12 @@ ignore_bom: bool = false, fatal: bool = false, encoding: EncodingLabel = EncodingLabel.@"UTF-8", -pub usingnamespace bun.New(TextDecoder); pub usingnamespace JSC.Codegen.JSTextDecoder; +pub const new = bun.TrivialNew(TextDecoder); + pub fn finalize(this: *TextDecoder) void { - this.destroy(); + bun.destroy(this); } pub fn getIgnoreBOM( diff --git a/src/bun.js/webcore/TextEncoderStreamEncoder.zig b/src/bun.js/webcore/TextEncoderStreamEncoder.zig index 4269577aae..a921ff962e 100644 --- a/src/bun.js/webcore/TextEncoderStreamEncoder.zig +++ b/src/bun.js/webcore/TextEncoderStreamEncoder.zig @@ -3,10 +3,10 @@ pending_lead_surrogate: ?u16 = null, const log = Output.scoped(.TextEncoderStreamEncoder, false); pub usingnamespace JSC.Codegen.JSTextEncoderStreamEncoder; -pub usingnamespace bun.New(TextEncoderStreamEncoder); +pub const new = bun.TrivialNew(TextEncoderStreamEncoder); pub fn finalize(this: *TextEncoderStreamEncoder) void { - this.destroy(); + bun.destroy(this); } pub fn constructor(_: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*TextEncoderStreamEncoder { diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index a8a7328d45..cf976ff632 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -57,7 +57,7 @@ const S3File = @import("S3File.zig"); pub const Blob = struct { const bloblog = Output.scoped(.Blob, false); - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub usingnamespace JSC.Codegen.JSBlob; const rf = @import("blob/ReadFile.zig"); @@ -649,10 +649,10 @@ pub const Blob = struct { } export fn Blob__dupe(ptr: *anyopaque) *Blob { - var this = bun.cast(*Blob, ptr); - var new = Blob.new(this.dupeWithContentType(true)); - new.allocator = bun.default_allocator; - return new; + const this = bun.cast(*Blob, ptr); + const new_ptr = new(this.dupeWithContentType(true)); + new_ptr.allocator = bun.default_allocator; + return new_ptr; } export fn Blob__destroy(this: *Blob) void { @@ -963,7 +963,7 @@ pub const Blob = struct { store: *Store, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn resolve(result: S3.S3UploadResult, opaque_this: *anyopaque) void { const this: *@This() = @ptrCast(@alignCast(opaque_this)); @@ -977,7 +977,7 @@ pub const Blob = struct { fn deinit(this: *@This()) void { this.promise.deinit(); this.store.deref(); - this.destroy(); + bun.destroy(this); } }; @@ -1158,7 +1158,7 @@ pub const Blob = struct { promise: JSC.JSPromise.Strong, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn resolve(result: S3.S3UploadResult, opaque_self: *anyopaque) void { const this: *@This() = @ptrCast(@alignCast(opaque_self)); @@ -2023,7 +2023,7 @@ pub const Blob = struct { is_all_ascii: ?bool = null, allocator: std.mem.Allocator, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn memoryCost(this: *const Store) usize { return if (this.hasOneRef()) @sizeOf(@This()) + switch (this.data) { @@ -2212,7 +2212,7 @@ pub const Blob = struct { }, } - this.destroy(); + bun.destroy(this); } const SerializeTag = enum(u8) { @@ -2660,7 +2660,7 @@ pub const Blob = struct { this.onComplete(this.read_write_loop.written); } - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn init( destination_file_store: *Store, @@ -2961,7 +2961,7 @@ pub const Blob = struct { this.source_file_store.deref(); this.promise.deinit(); this.io_request.deinit(); - this.destroy(); + bun.destroy(this); } fn mkdirp( @@ -3673,7 +3673,7 @@ pub const Blob = struct { store: *Store, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn resolve(result: S3.S3DeleteResult, opaque_self: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(opaque_self)); @@ -3689,10 +3689,10 @@ pub const Blob = struct { } } - fn deinit(self: *@This()) void { - self.store.deref(); - self.promise.deinit(); - self.destroy(); + fn deinit(wrap: *@This()) void { + wrap.store.deref(); + wrap.promise.deinit(); + bun.destroy(wrap); } }; const promise = JSC.JSPromise.Strong.init(globalThis); @@ -4049,7 +4049,7 @@ pub const Blob = struct { poll_ref: bun.Async.KeepAlive = .{}, handler: S3ReadHandler, - usingnamespace bun.New(S3BlobDownloadTask); + pub const new = bun.TrivialNew(S3BlobDownloadTask); pub const S3ReadHandler = *const fn (this: *Blob, globalthis: *JSGlobalObject, raw_bytes: []u8) JSValue; pub fn callHandler(this: *S3BlobDownloadTask, raw_bytes: []u8) JSValue { @@ -4104,7 +4104,7 @@ pub const Blob = struct { this.blob.store.?.deref(); this.poll_ref.unref(this.globalThis.bunVM()); this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; @@ -4191,13 +4191,13 @@ pub const Blob = struct { readable_stream_ref: JSC.WebCore.ReadableStream.Strong, sink: *JSC.WebCore.FileSink, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn deinit(this: *@This()) void { this.promise.deinit(); this.readable_stream_ref.deinit(); this.sink.deref(); - this.destroy(); + bun.destroy(this); } }; @@ -4218,7 +4218,7 @@ pub const Blob = struct { pub fn onFileStreamRejectRequestStream(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { const args = callframe.arguments_old(2); var this = args.ptr[args.len - 1].asPromisePtr(FileStreamWrapper); - defer this.sink.deinit(); + defer this.sink.deref(); const err = args.ptr[0]; var strong = this.readable_stream_ref; @@ -5269,13 +5269,13 @@ pub const Blob = struct { pub fn deinit(this: *Blob) void { this.detach(); this.name.deref(); - this.name = bun.String.dead; + this.name = .dead; // TODO: remove this field, make it a boolean. if (this.allocator) |alloc| { this.allocator = null; bun.debugAssert(alloc.vtable == bun.default_allocator.vtable); - this.destroy(); + bun.destroy(this); } } diff --git a/src/bun.js/webcore/blob/WriteFile.zig b/src/bun.js/webcore/blob/WriteFile.zig index 06416629c5..7c13a9a18e 100644 --- a/src/bun.js/webcore/blob/WriteFile.zig +++ b/src/bun.js/webcore/blob/WriteFile.zig @@ -618,7 +618,7 @@ pub const WriteFileWindows = struct { if (rc.int() != 0) bun.Output.panic("unexpected return code from uv_fs_write: {d}", .{rc.int()}); } - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn deinit(this: *@This()) void { const fd = this.fd; @@ -628,7 +628,7 @@ pub const WriteFileWindows = struct { this.file_blob.store.?.deref(); this.bytes_blob.store.?.deref(); uv.uv_fs_req_cleanup(&this.io_request); - this.destroy(); + bun.destroy(this); } pub fn create( diff --git a/src/bun.js/webcore/fetch.zig b/src/bun.js/webcore/fetch.zig index 3898b23d83..aef2b46b7f 100644 --- a/src/bun.js/webcore/fetch.zig +++ b/src/bun.js/webcore/fetch.zig @@ -2525,7 +2525,7 @@ pub fn Bun__fetch_( url_proxy_buffer: []const u8, global: *JSC.JSGlobalObject, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn resolve(result: s3.S3UploadResult, self: *@This()) void { const global = self.global; @@ -2565,7 +2565,7 @@ pub fn Bun__fetch_( }, } bun.default_allocator.free(self.url_proxy_buffer); - self.destroy(); + bun.destroy(self); } }; if (method != .PUT and method != .POST) { diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index f78d2dc414..dcbfcf58af 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -57,14 +57,14 @@ pub const Request = struct { method: Method = Method.GET, request_context: JSC.API.AnyRequestContext = JSC.API.AnyRequestContext.Null, https: bool = false, - weak_ptr_data: bun.WeakPtrData = .{}, + weak_ptr_data: WeakRef.Data = .empty, // We must report a consistent value for this reported_estimated_size: usize = 0, internal_event_callback: InternalJSEventCallback = .{}, const RequestMixin = BodyMixin(@This()); pub usingnamespace JSC.Codegen.JSRequest; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const getText = RequestMixin.getText; pub const getBytes = RequestMixin.getBytes; @@ -75,7 +75,7 @@ pub const Request = struct { pub const getBlob = RequestMixin.getBlob; pub const getFormData = RequestMixin.getFormData; pub const getBlobWithoutCallFrame = RequestMixin.getBlobWithoutCallFrame; - pub const WeakRef = bun.WeakPtr(Request, .weak_ptr_data); + pub const WeakRef = bun.ptr.WeakPtr(Request, "weak_ptr_data"); pub fn memoryCost(this: *const Request) usize { return @sizeOf(Request) + this.request_context.memoryCost() + this.url.byteSlice().len + this.body.value.memoryCost(); @@ -382,7 +382,7 @@ pub const Request = struct { this.finalizeWithoutDeinit(); _ = this.body.unref(); if (this.weak_ptr_data.onFinalize()) { - this.destroy(); + bun.destroy(this); } } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 9cbf8fe9cb..f652bdc153 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -2672,6 +2672,9 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { pub const HTTPSResponseSink = HTTPServerWritable(true); pub const HTTPResponseSink = HTTPServerWritable(false); pub const NetworkSink = struct { + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + task: ?HTTPWritableStream = null, signal: Signal = .{}, globalThis: *JSGlobalObject = undefined, @@ -2686,7 +2689,6 @@ pub const NetworkSink = struct { auto_flusher: AutoFlusher = AutoFlusher{}, - pub usingnamespace bun.New(NetworkSink); const HTTPWritableStream = union(enum) { fetch: *JSC.WebCore.Fetch.FetchTasklet, s3_upload: *bun.S3.MultiPartUpload, @@ -2844,7 +2846,7 @@ pub const NetworkSink = struct { } pub fn finalizeAndDestroy(this: *@This()) void { this.finalize(); - this.destroy(); + bun.destroy(this); } pub fn abort(this: *@This()) void { @@ -3034,10 +3036,12 @@ pub fn ReadableStreamSource( globalThis: *JSGlobalObject = undefined, this_jsvalue: JSC.JSValue = .zero, is_closed: bool = false, + const This = @This(); const ReadableStreamSourceType = @This(); - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); pub fn pull(this: *This, buf: []u8) StreamResult { return onPull(&this.context, buf, JSValue.zero); @@ -3425,7 +3429,7 @@ pub const FileSink = struct { writer: IOWriter = .{}, event_loop_handle: JSC.EventLoopHandle, written: usize = 0, - ref_count: u32 = 1, + ref_count: bun.ptr.RefCount(FileSink, "ref_count", deinit, .{}), pending: StreamResult.Writable.Pending = .{ .result = .{ .done = {} }, }, @@ -3448,7 +3452,14 @@ pub const FileSink = struct { const log = Output.scoped(.FileSink, false); - pub usingnamespace bun.NewRefCounted(FileSink, deinit, null); + // TODO: this usingnamespace is load-bearing, likely due to a compiler bug. + pub usingnamespace brk: { + const RefCount = bun.ptr.RefCount(FileSink, "ref_count", deinit, .{}); + break :brk struct { + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + }; + }; pub const IOWriter = bun.io.StreamingWriter(@This(), onWrite, onError, onReady, onClose); pub const Poll = IOWriter; @@ -3598,7 +3609,8 @@ pub const FileSink = struct { else => JSC.EventLoopHandle.init(event_loop_), }; - var this = FileSink.new(.{ + var this = bun.new(FileSink, .{ + .ref_count = .init(), .event_loop_handle = JSC.EventLoopHandle.init(evtloop), .fd = pipe.fd(), }); @@ -3615,7 +3627,8 @@ pub const FileSink = struct { JSC.EventLoopHandle => event_loop_, else => JSC.EventLoopHandle.init(event_loop_), }; - var this = FileSink.new(.{ + var this = bun.new(FileSink, .{ + .ref_count = .init(), .event_loop_handle = JSC.EventLoopHandle.init(evtloop), .fd = fd, }); @@ -3863,7 +3876,8 @@ pub const FileSink = struct { } pub fn init(fd: bun.FileDescriptor, event_loop_handle: anytype) *FileSink { - var this = FileSink.new(.{ + var this = bun.new(FileSink, .{ + .ref_count = .init(), .writer = .{}, .fd = fd, .event_loop_handle = JSC.EventLoopHandle.init(event_loop_handle), @@ -3873,12 +3887,9 @@ pub const FileSink = struct { return this; } - pub fn construct( - this: *FileSink, - allocator: std.mem.Allocator, - ) void { - _ = allocator; // autofix + pub fn construct(this: *FileSink, _: std.mem.Allocator) void { this.* = FileSink{ + .ref_count = .init(), .event_loop_handle = JSC.EventLoopHandle.init(JSC.VirtualMachine.get().eventLoop()), }; } @@ -3939,12 +3950,14 @@ pub const FileSink = struct { }, } } - pub fn deinit(this: *FileSink) void { + + fn deinit(this: *FileSink) void { this.pending.deinit(); this.writer.deinit(); if (this.event_loop_handle.globalObject()) |global| { AutoFlusher.unregisterDeferredMicrotaskWithType(@This(), this, global.bunVM()); } + bun.destroy(this); } pub fn toJS(this: *FileSink, globalThis: *JSGlobalObject) JSValue { @@ -4348,7 +4361,7 @@ pub const FileReader = struct { this.lazy = .none; } - this.parent().destroy(); + this.parent().deinit(); } pub fn onReadChunk(this: *@This(), init_buf: []const u8, state: bun.io.ReadState) bool { @@ -4820,8 +4833,7 @@ pub const ByteBlobLoader = struct { pub fn deinit(this: *ByteBlobLoader) void { this.clearStore(); - - this.parent().destroy(); + this.parent().deinit(); } fn clearStore(this: *ByteBlobLoader) void { @@ -5319,7 +5331,7 @@ pub const ByteStream = struct { if (this.buffer_action) |*action| { action.deinit(); } - this.parent().destroy(); + this.parent().deinit(); } pub fn drain(this: *@This()) bun.ByteList { diff --git a/src/bun.zig b/src/bun.zig index f7a344d64b..12843bb80c 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1981,39 +1981,6 @@ pub fn threadlocalAllocator() std.mem.Allocator { return default_allocator; } -pub fn Ref(comptime T: type) type { - return struct { - ref_count: u32, - allocator: std.mem.Allocator, - value: T, - - pub fn init(value: T, allocator: std.mem.Allocator) !*@This() { - var this = try allocator.create(@This()); - this.allocator = allocator; - this.ref_count = 1; - this.value = value; - return this; - } - - pub fn ref(this: *@This()) *@This() { - this.ref_count += 1; - return this; - } - - pub fn unref(this: *@This()) ?*@This() { - this.ref_count -= 1; - if (this.ref_count == 0) { - if (@hasDecl(T, "deinit")) { - this.value.deinit(); - } - this.allocator.destroy(this); - return null; - } - return this; - } - }; -} - pub fn HiveRef(comptime T: type, comptime capacity: u16) type { return struct { const HiveAllocator = HiveArray(@This(), capacity).Fallback; @@ -3038,13 +3005,10 @@ pub const heap_breakdown = @import("./heap_breakdown.zig"); /// Globally-allocate a value on the heap. /// -/// **Prefer `bun.New`, `bun.NewRefCounted`, or `bun.NewThreadSafeRefCounted` instead.** -/// Use this when the struct is a third-party struct you cannot modify, like a -/// Zig stdlib struct. Choosing the wrong allocator is an easy way to introduce -/// bugs. +/// Prefer this over `default_allocator.create` /// /// When used, you must call `bun.destroy` to free the memory. -/// default_allocator.destroy should not be used. +/// `default_allocator.destroy` should not be used. /// /// On macOS, you can use `Bun.unsafe.mimallocDump()` /// to dump the heap. @@ -3067,7 +3031,7 @@ pub inline fn new(comptime T: type, init: T) *T { } /// Free a globally-allocated a value from `bun.new()`. Using this with -/// pointers allocated from other means may cause crashes. +/// pointers allocated from other means will cause crashes. pub inline fn destroy(pointer: anytype) void { const T = std.meta.Child(@TypeOf(pointer)); @@ -3087,22 +3051,35 @@ pub inline fn dupe(comptime T: type, t: *T) *T { return new(T, t.*); } -pub fn New(comptime T: type) type { +/// Implements `fn new` for a type. +/// Pair with `TrivialDeinit` if the type contains no pointers. +pub fn TrivialNew(comptime T: type) fn (T) *T { return struct { - pub const ban_standard_library_allocator = true; - - pub inline fn destroy(self: *T) void { - bun.destroy(self); - } - - pub inline fn new(t: T) *T { + pub fn new(t: T) *T { return bun.new(T, t); } - }; + }.new; } -pub const NewRefCounted = ptr.NewRefCounted; -pub const NewThreadSafeRefCounted = ptr.NewThreadSafeRefCounted; +/// Implements `fn deinit` for a type. +/// Pair with `TrivialNew` if the type contains no pointers. +pub fn TrivialDeinit(comptime T: type) fn (*T) void { + return struct { + pub fn deinit(self: *T) void { + // TODO: assert that the structure contains no pointers. + // + // // Assert the structure contains no pointers. If there are + // // pointers, you must implement `deinit` manually, ideally + // // explaining why those pointers should or should not be freed. + // const fields = switch (@typeInfo(T)) { + // .@"struct", .@"union" => |i| i.fields, + // else => @compileError("please implement `deinit` manually"), + // }; + + bun.destroy(self); + } + }.deinit; +} pub fn exitThread() noreturn { const exiter = struct { @@ -3812,64 +3789,6 @@ pub fn getTotalMemorySize() usize { return Bun__ramSize(); } -pub const WeakPtrData = packed struct(u32) { - reference_count: u31 = 0, - finalized: bool = false, - - pub fn onFinalize(this: *WeakPtrData) bool { - bun.debugAssert(!this.finalized); - this.finalized = true; - return this.reference_count == 0; - } -}; - -pub fn WeakPtr(comptime T: type, comptime weakable_field: std.meta.FieldEnum(T)) type { - return struct { - const WeakRef = @This(); - - value: ?*T = null, - pub fn create(req: *T) WeakRef { - bun.debugAssert(!@field(req, @tagName(weakable_field)).finalized); - @field(req, @tagName(weakable_field)).reference_count += 1; - return .{ .value = req }; - } - - comptime { - if (@TypeOf(@field(@as(T, undefined), @tagName(weakable_field))) != WeakPtrData) { - @compileError("Expected " ++ @typeName(T) ++ " to have a " ++ @typeName(WeakPtrData) ++ " field named " ++ @tagName(weakable_field)); - } - } - - fn deinitInternal(this: *WeakRef, value: *T) void { - const weak_data: *WeakPtrData = &@field(value, @tagName(weakable_field)); - - this.value = null; - const count = weak_data.reference_count - 1; - weak_data.reference_count = count; - if (weak_data.finalized and count == 0) { - value.destroy(); - } - } - - pub fn deinit(this: *WeakRef) void { - if (this.value) |value| { - this.deinitInternal(value); - } - } - - pub fn get(this: *WeakRef) ?*T { - if (this.value) |value| { - if (!@field(value, @tagName(weakable_field)).finalized) { - return value; - } - - this.deinitInternal(value); - } - return null; - } - }; -} - pub const DebugThreadLock = if (Environment.allow_assert) struct { owning_thread: ?std.Thread.Id, @@ -3883,7 +3802,7 @@ pub const DebugThreadLock = if (Environment.allow_assert) pub fn lock(impl: *@This()) void { if (impl.owning_thread) |thread| { Output.err("assertion failure", "Locked by thread {d} here:", .{thread}); - crash_handler.dumpStackTrace(impl.locked_at.trace()); + crash_handler.dumpStackTrace(impl.locked_at.trace(), .{ .frame_count = 10, .stop_at_jsc_llint = true }); Output.panic("Safety lock violated on thread {d}", .{std.Thread.getCurrentId()}); } impl.owning_thread = std.Thread.getCurrentId(); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index d21428a214..01789c055c 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1850,7 +1850,8 @@ pub const BundleV2 = struct { event_loop: *bun.JSC.EventLoop, _: std.mem.Allocator, ) OOM!*JSBundleCompletionTask { - const completion = JSBundleCompletionTask.new(.{ + const completion = bun.new(JSBundleCompletionTask, .{ + .ref_count = .init(), .config = config, .jsc_event_loop = event_loop, .globalThis = globalThis, @@ -1917,6 +1918,11 @@ pub const BundleV2 = struct { }; pub const JSBundleCompletionTask = struct { + pub const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", @This().deinit, .{}); + // pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, config: bun.JSC.API.JSBundler.Config, jsc_event_loop: *bun.JSC.EventLoop, task: bun.JSC.AnyTask, @@ -1934,11 +1940,8 @@ pub const BundleV2 = struct { next: ?*JSBundleCompletionTask = null, transpiler: *BundleV2 = undefined, plugins: ?*bun.JSC.API.JSBundler.Plugin = null, - ref_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), started_at_ns: u64 = 0, - pub usingnamespace bun.NewThreadSafeRefCounted(JSBundleCompletionTask, _deinit, null); - pub fn configureBundler( completion: *JSBundleCompletionTask, transpiler: *Transpiler, @@ -2018,7 +2021,7 @@ pub const BundleV2 = struct { pub const TaskCompletion = bun.JSC.AnyTask.New(JSBundleCompletionTask, onComplete); - fn _deinit(this: *JSBundleCompletionTask) void { + fn deinit(this: *JSBundleCompletionTask) void { this.result.deinit(); this.log.deinit(); this.poll_ref.disable(); @@ -2027,7 +2030,7 @@ pub const BundleV2 = struct { } this.config.deinit(bun.default_allocator); this.promise.deinit(); - this.destroy(); + bun.destroy(this); } pub fn onComplete(this: *JSBundleCompletionTask) void { diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index f0d81c831a..5104c63944 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -588,11 +588,11 @@ pub const RunCommand = struct { const is_probably_trying_to_run_a_pkg_script = original_script_for_bun_run != null and ((code == 1 and bun.strings.eqlComptime(original_script_for_bun_run.?, "test")) or - (code == 2 and bun.strings.eqlAnyComptime(original_script_for_bun_run.?, &.{ - "install", - "kill", - "link", - }) and ctx.positionals.len == 1)); + (code == 2 and bun.strings.eqlAnyComptime(original_script_for_bun_run.?, &.{ + "install", + "kill", + "link", + }) and ctx.positionals.len == 1)); if (is_probably_trying_to_run_a_pkg_script) { // if you run something like `bun run test`, you get a confusing message because diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index 1994d3f23c..12f1372a09 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -177,7 +177,7 @@ pub const JunitReporter = struct { ); } - pub usingnamespace bun.New(JunitReporter); + pub const new = bun.TrivialNew(JunitReporter); fn generatePropertiesList(this: *JunitReporter) !void { const PropertiesList = struct { diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index 8f38f9eb0f..a00a1eafeb 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -656,14 +656,14 @@ pub const UpgradeCommand = struct { const powershell_path = bun.which(&buf, bun.getenvZ("PATH") orelse "", "", "powershell") orelse hardcoded_system_powershell: { - const system_root = bun.getenvZ("SystemRoot") orelse "C:\\Windows"; - const hardcoded_system_powershell = bun.path.joinAbsStringBuf(system_root, &buf, &.{ system_root, "System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, .windows); - if (bun.sys.exists(hardcoded_system_powershell)) { - break :hardcoded_system_powershell hardcoded_system_powershell; - } - Output.prettyErrorln("error: Failed to unzip {s} due to PowerShell not being installed.", .{tmpname}); - Global.exit(1); - }; + const system_root = bun.getenvZ("SystemRoot") orelse "C:\\Windows"; + const hardcoded_system_powershell = bun.path.joinAbsStringBuf(system_root, &buf, &.{ system_root, "System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, .windows); + if (bun.sys.exists(hardcoded_system_powershell)) { + break :hardcoded_system_powershell hardcoded_system_powershell; + } + Output.prettyErrorln("error: Failed to unzip {s} due to PowerShell not being installed.", .{tmpname}); + Global.exit(1); + }; var unzip_argv = [_]string{ powershell_path, diff --git a/src/codegen/class-definitions.ts b/src/codegen/class-definitions.ts index 334c1a4296..5ce2549620 100644 --- a/src/codegen/class-definitions.ts +++ b/src/codegen/class-definitions.ts @@ -94,23 +94,46 @@ export class ClassDefinition { /** * ## IMPORTANT * You _must_ free the pointer to your native class! + * + * Example for pointers only owned by JavaScript classes: * ```zig * pub const NativeClass = struct { - * pub usingnamespace bun.New(NativeClass); * * fn constructor(global: *JSC.JSGlobalObject, frame: *JSC.CallFrame) bun.JSError!*SocketAddress { * // do stuff - * return NativeClass.new(.{ + * return bun.new(NativeClass, .{ * // ... * }); * } * * fn finalize(this: *NativeClass) void { * // free allocations owned by this class, then free the struct itself. - * this.destroy(); + * bun.destroy(this); * } * }; * ``` + * Example with ref counting: + * ``` + * pub const RefCountedNativeClass = struct { + * const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + * pub const ref = RefCount.ref; + * pub const deref = RefCount.deref; + * + * fn constructor(global: *JSC.JSGlobalObject, frame: *JSC.CallFrame) bun.JSError!*SocketAddress { + * // do stuff + * return bun.new(NativeClass, .{ + * // ... + * }); + * } + * + * fn deinit(this: *NativeClass) void { + * // free allocations owned by this class, then free the struct itself. + * bun.destroy(this); + * } + * + * pub const finalize = deref; // GC will deref, which can free if no references are left. + * }; + * ``` * @todo remove this and require all classes to implement `finalize`. */ finalize?: boolean; diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 16dbdb1d5f..15b433dc2a 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -333,7 +333,7 @@ pub fn crashHandler( if (debug_trace) { has_printed_message = true; - dumpStackTrace(trace.*); + dumpStackTrace(trace.*, .{}); trace_str_buf.writer().print("{}", .{TraceString{ .trace = trace, @@ -1542,7 +1542,7 @@ noinline fn coldHandleErrorReturnTrace(err_int_workaround_for_zig_ccall_bug: std ); } Output.flush(); - dumpStackTrace(trace.*); + dumpStackTrace(trace.*, .{}); } else { const ts = TraceString{ .trace = trace, @@ -1593,7 +1593,7 @@ const stdDumpStackTrace = debug.dumpStackTrace; /// Version of the standard library dumpStackTrace that has some fallbacks for /// cases where such logic fails to run. -pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { +pub fn dumpStackTrace(trace: std.builtin.StackTrace, limits: WriteStackTraceLimits) void { Output.flush(); const stderr = std.io.getStdErr().writer(); if (!bun.Environment.isDebug) { @@ -1613,7 +1613,7 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; break :attempt_dump; }; - debug.writeStackTrace(trace, stderr, debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch |err| { + writeStackTrace(trace, stderr, debug_info, std.io.tty.detectConfig(std.io.getStdErr()), limits) catch |err| { stderr.print("Unable to dump stack trace: {s}\nFallback trace:\n", .{@errorName(err)}) catch return; break :attempt_dump; }; @@ -1624,7 +1624,15 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { // TODO(@paperclover): see if zig 0.14 fixes this }, else => { - stdDumpStackTrace(trace); + // Assume debug symbol tooling is reliable. + const debug_info = debug.getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; + return; + }; + writeStackTrace(trace, stderr, debug_info, std.io.tty.detectConfig(std.io.getStdErr()), limits) catch |err| { + stderr.print("Unable to dump stack trace: {s}", .{@errorName(err)}) catch return; + return; + }; return; }, } @@ -1681,11 +1689,11 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { stderr.writeAll(proc.stderr) catch return; } -pub fn dumpCurrentStackTrace(first_address: ?usize) void { +pub fn dumpCurrentStackTrace(first_address: ?usize, limits: WriteStackTraceLimits) void { var addrs: [32]usize = undefined; var stack: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &addrs }; std.debug.captureStackTrace(first_address orelse @returnAddress(), &stack); - dumpStackTrace(stack); + dumpStackTrace(stack, limits); } /// A variant of `std.builtin.StackTrace` that stores its data within itself @@ -1858,11 +1866,266 @@ pub fn isPanicking() bool { return panicking.load(.monotonic) > 0; } +const SourceLocation = debug.SourceLocation; +pub const SourceAtAddress = struct { + source_location: ?SourceLocation, + symbol_name: []const u8, + compile_unit_name: []const u8, + fn deinit(self: *@This(), debug_info: *debug.SelfInfo) void { + if (self.source_location) |sl| debug_info.allocator.free(sl.file_name); + } +}; + +pub const WriteStackTraceLimits = struct { + frame_count: usize = std.math.maxInt(usize), + stop_at_jsc_llint: bool = false, +}; + +/// Clone of `debug.writeStackTrace`, but can be configured to stop at either a +/// frame count, or when hitting jsc LLInt Additionally, the printing function +/// does not print the `^`, instead it highlights the word at the column. This +/// Makes each frame take up two lines instead of three. +pub fn writeStackTrace( + stack_trace: std.builtin.StackTrace, + out_stream: anytype, + debug_info: *debug.SelfInfo, + tty_config: std.io.tty.Config, + limits: WriteStackTraceLimits, +) !void { + if (builtin.strip_debug_info) return error.MissingDebugInfo; + var frame_index: usize = 0; + var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); + + while (frames_left != 0) : ({ + frames_left -= 1; + frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; + }) { + if (frame_index >= limits.frame_count) { + break; + } + const return_address = stack_trace.instruction_addresses[frame_index]; + const source = (try getSourceAtAddress(debug_info, return_address - 1)) orelse { + const module_name = debug_info.getModuleNameForAddress(return_address - 1); + try printLineInfo( + out_stream, + null, + return_address - 1, + "???", + module_name orelse "???", + tty_config, + ); + continue; + }; + + if (limits.stop_at_jsc_llint and bun.strings.includes(source.symbol_name, "_llint_")) { + break; + } + + try printLineInfo( + out_stream, + source.source_location, + return_address - 1, + source.symbol_name, + source.compile_unit_name, + tty_config, + ); + } + + if (stack_trace.index > stack_trace.instruction_addresses.len) { + const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; + + tty_config.setColor(out_stream, .bold) catch {}; + try out_stream.print("({d} additional stack frames not recorded...)\n", .{dropped_frames}); + tty_config.setColor(out_stream, .reset) catch {}; + } else if (frames_left != 0) { + tty_config.setColor(out_stream, .bold) catch {}; + try out_stream.print("({d} additional stack frames skipped...)\n", .{frames_left}); + tty_config.setColor(out_stream, .reset) catch {}; + } + out_stream.writeAll("\n") catch {}; +} + +/// Clone of `debug.printSourceAtAddress` but it returns the metadata as well. +pub fn getSourceAtAddress(debug_info: *debug.SelfInfo, address: usize) !?SourceAtAddress { + const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => return null, + else => return err, + }; + + const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => return null, + else => return err, + }; + + return .{ + .source_location = symbol_info.source_location, + .symbol_name = symbol_info.name, + .compile_unit_name = symbol_info.compile_unit_name, + }; +} + +/// Clone of `debug.printLineInfo` as it is private. +fn printLineInfo( + out_stream: anytype, + source_location: ?SourceLocation, + address: usize, + symbol_name: []const u8, + compile_unit_name: []const u8, + tty_config: std.io.tty.Config, +) !void { + const base_path = bun.Environment.base_path ++ std.fs.path.sep_str; + nosuspend { + if (source_location) |*sl| { + if (bun.strings.startsWith(sl.file_name, base_path)) { + try tty_config.setColor(out_stream, .dim); + try out_stream.print("{s}", .{base_path}); + try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(out_stream, .bold); + try out_stream.print("{s}", .{sl.file_name[base_path.len..]}); + } else { + try tty_config.setColor(out_stream, .bold); + try out_stream.print("{s}", .{sl.file_name}); + } + try out_stream.print(":{d}:{d}", .{ sl.line, sl.column }); + } else { + try tty_config.setColor(out_stream, .bold); + try out_stream.writeAll("???:?:?"); + } + + try tty_config.setColor(out_stream, .reset); + try out_stream.writeAll(": "); + try tty_config.setColor(out_stream, .dim); + try out_stream.print("0x{x} in", .{address}); + try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(out_stream, .yellow); + try out_stream.print(" {s}", .{symbol_name}); + try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(out_stream, .dim); + try out_stream.print(" ({s})", .{compile_unit_name}); + try tty_config.setColor(out_stream, .reset); + try out_stream.writeAll("\n"); + + // Show the matching source code line if possible + if (source_location) |sl| { + if (printLineFromFileAnyOs(out_stream, tty_config, sl)) { + if (sl.column > 0 and tty_config == .no_color) { + // The caret already takes one char + const space_needed = @as(usize, @intCast(sl.column - 1)); + try out_stream.writeByteNTimes(' ', space_needed); + try out_stream.writeAll("^\n"); + } + } else |err| switch (err) { + error.EndOfFile, error.FileNotFound => {}, + error.BadPathName => {}, + error.AccessDenied => {}, + else => return err, + } + } + } +} + +/// Modified version of `debug.printLineFromFileAnyOs` that uses two passes. +/// - Record the whole slice into a buffer +/// - Locate the column, expand a highlight to one word. +/// - Print the line, with the highlight. +fn printLineFromFileAnyOs(out_stream: anytype, tty_config: std.io.tty.Config, source_location: SourceLocation) !void { + // Need this to always block even in async I/O mode, because this could potentially + // be called from e.g. the event loop code crashing. + var f = try std.fs.cwd().openFile(source_location.file_name, .{}); + defer f.close(); + + var line_buf: [4096]u8 = undefined; + var fbs = std.io.fixedBufferStream(&line_buf); + read_line: { + var buf: [4096]u8 = undefined; + var amt_read = try f.read(buf[0..]); + const line_start = seek: { + var current_line_start: usize = 0; + var next_line: usize = 1; + while (next_line != source_location.line) { + const slice = buf[current_line_start..amt_read]; + if (bun.strings.indexOfChar(slice, '\n')) |pos| { + next_line += 1; + if (pos == slice.len - 1) { + amt_read = try f.read(buf[0..]); + current_line_start = 0; + } else current_line_start += pos + 1; + } else if (amt_read < buf.len) { + return error.EndOfFile; + } else { + amt_read = try f.read(buf[0..]); + current_line_start = 0; + } + } + break :seek current_line_start; + }; + const slice = buf[line_start..amt_read]; + if (bun.strings.indexOfChar(slice, '\n')) |pos| { + const line = slice[0..pos]; + std.mem.replaceScalar(u8, line, '\t', ' '); + fbs.writer().writeAll(line) catch {}; + break :read_line; + } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. + std.mem.replaceScalar(u8, slice, '\t', ' '); + fbs.writer().writeAll(slice) catch break :read_line; + while (amt_read == buf.len) { + amt_read = try f.read(buf[0..]); + if (bun.strings.indexOfChar(buf[0..amt_read], '\n')) |pos| { + const line = buf[0..pos]; + std.mem.replaceScalar(u8, line, '\t', ' '); + fbs.writer().writeAll(line) catch break :read_line; + break :read_line; + } else { + const line = buf[0..amt_read]; + std.mem.replaceScalar(u8, line, '\t', ' '); + fbs.writer().writeAll(line) catch break :read_line; + } + } + break :read_line; + } + return; + } + const line_without_newline = std.mem.trimRight(u8, fbs.getWritten(), "\n"); + if (source_location.column > line_without_newline.len) { + try out_stream.writeAll(line_without_newline); + try out_stream.writeByte('\n'); + return; + } + // expand the highlight to one word + var left = source_location.column -| 1; + var right = left + 1; + while (left > 0) switch (line_without_newline[left]) { + else => left -= 1, + 'a'...'z', 'A'...'Z', '0'...'9', '_', ' ', '\t' => break, + }; + while (left > 0) switch (line_without_newline[left]) { + 'a'...'z', 'A'...'Z', '0'...'9', '_' => left -= 1, + else => break, + }; + while (right < line_without_newline.len) switch (line_without_newline[right - 1]) { + 'a'...'z', 'A'...'Z', '0'...'9', '_' => right += 1, + else => break, + }; + const before = line_without_newline[0..left]; + const highlight = line_without_newline[left..right]; + const after = line_without_newline[right..]; + try tty_config.setColor(out_stream, .red); + try tty_config.setColor(out_stream, .dim); + try out_stream.writeAll(before); + try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(out_stream, .red); + try out_stream.writeAll(highlight); + try tty_config.setColor(out_stream, .dim); + try out_stream.writeAll(after); + try tty_config.setColor(out_stream, .reset); + try out_stream.writeByte('\n'); +} + export fn CrashHandler__setInsideNativePlugin(name: ?[*:0]const u8) callconv(.C) void { inside_native_plugin = name; } -fn unsupportUVFunction(name: ?[*:0]const u8) callconv(.C) void { +fn unsupportedUVFunction(name: ?[*:0]const u8) callconv(.C) void { bun.analytics.Features.unsupported_uv_function += 1; unsupported_uv_function = name; std.debug.panic("unsupported uv function: {s}", .{name.?}); @@ -1885,6 +2148,6 @@ export fn CrashHandler__setDlOpenAction(action: ?[*:0]const u8) void { comptime { _ = &Bun__crashHandler; if (!bun.Environment.isWindows) { - @export(&unsupportUVFunction, .{ .name = "CrashHandler__unsupportedUVFunction" }); + @export(&unsupportedUVFunction, .{ .name = "CrashHandler__unsupportedUVFunction" }); } } diff --git a/src/css/css_parser.zig b/src/css/css_parser.zig index 047c1237ec..639a6b3546 100644 --- a/src/css/css_parser.zig +++ b/src/css/css_parser.zig @@ -1697,15 +1697,15 @@ pub fn TopLevelRuleParser(comptime AtRuleParserT: type) type { const layer: ?struct { value: ?LayerName } = if (input.tryParse(Parser.expectIdentMatching, .{"layer"}) == .result) - .{ .value = null } - else if (input.tryParse(Parser.expectFunctionMatching, .{"layer"}) == .result) brk: { - break :brk .{ - .value = switch (input.parseNestedBlock(LayerName, {}, voidWrap(LayerName, LayerName.parse))) { - .result => |v| v, - .err => |e| return .{ .err = e }, - }, - }; - } else null; + .{ .value = null } + else if (input.tryParse(Parser.expectFunctionMatching, .{"layer"}) == .result) brk: { + break :brk .{ + .value = switch (input.parseNestedBlock(LayerName, {}, voidWrap(LayerName, LayerName.parse))) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }, + }; + } else null; const supports = if (input.tryParse(Parser.expectFunctionMatching, .{"supports"}) == .result) brk: { const Func = struct { @@ -4710,11 +4710,12 @@ pub const nth = struct { if (bytes.len >= 3 and bun.strings.eqlCaseInsensitiveASCIIICheckLength(bytes[0..2], "n-") and brk: { - for (bytes[2..]) |b| { - if (b < '0' or b > '9') break :brk false; - } - break :brk true; - }) { + for (bytes[2..]) |b| { + if (b < '0' or b > '9') break :brk false; + } + break :brk true; + }) + { return parse_number_saturate(allocator, str[1..]); // Include the minus sign } else { return .{ .err = {} }; diff --git a/src/css/properties/custom.zig b/src/css/properties/custom.zig index 29ac1c3f87..18b67d2d40 100644 --- a/src/css/properties/custom.zig +++ b/src/css/properties/custom.zig @@ -177,9 +177,10 @@ pub const TokenList = struct { if (!dest.minify and i != this.v.items.len - 1 and !(this.v.items[i + 1] == .token and switch (this.v.items[i + 1].token) { - .comma, .close_paren => true, - else => false, - })) { + .comma, .close_paren => true, + else => false, + })) + { // Whitespace is removed during parsing, so add it back if we aren't minifying. try dest.writeChar(' '); return true; diff --git a/src/css/properties/font.zig b/src/css/properties/font.zig index 93935b9228..407bba9ff5 100644 --- a/src/css/properties/font.zig +++ b/src/css/properties/font.zig @@ -351,11 +351,12 @@ pub const FontFamily = union(enum) { if (val.len > 0 and !css.parse_utility.parseString( - dest.allocator, - GenericFontFamily, - val, - GenericFontFamily.parse, - ).isOk()) { + dest.allocator, + GenericFontFamily, + val, + GenericFontFamily.parse, + ).isOk()) + { var id = ArrayList(u8){}; defer id.deinit(dest.allocator); var first = true; diff --git a/src/css/rules/rules.zig b/src/css/rules/rules.zig index 67055e2254..a775832064 100644 --- a/src/css/rules/rules.zig +++ b/src/css/rules/rules.zig @@ -497,7 +497,7 @@ pub fn CssRuleList(comptime AtRule: type) type { } else { if (!dest.minify and !(last_without_block and - (rule.* == .import or rule.* == .namespace or rule.* == .layer_statement))) + (rule.* == .import or rule.* == .namespace or rule.* == .layer_statement))) { try dest.writeChar('\n'); } diff --git a/src/css/rules/style.zig b/src/css/rules/style.zig index 9e3893fd0b..618d808dbb 100644 --- a/src/css/rules/style.zig +++ b/src/css/rules/style.zig @@ -99,9 +99,9 @@ pub fn StyleRule(comptime R: type) type { // If supported, or there are no targets, preserve nesting. Otherwise, write nested rules after parent. const supports_nesting = this.rules.v.items.len == 0 or !css.Targets.shouldCompileSame( - &dest.targets, - .nesting, - ); + &dest.targets, + .nesting, + ); const len = this.declarations.declarations.items.len + this.declarations.important_declarations.items.len; const has_declarations = supports_nesting or len > 0 or this.rules.v.items.len == 0; @@ -246,16 +246,16 @@ pub fn StyleRule(comptime R: type) type { return this.declarations.len() == other.declarations.len() and this.selectors.eql(&other.selectors) and brk: { - var len = @min(this.declarations.declarations.items.len, other.declarations.declarations.items.len); - for (this.declarations.declarations.items[0..len], other.declarations.declarations.items[0..len]) |*a, *b| { - if (!a.propertyId().eql(&b.propertyId())) break :brk false; - } - len = @min(this.declarations.important_declarations.items.len, other.declarations.important_declarations.items.len); - for (this.declarations.important_declarations.items[0..len], other.declarations.important_declarations.items[0..len]) |*a, *b| { - if (!a.propertyId().eql(&b.propertyId())) break :brk false; - } - break :brk true; - }; + var len = @min(this.declarations.declarations.items.len, other.declarations.declarations.items.len); + for (this.declarations.declarations.items[0..len], other.declarations.declarations.items[0..len]) |*a, *b| { + if (!a.propertyId().eql(&b.propertyId())) break :brk false; + } + len = @min(this.declarations.important_declarations.items.len, other.declarations.important_declarations.items.len); + for (this.declarations.important_declarations.items[0..len], other.declarations.important_declarations.items[0..len]) |*a, *b| { + if (!a.propertyId().eql(&b.propertyId())) break :brk false; + } + break :brk true; + }; } }; } diff --git a/src/css/selectors/selector.zig b/src/css/selectors/selector.zig index ea453c6ce8..c7d2cee998 100644 --- a/src/css/selectors/selector.zig +++ b/src/css/selectors/selector.zig @@ -1176,7 +1176,7 @@ pub const serialize = struct { // so use :is() if that is not the case. if (ctx.selectors.v.len() == 1 and (first or (!hasTypeSelector(ctx.selectors.v.at(0)) and - isSimple(ctx.selectors.v.at(0))))) + isSimple(ctx.selectors.v.at(0))))) { try serializeSelector(ctx.selectors.v.at(0), W, dest, ctx.parent, false); } else { diff --git a/src/css/values/easing.zig b/src/css/values/easing.zig index 6de9a827b4..ee3a847e4c 100644 --- a/src/css/values/easing.zig +++ b/src/css/values/easing.zig @@ -231,11 +231,11 @@ pub const EasingFunction = union(enum) { pub fn isEase(this: *const EasingFunction) bool { return this.* == .ease or (this.* == .cubic_bezier and this.cubic_bezier.eql(&.{ - .x1 = 0.25, - .y1 = 0.1, - .x2 = 0.25, - .y2 = 1.0, - })); + .x1 = 0.25, + .y1 = 0.1, + .x2 = 0.25, + .y2 = 1.0, + })); } }; diff --git a/src/css/values/ident.zig b/src/css/values/ident.zig index 0bc12530d6..c86c6e3700 100644 --- a/src/css/values/ident.zig +++ b/src/css/values/ident.zig @@ -308,9 +308,9 @@ pub const CustomIdent = struct { ) PrintErr!void { const css_module_custom_idents_enabled = enabled_css_modules and if (dest.css_module) |*css_module| - css_module.config.custom_idents - else - false; + css_module.config.custom_idents + else + false; return dest.writeIdent(this.v, css_module_custom_idents_enabled); } diff --git a/src/css/values/image.zig b/src/css/values/image.zig index 5c3ae53121..e8a5cb8844 100644 --- a/src/css/values/image.zig +++ b/src/css/values/image.zig @@ -131,7 +131,7 @@ pub const Image = union(enum) { // Legacy -webkit-gradient() if (prefixes.contains(VendorPrefix.WEBKIT) and if (targets.browsers) |browsers| css.prefixes.Feature.isWebkitGradient(browsers) else false and - prefix_image.* == .gradient) + prefix_image.* == .gradient) { if (prefix_image.getLegacyWebkit(allocator)) |legacy| { res.append(allocator, legacy); @@ -262,10 +262,9 @@ pub const ImageSet = struct { pub fn isCompatible(this: *const @This(), browsers: css.targets.Browsers) bool { return css.Feature.isCompatible(.image_set, browsers) and - for (this.options.items) |opt| - { - if (!opt.image.isCompatible(browsers)) break false; - } else true; + for (this.options.items) |opt| { + if (!opt.image.isCompatible(browsers)) break false; + } else true; } /// Returns the `image-set()` value with the given vendor prefix. diff --git a/src/deps/c_ares.zig b/src/deps/c_ares.zig index 110f3b90e3..336c0e86e0 100644 --- a/src/deps/c_ares.zig +++ b/src/deps/c_ares.zig @@ -1685,7 +1685,7 @@ pub const Error = enum(i32) { hostname: ?bun.String, promise: JSC.JSPromise.Strong, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn init(errno: Error, syscall: []const u8, hostname: ?bun.String, promise: JSC.JSPromise.Strong) *Deferred { return Deferred.new(.{ @@ -1734,7 +1734,7 @@ pub const Error = enum(i32) { hostname.deref(); } this.promise.deinit(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/deps/diffz/DiffMatchPatch.zig b/src/deps/diffz/DiffMatchPatch.zig index e6dc26c17c..0cdcc4c585 100644 --- a/src/deps/diffz/DiffMatchPatch.zig +++ b/src/deps/diffz/DiffMatchPatch.zig @@ -522,7 +522,7 @@ fn diffBisect( var y2: isize = x2 - k2; while (x2 < before_length and y2 < after_length and before[@intCast(before_length - x2 - 1)] == - after[@intCast(after_length - y2 - 1)]) + after[@intCast(after_length - y2 - 1)]) { x2 += 1; y2 += 1; @@ -1231,9 +1231,9 @@ fn diffCleanupSemanticScore(one: []const u8, two: []const u8) usize { const blankLine2 = lineBreak2 and // BLANKLINESTART.IsMatch(two); (std.mem.startsWith(u8, two, "\n\n") or - std.mem.startsWith(u8, two, "\r\n\n") or - std.mem.startsWith(u8, two, "\n\r\n") or - std.mem.startsWith(u8, two, "\r\n\r\n")); + std.mem.startsWith(u8, two, "\r\n\n") or + std.mem.startsWith(u8, two, "\n\r\n") or + std.mem.startsWith(u8, two, "\r\n\r\n")); if (blankLine1 or blankLine2) { // Five points for blank lines. @@ -1314,8 +1314,8 @@ pub fn diffCleanupEfficiency( // ABXC if ((last_equality.Length != 0) and ((pre_ins and pre_del and post_ins and post_del) or - ((last_equality.Length < dmp.diff_edit_cost / 2) and - ((if (pre_ins) 1 else 0) + (if (pre_del) 1 else 0) + (if (post_ins) 1 else 0) + (if (post_del) 1 else 0)) == 3))) + ((last_equality.Length < dmp.diff_edit_cost / 2) and + ((if (pre_ins) 1 else 0) + (if (pre_del) 1 else 0) + (if (post_ins) 1 else 0) + (if (post_del) 1 else 0)) == 3))) { // Duplicate record. try diffs.insert( diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index 8a6dbe7252..de6c4ff064 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -617,7 +617,7 @@ pub const Loop = extern struct { // This log may be helpful if you are curious where KeepAlives are being created from // if (Env.isDebug) { - // std.debug.dumpCurrentStackTrace(@returnAddress()); + // std.debug.dumpCurrentStackTrace(@returnAddress(), .{}); // } this.active_handles += 1; } diff --git a/src/env_loader.zig b/src/env_loader.zig index ff54acd599..e4a1f15b6b 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -139,6 +139,7 @@ pub const Loader = struct { session_token = token; } this.aws_credentials = .{ + .ref_count = .init(), .accessKeyId = accessKeyId, .secretAccessKey = secretAccessKey, .region = region, diff --git a/src/http.zig b/src/http.zig index c759dd69d5..60fdbcc492 100644 --- a/src/http.zig +++ b/src/http.zig @@ -222,6 +222,10 @@ pub const Sendfile = struct { }; const ProxyTunnel = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", ProxyTunnel.deinit, .{}); + pub const ref = ProxyTunnel.RefCount.ref; + pub const deref = ProxyTunnel.RefCount.deref; + wrapper: ?ProxyTunnelWrapper = null, shutdown_err: anyerror = error.ConnectionClosed, // active socket is the socket that is currently being used @@ -231,12 +235,10 @@ const ProxyTunnel = struct { none: void, } = .{ .none = {} }, write_buffer: bun.io.StreamBuffer = .{}, - ref_count: u32 = 1, + ref_count: RefCount, const ProxyTunnelWrapper = SSLWrapper(*HTTPClient); - usingnamespace bun.NewRefCounted(ProxyTunnel, _deinit, null); - fn onOpen(this: *HTTPClient) void { this.state.response_stage = .proxy_handshake; this.state.request_stage = .proxy_handshake; @@ -436,7 +438,9 @@ const ProxyTunnel = struct { } fn start(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket, ssl_options: JSC.API.ServerConfig.SSLConfig) void { - const proxy_tunnel = ProxyTunnel.new(.{}); + const proxy_tunnel = bun.new(ProxyTunnel, .{ + .ref_count = .init(), + }); var custom_options = ssl_options; // we always request the cert so we can verify it and also we manually abort the connection if the hostname doesn't match @@ -520,14 +524,14 @@ const ProxyTunnel = struct { this.deref(); } - fn _deinit(this: *ProxyTunnel) void { + fn deinit(this: *ProxyTunnel) void { this.socket = .{ .none = {} }; if (this.wrapper) |*wrapper| { wrapper.deinit(); this.wrapper = null; } this.write_buffer.deinit(); - this.destroy(); + bun.destroy(this); } }; @@ -1063,7 +1067,8 @@ pub const HTTPThread = struct { buffer: [512 * 1024]u8 = undefined, fixed_buffer_allocator: std.heap.FixedBufferAllocator, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); pub fn init() *@This() { var this = HeapRequestBodyBuffer.new(.{ @@ -1082,10 +1087,6 @@ pub const HTTPThread = struct { this.deinit(); } } - - pub fn deinit(this: *@This()) void { - this.destroy(); - } }; pub const RequestBodyBuffer = union(enum) { @@ -1138,7 +1139,7 @@ pub const HTTPThread = struct { decompressor: *bun.libdeflate.Decompressor = undefined, shared_buffer: [512 * 1024]u8 = undefined, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); }; const request_body_send_stack_buffer_size = 32 * 1024; @@ -2523,7 +2524,7 @@ pub const AsyncHTTP = struct { url: bun.URL, is_url_owned: bool, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn onResult(this: *Preconnect, _: *AsyncHTTP, _: HTTPClientResult) void { this.response_buffer.deinit(); @@ -2533,7 +2534,7 @@ pub const AsyncHTTP = struct { bun.default_allocator.free(this.url.href); } - this.destroy(); + bun.destroy(this); } }; @@ -2847,7 +2848,7 @@ pub const AsyncHTTP = struct { { this.client.deinit(); var threadlocal_http: *ThreadlocalAsyncHTTP = @fieldParentPtr("async_http", async_http); - defer threadlocal_http.destroy(); + defer threadlocal_http.deinit(); log("onAsyncHTTPCallback: {any}", .{std.fmt.fmtDuration(this.elapsed)}); callback.function(callback.ctx, async_http, result); } @@ -4665,6 +4666,8 @@ const assert = bun.assert; // Exists for heap stats reasons. const ThreadlocalAsyncHTTP = struct { + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + async_http: AsyncHTTP, - pub usingnamespace bun.New(@This()); }; diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index c2dedf4d61..91965c3567 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -206,7 +206,12 @@ const CppWebSocket = opaque { pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { return struct { + pub const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; pub const Socket = uws.NewSocketHandler(ssl); + + ref_count: RefCount, tcp: Socket, outgoing_websocket: ?*CppWebSocket, input_body_buf: []u8 = &[_]u8{}, @@ -219,12 +224,9 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { hostname: [:0]const u8 = "", poll_ref: Async.KeepAlive = Async.KeepAlive.init(), state: State = .initializing, - ref_count: u32 = 1, const State = enum { initializing, reading, failed }; - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); - const HTTPClient = @This(); pub fn register(_: *JSC.JSGlobalObject, _: *anyopaque, ctx: *uws.SocketContext) callconv(.C) void { Socket.configure( @@ -245,10 +247,10 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { ); } - pub fn deinit(this: *HTTPClient) void { + fn deinit(this: *HTTPClient) void { this.clearData(); bun.debugAssert(this.tcp.isDetached()); - this.destroy(); + bun.destroy(this); } /// On error, this returns null. @@ -281,7 +283,8 @@ pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { NonUTF8Headers.init(header_names, header_values, header_count), ) catch return null; - var client = HTTPClient.new(.{ + var client = bun.new(HTTPClient, .{ + .ref_count = .init(), .tcp = .{ .socket = .{ .detached = {} } }, .outgoing_websocket = websocket, .input_body_buf = body, @@ -986,6 +989,13 @@ const Copy = union(enum) { pub fn NewWebSocketClient(comptime ssl: bool) type { return struct { pub const Socket = uws.NewSocketHandler(ssl); + + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, + tcp: Socket, outgoing_websocket: ?*CppWebSocket = null, @@ -1016,13 +1026,11 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { initial_data_handler: ?*InitialDataHandler = null, event_loop: *JSC.EventLoop = undefined, - ref_count: u32 = 1, const stack_frame_size = 1024; const WebSocket = @This(); - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn register(global: *JSC.JSGlobalObject, loop_: *anyopaque, ctx_: *anyopaque) callconv(.C) void { const vm = global.bunVM(); const loop = @as(*uws.Loop, @ptrCast(@alignCast(loop_))); @@ -1868,7 +1876,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { pub const Handle = JSC.AnyTask.New(@This(), handle); - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn handleWithoutDeinit(this: *@This()) void { var this_socket = this.adopted orelse return; @@ -1889,7 +1897,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { pub fn deinit(this: *@This()) void { bun.default_allocator.free(this.slice); - this.destroy(); + bun.destroy(this); } }; @@ -1903,7 +1911,8 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { ) callconv(.C) ?*anyopaque { const tcp = @as(*uws.Socket, @ptrCast(input_socket)); const ctx = @as(*uws.SocketContext, @ptrCast(socket_ctx)); - var ws = WebSocket.new(WebSocket{ + var ws = bun.new(WebSocket, .{ + .ref_count = .init(), .tcp = .{ .socket = .{ .detached = {} } }, .outgoing_websocket = outgoing, .globalThis = globalThis, @@ -1974,7 +1983,7 @@ pub fn NewWebSocketClient(comptime ssl: bool) type { pub fn deinit(this: *WebSocket) void { this.clearData(); - this.destroy(); + bun.destroy(this); } pub fn memoryCost(this: *WebSocket) callconv(.C) usize { diff --git a/src/install/install.zig b/src/install/install.zig index 3c58e29373..129f09991c 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -1800,10 +1800,10 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { pub fn deinit(task: *@This()) void { bun.default_allocator.free(task.bytes); - task.destroy(); + bun.destroy(task); } - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn run(task: *@This()) ?anyerror { const src = task.src; @@ -2140,8 +2140,11 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { // const UninstallTask = struct { + pub const new = bun.TrivialNew(@This()); + absolute_path: []const u8, task: JSC.WorkPoolTask = .{ .callback = &run }, + pub fn run(task: *JSC.WorkPoolTask) void { var unintall_task: *@This() = @fieldParentPtr("task", task); var debug_timer = bun.Output.DebugTimer.start(); @@ -2179,10 +2182,8 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type { pub fn deinit(uninstall_task: *@This()) void { bun.default_allocator.free(uninstall_task.absolute_path); - uninstall_task.destroy(); + bun.destroy(uninstall_task); } - - pub usingnamespace bun.New(@This()); }; var task = UninstallTask.new(.{ .absolute_path = bun.default_allocator.dupeZ(u8, bun.path.joinAbsString(FileSystem.instance.top_level_dir, &.{ this.node_modules.path.items, temp_path }, .auto)) catch bun.outOfMemory(), @@ -13700,7 +13701,7 @@ pub const PackageManager = struct { ); if (Environment.isDebug) { var t = cause.debug_trace; - bun.crash_handler.dumpStackTrace(t.trace()); + bun.crash_handler.dumpStackTrace(t.trace(), .{}); } this.summary.fail += 1; } diff --git a/src/install/lifecycle_script_runner.zig b/src/install/lifecycle_script_runner.zig index 6c56425b40..be630172f8 100644 --- a/src/install/lifecycle_script_runner.zig +++ b/src/install/lifecycle_script_runner.zig @@ -45,7 +45,7 @@ pub const LifecycleScriptSubprocess = struct { return a.started_at < b.started_at; } - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const min_milliseconds_to_log = 500; @@ -460,7 +460,7 @@ pub const LifecycleScriptSubprocess = struct { this.stderr.deinit(); } - this.destroy(); + bun.destroy(this); } pub fn deinitAndDeletePackage(this: *LifecycleScriptSubprocess) void { diff --git a/src/install/npm.zig b/src/install/npm.zig index baa076e5e4..87160eaf94 100644 --- a/src/install/npm.zig +++ b/src/install/npm.zig @@ -1227,16 +1227,14 @@ pub const PackageManifest = struct { cache_dir: std.fs.Dir, task: bun.ThreadPool.Task = .{ .callback = &run }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn run(task: *bun.ThreadPool.Task) void { const tracer = bun.perf.trace("PackageManifest.Serializer.save"); defer tracer.end(); const save_task: *@This() = @fieldParentPtr("task", task); - defer { - save_task.destroy(); - } + defer bun.destroy(save_task); Serializer.save(&save_task.manifest, save_task.scope, save_task.tmpdir, save_task.cache_dir) catch |err| { if (PackageManager.verbose_install) { diff --git a/src/install/windows-shim/BinLinkingShim.zig b/src/install/windows-shim/BinLinkingShim.zig index e546579304..a76bad79ff 100644 --- a/src/install/windows-shim/BinLinkingShim.zig +++ b/src/install/windows-shim/BinLinkingShim.zig @@ -297,7 +297,7 @@ pub fn looseDecode(input: []const u8) ?Decoded { } break :bin_path_u8 input[0..bin_path_byte_len]; } else ( - // path slice is 0..flags-2 + // path slice is 0..flags-2 input[0 .. input.len - @sizeOf(Flags)]); if (bin_path_u8.len % 2 != 0) { diff --git a/src/io/PipeReader.zig b/src/io/PipeReader.zig index b9121328a3..a8e44bc48d 100644 --- a/src/io/PipeReader.zig +++ b/src/io/PipeReader.zig @@ -660,7 +660,7 @@ const PosixBufferedReader = struct { const JSC = bun.JSC; -const WindowsOutputReaderVTable = struct { +const WindowsBufferedReaderVTable = struct { onReaderDone: *const fn (*anyopaque) void, onReaderError: *const fn (*anyopaque, bun.sys.Error) void, onReadChunk: ?*const fn ( @@ -680,13 +680,9 @@ pub const WindowsBufferedReader = struct { flags: Flags = .{}, parent: *anyopaque = undefined, - vtable: WindowsOutputReaderVTable = undefined, - ref_count: u32 = 1, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); + vtable: WindowsBufferedReaderVTable = undefined, - const WindowsOutputReader = @This(); - - pub fn memoryCost(this: *const WindowsOutputReader) usize { + pub fn memoryCost(this: *const WindowsBufferedReader) usize { return @sizeOf(@This()) + this._buffer.capacity; } @@ -703,7 +699,7 @@ pub const WindowsBufferedReader = struct { use_pread: bool = false, }; - pub fn init(comptime Type: type) WindowsOutputReader { + pub fn init(comptime Type: type) WindowsBufferedReader { const fns = struct { fn onReadChunk(this: *anyopaque, chunk: []const u8, hasMore: ReadState) bool { return Type.onReadChunk(@as(*Type, @alignCast(@ptrCast(this))), chunk, hasMore); @@ -724,11 +720,11 @@ pub const WindowsBufferedReader = struct { }; } - pub inline fn isDone(this: *WindowsOutputReader) bool { + pub inline fn isDone(this: *WindowsBufferedReader) bool { return this.flags.is_done or this.flags.received_eof or this.flags.closed_without_reporting; } - pub fn from(to: *WindowsOutputReader, other: anytype, parent: anytype) void { + pub fn from(to: *WindowsBufferedReader, other: anytype, parent: anytype) void { bun.assert(other.source != null and to.source == null); to.* = .{ .vtable = to.vtable, @@ -744,16 +740,16 @@ pub const WindowsBufferedReader = struct { to.setParent(parent); } - pub fn getFd(this: *const WindowsOutputReader) bun.FileDescriptor { + pub fn getFd(this: *const WindowsBufferedReader) bun.FileDescriptor { const source = this.source orelse return bun.invalid_fd; return source.getFd(); } - pub fn watch(_: *WindowsOutputReader) void { + pub fn watch(_: *WindowsBufferedReader) void { // No-op on windows. } - pub fn setParent(this: *WindowsOutputReader, parent: anytype) void { + pub fn setParent(this: *WindowsBufferedReader, parent: anytype) void { this.parent = parent; if (!this.flags.is_done) { if (this.source) |source| { @@ -762,7 +758,7 @@ pub const WindowsBufferedReader = struct { } } - pub fn updateRef(this: *WindowsOutputReader, value: bool) void { + pub fn updateRef(this: *WindowsBufferedReader, value: bool) void { if (this.source) |source| { if (value) { source.ref(); @@ -772,36 +768,36 @@ pub const WindowsBufferedReader = struct { } } - pub fn enableKeepingProcessAlive(this: *WindowsOutputReader, _: anytype) void { + pub fn enableKeepingProcessAlive(this: *WindowsBufferedReader, _: anytype) void { this.updateRef(true); } - pub fn disableKeepingProcessAlive(this: *WindowsOutputReader, _: anytype) void { + pub fn disableKeepingProcessAlive(this: *WindowsBufferedReader, _: anytype) void { this.updateRef(false); } - pub fn takeBuffer(this: *WindowsOutputReader) std.ArrayList(u8) { + pub fn takeBuffer(this: *WindowsBufferedReader) std.ArrayList(u8) { const out = this._buffer; this._buffer = std.ArrayList(u8).init(out.allocator); return out; } - pub fn buffer(this: *WindowsOutputReader) *std.ArrayList(u8) { + pub fn buffer(this: *WindowsBufferedReader) *std.ArrayList(u8) { return &this._buffer; } pub const finalBuffer = buffer; - pub fn hasPendingActivity(this: *const WindowsOutputReader) bool { + pub fn hasPendingActivity(this: *const WindowsBufferedReader) bool { const source = this.source orelse return false; return source.isActive(); } - pub fn hasPendingRead(this: *const WindowsOutputReader) bool { + pub fn hasPendingRead(this: *const WindowsBufferedReader) bool { return this.flags.has_inflight_read; } - fn _onReadChunk(this: *WindowsOutputReader, buf: []u8, hasMore: ReadState) bool { + fn _onReadChunk(this: *WindowsBufferedReader, buf: []u8, hasMore: ReadState) bool { this.flags.has_inflight_read = false; if (hasMore == .eof) { this.flags.received_eof = true; @@ -811,12 +807,12 @@ pub const WindowsBufferedReader = struct { return onReadChunkFn(this.parent, buf, hasMore); } - fn finish(this: *WindowsOutputReader) void { + fn finish(this: *WindowsBufferedReader) void { this.flags.has_inflight_read = false; this.flags.is_done = true; } - pub fn done(this: *WindowsOutputReader) void { + pub fn done(this: *WindowsBufferedReader) void { if (this.source) |source| bun.assert(source.isClosed()); this.finish(); @@ -824,19 +820,19 @@ pub const WindowsBufferedReader = struct { this.vtable.onReaderDone(this.parent); } - pub fn onError(this: *WindowsOutputReader, err: bun.sys.Error) void { + pub fn onError(this: *WindowsBufferedReader, err: bun.sys.Error) void { this.finish(); this.vtable.onReaderError(this.parent, err); } - pub fn getReadBufferWithStableMemoryAddress(this: *WindowsOutputReader, suggested_size: usize) []u8 { + pub fn getReadBufferWithStableMemoryAddress(this: *WindowsBufferedReader, suggested_size: usize) []u8 { this.flags.has_inflight_read = true; this._buffer.ensureUnusedCapacity(suggested_size) catch bun.outOfMemory(); const res = this._buffer.allocatedSlice()[this._buffer.items.len..]; return res; } - pub fn startWithCurrentPipe(this: *WindowsOutputReader) bun.JSC.Maybe(void) { + pub fn startWithCurrentPipe(this: *WindowsBufferedReader) bun.JSC.Maybe(void) { bun.assert(!this.source.?.isClosed()); this.source.?.setData(this); this.buffer().clearRetainingCapacity(); @@ -844,12 +840,12 @@ pub const WindowsBufferedReader = struct { return this.startReading(); } - pub fn startWithPipe(this: *WindowsOutputReader, pipe: *uv.Pipe) bun.JSC.Maybe(void) { + pub fn startWithPipe(this: *WindowsBufferedReader, pipe: *uv.Pipe) bun.JSC.Maybe(void) { this.source = .{ .pipe = pipe }; return this.startWithCurrentPipe(); } - pub fn start(this: *WindowsOutputReader, fd: bun.FileDescriptor, _: bool) bun.JSC.Maybe(void) { + pub fn start(this: *WindowsBufferedReader, fd: bun.FileDescriptor, _: bool) bun.JSC.Maybe(void) { bun.assert(this.source == null); const source = switch (Source.open(uv.Loop.get(), fd)) { .err => |err| return .{ .err = err }, @@ -860,13 +856,13 @@ pub const WindowsBufferedReader = struct { return this.startWithCurrentPipe(); } - pub fn startFileOffset(this: *WindowsOutputReader, fd: bun.FileDescriptor, poll: bool, offset: usize) bun.JSC.Maybe(void) { + pub fn startFileOffset(this: *WindowsBufferedReader, fd: bun.FileDescriptor, poll: bool, offset: usize) bun.JSC.Maybe(void) { this._offset = offset; this.flags.use_pread = true; return this.start(fd, poll); } - pub fn deinit(this: *WindowsOutputReader) void { + pub fn deinit(this: *WindowsBufferedReader) void { this.buffer().deinit(); const source = this.source orelse return; if (!source.isClosed()) { @@ -1126,7 +1122,7 @@ pub const WindowsBufferedReader = struct { } comptime { - bun.meta.banFieldType(WindowsOutputReader, bool); // Don't increase the size of the struct. Put them in flags instead. + bun.meta.banFieldType(WindowsBufferedReader, bool); // Don't increase the size of the struct. Put them in flags instead. } }; diff --git a/src/js_lexer.zig b/src/js_lexer.zig index e047ae2c85..264d80dea8 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -1902,9 +1902,9 @@ fn NewLexer_( // Omit the trailing "*/" from the checks below const end_comment_text = if (is_multiline_comment) - text.len - 2 - else - text.len; + text.len - 2 + else + text.len; if (has_legal_annotation or lexer.preserve_all_comments_before) { if (is_multiline_comment) { diff --git a/src/macho.zig b/src/macho.zig index 1fbca63bd1..c5bbca81d2 100644 --- a/src/macho.zig +++ b/src/macho.zig @@ -149,14 +149,14 @@ pub const MachoFile = struct { const code_sign_cmd: ?*align(1) macho.linkedit_data_command = if (code_sign_cmd_idx) |idx| - @as(*align(1) macho.linkedit_data_command, @ptrCast(@constCast(@alignCast(&self.data.items[idx])))) - else - null; + @as(*align(1) macho.linkedit_data_command, @ptrCast(@constCast(@alignCast(&self.data.items[idx])))) + else + null; const linkedit_seg: *align(1) macho.segment_command_64 = if (linkedit_seg_idx) |idx| - @as(*align(1) macho.segment_command_64, @ptrCast(@constCast(@alignCast(&self.data.items[idx])))) - else - return error.MissingLinkeditSegment; + @as(*align(1) macho.segment_command_64, @ptrCast(@constCast(@alignCast(&self.data.items[idx])))) + else + return error.MissingLinkeditSegment; // Handle code signature specially var sig_data: ?[]u8 = null; diff --git a/src/napi/napi.zig b/src/napi/napi.zig index e2c48f9615..9700938038 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1465,7 +1465,7 @@ pub const ThreadSafeFunction = struct { closing: std.atomic.Value(ClosingState) = std.atomic.Value(ClosingState).init(.not_closing), aborted: std.atomic.Value(bool) = std.atomic.Value(bool).init(true), - pub usingnamespace bun.New(ThreadSafeFunction); + pub const new = bun.TrivialNew(ThreadSafeFunction); const ClosingState = enum(u8) { not_closing, @@ -1679,7 +1679,7 @@ pub const ThreadSafeFunction = struct { this.callback.deinit(); this.queue.deinit(); - this.destroy(); + bun.destroy(this); } pub fn ref(this: *ThreadSafeFunction) void { diff --git a/src/ptr.zig b/src/ptr.zig index 9115815701..13d538a26b 100644 --- a/src/ptr.zig +++ b/src/ptr.zig @@ -1,9 +1,16 @@ //! The `ptr` module contains smart pointer types that are used throughout Bun. pub const Cow = @import("ptr/Cow.zig").Cow; + pub const CowSlice = @import("ptr/CowSlice.zig").CowSlice; pub const CowSliceZ = @import("ptr/CowSlice.zig").CowSliceZ; pub const CowString = CowSlice(u8); -pub const NewRefCounted = @import("ptr/ref_count.zig").NewRefCounted; -pub const NewThreadSafeRefCounted = @import("ptr/ref_count.zig").NewThreadSafeRefCounted; + +const ref_count = @import("ptr/ref_count.zig"); +pub const RefCount = ref_count.RefCount; +pub const ThreadSafeRefCount = ref_count.ThreadSafeRefCount; +pub const RefPtr = ref_count.RefPtr; + pub const TaggedPointer = @import("ptr/tagged_pointer.zig").TaggedPointer; pub const TaggedPointerUnion = @import("ptr/tagged_pointer.zig").TaggedPointerUnion; + +pub const WeakPtr = @import("ptr/weak_ptr.zig").WeakPtr; diff --git a/src/ptr/ref_count.zig b/src/ptr/ref_count.zig index 063951843a..b418deb1bc 100644 --- a/src/ptr/ref_count.zig +++ b/src/ptr/ref_count.zig @@ -1,133 +1,491 @@ +const enable_debug = bun.Environment.isDebug; + +pub const RefCountOptions = struct { + /// Defaults to the type basename. + debug_name: ?[]const u8 = null, +}; + +/// Add managed reference counting to a struct type. This implements a `ref()` +/// and `deref()` method to add to the struct itself. This mixin doesn't handle +/// memory management, but is very easy to integrate with bun.new + bun.destroy. +/// +/// Avoid reference counting when an object only has one owner. +/// +/// const Thing = struct { +/// const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); +/// // expose `ref` and `deref` as public methods +/// pub const ref = RefCount.ref; +/// pub const deref = RefCount.deref; +/// +/// ref_count: RefCount, +/// other_field: u32, +/// +/// pub fn init(field: u32) *T { +/// return bun.new(T, .{ .ref_count = .init(), .other_field = field }); +/// } +/// +/// // Destructor should be private so it is only called once ref_count is 0 +/// // When this is empty, `bun.destroy` can be passed directly to `RefCount` +/// fn deinit(thing: *T) void { +/// std.debug.print("deinit {d}\n", .{thing.other_field}); +/// bun.destroy(thing); +/// } +/// }; +/// +/// When `RefCount` is implemented, it can be used with `RefPtr(T)` to track where +/// a reference leak may be happening. +/// +/// const my_ptr = RefPtr(Thing).adoptRef(.init(123)); +/// assert(my_ptr.data.other_field == 123); +/// +/// const second_pointer = my_ptr.dupeRef(); +/// assert(second_pointer.data == my_ptr.data); +/// +/// my_ptr.deref(); // pointer stays valid +/// +/// // my_ptr.data // INVALID ACCESS +/// assert(second_pointer.data.other_field == 123); +/// +/// second_pointer.deref(); // pointer is destroyed +/// +/// Code that migrates to using `RefPtr` would put the .adoptRef call in it's +/// initializer. To combine `bun.new` and `adoptRef`, you can use `RefPtr(T).new`. +/// +/// pub fn init(field: u32) RefPtr(T) { +/// return .new(.{ +/// .ref_count = .init(), +/// .other_field = field, +/// }); +/// } +/// +/// Code can incrementally migrate to `RefPtr`, using `.initRef` to increment the count +/// +/// const ref_ptr = RefPtr(T).initRef(existing_raw_pointer); +/// +pub fn RefCount(T: type, field_name: []const u8, destructor: fn (*T) void, options: RefCountOptions) type { + return struct { + active_counts: u32, + thread: ?bun.DebugThreadLock, + debug: if (enable_debug) DebugData(false) else void, + + const debug_name = options.debug_name orelse bun.meta.typeBaseName(@typeName(T)); + pub const scope = bun.Output.Scoped(debug_name, true); + + pub fn init() @This() { + return .initExactRefs(1); + } + + /// Caller will have to call `unref()` exactly `count` times to destroy. + pub fn initExactRefs(count: u32) @This() { + assert(count > 0); + return .{ + .thread = if (@inComptime()) null else .initLocked(), + .active_counts = count, + .debug = if (enable_debug) .empty else undefined, + }; + } + + // trait implementation + + pub fn ref(self: *T) void { + const counter = getCounter(self); + scope.log("0x{x} ref {d} -> {d}", .{ + @intFromPtr(self), + counter.active_counts, + counter.active_counts + 1, + }); + counter.assertNonThreadSafeCountIsSingleThreaded(); + counter.active_counts += 1; + } + + pub fn deref(self: *T) void { + const counter = getCounter(self); + scope.log("0x{x} deref {d} -> {d}", .{ + @intFromPtr(self), + counter.active_counts, + counter.active_counts - 1, + }); + counter.assertNonThreadSafeCountIsSingleThreaded(); + counter.active_counts -= 1; + if (counter.active_counts == 0) { + if (enable_debug) { + counter.debug.deinit(std.mem.asBytes(self), @returnAddress()); + } + destructor(self); + } + } + + pub fn dupeRef(self: anytype) RefPtr(@TypeOf(self)) { + _ = @as(*const T, self); // ensure ptr child is T + return .initRef(self); + } + + // utility functions + + pub fn hasOneRef(self: *T) bool { + const counter = getCounter(self); + counter.assertNonThreadSafeCountIsSingleThreaded(); + return counter.active_counts == 1; + } + + pub fn dumpActiveRefs(count: *@This()) void { + if (enable_debug) { + const ptr: *T = @fieldParentPtr(field_name, count); + count.debug.dump(@typeName(T), ptr, count.active_counts); + } + } + + /// The active_counts value is 0 after the destructor is called. + pub fn assertNoRefs(count: *@This()) void { + if (enable_debug) { + bun.assert(count.active_counts == 0); + } + } + + fn assertNonThreadSafeCountIsSingleThreaded(count: *@This()) void { + const thread = if (count.thread) |*ptr| ptr else { + count.thread = .initLocked(); + return; + }; + thread.assertLocked(); // this counter is not thread-safe + } + + fn getCounter(self: *T) *@This() { + return &@field(self, field_name); + } + }; +} + +/// Add thread-safe reference counting to a struct type. This implements a `ref()` +/// and `deref()` method to add to the struct itself. This mixin doesn't handle +/// memory management, but is very easy to integrate with bun.new + bun.destroy. +/// +/// See `RefCount`'s comment defined above for examples & best practices. +/// +/// Avoid reference counting when an object only has one owner. +/// Avoid thread-safe reference counting when only one thread allocates and frees. +pub fn ThreadSafeRefCount(T: type, field_name: []const u8, destructor: fn (*T) void, options: RefCountOptions) type { + return struct { + active_counts: std.atomic.Value(u32), + debug: if (enable_debug) DebugData(true) else void, + + const debug_name = options.debug_name orelse bun.meta.typeBaseName(@typeName(T)); + pub const scope = bun.Output.Scoped(debug_name, true); + + pub fn init() @This() { + return .initExactRefs(1); + } + + /// Caller will have to call `unref()` exactly `count` times to destroy. + pub fn initExactRefs(count: u32) @This() { + assert(count > 0); + return .{ + .active_counts = .init(count), + .debug = if (enable_debug) .empty, + }; + } + + // trait implementation + + pub fn ref(self: *T) void { + const counter = getCounter(self); + const new_count = counter.active_counts.fetchAdd(1, .seq_cst); + scope.log("0x{x} ref {d} -> {d}", .{ + @intFromPtr(self), + new_count - 1, + new_count, + }); + bun.debugAssert(new_count > 0); + } + + pub fn deref(self: *T) void { + const counter = getCounter(self); + const new_count = counter.active_counts.fetchSub(1, .seq_cst); + scope.log("0x{x} deref {d} -> {d}", .{ + @intFromPtr(self), + new_count + 1, + new_count, + }); + bun.debugAssert(new_count > 0); + if (new_count == 1) { + if (enable_debug) { + counter.debug.deinit(std.mem.asBytes(self), @returnAddress()); + } + destructor(self); + } + } + + pub fn dupeRef(self: anytype) RefPtr(@TypeOf(self)) { + _ = @as(*const T, self); // ensure ptr child is T + return .initRef(self); + } + + // utility functions + + pub fn hasOneRef(self: *T) bool { + const counter = getCounter(self); + return counter.active_counts.load(.seq_cst) == 1; + } + + pub fn dumpActiveRefs(count: *@This()) void { + if (enable_debug) { + const ptr: *T = @fieldParentPtr(field_name, count); + count.debug.dump(@typeName(T), ptr, count.active_counts.load(.seq_cst)); + } + } + + /// The active_counts value is 0 after the destructor is called. + pub fn assertNoRefs(count: *@This()) void { + if (enable_debug) { + bun.assert(count.active_counts.load(.seq_cst) == 0); + } + } + + fn getCounter(self: *T) *@This() { + return &@field(self, field_name); + } + }; +} + +/// A pointer to an object implementing `RefCount` or `ThreadSafeRefCount` +/// The benefit of this over `T*` is that instances of `RefPtr` are tracked. +/// +/// By using this, you gain the following memory debugging tools: +/// +/// - `T.ref_count.dump()` to dump all active references. +/// - AllocationScope integration via `.newTracked()` and `.trackAll()` +/// +/// See `RefCount`'s comment defined above for examples & best practices. +pub fn RefPtr(T: type) type { + return struct { + data: *T, + debug: DebugId, + + // For simplicity, RefPtr only supports `ref_count` as the field name + const kind = implementsRefCount(T, "ref_count"); + comptime { + bun.assert(kind != .not_ref_counted); + } + const DebugId = if (enable_debug) TrackedRef.Id else void; + + /// Increment the reference count, and return a structure boxing the pointer. + pub fn initRef(raw_ptr: *T) @This() { + raw_ptr.ref(); + return uncheckedAndUnsafeInit(raw_ptr, @returnAddress()); + } + + /// Decrement the reference count, and destroy the object if the count is 0. + pub fn deref(self: *const @This()) void { + if (enable_debug) { + self.data.ref_count.debug.release(self.debug, @returnAddress()); + } + self.data.deref(); + + if (bun.Environment.isDebug) { + // make UAF fail faster (ideally integrate this with ASAN) + // this @constCast is "okay" because it makes no sense to store + // an object with a heap pointer in the read only segment + @constCast(self).data = undefined; + } + } + + pub fn dupeRef(ref: @This()) @This() { + return .initRef(ref.data); + } + + // Allocate a new object, returning a RefPtr to it. + pub fn new(init_data: T) @This() { + return .adoptRef(bun.new(T, init_data)); + } + + /// Initialize a newly allocated pointer, returning a RefPtr to it. + /// Care must be taken when using non-default allocators. + pub fn adoptRef(raw_ptr: *T) @This() { + if (enable_debug) { + bun.assert(raw_ptr.ref_count.hasOneRef()); + bun.assert(!raw_ptr.ref_count.debug.isEmpty()); + } + return uncheckedAndUnsafeInit(raw_ptr); + } + + /// This will assert that ALL references are cleaned up by the time the allocation scope ends. + pub fn newTracked(scope: *AllocationScope, init_data: T) @This() { + const ptr: @This() = .new(init_data); + ptr.trackImpl(scope, @returnAddress()); + return ptr; + } + + /// This will assert that ALL references are cleaned up by the time the allocation scope ends. + pub fn trackAll(ref: @This(), scope: *AllocationScope) void { + ref.trackImpl(scope, @returnAddress()); + } + + fn trackImpl(ref: @This(), scope: *AllocationScope, ret_addr: usize) void { + const debug = &ref.data.ref_count.debug; + debug.allocation_scope = &scope; + scope.trackExternalAllocation( + std.mem.asBytes(ref.data), + ret_addr, + .{ .ref_count = debug }, + ); + } + + fn uncheckedAndUnsafeInit(raw_ptr: *T, ret_addr: ?usize) @This() { + return .{ + .data = raw_ptr, + .debug = if (enable_debug) raw_ptr.ref_count.debug.acquire( + &raw_ptr.ref_count.active_counts, + ret_addr orelse @returnAddress(), + ), + }; + } + }; +} + +const TrackedRef = struct { + acquired_at: bun.crash_handler.StoredTrace, + + /// Not an index, just a unique identifier for the debug data + pub const Id = bun.GenericIndex(u32, TrackedRef); +}; + +const TrackedDeref = struct { + acquired_at: bun.crash_handler.StoredTrace, + released_at: bun.crash_handler.StoredTrace, +}; + +/// Provides Ref tracking. This is not generic over the pointer T to reduce analysis complexity. +pub fn DebugData(thread_safe: bool) type { + return struct { + const Debug = @This(); + const Count = if (thread_safe) std.atomic.Value(u32) else u32; + + lock: if (thread_safe) std.debug.SafetyLock else bun.Mutex, + next_id: u32, + map: std.AutoHashMapUnmanaged(TrackedRef.Id, TrackedRef), + frees: std.AutoArrayHashMapUnmanaged(TrackedRef.Id, TrackedDeref), + // Allocation Scope integration + allocation_scope: ?*AllocationScope, + count_pointer: ?*Count, + + pub const empty: @This() = .{ + .lock = .{}, + .next_id = 0, + .map = .empty, + .frees = .empty, + .allocation_scope = null, + .count_pointer = null, + }; + + fn dump(debug: *@This(), type_name: ?[]const u8, ptr: *anyopaque, ref_count: u32) void { + debug.lock.lock(); + defer debug.lock.unlock(); + + genericDump(type_name, ptr, ref_count, &debug.map); + } + + fn nextId(debug: *@This()) TrackedRef.Id { + if (thread_safe) { + return .init(@atomicRmw(u32, &debug.next_id, .Add, 1, .seq_cst)); + } else { + defer debug.next_id += 1; + return .init(debug.next_id); + } + } + + fn acquire(debug: *@This(), count_pointer: *Count, return_address: usize) TrackedRef.Id { + debug.lock.lock(); + defer debug.lock.unlock(); + debug.count_pointer = count_pointer; + const id = nextId(debug); + debug.map.put(bun.default_allocator, id, .{ + .acquired_at = .capture(return_address), + }) catch bun.outOfMemory(); + return id; + } + + fn release(debug: *@This(), id: TrackedRef.Id, return_address: usize) void { + debug.lock.lock(); + defer debug.lock.unlock(); + const entry = debug.map.fetchRemove(id) orelse { + return; + }; + debug.frees.put(bun.default_allocator, id, .{ + .acquired_at = entry.value.acquired_at, + .released_at = .capture(return_address), + }) catch bun.outOfMemory(); + } + + fn deinit(debug: *@This(), data: []const u8, ret_addr: usize) void { + debug.lock.lock(); + debug.map.clearAndFree(bun.default_allocator); + debug.frees.clearAndFree(bun.default_allocator); + if (debug.allocation_scope) |scope| { + _ = scope.trackExternalFree(data, ret_addr); + } + } + + // Trait function for AllocationScope + pub fn onAllocationLeak(debug: *@This(), data: []u8) void { + debug.lock.lock(); + defer debug.lock.unlock(); + const count = debug.count_pointer.?; + debug.dump(null, data.ptr, if (thread_safe) count.load(.seq_cst) else count.*); + } + }; +} + +fn genericDump( + type_name: ?[]const u8, + ptr: *anyopaque, + total_ref_count: usize, + map: *std.AutoHashMapUnmanaged(TrackedRef.Id, TrackedRef), +) void { + const tracked_refs = map.count(); + const untracked_refs = total_ref_count - tracked_refs; + bun.Output.prettyError("{s}{s}{x} has ", .{ + type_name orelse "", + if (type_name != null) "@" else "", + @intFromPtr(ptr), + }); + if (tracked_refs > 0) { + bun.Output.prettyError("{d} tracked{s}", .{ tracked_refs, if (untracked_refs > 0) ", " else "" }); + } + if (untracked_refs > 0) { + bun.Output.prettyError("{d} untracked refs\n", .{untracked_refs}); + } else { + bun.Output.prettyError("refs\n", .{}); + } + var i: usize = 0; + var it = map.iterator(); + while (it.next()) |entry| { + bun.Output.prettyError("RefPtr acquired at:\n", .{}); + bun.crash_handler.dumpStackTrace(entry.value_ptr.acquired_at.trace(), AllocationScope.trace_limits); + i += 1; + if (i >= 3) { + bun.Output.prettyError(" {d} omitted ...\n", .{map.count() - i}); + break; + } + } +} + +fn implementsRefCount(T: type, field_name: []const u8) enum { threadsafe, normal, not_ref_counted } { + comptime { + if (!@hasField(T, field_name)) return .not_ref_counted; + const R = @FieldType(T, field_name); + if (!@hasField(R, "debug")) return .not_ref_counted; + const kind = switch (@FieldType(R, "debug")) { + DebugData(false) => .normal, + DebugData(true) => .threadsafe, + else => return .not_ref_counted, + }; + bun.assert(@field(T, "ref") == @field(R, "ref")); + bun.assert(@field(T, "deref") == @field(R, "deref")); + return kind; + } +} + const std = @import("std"); const bun = @import("root").bun; -const Output = bun.Output; - -const strings = bun.strings; -const meta = bun.meta; - -/// Reference-counted heap-allocated instance value. -/// -/// `ref_count` is expected to be defined on `T` with a default value set to `1` -pub fn NewRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void, debug_name: ?[:0]const u8) type { - if (!@hasField(T, "ref_count")) { - @compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T)); - } - - for (std.meta.fields(T)) |field| { - if (strings.eqlComptime(field.name, "ref_count")) { - if (field.default_value_ptr == null) { - @compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T)); - } - } - } - - const output_name = debug_name orelse meta.typeBaseName(@typeName(T)); - const log = Output.scoped(output_name, true); - - return struct { - pub fn destroy(self: *T) void { - if (bun.Environment.allow_assert) { - bun.assert(self.ref_count == 0); - } - - bun.destroy(self); - } - - pub fn ref(self: *T) void { - if (bun.Environment.isDebug) log("0x{x} ref {d} + 1 = {d}", .{ @intFromPtr(self), self.ref_count, self.ref_count + 1 }); - - self.ref_count += 1; - } - - pub fn deref(self: *T) void { - const ref_count = self.ref_count; - if (bun.Environment.isDebug) { - if (ref_count == 0 or ref_count == std.math.maxInt(@TypeOf(ref_count))) { - @panic("Use after-free detected on " ++ output_name); - } - } - - if (bun.Environment.isDebug) log("0x{x} deref {d} - 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count - 1 }); - - self.ref_count = ref_count - 1; - - if (ref_count == 1) { - if (comptime deinit_fn) |deinit| { - deinit(self); - } else { - self.destroy(); - } - } - } - - pub inline fn new(t: T) *T { - const ptr = bun.new(T, t); - - if (bun.Environment.enable_logs) { - if (ptr.ref_count == 0) { - Output.panic("Expected ref_count to be > 0, got {d}", .{ptr.ref_count}); - } - } - - return ptr; - } - }; -} - -pub fn NewThreadSafeRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void, debug_name: ?[:0]const u8) type { - if (!@hasField(T, "ref_count")) { - @compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T)); - } - - for (std.meta.fields(T)) |field| { - if (strings.eqlComptime(field.name, "ref_count")) { - if (field.default_value_ptr == null) { - @compileError("Expected a field named \"ref_count\" with a default value of 1 on " ++ @typeName(T)); - } - } - } - - const output_name = debug_name orelse meta.typeBaseName(@typeName(T)); - const log = Output.scoped(output_name, true); - - return struct { - pub fn destroy(self: *T) void { - if (bun.Environment.allow_assert) { - bun.assert(self.ref_count.load(.seq_cst) == 0); - } - - bun.destroy(self); - } - - pub fn ref(self: *T) void { - const ref_count = self.ref_count.fetchAdd(1, .seq_cst); - if (bun.Environment.isDebug) log("0x{x} ref {d} + 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count - 1 }); - bun.debugAssert(ref_count > 0); - } - - pub fn deref(self: *T) void { - const ref_count = self.ref_count.fetchSub(1, .seq_cst); - if (bun.Environment.isDebug) log("0x{x} deref {d} - 1 = {d}", .{ @intFromPtr(self), ref_count, ref_count -| 1 }); - - if (ref_count == 1) { - if (comptime deinit_fn) |deinit| { - deinit(self); - } else { - self.destroy(); - } - } - } - - pub inline fn new(t: T) *T { - const ptr = bun.new(T, t); - - if (bun.Environment.enable_logs) { - if (ptr.ref_count.load(.seq_cst) != 1) { - Output.panic("Expected ref_count to be 1, got {d}", .{ptr.ref_count.load(.seq_cst)}); - } - } - - return ptr; - } - }; -} +const assert = bun.assert; +const AllocationScope = bun.AllocationScope; diff --git a/src/ptr/weak_ptr.zig b/src/ptr/weak_ptr.zig new file mode 100644 index 0000000000..cf2da36f13 --- /dev/null +++ b/src/ptr/weak_ptr.zig @@ -0,0 +1,70 @@ +pub const WeakPtrData = packed struct(u32) { + reference_count: u31, + finalized: bool, + + pub const empty: @This() = .{ + .reference_count = 0, + .finalized = false, + }; + + pub fn onFinalize(this: *WeakPtrData) bool { + bun.debugAssert(!this.finalized); + this.finalized = true; + return this.reference_count == 0; + } +}; + +/// Allow a type to be weakly referenced. This keeps a reference count of how +/// many weak-references exist, so that when the object is destroyed, the inner +/// contents can be freed, but the object itself is not destroyed until all +/// `WeakPtr`s are released. Even if the allocation is present, `WeakPtr(T).get` +/// will return null after the inner contents are freed. +pub fn WeakPtr(comptime T: type, data_field: []const u8) type { + return struct { + pub const Data = WeakPtrData; + + raw_ptr: ?*T, + + pub const empty: @This() = .{ .raw_ptr = null }; + + pub fn initRef(req: *T) @This() { + bun.debugAssert(!data(req).finalized); + data(req).reference_count += 1; + return .{ .raw_ptr = req }; + } + + pub fn deref(this: *@This()) void { + if (this.raw_ptr) |value| { + this.derefInternal(value); + } + } + + pub fn get(this: *@This()) ?*T { + if (this.raw_ptr) |value| { + if (!data(value).finalized) { + return value; + } + + this.derefInternal(value); + } + return null; + } + + fn derefInternal(this: *@This(), value: *T) void { + const weak_data = data(value); + this.raw_ptr = null; + const count = weak_data.reference_count - 1; + weak_data.reference_count = count; + if (weak_data.finalized and count == 0) { + bun.destroy(value); + } + } + + fn data(value: *T) *WeakPtrData { + return &@field(value, data_field); + } + }; +} + +pub const bun = @import("root").bun; +const std = @import("std"); diff --git a/src/resolver/data_url.zig b/src/resolver/data_url.zig index 4c536d3687..991967286c 100644 --- a/src/resolver/data_url.zig +++ b/src/resolver/data_url.zig @@ -212,8 +212,8 @@ pub const DataURL = struct { first_byte == '#' or i >= trailing_start or (first_byte == '%' and i + 2 < text.len and - PercentEncoding.isHex(text[i + 1]) and - PercentEncoding.isHex(text[i + 2])); + PercentEncoding.isHex(text[i + 1]) and + PercentEncoding.isHex(text[i + 2])); if (needs_escape) { if (run_start < i) { diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index 7e86224048..7936240565 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -56,7 +56,7 @@ pub const PackageJSON = struct { production, }; - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); const node_modules_path = std.fs.path.sep_str ++ "node_modules" ++ std.fs.path.sep_str; diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index 2da0564e73..34ffa02c86 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -27,6 +27,9 @@ fn FlagSet(comptime Type: type) type { const JSXFieldSet = FlagSet(options.JSX.Pragma); pub const TSConfigJSON = struct { + pub const new = bun.TrivialNew(@This()); + pub const deinit = bun.TrivialDeinit(@This()); + abs_path: string, // The absolute path of "compilerOptions.baseUrl" @@ -57,7 +60,6 @@ pub const TSConfigJSON = struct { emit_decorator_metadata: bool = false, - pub usingnamespace bun.New(@This()); pub fn hasBaseURL(tsconfig: *const TSConfigJSON) bool { return tsconfig.base_url.len > 0; } @@ -342,13 +344,14 @@ pub const TSConfigJSON = struct { allocator, ) and (has_base_url or - TSConfigJSON.isValidTSConfigPathNoBaseURLPattern( - str, - log, - &source, - allocator, - expr.loc, - ))) { + TSConfigJSON.isValidTSConfigPathNoBaseURLPattern( + str, + log, + &source, + allocator, + expr.loc, + ))) + { values[count] = str; count += 1; } diff --git a/src/s3/client.zig b/src/s3/client.zig index 0ebf5efcaa..323622e820 100644 --- a/src/s3/client.zig +++ b/src/s3/client.zig @@ -155,7 +155,8 @@ pub fn writableStream( }; const proxy_url = (proxy orelse ""); this.ref(); // ref the credentials - const task = MultiPartUpload.new(.{ + const task = bun.new(MultiPartUpload, .{ + .ref_count = .init(), .credentials = this, .path = bun.default_allocator.dupe(u8, path) catch bun.outOfMemory(), .proxy = if (proxy_url.len > 0) bun.default_allocator.dupe(u8, proxy_url) catch bun.outOfMemory() else "", @@ -193,16 +194,19 @@ pub fn writableStream( } const S3UploadStreamWrapper = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, readable_stream_ref: JSC.WebCore.ReadableStream.Strong, sink: *JSC.WebCore.NetworkSink, task: *MultiPartUpload, callback: ?*const fn (S3UploadResult, *anyopaque) void, callback_context: *anyopaque, - ref_count: u32 = 1, path: []const u8, // this is owned by the task not by the wrapper global: *JSC.JSGlobalObject, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn resolve(result: S3UploadResult, self: *@This()) void { const sink = self.sink; defer self.deref(); @@ -223,12 +227,12 @@ const S3UploadStreamWrapper = struct { } } - pub fn deinit(self: *@This()) void { + fn deinit(self: *@This()) void { self.readable_stream_ref.deinit(); self.sink.finalize(); - self.sink.destroy(); + self.sink.deinit(); self.task.deref(); - self.destroy(); + bun.destroy(self); } }; @@ -320,7 +324,8 @@ pub fn uploadStream( else => {}, } - const task = MultiPartUpload.new(.{ + const task = bun.new(MultiPartUpload, .{ + .ref_count = .init(), .credentials = this, .path = bun.default_allocator.dupe(u8, path) catch bun.outOfMemory(), .proxy = if (proxy_url.len > 0) bun.default_allocator.dupe(u8, proxy_url) catch bun.outOfMemory() else "", @@ -349,7 +354,8 @@ pub fn uploadStream( task.ref(); // + 1 for the stream wrapper const endPromise = response_stream.sink.endPromise.value(); - const ctx = S3UploadStreamWrapper.new(.{ + const ctx = bun.new(S3UploadStreamWrapper, .{ + .ref_count = .init(), .readable_stream_ref = JSC.WebCore.ReadableStream.Strong.init(readable_stream, globalThis), .sink = &response_stream.sink, .callback = callback, @@ -568,7 +574,7 @@ pub fn readableStream( const readable_value = reader.toReadableStream(globalThis); const S3DownloadStreamWrapper = struct { - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); readable_stream_ref: JSC.WebCore.ReadableStream.Strong, path: []const u8, @@ -606,7 +612,7 @@ pub fn readableStream( pub fn deinit(self: *@This()) void { self.readable_stream_ref.deinit(); bun.default_allocator.free(self.path); - self.destroy(); + bun.destroy(self); } pub fn opaqueCallback(chunk: bun.MutableString, has_more: bool, err: ?Error.S3Error, opaque_self: *anyopaque) void { diff --git a/src/s3/credentials.zig b/src/s3/credentials.zig index 0e5d763824..54680cac53 100644 --- a/src/s3/credentials.zig +++ b/src/s3/credentials.zig @@ -12,6 +12,11 @@ const strings = bun.strings; const DotEnv = bun.DotEnv; pub const S3Credentials = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + ref_count: RefCount, accessKeyId: []const u8, secretAccessKey: []const u8, region: []const u8, @@ -23,8 +28,6 @@ pub const S3Credentials = struct { insecure_http: bool = false, /// indicates if the endpoint is a virtual hosted style bucket virtual_hosted_style: bool = false, - ref_count: u32 = 1, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); pub fn estimatedSize(this: *const @This()) usize { return @sizeOf(S3Credentials) + this.accessKeyId.len + this.region.len + this.secretAccessKey.len + this.endpoint.len + this.bucket.len; @@ -216,7 +219,8 @@ pub const S3Credentials = struct { return new_credentials; } pub fn dupe(this: *const @This()) *S3Credentials { - return S3Credentials.new(.{ + return bun.new(S3Credentials, .{ + .ref_count = .init(), .accessKeyId = if (this.accessKeyId.len > 0) bun.default_allocator.dupe(u8, this.accessKeyId) catch bun.outOfMemory() else @@ -251,7 +255,7 @@ pub const S3Credentials = struct { .virtual_hosted_style = this.virtual_hosted_style, }); } - pub fn deinit(this: *@This()) void { + fn deinit(this: *@This()) void { if (this.accessKeyId.len > 0) { bun.default_allocator.free(this.accessKeyId); } @@ -270,7 +274,7 @@ pub const S3Credentials = struct { if (this.sessionToken.len > 0) { bun.default_allocator.free(this.sessionToken); } - this.destroy(); + bun.destroy(this); } const log = bun.Output.scoped(.AWS, false); diff --git a/src/s3/download_stream.zig b/src/s3/download_stream.zig index fd2470b19b..113a191a52 100644 --- a/src/s3/download_stream.zig +++ b/src/s3/download_stream.zig @@ -8,6 +8,8 @@ const SignResult = S3Credentials.SignResult; const strings = bun.strings; const log = bun.Output.scoped(.S3, true); pub const S3HttpDownloadStreamingTask = struct { + pub const new = bun.TrivialNew(@This()); + http: bun.http.AsyncHTTP, vm: *JSC.VirtualMachine, sign_result: SignResult, @@ -41,7 +43,6 @@ pub const S3HttpDownloadStreamingTask = struct { range: ?[]const u8, proxy_url: []const u8, - pub usingnamespace bun.New(@This()); pub const State = packed struct(u64) { pub const AtomicType = std.atomic.Value(u64); status_code: u32 = 0, @@ -72,8 +73,7 @@ pub const S3HttpDownloadStreamingTask = struct { if (this.proxy_url.len > 0) { bun.default_allocator.free(this.proxy_url); } - - this.destroy(); + bun.destroy(this); } fn reportProgress(this: *@This(), state: State) void { diff --git a/src/s3/multipart.zig b/src/s3/multipart.zig index 5fa8502cd7..8f515b1971 100644 --- a/src/s3/multipart.zig +++ b/src/s3/multipart.zig @@ -110,7 +110,7 @@ pub const MultiPartUpload = struct { available: bun.bit_set.IntegerBitSet(MAX_QUEUE_SIZE) = bun.bit_set.IntegerBitSet(MAX_QUEUE_SIZE).initFull(), currentPartNumber: u16 = 1, - ref_count: u16 = 1, + ref_count: RefCount, ended: bool = false, options: MultiPartUploadOptions = .{}, @@ -145,7 +145,10 @@ pub const MultiPartUpload = struct { callback: *const fn (S3SimpleRequest.S3UploadResult, *anyopaque) void, callback_context: *anyopaque, - pub usingnamespace bun.NewRefCounted(@This(), deinit, null); + const Self = @This(); + const RefCount = bun.ptr.RefCount(Self, "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; const log = bun.Output.scoped(.S3MultiPartUpload, true); @@ -292,7 +295,7 @@ pub const MultiPartUpload = struct { this.multipart_etags.deinit(bun.default_allocator); if (this.multipart_upload_list.cap > 0) this.multipart_upload_list.deinitWithAllocator(bun.default_allocator); - this.destroy(); + bun.destroy(this); } pub fn singleSendUploadResponse(result: S3SimpleRequest.S3UploadResult, this: *@This()) void { diff --git a/src/s3/simple_request.zig b/src/s3/simple_request.zig index 0b03fc79f9..63ba4e6871 100644 --- a/src/s3/simple_request.zig +++ b/src/s3/simple_request.zig @@ -80,7 +80,7 @@ pub const S3HttpSimpleTask = struct { range: ?[]const u8, poll_ref: bun.Async.KeepAlive = bun.Async.KeepAlive.init(), - usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub const Callback = union(enum) { stat: *const fn (S3StatResult, *anyopaque) void, download: *const fn (S3DownloadResult, *anyopaque) void, @@ -120,6 +120,7 @@ pub const S3HttpSimpleTask = struct { } } }; + pub fn deinit(this: *@This()) void { if (this.result.certificate_info) |*certificate| { certificate.deinit(bun.default_allocator); @@ -135,7 +136,7 @@ pub const S3HttpSimpleTask = struct { if (this.result.metadata) |*metadata| { metadata.deinit(bun.default_allocator); } - this.destroy(); + bun.destroy(this); } const ErrorType = enum { diff --git a/src/semver/SemverRange.zig b/src/semver/SemverRange.zig index ccc0f4ec50..5a9c138710 100644 --- a/src/semver/SemverRange.zig +++ b/src/semver/SemverRange.zig @@ -226,14 +226,14 @@ pub fn satisfiesPre(range: Range, version: Version, range_buf: string, version_b // not, check the same with right if right exists and has prerelease. pre_matched.* = pre_matched.* or (range.left.version.tag.hasPre() and - version.patch == range.left.version.patch and - version.minor == range.left.version.minor and - version.major == range.left.version.major) or + version.patch == range.left.version.patch and + version.minor == range.left.version.minor and + version.major == range.left.version.major) or (has_right and - range.right.version.tag.hasPre() and - version.patch == range.right.version.patch and - version.minor == range.right.version.minor and - version.major == range.right.version.major); + range.right.version.tag.hasPre() and + version.patch == range.right.version.patch and + version.minor == range.right.version.minor and + version.major == range.right.version.major); if (!range.left.satisfies(version, range_buf, version_buf)) { return false; diff --git a/src/semver/SemverString.zig b/src/semver/SemverString.zig index 04d3f75dc8..c1e7e0aee1 100644 --- a/src/semver/SemverString.zig +++ b/src/semver/SemverString.zig @@ -12,7 +12,7 @@ pub const String = extern struct { comptime { if (inlinable_buffer.len > max_inline_len or inlinable_buffer.len == max_inline_len and - inlinable_buffer[max_inline_len - 1] >= 0x80) + inlinable_buffer[max_inline_len - 1] >= 0x80) { @compileError("string constant too long to be inlined"); } diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index 21e0fb1436..35bbe628a7 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -581,7 +581,7 @@ pub const ShellArgs = struct { /// Root ast node script_ast: ast.Script = .{ .stmts = &[_]ast.Stmt{} }, - pub usingnamespace bun.New(@This()); + pub const new = bun.TrivialNew(@This()); pub fn arena_allocator(this: *ShellArgs) std.mem.Allocator { return this.__arena.allocator(); @@ -590,7 +590,7 @@ pub const ShellArgs = struct { pub fn deinit(this: *ShellArgs) void { this.__arena.deinit(); bun.destroy(this.__arena); - this.destroy(); + bun.destroy(this); } pub fn init() *ShellArgs { @@ -5338,11 +5338,14 @@ pub const Interpreter = struct { }; const Blob = struct { - ref_count: usize = 1, - blob: bun.JSC.WebCore.Blob, - pub usingnamespace bun.NewRefCounted(Blob, _deinit, null); + const RefCount = bun.ptr.RefCount(@This(), "ref_count", Blob.deinit, .{}); + pub const ref = Blob.RefCount.ref; + pub const deref = Blob.RefCount.deref; - fn _deinit(this: *Blob) void { + ref_count: Blob.RefCount, + blob: bun.JSC.WebCore.Blob, + + fn deinit(this: *Blob) void { this.blob.deinit(); bun.destroy(this); } @@ -5557,6 +5560,7 @@ pub const Interpreter = struct { defer original_blob.deinit(); const blob: *BuiltinIO.Blob = bun.new(BuiltinIO.Blob, .{ + .ref_count = .init(), .blob = original_blob.dupe(), }); @@ -5581,7 +5585,7 @@ pub const Interpreter = struct { return .yield; } - const theblob: *BuiltinIO.Blob = bun.new(BuiltinIO.Blob, .{ .blob = blob.dupe() }); + const theblob: *BuiltinIO.Blob = bun.new(BuiltinIO.Blob, .{ .ref_count = .init(), .blob = blob.dupe() }); if (node.redirect.stdin) { cmd.exec.bltn.stdin.deref(); @@ -11121,12 +11125,16 @@ pub const Interpreter = struct { /// This type is reference counted, but deinitialization is queued onto the event loop pub const IOReader = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", asyncDeinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + fd: bun.FileDescriptor, reader: ReaderImpl, buf: std.ArrayListUnmanaged(u8) = .{}, readers: Readers = .{ .inlined = .{} }, read: usize = 0, - ref_count: u32 = 1, + ref_count: RefCount, err: ?JSC.SystemError = null, evtloop: JSC.EventLoopHandle, concurrent_task: JSC.EventLoopTask, @@ -11136,8 +11144,6 @@ pub const Interpreter = struct { pub const ChildPtr = IOReaderChildPtr; pub const ReaderImpl = bun.io.BufferedReader; - pub usingnamespace bun.NewRefCounted(@This(), asyncDeinit, "IOReaderRefCount"); - const InitFlags = packed struct(u8) { pollable: bool = false, nonblocking: bool = false, @@ -11159,7 +11165,8 @@ pub const Interpreter = struct { } pub fn init(fd: bun.FileDescriptor, evtloop: JSC.EventLoopHandle) *IOReader { - const this = IOReader.new(.{ + const this = bun.new(IOReader, .{ + .ref_count = .init(), .fd = fd, .reader = ReaderImpl.init(@This()), .evtloop = evtloop, @@ -11293,12 +11300,12 @@ pub const Interpreter = struct { } } - pub fn asyncDeinit(this: *@This()) void { + fn asyncDeinit(this: *@This()) void { log("IOReader(0x{x}) asyncDeinit", .{@intFromPtr(this)}); - this.async_deinit.enqueue(); + this.async_deinit.enqueue(); // calls `asyncDeinitCallback` } - pub fn __deinit(this: *@This()) void { + fn asyncDeinitCallback(this: *@This()) void { if (this.fd != bun.invalid_fd) { // windows reader closes the file descriptor if (bun.Environment.isWindows) { @@ -11373,7 +11380,7 @@ pub const Interpreter = struct { pub fn runFromMainThread(this: *AsyncDeinitReader) void { const ioreader: *IOReader = @alignCast(@fieldParentPtr("async_deinit", this)); - ioreader.__deinit(); + ioreader.asyncDeinitCallback(); } pub fn runFromMainThreadMini(this: *AsyncDeinitReader, _: *void) void { @@ -11382,6 +11389,11 @@ pub const Interpreter = struct { }; pub const IOWriter = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", asyncDeinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + const This = @This(); + writer: WriterImpl = if (bun.Environment.isWindows) .{} else .{ .close_fd = false, }, @@ -11393,7 +11405,7 @@ pub const Interpreter = struct { winbuf: if (bun.Environment.isWindows) std.ArrayListUnmanaged(u8) else u0 = if (bun.Environment.isWindows) .{} else 0, __idx: usize = 0, total_bytes_written: usize = 0, - ref_count: u32 = 1, + ref_count: RefCount, err: ?JSC.SystemError = null, evtloop: JSC.EventLoopHandle, concurrent_task: JSC.EventLoopTask, @@ -11413,8 +11425,6 @@ pub const Interpreter = struct { pub const auto_poll = false; - pub usingnamespace bun.NewRefCounted(@This(), asyncDeinit, "IOWriterRefCount"); - const This = @This(); pub const WriterImpl = bun.io.BufferedWriter( This, onWrite, @@ -11441,7 +11451,8 @@ pub const Interpreter = struct { }; pub fn init(fd: bun.FileDescriptor, flags: InitFlags, evtloop: JSC.EventLoopHandle) *This { - const this = IOWriter.new(.{ + const this = bun.new(IOWriter, .{ + .ref_count = .init(), .fd = fd, .evtloop = evtloop, .concurrent_task = JSC.EventLoopTask.fromEventLoop(evtloop), @@ -11835,7 +11846,6 @@ pub const Interpreter = struct { pub fn __deinit(this: *This) void { debug("IOWriter(0x{x}, fd={}) deinit", .{ @intFromPtr(this), this.fd }); - if (bun.Environment.allow_assert) assert(this.ref_count == 0); this.buf.deinit(bun.default_allocator); if (comptime bun.Environment.isPosix) { if (this.writer.handle == .poll and this.writer.handle.poll.isRegistered()) { @@ -11844,7 +11854,7 @@ pub const Interpreter = struct { } else this.winbuf.deinit(bun.default_allocator); if (this.fd != bun.invalid_fd) _ = bun.sys.close(this.fd); this.writer.disableKeepingProcessAlive(this.evtloop); - this.destroy(); + bun.destroy(this); } pub fn isLastIdx(this: *This, idx: usize) bool { diff --git a/src/shell/shell.zig b/src/shell/shell.zig index 4c6e1cd088..83edd3691b 100644 --- a/src/shell/shell.zig +++ b/src/shell/shell.zig @@ -2381,9 +2381,9 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; const whitespace_preceding = if (self.chars.prev) |prev| - Chars.isWhitespace(prev) - else - true; + Chars.isWhitespace(prev) + else + true; if (!whitespace_preceding) break :escaped; try self.break_word(true); self.eatComment(); @@ -2744,10 +2744,10 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { ) { const tok: Token = switch (self.chars.state) { - .Normal => @unionInit(Token, "Text", .{ .start = start, .end = end }), - .Single => @unionInit(Token, "SingleQuotedText", .{ .start = start, .end = end }), - .Double => @unionInit(Token, "DoubleQuotedText", .{ .start = start, .end = end }), - }; + .Normal => @unionInit(Token, "Text", .{ .start = start, .end = end }), + .Single => @unionInit(Token, "SingleQuotedText", .{ .start = start, .end = end }), + .Double => @unionInit(Token, "DoubleQuotedText", .{ .start = start, .end = end }), + }; try self.tokens.append(tok); if (add_delimiter) { try self.tokens.append(.Delimit); @@ -2755,39 +2755,40 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { } else if ((in_normal_space or in_operator) and self.tokens.items.len > 0 and // whether or not to add a delimiter token switch (self.tokens.items[self.tokens.items.len - 1]) { - .Var, - .VarArgv, - .Text, - .SingleQuotedText, - .DoubleQuotedText, - .BraceBegin, - .Comma, - .BraceEnd, - .CmdSubstEnd, - .Asterisk, - => true, + .Var, + .VarArgv, + .Text, + .SingleQuotedText, + .DoubleQuotedText, + .BraceBegin, + .Comma, + .BraceEnd, + .CmdSubstEnd, + .Asterisk, + => true, - .Pipe, - .DoublePipe, - .Ampersand, - .DoubleAmpersand, - .Redirect, - .Dollar, - .DoubleAsterisk, - .Eq, - .Semicolon, - .Newline, - .CmdSubstBegin, - .CmdSubstQuoted, - .OpenParen, - .CloseParen, - .JSObjRef, - .DoubleBracketOpen, - .DoubleBracketClose, - .Delimit, - .Eof, - => false, - }) { + .Pipe, + .DoublePipe, + .Ampersand, + .DoubleAmpersand, + .Redirect, + .Dollar, + .DoubleAsterisk, + .Eq, + .Semicolon, + .Newline, + .CmdSubstBegin, + .CmdSubstQuoted, + .OpenParen, + .CloseParen, + .JSObjRef, + .DoubleBracketOpen, + .DoubleBracketClose, + .Delimit, + .Eof, + => false, + }) + { try self.tokens.append(.Delimit); self.delimit_quote = false; } diff --git a/src/shell/subproc.zig b/src/shell/subproc.zig index cec72799ae..560953ccfb 100644 --- a/src/shell/subproc.zig +++ b/src/shell/subproc.zig @@ -952,6 +952,10 @@ pub const ShellSubprocess = struct { const WaiterThread = bun.spawn.WaiterThread; pub const PipeReader = struct { + const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + reader: IOReader = undefined, process: ?*ShellSubprocess = null, event_loop: JSC.EventLoopHandle = undefined, @@ -964,7 +968,7 @@ pub const PipeReader = struct { out_type: bun.shell.subproc.ShellSubprocess.OutKind, captured_writer: CapturedWriter = .{}, buffered_output: BufferedOutput = .{ .bytelist = .{} }, - ref_count: u32 = 1, + ref_count: RefCount, const BufferedOutput = union(enum) { bytelist: bun.ByteList, @@ -1013,8 +1017,6 @@ pub const PipeReader = struct { } }; - pub usingnamespace bun.NewRefCounted(PipeReader, deinit, null); - pub const CapturedWriter = struct { dead: bool = true, writer: *sh.IOWriter = undefined, @@ -1104,7 +1106,8 @@ pub const PipeReader = struct { } pub fn create(event_loop: JSC.EventLoopHandle, process: *ShellSubprocess, result: StdioResult, capture: ?*sh.IOWriter, out_type: bun.shell.Subprocess.OutKind) *PipeReader { - var this: *PipeReader = PipeReader.new(.{ + var this: *PipeReader = bun.new(PipeReader, .{ + .ref_count = .init(), .process = process, .reader = IOReader.init(@This()), .event_loop = event_loop, @@ -1337,7 +1340,7 @@ pub const PipeReader = struct { return this.event_loop.loop(); } - pub fn deinit(this: *PipeReader) void { + fn deinit(this: *PipeReader) void { log("PipeReader(0x{x}, {s}) deinit()", .{ @intFromPtr(this), @tagName(this.out_type) }); if (comptime Environment.isPosix) { assert(this.reader.isDone() or this.state == .err); @@ -1365,7 +1368,7 @@ pub const PipeReader = struct { this.buffered_output.deinit(); this.reader.deinit(); - this.destroy(); + bun.destroy(this); } }; diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index e0e2ef31da..5d3275c2ed 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -191,7 +191,7 @@ pub fn parseJSON( .fail => |fail| return fail.err, }; - const ptr = ParsedSourceMap.new(map_data); + const ptr = bun.new(ParsedSourceMap, map_data); ptr.external_source_names = source_paths_slice.?; break :map ptr; } else null; @@ -582,6 +582,7 @@ pub const Mapping = struct { } return .{ .success = .{ + .ref_count = .init(), .mappings = mapping, .input_line_count = input_line_count, } }; @@ -612,6 +613,14 @@ pub const ParseResult = union(enum) { }; pub const ParsedSourceMap = struct { + const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{}); + pub const ref = RefCount.ref; + pub const deref = RefCount.deref; + + /// ParsedSourceMap can be acquired by different threads via the thread-safe + /// source map store (SavedSourceMap), so the reference count must be thread-safe. + ref_count: RefCount, + input_line_count: usize = 0, mappings: Mapping.List = .{}, /// If this is empty, this implies that the source code is a single file @@ -629,12 +638,8 @@ pub const ParsedSourceMap = struct { /// rely on source contents) underlying_provider: SourceContentPtr = .none, - ref_count: std.atomic.Value(u32) = .init(1), - is_standalone_module_graph: bool = false, - pub usingnamespace bun.NewThreadSafeRefCounted(ParsedSourceMap, deinitFn, null); - const SourceContentPtr = packed struct(u64) { load_hint: SourceMapLoadHint, data: u62, @@ -654,11 +659,9 @@ pub const ParsedSourceMap = struct { return psm.external_source_names.len != 0; } - fn deinitFn(this: *ParsedSourceMap) void { - this.deinitWithAllocator(bun.default_allocator); - } + fn deinit(this: *ParsedSourceMap) void { + const allocator = bun.default_allocator; - fn deinitWithAllocator(this: *ParsedSourceMap, allocator: std.mem.Allocator) void { this.mappings.deinit(allocator); if (this.external_source_names.len > 0) { @@ -667,7 +670,7 @@ pub const ParsedSourceMap = struct { allocator.free(this.external_source_names); } - this.destroy(); + bun.destroy(this); } fn standaloneModuleGraphData(this: *ParsedSourceMap) *bun.StandaloneModuleGraph.SerializedSourceMap.Loaded { diff --git a/src/sql/postgres/postgres_protocol.zig b/src/sql/postgres/postgres_protocol.zig index c4347c9015..0416db70a0 100644 --- a/src/sql/postgres/postgres_protocol.zig +++ b/src/sql/postgres/postgres_protocol.zig @@ -809,9 +809,9 @@ pub const ErrorResponse = struct { const error_code: JSC.Error = // https://www.postgresql.org/docs/8.1/errcodes-appendix.html if (code.eqlComptime("42601")) - JSC.Error.ERR_POSTGRES_SYNTAX_ERROR - else - JSC.Error.ERR_POSTGRES_SERVER_ERROR; + JSC.Error.ERR_POSTGRES_SYNTAX_ERROR + else + JSC.Error.ERR_POSTGRES_SERVER_ERROR; const err = error_code.fmt(globalObject, "{s}", .{b.allocatedSlice()[0..b.len]}); inline for (possible_fields) |field| { diff --git a/src/windows.zig b/src/windows.zig index 7ea173f4a1..dca9a7d0c4 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -3064,14 +3064,14 @@ pub fn translateNTStatusToErrno(err: win32.NTSTATUS) bun.C.E { } else .BUSY, .OBJECT_NAME_INVALID => if (comptime Environment.isDebug) brk: { bun.Output.debugWarn("Received OBJECT_NAME_INVALID, indicates a file path conversion issue.", .{}); - bun.crash_handler.dumpCurrentStackTrace(null); + bun.crash_handler.dumpCurrentStackTrace(null, .{ .frame_count = 10 }); break :brk .INVAL; } else .INVAL, else => |t| { if (bun.Environment.isDebug) { bun.Output.warn("Called translateNTStatusToErrno with {s} which does not have a mapping to errno.", .{@tagName(t)}); - bun.crash_handler.dumpCurrentStackTrace(null); + bun.crash_handler.dumpCurrentStackTrace(null, .{ .frame_count = 10 }); } return .UNKNOWN; }, diff --git a/test/internal/ban-words.test.ts b/test/internal/ban-words.test.ts index ff08d7cd51..241cbf050e 100644 --- a/test/internal/ban-words.test.ts +++ b/test/internal/ban-words.test.ts @@ -28,7 +28,7 @@ const words: Record "== alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, "!= alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, [String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 246, regex: true }, - "usingnamespace": { reason: "This brings Bun away from incremental / faster compile times.", limit: 492 }, + "usingnamespace": { reason: "Zig deprecates this, and will not support it in incremental compilation.", limit: 369 }, }; const words_keys = [...Object.keys(words)];