From fa74b80ab6c4932d10fffd267d88da4a3c01b4a4 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Mon, 12 Jan 2026 09:40:53 +0000 Subject: [PATCH] std: introduce Io.Debug proof of concept --- lib/std/Io.zig | 1 + lib/std/Io/Debug.zig | 659 +++++++++++++++++++++++++++++++++++++++++++ lib/std/start.zig | 16 +- 3 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 lib/std/Io/Debug.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b4d6efc715..8a7dc9264f 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -474,6 +474,7 @@ pub fn PollFiles(comptime StreamEnum: type) type { userdata: ?*anyopaque, vtable: *const VTable, +pub const Debug = @import("Io/Debug.zig"); pub const Threaded = @import("Io/Threaded.zig"); pub const Evented = switch (builtin.os.tag) { .linux => switch (builtin.cpu.arch) { diff --git a/lib/std/Io/Debug.zig b/lib/std/Io/Debug.zig new file mode 100644 index 0000000000..5dff239f0d --- /dev/null +++ b/lib/std/Io/Debug.zig @@ -0,0 +1,659 @@ +const Debug = @This(); + +impl: Io, + +/// It is safe for this to be a `std.Thread.Mutex` because it does not guard any +/// `Io` operations, only data structure accesses. Using an `Io.Mutex` would be +/// undesirable as it may significantly change the scheduling behavior of the +/// application. +mutex: std.Thread.Mutex, + +gpa: Allocator, +oom_count: u32, +open_files: std.AutoArrayHashMapUnmanaged(Io.File.Handle, StackTrace) = .empty, +open_dirs: std.AutoArrayHashMapUnmanaged(Io.Dir.Handle, StackTrace) = .empty, + +const StackTrace = struct { index: usize, buf: [6]usize }; + +pub fn init(impl: Io, gpa: Allocator) Debug { + return .{ + .impl = impl, + .mutex = .{}, + .gpa = gpa, + .oom_count = 0, + .open_files = .empty, + .open_dirs = .empty, + }; +} + +pub fn deinit(dbg: *Debug) void { + if (dbg.oom_count > 0) { + std.log.warn("ran out of memory to track {d} handles; some leaks may not be detected", .{dbg.oom_count}); + } + for (dbg.open_files.keys(), dbg.open_files.values()) |handle, *st| { + std.log.err("file handle '{any}' leaked: {f}", .{ + handle, + @as(std.debug.FormatStackTrace, .{ + .stack_trace = .{ .instruction_addresses = &st.buf, .index = st.index }, + .terminal_mode = std.log.terminalMode(), + }), + }); + } + for (dbg.open_dirs.keys(), dbg.open_dirs.values()) |handle, *st| { + std.log.err("dir handle '{any}' leaked: {f}", .{ + handle, + @as(std.debug.FormatStackTrace, .{ + .stack_trace = .{ .instruction_addresses = &st.buf, .index = st.index }, + .terminal_mode = std.log.terminalMode(), + }), + }); + } + dbg.open_files.deinit(dbg.gpa); + dbg.open_dirs.deinit(dbg.gpa); +} + +pub fn io(dbg: *Debug) Io { + return .{ + .userdata = dbg, + .vtable = &.{ + .async = &async, + .concurrent = &concurrent, + .await = &await, + .cancel = &cancel, + .select = &select, + + .groupAsync = &groupAsync, + .groupConcurrent = &groupConcurrent, + .groupAwait = &groupAwait, + .groupCancel = &groupCancel, + + .recancel = &recancel, + .swapCancelProtection = &swapCancelProtection, + .checkCancel = &checkCancel, + + .futexWait = &futexWait, + .futexWaitUncancelable = &futexWaitUncancelable, + .futexWake = &futexWake, + + .dirCreateDir = &dirCreateDir, + .dirCreateDirPath = &dirCreateDirPath, + .dirCreateDirPathOpen = &dirCreateDirPathOpen, + .dirOpenDir = &dirOpenDir, + .dirStat = &dirStat, + .dirStatFile = &dirStatFile, + .dirAccess = &dirAccess, + .dirCreateFile = &dirCreateFile, + .dirCreateFileAtomic = &dirCreateFileAtomic, + .dirOpenFile = &dirOpenFile, + .dirClose = &dirClose, + .dirRead = &dirRead, + .dirRealPath = &dirRealPath, + .dirRealPathFile = &dirRealPathFile, + .dirDeleteFile = &dirDeleteFile, + .dirDeleteDir = &dirDeleteDir, + .dirRename = &dirRename, + .dirRenamePreserve = &dirRenamePreserve, + .dirSymLink = &dirSymLink, + .dirReadLink = &dirReadLink, + .dirSetOwner = &dirSetOwner, + .dirSetFileOwner = &dirSetFileOwner, + .dirSetPermissions = &dirSetPermissions, + .dirSetFilePermissions = &dirSetFilePermissions, + .dirSetTimestamps = &dirSetTimestamps, + .dirHardLink = &dirHardLink, + + .fileStat = &fileStat, + .fileLength = &fileLength, + .fileClose = &fileClose, + .fileWriteStreaming = &fileWriteStreaming, + .fileWritePositional = &fileWritePositional, + .fileWriteFileStreaming = &fileWriteFileStreaming, + .fileWriteFilePositional = &fileWriteFilePositional, + .fileReadStreaming = &fileReadStreaming, + .fileReadPositional = &fileReadPositional, + .fileSeekBy = &fileSeekBy, + .fileSeekTo = &fileSeekTo, + .fileSync = &fileSync, + .fileIsTty = &fileIsTty, + .fileEnableAnsiEscapeCodes = &fileEnableAnsiEscapeCodes, + .fileSupportsAnsiEscapeCodes = &fileSupportsAnsiEscapeCodes, + .fileSetLength = &fileSetLength, + .fileSetOwner = &fileSetOwner, + .fileSetPermissions = &fileSetPermissions, + .fileSetTimestamps = &fileSetTimestamps, + .fileLock = &fileLock, + .fileTryLock = &fileTryLock, + .fileUnlock = &fileUnlock, + .fileDowngradeLock = &fileDowngradeLock, + .fileRealPath = &fileRealPath, + .fileHardLink = &fileHardLink, + + .processExecutableOpen = &processExecutableOpen, + .processExecutablePath = &processExecutablePath, + .lockStderr = &lockStderr, + .tryLockStderr = &tryLockStderr, + .unlockStderr = &unlockStderr, + .processSetCurrentDir = &processSetCurrentDir, + .processReplace = &processReplace, + .processReplacePath = &processReplacePath, + .processSpawn = &processSpawn, + .processSpawnPath = &processSpawnPath, + .childWait = &childWait, + .childKill = &childKill, + + .progressParentFile = &progressParentFile, + + .now = &now, + .sleep = &sleep, + + .random = &random, + .randomSecure = &randomSecure, + + .netListenIp = &netListenIp, + .netAccept = &netAccept, + .netBindIp = &netBindIp, + .netConnectIp = &netConnectIp, + .netListenUnix = &netListenUnix, + .netConnectUnix = &netConnectUnix, + .netSend = &netSend, + .netReceive = &netReceive, + + .netRead = &netRead, + .netWrite = &netWrite, + .netWriteFile = &netWriteFile, + .netClose = &netClose, + .netShutdown = &netShutdown, + .netInterfaceNameResolve = &netInterfaceNameResolve, + .netInterfaceName = &netInterfaceName, + .netLookup = &netLookup, + }, + }; +} + +fn trackOpenFile(dbg: *Debug, file: Io.File, ra: usize) void { + dbg.mutex.lock(); + defer dbg.mutex.unlock(); + const gop = dbg.open_files.getOrPut(dbg.gpa, file.handle) catch |err| switch (err) { + error.OutOfMemory => { + dbg.oom_count += 1; + return; + }, + }; + assert(!gop.found_existing); // underlying implementation returned duplicate handle + const st = std.debug.captureCurrentStackTrace(.{ .first_address = ra }, &gop.value_ptr.buf); + gop.value_ptr.index = st.index; +} + +fn trackCloseFile(dbg: *Debug, file: Io.File, ra: usize) void { + dbg.mutex.lock(); + defer dbg.mutex.unlock(); + if (!dbg.open_files.swapRemove(file.handle)) { + // If there was an OOM we might have failed to track the handle, but otherwise this is + // definitely incorrect usage. + if (dbg.oom_count == 0) { + std.debug.panicExtra(ra, "attempted to close file handle '{any}' which is not open", .{file.handle}); + } + } +} + +fn trackOpenDir(dbg: *Debug, dir: Io.Dir, ra: usize) void { + dbg.mutex.lock(); + defer dbg.mutex.unlock(); + const gop = dbg.open_dirs.getOrPut(dbg.gpa, dir.handle) catch |err| switch (err) { + error.OutOfMemory => { + dbg.oom_count += 1; + return; + }, + }; + assert(!gop.found_existing); // underlying implementation returned duplicate handle + const st = std.debug.captureCurrentStackTrace(.{ .first_address = ra }, &gop.value_ptr.buf); + gop.value_ptr.index = st.index; +} + +fn trackCloseDir(dbg: *Debug, dir: Io.Dir, ra: usize) void { + dbg.mutex.lock(); + defer dbg.mutex.unlock(); + if (!dbg.open_dirs.swapRemove(dir.handle)) { + // If there was an OOM we might have failed to track the handle, but otherwise this is + // definitely incorrect usage. + if (dbg.oom_count == 0) { + std.debug.panicExtra(ra, "attempted to close dir handle '{any}' which is not open", .{dir.handle}); + } + } +} + +fn async(userdata: ?*anyopaque, result: []u8, result_alignment: std.mem.Alignment, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque, result: *anyopaque) void) ?*Io.AnyFuture { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.async(dbg.impl.userdata, result, result_alignment, context, context_alignment, start); +} + +fn concurrent(userdata: ?*anyopaque, result_len: usize, result_alignment: std.mem.Alignment, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque, result: *anyopaque) void) Io.ConcurrentError!*Io.AnyFuture { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.concurrent(dbg.impl.userdata, result_len, result_alignment, context, context_alignment, start); +} + +fn await(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8, result_alignment: std.mem.Alignment) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.await(dbg.impl.userdata, any_future, result, result_alignment); +} + +fn cancel(userdata: ?*anyopaque, any_future: *Io.AnyFuture, result: []u8, result_alignment: std.mem.Alignment) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.cancel(dbg.impl.userdata, any_future, result, result_alignment); +} + +fn groupAsync(userdata: ?*anyopaque, group: *Io.Group, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque) Cancelable!void) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.groupAsync(dbg.impl.userdata, group, context, context_alignment, start); +} + +fn groupConcurrent(userdata: ?*anyopaque, group: *Io.Group, context: []const u8, context_alignment: std.mem.Alignment, start: *const fn (context: *const anyopaque) Cancelable!void) Io.ConcurrentError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.groupConcurrent(dbg.impl.userdata, group, context, context_alignment, start); +} +fn groupAwait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Cancelable!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.groupAwait(dbg.impl.userdata, group, token); +} +fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.groupCancel(dbg.impl.userdata, group, token); +} + +fn recancel(userdata: ?*anyopaque) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.recancel(dbg.impl.userdata); +} +fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.swapCancelProtection(dbg.impl.userdata, new); +} +fn checkCancel(userdata: ?*anyopaque) Cancelable!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.checkCancel(dbg.impl.userdata); +} + +fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Cancelable!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.select(dbg.impl.userdata, futures); +} + +fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Cancelable!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.futexWait(dbg.impl.userdata, ptr, expected, timeout); +} +fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.futexWaitUncancelable(dbg.impl.userdata, ptr, expected); +} +fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.futexWake(dbg.impl.userdata, ptr, max_waiters); +} + +fn dirCreateDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions) Io.Dir.CreateDirError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirCreateDir(dbg.impl.userdata, dir, sub_path, permissions); +} +fn dirCreateDirPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions) Io.Dir.CreateDirPathError!Io.Dir.CreatePathStatus { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirCreateDirPath(dbg.impl.userdata, dir, sub_path, permissions); +} +fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions, options: Io.Dir.OpenOptions) Io.Dir.CreateDirPathOpenError!Io.Dir { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const new_dir = try dbg.impl.vtable.dirCreateDirPathOpen(dbg.impl.userdata, dir, sub_path, permissions, options); + dbg.trackOpenDir(new_dir, @returnAddress()); + return new_dir; +} +fn dirOpenDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.OpenOptions) Io.Dir.OpenError!Io.Dir { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const new_dir = try dbg.impl.vtable.dirOpenDir(dbg.impl.userdata, dir, sub_path, options); + dbg.trackOpenDir(new_dir, @returnAddress()); + return new_dir; +} +fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirStat(dbg.impl.userdata, dir); +} +fn dirStatFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.StatFileOptions) Io.Dir.StatFileError!Io.File.Stat { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirStatFile(dbg.impl.userdata, dir, sub_path, options); +} +fn dirAccess(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) Io.Dir.AccessError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirAccess(dbg.impl.userdata, dir, sub_path, options); +} +fn dirCreateFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, flags: Io.File.CreateFlags) Io.File.OpenError!Io.File { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const file = try dbg.impl.vtable.dirCreateFile(dbg.impl.userdata, dir, sub_path, flags); + dbg.trackOpenFile(file, @returnAddress()); + return file; +} +fn dirCreateFileAtomic(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.CreateFileAtomicOptions) Io.Dir.CreateFileAtomicError!Io.File.Atomic { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const af = try dbg.impl.vtable.dirCreateFileAtomic(dbg.impl.userdata, dir, sub_path, options); + if (af.file_open) dbg.trackOpenFile(af.file, @returnAddress()); + return af; +} +fn dirOpenFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, flags: Io.File.OpenFlags) Io.File.OpenError!Io.File { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const file = try dbg.impl.vtable.dirOpenFile(dbg.impl.userdata, dir, sub_path, flags); + dbg.trackOpenFile(file, @returnAddress()); + return file; +} +fn dirClose(userdata: ?*anyopaque, dirs: []const Io.Dir) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + for (dirs) |dir| dbg.trackCloseDir(dir, @returnAddress()); + return dbg.impl.vtable.dirClose(dbg.impl.userdata, dirs); +} +fn dirRead(userdata: ?*anyopaque, dr: *Io.Dir.Reader, buffer: []Io.Dir.Entry) Io.Dir.Reader.Error!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirRead(dbg.impl.userdata, dr, buffer); +} +fn dirRealPath(userdata: ?*anyopaque, dir: Io.Dir, out_buffer: []u8) Io.Dir.RealPathError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirRealPath(dbg.impl.userdata, dir, out_buffer); +} +fn dirRealPathFile(userdata: ?*anyopaque, dir: Io.Dir, path_name: []const u8, out_buffer: []u8) Io.Dir.RealPathFileError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirRealPathFile(dbg.impl.userdata, dir, path_name, out_buffer); +} +fn dirDeleteFile(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirDeleteFile(dbg.impl.userdata, dir, sub_path); +} +fn dirDeleteDir(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirDeleteDir(dbg.impl.userdata, dir, sub_path); +} +fn dirRename(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8) Io.Dir.RenameError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirRename(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path); +} +fn dirRenamePreserve(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8) Io.Dir.RenamePreserveError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirRenamePreserve(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path); +} +fn dirSymLink(userdata: ?*anyopaque, dir: Io.Dir, target_path: []const u8, sym_link_path: []const u8, flags: Io.Dir.SymLinkFlags) Io.Dir.SymLinkError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSymLink(dbg.impl.userdata, dir, target_path, sym_link_path, flags); +} +fn dirReadLink(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirReadLink(dbg.impl.userdata, dir, sub_path, buffer); +} +fn dirSetOwner(userdata: ?*anyopaque, dir: Io.Dir, uid: ?Io.File.Uid, gid: ?Io.File.Gid) Io.Dir.SetOwnerError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSetOwner(dbg.impl.userdata, dir, uid, gid); +} +fn dirSetFileOwner(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, uid: ?Io.File.Uid, gid: ?Io.File.Gid, options: Io.Dir.SetFileOwnerOptions) Io.Dir.SetFileOwnerError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSetFileOwner(dbg.impl.userdata, dir, sub_path, uid, gid, options); +} +fn dirSetPermissions(userdata: ?*anyopaque, dir: Io.Dir, permissions: Io.Dir.Permissions) Io.Dir.SetPermissionsError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSetPermissions(dbg.impl.userdata, dir, permissions); +} +fn dirSetFilePermissions(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.File.Permissions, options: Io.Dir.SetFilePermissionsOptions) Io.Dir.SetFilePermissionsError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSetFilePermissions(dbg.impl.userdata, dir, sub_path, permissions, options); +} +fn dirSetTimestamps(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.SetTimestampsOptions) Io.Dir.SetTimestampsError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirSetTimestamps(dbg.impl.userdata, dir, sub_path, options); +} +fn dirHardLink(userdata: ?*anyopaque, old_dir: Io.Dir, old_sub_path: []const u8, new_dir: Io.Dir, new_sub_path: []const u8, options: Io.Dir.HardLinkOptions) Io.Dir.HardLinkError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.dirHardLink(dbg.impl.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options); +} + +fn fileStat(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileStat(dbg.impl.userdata, file); +} +fn fileLength(userdata: ?*anyopaque, file: Io.File) Io.File.LengthError!u64 { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileLength(dbg.impl.userdata, file); +} +fn fileClose(userdata: ?*anyopaque, files: []const Io.File) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + for (files) |file| dbg.trackCloseFile(file, @returnAddress()); + return dbg.impl.vtable.fileClose(dbg.impl.userdata, files); +} +fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, header: []const u8, data: []const []const u8, splat: usize) Io.File.Writer.Error!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileWriteStreaming(dbg.impl.userdata, file, header, data, splat); +} +fn fileWritePositional(userdata: ?*anyopaque, file: Io.File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) Io.File.WritePositionalError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileWritePositional(dbg.impl.userdata, file, header, data, splat, offset); +} +fn fileWriteFileStreaming(userdata: ?*anyopaque, file: Io.File, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit) Io.File.Writer.WriteFileError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileWriteFileStreaming(dbg.impl.userdata, file, header, fr, limit); +} +fn fileWriteFilePositional(userdata: ?*anyopaque, file: Io.File, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit, offset: u64) Io.File.WriteFilePositionalError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileWriteFilePositional(dbg.impl.userdata, file, header, fr, limit, offset); +} + +fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: []const []u8) Io.File.Reader.Error!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileReadStreaming(dbg.impl.userdata, file, data); +} + +fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: []const []u8, offset: u64) Io.File.ReadPositionalError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileReadPositional(dbg.impl.userdata, file, data, offset); +} +fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, relative_offset: i64) Io.File.SeekError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSeekBy(dbg.impl.userdata, file, relative_offset); +} +fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, absolute_offset: u64) Io.File.SeekError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSeekTo(dbg.impl.userdata, file, absolute_offset); +} +fn fileSync(userdata: ?*anyopaque, file: Io.File) Io.File.SyncError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSync(dbg.impl.userdata, file); +} +fn fileIsTty(userdata: ?*anyopaque, file: Io.File) Cancelable!bool { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileIsTty(dbg.impl.userdata, file); +} +fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Io.File.EnableAnsiEscapeCodesError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileEnableAnsiEscapeCodes(dbg.impl.userdata, file); +} +fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: Io.File) Cancelable!bool { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSupportsAnsiEscapeCodes(dbg.impl.userdata, file); +} +fn fileSetLength(userdata: ?*anyopaque, file: Io.File, length: u64) Io.File.SetLengthError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSetLength(dbg.impl.userdata, file, length); +} +fn fileSetOwner(userdata: ?*anyopaque, file: Io.File, uid: ?Io.File.Uid, gid: ?Io.File.Gid) Io.File.SetOwnerError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSetOwner(dbg.impl.userdata, file, uid, gid); +} +fn fileSetPermissions(userdata: ?*anyopaque, file: Io.File, permissions: Io.File.Permissions) Io.File.SetPermissionsError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSetPermissions(dbg.impl.userdata, file, permissions); +} +fn fileSetTimestamps(userdata: ?*anyopaque, file: Io.File, options: Io.File.SetTimestampsOptions) Io.File.SetTimestampsError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileSetTimestamps(dbg.impl.userdata, file, options); +} +fn fileLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileLock(dbg.impl.userdata, file, lock); +} +fn fileTryLock(userdata: ?*anyopaque, file: Io.File, lock: Io.File.Lock) Io.File.LockError!bool { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileTryLock(dbg.impl.userdata, file, lock); +} +fn fileUnlock(userdata: ?*anyopaque, file: Io.File) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileUnlock(dbg.impl.userdata, file); +} +fn fileDowngradeLock(userdata: ?*anyopaque, file: Io.File) Io.File.DowngradeLockError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileDowngradeLock(dbg.impl.userdata, file); +} +fn fileRealPath(userdata: ?*anyopaque, file: Io.File, out_buffer: []u8) Io.File.RealPathError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileRealPath(dbg.impl.userdata, file, out_buffer); +} +fn fileHardLink(userdata: ?*anyopaque, file: Io.File, dir: Io.Dir, sub_path: []const u8, options: Io.File.HardLinkOptions) Io.File.HardLinkError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.fileHardLink(dbg.impl.userdata, file, dir, sub_path, options); +} + +fn processExecutableOpen(userdata: ?*anyopaque, flags: Io.File.OpenFlags) std.process.OpenExecutableError!Io.File { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + const file = try dbg.impl.vtable.processExecutableOpen(dbg.impl.userdata, flags); + dbg.trackOpenFile(file, @returnAddress()); + return file; +} +fn processExecutablePath(userdata: ?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processExecutablePath(dbg.impl.userdata, buffer); +} +fn lockStderr(userdata: ?*anyopaque, mode: ?Io.Terminal.Mode) Cancelable!Io.LockedStderr { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.lockStderr(dbg.impl.userdata, mode); +} +fn tryLockStderr(userdata: ?*anyopaque, mode: ?Io.Terminal.Mode) Cancelable!?Io.LockedStderr { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.tryLockStderr(dbg.impl.userdata, mode); +} +fn unlockStderr(userdata: ?*anyopaque) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.unlockStderr(dbg.impl.userdata); +} +fn processSetCurrentDir(userdata: ?*anyopaque, dir: Io.Dir) std.process.SetCurrentDirError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processSetCurrentDir(dbg.impl.userdata, dir); +} +fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processReplace(dbg.impl.userdata, options); +} +fn processReplacePath(userdata: ?*anyopaque, dir: Io.Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processReplacePath(dbg.impl.userdata, dir, options); +} +fn processSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processSpawn(dbg.impl.userdata, options); +} +fn processSpawnPath(userdata: ?*anyopaque, dir: Io.Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.processSpawnPath(dbg.impl.userdata, dir, options); +} +fn childWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.childWait(dbg.impl.userdata, child); +} +fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.childKill(dbg.impl.userdata, child); +} + +fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!Io.File { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.progressParentFile(dbg.impl.userdata); +} + +fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.now(dbg.impl.userdata, clock); +} +fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.sleep(dbg.impl.userdata, timeout); +} + +fn random(userdata: ?*anyopaque, buffer: []u8) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.random(dbg.impl.userdata, buffer); +} +fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.randomSecure(dbg.impl.userdata, buffer); +} + +fn netListenIp(userdata: ?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netListenIp(dbg.impl.userdata, address, options); +} +fn netAccept(userdata: ?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netAccept(dbg.impl.userdata, server); +} +fn netBindIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netBindIp(dbg.impl.userdata, address, options); +} +fn netConnectIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netConnectIp(dbg.impl.userdata, address, options); +} +fn netListenUnix(userdata: ?*anyopaque, address: *const net.UnixAddress, options: net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netListenUnix(dbg.impl.userdata, address, options); +} +fn netConnectUnix(userdata: ?*anyopaque, address: *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netConnectUnix(dbg.impl.userdata, address); +} +fn netSend(userdata: ?*anyopaque, handle: net.Socket.Handle, messages: []net.OutgoingMessage, flags: net.SendFlags) struct { ?net.Socket.SendError, usize } { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netSend(dbg.impl.userdata, handle, messages, flags); +} +fn netReceive(userdata: ?*anyopaque, handle: net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, flags: net.ReceiveFlags, timeout: Io.Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize } { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netReceive(dbg.impl.userdata, handle, message_buffer, data_buffer, flags, timeout); +} + +fn netRead(userdata: ?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netRead(dbg.impl.userdata, src, data); +} +fn netWrite(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netWrite(dbg.impl.userdata, dest, header, data, splat); +} +fn netWriteFile(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, fr: *Io.File.Reader, limit: Io.Limit) net.Stream.Writer.WriteFileError!usize { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netWriteFile(dbg.impl.userdata, dest, header, fr, limit); +} +fn netClose(userdata: ?*anyopaque, handle: []const net.Socket.Handle) void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netClose(dbg.impl.userdata, handle); +} +fn netShutdown(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netShutdown(dbg.impl.userdata, handle, how); +} +fn netInterfaceNameResolve(userdata: ?*anyopaque, name: *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netInterfaceNameResolve(dbg.impl.userdata, name); +} +fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netInterfaceName(dbg.impl.userdata, interface); +} +fn netLookup(userdata: ?*anyopaque, host_name: net.HostName, resolved: *Io.Queue(net.HostName.LookupResult), options: net.HostName.LookupOptions) net.HostName.LookupError!void { + const dbg: *Debug = @ptrCast(@alignCast(userdata)); + return dbg.impl.vtable.netLookup(dbg.impl.userdata, host_name, resolved, options); +} + +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const Cancelable = Io.Cancelable; +const Io = std.Io; +const assert = std.debug.assert; +const net = Io.net; diff --git a/lib/std/start.zig b/lib/std/start.zig index 685fda3635..8339521260 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -669,6 +669,11 @@ const use_debug_allocator = !is_wasm and switch (builtin.mode) { .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal. }; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; +const use_debug_io = switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, +}; +var debug_io: std.Io.Debug = undefined; inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 { const fn_info = @typeInfo(@TypeOf(root.main)).@"fn"; @@ -704,6 +709,15 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B }); defer threaded.deinit(); + if (use_debug_io) { + debug_io = .init(threaded.io(), gpa); + } + defer if (use_debug_io) { + debug_io.deinit(); + }; + + const io = if (use_debug_io) debug_io.io() else threaded.io(); + var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err| std.process.fatal("failed to parse environment variables: {t}", .{err}); defer environ_map.deinit(); @@ -718,7 +732,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B }, .arena = &arena_allocator, .gpa = gpa, - .io = threaded.io(), + .io = io, .environ_map = &environ_map, .preopens = preopens, }));