diff options
Diffstat (limited to 'lib/std/debug.zig')
| -rw-r--r-- | lib/std/debug.zig | 350 |
1 files changed, 182 insertions, 168 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f2b8fcc889..60288bdecc 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -12,6 +12,7 @@ const windows = std.os.windows; const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; const native_endian = native_arch.endian(); +const Writer = std.io.Writer; pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); pub const FixedBufferReader = @import("debug/FixedBufferReader.zig"); @@ -204,13 +205,26 @@ pub fn unlockStdErr() void { std.Progress.unlockStdErr(); } +/// Allows the caller to freely write to stderr until `unlockStdErr` is called. +/// +/// During the lock, any `std.Progress` information is cleared from the terminal. +/// +/// Returns a `Writer` with empty buffer, meaning that it is +/// in fact unbuffered and does not need to be flushed. +pub fn lockStderrWriter(buffer: []u8) *Writer { + return std.Progress.lockStderrWriter(buffer); +} + +pub fn unlockStderrWriter() void { + std.Progress.unlockStderrWriter(); +} + /// Print to stderr, unbuffered, and silently returning on failure. Intended -/// for use in "printf debugging." Use `std.log` functions for proper logging. +/// for use in "printf debugging". Use `std.log` functions for proper logging. pub fn print(comptime fmt: []const u8, args: anytype) void { - lockStdErr(); - defer unlockStdErr(); - const stderr = fs.File.stderr().writer(); - nosuspend stderr.print(fmt, args) catch return; + const bw = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + nosuspend bw.print(fmt, args) catch return; } pub fn getStderrMutex() *std.Thread.Mutex { @@ -232,50 +246,44 @@ pub fn getSelfDebugInfo() !*SelfInfo { /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. /// Obtains the stderr mutex while dumping. pub fn dumpHex(bytes: []const u8) void { - lockStdErr(); - defer unlockStdErr(); - dumpHexFallible(bytes) catch {}; -} - -/// Prints a hexadecimal view of the bytes, unbuffered, returning any error that occurs. -pub fn dumpHexFallible(bytes: []const u8) !void { - const stderr: fs.File = .stderr(); - const ttyconf = std.io.tty.detectConfig(stderr); - const writer = stderr.writer(); - try dumpHexInternal(bytes, ttyconf, writer); + const bw = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + const ttyconf = std.io.tty.detectConfig(.stderr()); + dumpHexFallible(bw, ttyconf, bytes) catch {}; } -fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytype) !void { +/// Prints a hexadecimal view of the bytes, returning any error that occurs. +pub fn dumpHexFallible(bw: *Writer, ttyconf: std.io.tty.Config, bytes: []const u8) !void { var chunks = mem.window(u8, bytes, 16, 16); while (chunks.next()) |window| { // 1. Print the address. const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; - try ttyconf.setColor(writer, .dim); + try ttyconf.setColor(bw, .dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. - try writer.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); - try ttyconf.setColor(writer, .reset); + try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); + try ttyconf.setColor(bw, .reset); // 2. Print the bytes. for (window, 0..) |byte, index| { - try writer.print("{X:0>2} ", .{byte}); - if (index == 7) try writer.writeByte(' '); + try bw.print("{X:0>2} ", .{byte}); + if (index == 7) try bw.writeByte(' '); } - try writer.writeByte(' '); + try bw.writeByte(' '); if (window.len < 16) { var missing_columns = (16 - window.len) * 3; if (window.len < 8) missing_columns += 1; - try writer.writeByteNTimes(' ', missing_columns); + try bw.splatByteAll(' ', missing_columns); } // 3. Print the characters. for (window) |byte| { if (std.ascii.isPrint(byte)) { - try writer.writeByte(byte); + try bw.writeByte(byte); } else { // Related: https://github.com/ziglang/zig/issues/7600 if (ttyconf == .windows_api) { - try writer.writeByte('.'); + try bw.writeByte('.'); continue; } @@ -283,22 +291,23 @@ fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytyp // We don't want to do this for all control codes because most control codes apart from // the ones that Zig has escape sequences for are likely not very useful to print as symbols. switch (byte) { - '\n' => try writer.writeAll("␊"), - '\r' => try writer.writeAll("␍"), - '\t' => try writer.writeAll("␉"), - else => try writer.writeByte('.'), + '\n' => try bw.writeAll("␊"), + '\r' => try bw.writeAll("␍"), + '\t' => try bw.writeAll("␉"), + else => try bw.writeByte('.'), } } } - try writer.writeByte('\n'); + try bw.writeByte('\n'); } } -test dumpHexInternal { +test dumpHexFallible { const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; - var output = std.ArrayList(u8).init(std.testing.allocator); - defer output.deinit(); - try dumpHexInternal(bytes, .no_color, output.writer()); + var aw: std.io.Writer.Allocating = .init(std.testing.allocator); + defer aw.deinit(); + + try dumpHexFallible(&aw.interface, .no_color, bytes); const expected = try std.fmt.allocPrint(std.testing.allocator, \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ \\{x:0>[2]} 01 12 13 ... @@ -309,34 +318,36 @@ test dumpHexInternal { @sizeOf(usize) * 2, }); defer std.testing.allocator.free(expected); - try std.testing.expectEqualStrings(expected, output.items); + try std.testing.expectEqualStrings(expected, aw.getWritten()); } /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. -/// TODO multithreaded awareness pub fn dumpCurrentStackTrace(start_addr: ?usize) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = fs.File.stderr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; - } - const stderr = fs.File.stderr().writer(); - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return; +} + +/// Prints the current stack trace to the provided writer. +pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void { + if (builtin.target.cpu.arch.isWasm()) { + if (native_os == .wasi) { + try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n"); } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(fs.File.stderr()), start_addr) catch |err| { - stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; - return; - }; + return; } + if (builtin.strip_debug_info) { + try writer.writeAll("Unable to dump stack trace: debug info stripped\n"); + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); + return; + }; + writeCurrentStackTrace(writer, debug_info, io.tty.detectConfig(.stderr()), start_addr) catch |err| { + try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); + return; + }; } pub const have_ucontext = posix.ucontext_t != void; @@ -402,16 +413,14 @@ pub inline fn getContext(context: *ThreadContext) bool { /// Tries to print the stack trace starting from the supplied base pointer to stderr, /// unbuffered, and ignores any error returned. /// TODO multithreaded awareness -pub fn dumpStackTraceFromBase(context: *ThreadContext) void { +pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void { nosuspend { if (builtin.target.cpu.arch.isWasm()) { if (native_os == .wasi) { - const stderr = fs.File.stderr().writer(); stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; } return; } - const stderr = fs.File.stderr().writer(); if (builtin.strip_debug_info) { stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; return; @@ -420,7 +429,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - const tty_config = io.tty.detectConfig(fs.File.stderr()); + const tty_config = io.tty.detectConfig(.stderr()); if (native_os == .windows) { // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace @@ -510,21 +519,23 @@ pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { nosuspend { if (builtin.target.cpu.arch.isWasm()) { if (native_os == .wasi) { - const stderr = fs.File.stderr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return; } return; } - const stderr = fs.File.stderr().writer(); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return; return; } const debug_info = getSelfDebugInfo() catch |err| { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(fs.File.stderr())) catch |err| { + writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(.stderr())) catch |err| { stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; @@ -573,14 +584,13 @@ pub fn panicExtra( const size = 0x1000; const trunc_msg = "(msg truncated)"; var buf: [size + trunc_msg.len]u8 = undefined; + var bw: Writer = .fixed(buf[0..size]); // a minor annoyance with this is that it will result in the NoSpaceLeft // error being part of the @panic stack trace (but that error should // only happen rarely) - const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) { - error.NoSpaceLeft => blk: { - @memcpy(buf[size..], trunc_msg); - break :blk &buf; - }, + const msg = if (bw.print(format, args)) |_| bw.buffered() else |_| blk: { + @memcpy(buf[size..], trunc_msg); + break :blk &buf; }; std.builtin.panic.call(msg, ret_addr); } @@ -675,10 +685,9 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); { - lockStdErr(); - defer unlockStdErr(); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); - const stderr = fs.File.stderr().writer(); if (builtin.single_threaded) { stderr.print("panic: ", .{}) catch posix.abort(); } else { @@ -688,7 +697,7 @@ pub fn defaultPanic( stderr.print("{s}\n", .{msg}) catch posix.abort(); if (@errorReturnTrace()) |t| dumpStackTrace(t.*); - dumpCurrentStackTrace(first_trace_addr orelse @returnAddress()); + dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {}; } waitForOtherThreadToFinishPanicking(); @@ -723,7 +732,7 @@ fn waitForOtherThreadToFinishPanicking() void { pub fn writeStackTrace( stack_trace: std.builtin.StackTrace, - out_stream: anytype, + writer: *Writer, debug_info: *SelfInfo, tty_config: io.tty.Config, ) !void { @@ -736,15 +745,15 @@ pub fn writeStackTrace( frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; }) { const return_address = stack_trace.instruction_addresses[frame_index]; - try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config); + try printSourceAtAddress(debug_info, writer, return_address - 1, 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 skipped...)\n", .{dropped_frames}); - tty_config.setColor(out_stream, .reset) catch {}; + tty_config.setColor(writer, .bold) catch {}; + try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); + tty_config.setColor(writer, .reset) catch {}; } } @@ -954,7 +963,7 @@ pub const StackIterator = struct { }; pub fn writeCurrentStackTrace( - out_stream: anytype, + writer: *Writer, debug_info: *SelfInfo, tty_config: io.tty.Config, start_addr: ?usize, @@ -962,7 +971,7 @@ pub fn writeCurrentStackTrace( if (native_os == .windows) { var context: ThreadContext = undefined; assert(getContext(&context)); - return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); + return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr); } var context: ThreadContext = undefined; const has_context = getContext(&context); @@ -973,7 +982,7 @@ pub fn writeCurrentStackTrace( defer it.deinit(); while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, out_stream, tty_config); + printLastUnwindError(&it, debug_info, writer, tty_config); // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid @@ -981,8 +990,8 @@ pub fn writeCurrentStackTrace( // condition on the subsequent iteration and return `null` thus terminating the loop. // same behaviour for x86-windows-msvc const address = return_address -| 1; - try printSourceAtAddress(debug_info, out_stream, address, tty_config); - } else printLastUnwindError(&it, debug_info, out_stream, tty_config); + try printSourceAtAddress(debug_info, writer, address, tty_config); + } else printLastUnwindError(&it, debug_info, writer, tty_config); } pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { @@ -1042,7 +1051,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w } pub fn writeStackTraceWindows( - out_stream: anytype, + writer: *Writer, debug_info: *SelfInfo, tty_config: io.tty.Config, context: *const windows.CONTEXT, @@ -1058,14 +1067,14 @@ pub fn writeStackTraceWindows( return; } else 0; for (addrs[start_i..]) |addr| { - try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config); + try printSourceAtAddress(debug_info, writer, addr - 1, tty_config); } } -fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( - out_stream, + writer, null, address, "???", @@ -1075,38 +1084,38 @@ fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize ); } -fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void { +fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: io.tty.Config) void { if (!have_ucontext) return; if (it.getLastError()) |unwind_error| { - printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; + printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {}; } } -fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { +fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; - try tty_config.setColor(out_stream, .dim); + try tty_config.setColor(writer, .dim); if (err == error.MissingDebugInfo) { - try out_stream.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); + try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); } else { - try out_stream.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); + try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); } - try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(writer, .reset); } -pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: io.tty.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), + error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), else => return err, }; const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), + error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), else => return err, }; defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); return printLineInfo( - out_stream, + writer, symbol_info.source_location, address, symbol_info.name, @@ -1117,7 +1126,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: } fn printLineInfo( - out_stream: anytype, + writer: *Writer, source_location: ?SourceLocation, address: usize, symbol_name: []const u8, @@ -1126,34 +1135,34 @@ fn printLineInfo( comptime printLineFromFile: anytype, ) !void { nosuspend { - try tty_config.setColor(out_stream, .bold); + try tty_config.setColor(writer, .bold); if (source_location) |*sl| { - try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); + try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); } else { - try out_stream.writeAll("???:?:?"); + try writer.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 {s} ({s})", .{ address, symbol_name, compile_unit_name }); - try tty_config.setColor(out_stream, .reset); - try out_stream.writeAll("\n"); + try tty_config.setColor(writer, .reset); + try writer.writeAll(": "); + try tty_config.setColor(writer, .dim); + try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); + try tty_config.setColor(writer, .reset); + try writer.writeAll("\n"); // Show the matching source code line if possible if (source_location) |sl| { - if (printLineFromFile(out_stream, sl)) { + if (printLineFromFile(writer, sl)) { if (sl.column > 0) { // The caret already takes one char const space_needed = @as(usize, @intCast(sl.column - 1)); - try out_stream.writeByteNTimes(' ', space_needed); - try tty_config.setColor(out_stream, .green); - try out_stream.writeAll("^"); - try tty_config.setColor(out_stream, .reset); + try writer.splatByteAll(' ', space_needed); + try tty_config.setColor(writer, .green); + try writer.writeAll("^"); + try tty_config.setColor(writer, .reset); } - try out_stream.writeAll("\n"); + try writer.writeAll("\n"); } else |err| switch (err) { error.EndOfFile, error.FileNotFound => {}, error.BadPathName => {}, @@ -1164,7 +1173,7 @@ fn printLineInfo( } } -fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { +fn printLineFromFileAnyOs(writer: *Writer, 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 fs.cwd().openFile(source_location.file_name, .{}); @@ -1197,31 +1206,31 @@ fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) if (mem.indexOfScalar(u8, slice, '\n')) |pos| { const line = slice[0 .. pos + 1]; mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); + return writer.writeAll(line); } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. mem.replaceScalar(u8, slice, '\t', ' '); - try out_stream.writeAll(slice); + try writer.writeAll(slice); while (amt_read == buf.len) { amt_read = try f.read(buf[0..]); if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| { const line = buf[0 .. pos + 1]; mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); + return writer.writeAll(line); } else { const line = buf[0..amt_read]; mem.replaceScalar(u8, line, '\t', ' '); - try out_stream.writeAll(line); + try writer.writeAll(line); } } // Make sure printing last line of file inserts extra newline - try out_stream.writeByte('\n'); + try writer.writeByte('\n'); } } test printLineFromFileAnyOs { - var output = std.ArrayList(u8).init(std.testing.allocator); - defer output.deinit(); - const output_stream = output.writer(); + var aw: Writer.Allocating = .init(std.testing.allocator); + defer aw.deinit(); + const output_stream = &aw.interface; const allocator = std.testing.allocator; const join = std.fs.path.join; @@ -1243,8 +1252,8 @@ test printLineFromFileAnyOs { try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.getWritten()); + aw.clearRetainingCapacity(); } { const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); @@ -1259,12 +1268,12 @@ test printLineFromFileAnyOs { }); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings("1\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("1\n", aw.getWritten()); + aw.clearRetainingCapacity(); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); - try expectEqualStrings("3\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("3\n", aw.getWritten()); + aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); @@ -1273,14 +1282,15 @@ test printLineFromFileAnyOs { defer allocator.free(path); const overlap = 10; - var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_min - overlap); + var file_writer = file.writer(&.{}); + const writer = &file_writer.interface; + try writer.splatByteAll('a', std.heap.page_size_min - overlap); try writer.writeByte('\n'); - try writer.writeByteNTimes('a', overlap); + try writer.splatByteAll('a', overlap); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); - try expectEqualStrings(("a" ** overlap) ++ "\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings(("a" ** overlap) ++ "\n", aw.getWritten()); + aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); @@ -1288,12 +1298,13 @@ test printLineFromFileAnyOs { const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); defer allocator.free(path); - var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_max); + var file_writer = file.writer(&.{}); + const writer = &file_writer.interface; + try writer.splatByteAll('a', std.heap.page_size_max); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.getWritten()); + aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); @@ -1301,24 +1312,25 @@ test printLineFromFileAnyOs { const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); defer allocator.free(path); - var writer = file.writer(); - try writer.writeByteNTimes('a', 3 * std.heap.page_size_max); + var file_writer = file.writer(&.{}); + const writer = &file_writer.interface; + try writer.splatByteAll('a', 3 * std.heap.page_size_max); try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.getWritten()); + aw.clearRetainingCapacity(); try writer.writeAll("a\na"); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.getWritten()); + aw.clearRetainingCapacity(); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); - try expectEqualStrings("a\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("a\n", aw.getWritten()); + aw.clearRetainingCapacity(); } { const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); @@ -1326,18 +1338,19 @@ test printLineFromFileAnyOs { const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); defer allocator.free(path); - var writer = file.writer(); + var file_writer = file.writer(&.{}); + const writer = &file_writer.interface; const real_file_start = 3 * std.heap.page_size_min; - try writer.writeByteNTimes('\n', real_file_start); + try writer.splatByteAll('\n', real_file_start); try writer.writeAll("abc\ndef"); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); - try expectEqualStrings("abc\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("abc\n", aw.getWritten()); + aw.clearRetainingCapacity(); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); - try expectEqualStrings("def\n", output.items); - output.clearRetainingCapacity(); + try expectEqualStrings("def\n", aw.getWritten()); + aw.clearRetainingCapacity(); } } @@ -1461,7 +1474,8 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa } fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { - const stderr = fs.File.stderr().writer(); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); _ = switch (sig) { posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL // x86_64 doesn't have a full 64-bit virtual address space. @@ -1471,7 +1485,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) // but can also happen when no addressable memory is involved; // for example when reading/writing model-specific registers // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). - stderr.print("General protection exception (no address available)\n", .{}) + stderr.writeAll("General protection exception (no address available)\n") else stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), @@ -1509,7 +1523,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) }, @ptrCast(ctx)).__mcontext_data; } relocateContext(&new_ctx); - dumpStackTraceFromBase(&new_ctx); + dumpStackTraceFromBase(&new_ctx, stderr); }, else => {}, } @@ -1539,10 +1553,10 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: _ = panicking.fetchAdd(1, .seq_cst); { - lockStdErr(); - defer unlockStdErr(); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); - dumpSegfaultInfoWindows(info, msg, label); + dumpSegfaultInfoWindows(info, msg, label, stderr); } waitForOtherThreadToFinishPanicking(); @@ -1556,8 +1570,7 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: posix.abort(); } -fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { - const stderr = fs.File.stderr().writer(); +fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void { _ = switch (msg) { 0 => stderr.print("{s}\n", .{label.?}), 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), @@ -1565,7 +1578,7 @@ fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[ else => unreachable, } catch posix.abort(); - dumpStackTraceFromBase(info.ContextRecord); + dumpStackTraceFromBase(info.ContextRecord, stderr); } pub fn dumpStackPointerAddr(prefix: []const u8) void { @@ -1588,10 +1601,10 @@ test "manage resources correctly" { // self-hosted debug info is still too buggy if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; - const writer = std.io.null_writer; + var writer: std.io.Writer = .discarding(&.{}); var di = try SelfInfo.open(testing.allocator); defer di.deinit(); - try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.fs.File.stderr())); + try printSourceAtAddress(&di, &writer, showMyTrace(), io.tty.detectConfig(.stderr())); } noinline fn showMyTrace() usize { @@ -1657,8 +1670,9 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub fn dump(t: @This()) void { if (!enabled) return; - const tty_config = io.tty.detectConfig(std.fs.File.stderr()); - const stderr = fs.File.stderr().writer(); + const tty_config = io.tty.detectConfig(.stderr()); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); const end = @min(t.index, size); const debug_info = getSelfDebugInfo() catch |err| { stderr.print( @@ -1688,7 +1702,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize t: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, - writer: anytype, + writer: *Writer, ) !void { if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t); _ = options; |
