aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrewrk@noreply.codeberg.org>2025-12-27 14:10:46 +0100
committerAndrew Kelley <andrewrk@noreply.codeberg.org>2025-12-27 14:10:46 +0100
commite55e6b5528bb2f01de242fcf32b172e244e98e74 (patch)
tree3a5eb3193d3d192c54ab0c2b7295a7f21861c27e /lib/std/debug.zig
parentc3f2de5e519926eb0029062fe8e782a6f9df9c05 (diff)
parent60a1ba0a8f3517356fa2941462f002a7f580545b (diff)
downloadzig-e55e6b5528bb2f01de242fcf32b172e244e98e74.tar.gz
zig-e55e6b5528bb2f01de242fcf32b172e244e98e74.zip
Merge pull request 'std: migrate all `fs` APIs to `Io`' (#30232) from std.Io-fs into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30232
Diffstat (limited to 'lib/std/debug.zig')
-rw-r--r--lib/std/debug.zig528
1 files changed, 250 insertions, 278 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index feea5f9a41..d000bda62e 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -1,14 +1,13 @@
const std = @import("std.zig");
const Io = std.Io;
const Writer = std.Io.Writer;
-const tty = std.Io.tty;
const math = std.math;
const mem = std.mem;
const posix = std.posix;
const fs = std.fs;
const testing = std.testing;
const Allocator = mem.Allocator;
-const File = std.fs.File;
+const File = std.Io.File;
const windows = std.os.windows;
const builtin = @import("builtin");
@@ -60,7 +59,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");
/// };
/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's
/// /// return address, or 0 if the end of the stack has been reached.
-/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize;
+/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) SelfInfoError!usize;
/// ```
pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
root.debug.SelfInfo
@@ -262,53 +261,54 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
else => true,
};
-/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
+/// Allows the caller to freely write to stderr until `unlockStderr` is called.
///
/// During the lock, any `std.Progress` information is cleared from the terminal.
-pub fn lockStdErr() void {
- std.Progress.lockStdErr();
-}
-
-pub fn unlockStdErr() void {
- std.Progress.unlockStdErr();
-}
-
-/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
///
-/// During the lock, any `std.Progress` information is cleared from the terminal.
+/// The lock is recursive, so it is valid for the same thread to call
+/// `lockStderr` multiple times, allowing the panic handler to safely
+/// dump the stack trace and panic message even if the mutex was held at the
+/// panic site.
///
-/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
-/// times. The primary motivation is that this allows the panic handler to safely dump the stack
-/// trace and panic message even if the mutex was held at the panic site.
+/// The returned `Writer` does not need to be manually flushed: flushing is
+/// performed automatically when the matching `unlockStderr` call occurs.
///
-/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically
-/// when the matching `unlockStderrWriter` call occurs.
-pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
- const global = struct {
- var conf: ?tty.Config = null;
+/// This is a low-level debugging primitive that bypasses the `Io` interface,
+/// writing directly to stderr using the most basic syscalls available. This
+/// function does not switch threads, switch stacks, or suspend.
+///
+/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
+/// application's chosen `Io` implementation.
+pub fn lockStderr(buffer: []u8) Io.LockedStderr {
+ const io = std.Options.debug_io;
+ const prev = io.swapCancelProtection(.blocked);
+ defer _ = io.swapCancelProtection(prev);
+ return io.lockStderr(buffer, null) catch |err| switch (err) {
+ error.Canceled => unreachable, // Cancel protection enabled above.
};
- const w = std.Progress.lockStderrWriter(buffer);
- // The stderr lock also locks access to `global.conf`.
- if (global.conf == null) {
- global.conf = .detect(.stderr());
- }
- return .{ w, global.conf.? };
}
-pub fn unlockStderrWriter() void {
- std.Progress.unlockStderrWriter();
+pub fn unlockStderr() void {
+ const io = std.Options.debug_io;
+ io.unlockStderr();
}
-/// Print to stderr, silently returning on failure. Intended for use in "printf
-/// debugging". Use `std.log` functions for proper logging.
+/// Writes to stderr, ignoring errors.
+///
+/// This is a low-level debugging primitive that bypasses the `Io` interface,
+/// writing directly to stderr using the most basic syscalls available. This
+/// function does not switch threads, switch stacks, or suspend.
///
/// Uses a 64-byte buffer for formatted printing which is flushed before this
/// function returns.
+///
+/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
+/// integrate with the application's chosen `Io` implementation.
pub fn print(comptime fmt: []const u8, args: anytype) void {
var buffer: [64]u8 = undefined;
- const bw, _ = lockStderrWriter(&buffer);
- defer unlockStderrWriter();
- nosuspend bw.print(fmt, args) catch return;
+ const stderr = lockStderr(&buffer);
+ defer unlockStderr();
+ stderr.file_writer.interface.print(fmt, args) catch return;
}
/// Marked `inline` to propagate a comptime-known error to callers.
@@ -323,43 +323,44 @@ pub inline 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 {
- const bw, const ttyconf = lockStderrWriter(&.{});
- defer unlockStderrWriter();
- dumpHexFallible(bw, ttyconf, bytes) catch {};
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
+ dumpHexFallible(stderr, bytes) catch {};
}
/// Prints a hexadecimal view of the bytes, returning any error that occurs.
-pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void {
+pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void {
+ const w = t.writer;
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 tty_config.setColor(bw, .dim);
+ try t.setColor(.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 bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
- try tty_config.setColor(bw, .reset);
+ try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
+ try t.setColor(.reset);
// 2. Print the bytes.
for (window, 0..) |byte, index| {
- try bw.print("{X:0>2} ", .{byte});
- if (index == 7) try bw.writeByte(' ');
+ try w.print("{X:0>2} ", .{byte});
+ if (index == 7) try w.writeByte(' ');
}
- try bw.writeByte(' ');
+ try w.writeByte(' ');
if (window.len < 16) {
var missing_columns = (16 - window.len) * 3;
if (window.len < 8) missing_columns += 1;
- try bw.splatByteAll(' ', missing_columns);
+ try w.splatByteAll(' ', missing_columns);
}
// 3. Print the characters.
for (window) |byte| {
if (std.ascii.isPrint(byte)) {
- try bw.writeByte(byte);
+ try w.writeByte(byte);
} else {
// Related: https://github.com/ziglang/zig/issues/7600
- if (tty_config == .windows_api) {
- try bw.writeByte('.');
+ if (t.mode == .windows_api) {
+ try w.writeByte('.');
continue;
}
@@ -367,24 +368,25 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
// 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 bw.writeAll("␊"),
- '\r' => try bw.writeAll("␍"),
- '\t' => try bw.writeAll("␉"),
- else => try bw.writeByte('.'),
+ '\n' => try w.writeAll("␊"),
+ '\r' => try w.writeAll("␍"),
+ '\t' => try w.writeAll("␉"),
+ else => try w.writeByte('.'),
}
}
}
- try bw.writeByte('\n');
+ try w.writeByte('\n');
}
}
test dumpHexFallible {
+ const gpa = testing.allocator;
const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
- var aw: Writer.Allocating = .init(std.testing.allocator);
+ var aw: Writer.Allocating = .init(gpa);
defer aw.deinit();
- try dumpHexFallible(&aw.writer, .no_color, bytes);
- const expected = try std.fmt.allocPrint(std.testing.allocator,
+ try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes);
+ const expected = try std.fmt.allocPrint(gpa,
\\{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 ...
\\
@@ -393,8 +395,8 @@ test dumpHexFallible {
@intFromPtr(bytes.ptr) + 16,
@sizeOf(usize) * 2,
});
- defer std.testing.allocator.free(expected);
- try std.testing.expectEqualStrings(expected, aw.written());
+ defer gpa.free(expected);
+ try testing.expectEqualStrings(expected, aw.written());
}
/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
@@ -409,7 +411,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
/// away, and in fact the optimizer is able to use the assertion in its
/// heuristics.
///
-/// Inside a test block, it is best to use the `std.testing` module rather than
+/// Inside a test block, it is best to use the `testing` module rather than
/// this function, because this function may not detect a test failure in
/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
/// function is the correct function to use.
@@ -483,10 +485,7 @@ const use_trap_panic = switch (builtin.zig_backend) {
};
/// Dumps a stack trace to standard error, then aborts.
-pub fn defaultPanic(
- msg: []const u8,
- first_trace_addr: ?usize,
-) noreturn {
+pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
@branchHint(.cold);
if (use_trap_panic) @trap();
@@ -522,7 +521,7 @@ pub fn defaultPanic(
}
@trap();
},
- .cuda, .amdhsa => std.posix.abort(),
+ .cuda, .amdhsa => std.process.abort(),
.plan9 => {
var status: [std.os.plan9.ERRMAX]u8 = undefined;
const len = @min(msg.len, status.len - 1);
@@ -546,26 +545,27 @@ pub fn defaultPanic(
_ = panicking.fetchAdd(1, .seq_cst);
trace: {
- const stderr, const tty_config = lockStderrWriter(&.{});
- defer unlockStderrWriter();
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
+ const writer = stderr.writer;
if (builtin.single_threaded) {
- stderr.print("panic: ", .{}) catch break :trace;
+ writer.print("panic: ", .{}) catch break :trace;
} else {
const current_thread_id = std.Thread.getCurrentId();
- stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
+ writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
}
- stderr.print("{s}\n", .{msg}) catch break :trace;
+ writer.print("{s}\n", .{msg}) catch break :trace;
if (@errorReturnTrace()) |t| if (t.index > 0) {
- stderr.writeAll("error return context:\n") catch break :trace;
- writeStackTrace(t, stderr, tty_config) catch break :trace;
- stderr.writeAll("\nstack trace:\n") catch break :trace;
+ writer.writeAll("error return context:\n") catch break :trace;
+ writeStackTrace(t, stderr) catch break :trace;
+ writer.writeAll("\nstack trace:\n") catch break :trace;
};
writeCurrentStackTrace(.{
.first_address = first_trace_addr orelse @returnAddress(),
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
- }, stderr, tty_config) catch break :trace;
+ }, stderr) catch break :trace;
}
waitForOtherThreadToFinishPanicking();
@@ -575,12 +575,13 @@ pub fn defaultPanic(
// A panic happened while trying to print a previous panic message.
// We're still holding the mutex but that's fine as we're going to
// call abort().
- fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
+ const stderr = lockStderr(&.{}).terminal();
+ stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
},
else => {}, // Panicked while printing the recursive panic message.
}
- posix.abort();
+ std.process.abort();
}
/// Must be called only after adding 1 to `panicking`. There are three callsites.
@@ -621,13 +622,16 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
var it: StackIterator = .init(options.context);
defer it.deinit();
if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
+
+ const io = std.Options.debug_io;
+
var total_frames: usize = 0;
var index: usize = 0;
var wait_for = options.first_address;
// Ideally, we would iterate the whole stack so that the `index` in the returned trace was
// indicative of how many frames were skipped. However, this has a significant runtime cost
// in some cases, so at least for now, we don't do that.
- while (index < addr_buf.len) switch (it.next()) {
+ while (index < addr_buf.len) switch (it.next(io)) {
.switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
.end => break,
.frame => |ret_addr| {
@@ -653,37 +657,36 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
/// Write the current stack trace to `writer`, annotated with source locations.
///
/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
-pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
- var threaded: Io.Threaded = .init_single_threaded;
- const io = threaded.ioBasic();
-
+pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
+ const writer = t.writer;
if (!std.options.allow_stack_tracing) {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
}
const di_gpa = getDebugInfoAllocator();
const di = getSelfDebugInfo() catch |err| switch (err) {
error.UnsupportedTarget => {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
},
};
var it: StackIterator = .init(options.context);
defer it.deinit();
if (!it.stratOk(options.allow_unsafe_unwind)) {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
}
var total_frames: usize = 0;
var wait_for = options.first_address;
var printed_any_frame = false;
- while (true) switch (it.next()) {
+ const io = std.Options.debug_io;
+ while (true) switch (it.next(io)) {
.switch_to_fp => |unwind_error| {
switch (StackIterator.fp_usability) {
.useless, .unsafe => {},
@@ -700,31 +703,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
error.Unexpected => "unexpected error",
};
if (it.stratOk(options.allow_unsafe_unwind)) {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print(
"Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
.{ module_name, unwind_error.address, caption },
);
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
} else {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print(
"Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
.{ module_name, unwind_error.address, caption },
);
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
}
},
.end => break,
.frame => |ret_addr| {
if (total_frames > 10_000) {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print(
"Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
.{total_frames},
);
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
}
total_frames += 1;
@@ -734,7 +737,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
}
// `ret_addr` is the return address, which is *after* the function call.
// Subtract 1 to get an address *in* the function call for a better source location.
- try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
+ try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
printed_any_frame = true;
},
};
@@ -742,8 +745,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
}
/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
- const stderr, const tty_config = lockStderrWriter(&.{});
- defer unlockStderrWriter();
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
writeCurrentStackTrace(.{
.first_address = a: {
if (options.first_address) |a| break :a a;
@@ -752,33 +755,30 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
},
.context = options.context,
.allow_unsafe_unwind = options.allow_unsafe_unwind,
- }, stderr, tty_config) catch |err| switch (err) {
+ }, stderr) catch |err| switch (err) {
error.WriteFailed => {},
};
}
pub const FormatStackTrace = struct {
stack_trace: StackTrace,
- tty_config: tty.Config,
+ terminal_mode: Io.Terminal.Mode = .no_color,
- pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
- try writer.writeAll("\n");
- try writeStackTrace(&context.stack_trace, writer, context.tty_config);
+ pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
+ try writer.writeByte('\n');
+ try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
}
};
/// Write a previously captured stack trace to `writer`, annotated with source locations.
-pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
+pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
+ const writer = t.writer;
if (!std.options.allow_stack_tracing) {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
}
- // We use an independent Io implementation here in case there was a problem
- // with the application's Io implementation itself.
- var threaded: Io.Threaded = .init_single_threaded;
- const io = threaded.ioBasic();
// Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
// `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
@@ -787,29 +787,30 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C
const di_gpa = getDebugInfoAllocator();
const di = getSelfDebugInfo() catch |err| switch (err) {
error.UnsupportedTarget => {
- tty_config.setColor(writer, .dim) catch {};
+ t.setColor(.dim) catch {};
try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
return;
},
};
+ const io = std.Options.debug_io;
const captured_frames = @min(n_frames, st.instruction_addresses.len);
for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
// `ret_addr` is the return address, which is *after* the function call.
// Subtract 1 to get an address *in* the function call for a better source location.
- try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
+ try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
}
if (n_frames > captured_frames) {
- tty_config.setColor(writer, .bold) catch {};
+ t.setColor(.bold) catch {};
try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.reset) catch {};
}
}
/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
pub fn dumpStackTrace(st: *const StackTrace) void {
- const stderr, const tty_config = lockStderrWriter(&.{});
- defer unlockStderrWriter();
- writeStackTrace(st, stderr, tty_config) catch |err| switch (err) {
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
+ writeStackTrace(st, stderr) catch |err| switch (err) {
error.WriteFailed => {},
};
}
@@ -960,7 +961,7 @@ const StackIterator = union(enum) {
},
};
- fn next(it: *StackIterator) Result {
+ fn next(it: *StackIterator, io: Io) Result {
switch (it.*) {
.ctx_first => |context_ptr| {
// After the first frame, start actually unwinding.
@@ -976,7 +977,7 @@ const StackIterator = union(enum) {
.di => |*unwind_context| {
const di = getSelfDebugInfo() catch unreachable;
const di_gpa = getDebugInfoAllocator();
- const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| {
+ const ret_addr = di.unwindFrame(di_gpa, io, unwind_context) catch |err| {
const pc = unwind_context.pc;
const fp = unwind_context.getFp();
it.* = .{ .fp = fp };
@@ -1104,170 +1105,146 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
return ptr;
}
-fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
+fn printSourceAtAddress(
+ gpa: Allocator,
+ io: Io,
+ debug_info: *SelfInfo,
+ t: Io.Terminal,
+ address: usize,
+) Writer.Error!void {
const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
error.MissingDebugInfo,
error.UnsupportedDebugInfo,
error.InvalidDebugInfo,
=> .unknown,
error.ReadFailed, error.Unexpected, error.Canceled => s: {
- tty_config.setColor(writer, .dim) catch {};
- try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.dim) catch {};
+ try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
+ t.setColor(.reset) catch {};
break :s .unknown;
},
error.OutOfMemory => s: {
- tty_config.setColor(writer, .dim) catch {};
- try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
- tty_config.setColor(writer, .reset) catch {};
+ t.setColor(.dim) catch {};
+ try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
+ t.setColor(.reset) catch {};
break :s .unknown;
},
};
defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
return printLineInfo(
- writer,
+ io,
+ t,
symbol.source_location,
address,
symbol.name orelse "???",
symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
- tty_config,
);
}
fn printLineInfo(
- writer: *Writer,
+ io: Io,
+ t: Io.Terminal,
source_location: ?SourceLocation,
address: usize,
symbol_name: []const u8,
compile_unit_name: []const u8,
- tty_config: tty.Config,
) Writer.Error!void {
- nosuspend {
- tty_config.setColor(writer, .bold) catch {};
+ const writer = t.writer;
+ t.setColor(.bold) catch {};
- if (source_location) |*sl| {
- try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
- } else {
- try writer.writeAll("???:?:?");
- }
+ if (source_location) |*sl| {
+ try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
+ } else {
+ try writer.writeAll("???:?:?");
+ }
- tty_config.setColor(writer, .reset) catch {};
- try writer.writeAll(": ");
- tty_config.setColor(writer, .dim) catch {};
- try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
- tty_config.setColor(writer, .reset) catch {};
- try writer.writeAll("\n");
-
- // Show the matching source code line if possible
- if (source_location) |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 writer.splatByteAll(' ', space_needed);
- tty_config.setColor(writer, .green) catch {};
- try writer.writeAll("^");
- tty_config.setColor(writer, .reset) catch {};
- }
- try writer.writeAll("\n");
- } else |_| {
- // Ignore all errors; it's a better UX to just print the source location without the
- // corresponding line number. The user can always open the source file themselves.
+ t.setColor(.reset) catch {};
+ try writer.writeAll(": ");
+ t.setColor(.dim) catch {};
+ try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
+ t.setColor(.reset) catch {};
+ try writer.writeAll("\n");
+
+ // Show the matching source code line if possible
+ if (source_location) |sl| {
+ if (printLineFromFile(io, writer, sl)) {
+ if (sl.column > 0) {
+ // The caret already takes one char
+ const space_needed = @as(usize, @intCast(sl.column - 1));
+
+ try writer.splatByteAll(' ', space_needed);
+ t.setColor(.green) catch {};
+ try writer.writeAll("^");
+ t.setColor(.reset) catch {};
}
+ try writer.writeAll("\n");
+ } else |_| {
+ // Ignore all errors; it's a better UX to just print the source location without the
+ // corresponding line number. The user can always open the source file themselves.
}
}
}
-fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
+fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
// Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
- return root.debug.printLineFromFile(writer, source_location);
+ return root.debug.printLineFromFile(io, writer, source_location);
}
// 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, .{});
- defer f.close();
- // TODO fstat and make sure that the file has the correct size
-
- 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 (mem.findScalar(u8, 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 (mem.findScalar(u8, slice, '\n')) |pos| {
- const line = slice[0 .. pos + 1];
- mem.replaceScalar(u8, line, '\t', ' ');
- 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 writer.writeAll(slice);
- while (amt_read == buf.len) {
- amt_read = try f.read(buf[0..]);
- if (mem.findScalar(u8, buf[0..amt_read], '\n')) |pos| {
- const line = buf[0 .. pos + 1];
- mem.replaceScalar(u8, line, '\t', ' ');
- return writer.writeAll(line);
- } else {
- const line = buf[0..amt_read];
- mem.replaceScalar(u8, line, '\t', ' ');
- try writer.writeAll(line);
- }
+ const cwd: Io.Dir = .cwd();
+ var file = try cwd.openFile(io, source_location.file_name, .{});
+ defer file.close(io);
+
+ var buffer: [4096]u8 = undefined;
+ var file_reader: File.Reader = .init(file, io, &buffer);
+ var line_index: usize = 0;
+ const r = &file_reader.interface;
+ while (true) {
+ line_index += 1;
+ if (line_index == source_location.line) {
+ // TODO delete hard tabs from the language
+ _ = try r.streamDelimiterEnding(writer, '\n');
+ try writer.writeByte('\n');
+ return;
}
- // Make sure printing last line of file inserts extra newline
- try writer.writeByte('\n');
+ _ = try r.discardDelimiterInclusive('\n');
}
}
test printLineFromFile {
- var aw: Writer.Allocating = .init(std.testing.allocator);
+ const io = testing.io;
+ const gpa = testing.allocator;
+
+ var aw: Writer.Allocating = .init(gpa);
defer aw.deinit();
const output_stream = &aw.writer;
- const allocator = std.testing.allocator;
const join = std.fs.path.join;
- const expectError = std.testing.expectError;
- const expectEqualStrings = std.testing.expectEqualStrings;
+ const expectError = testing.expectError;
+ const expectEqualStrings = testing.expectEqualStrings;
- var test_dir = std.testing.tmpDir(.{});
+ var test_dir = testing.tmpDir(.{});
defer test_dir.cleanup();
// Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
- const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
- defer allocator.free(test_dir_path);
+ const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
+ defer gpa.free(test_dir_path);
// Cases
{
- const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });
- defer allocator.free(path);
- try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
+ const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
+ defer gpa.free(path);
+ try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
- try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
+ try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
aw.clearRetainingCapacity();
}
{
- const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
- defer allocator.free(path);
- try test_dir.dir.writeFile(.{
+ const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
+ defer gpa.free(path);
+ try test_dir.dir.writeFile(io, .{
.sub_path = "three_lines.zig",
.data =
\\1
@@ -1276,90 +1253,90 @@ test printLineFromFile {
,
});
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("1\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 });
try expectEqualStrings("3\n", aw.written());
aw.clearRetainingCapacity();
}
{
- const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
- defer file.close();
- const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
- defer allocator.free(path);
+ const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{});
+ defer file.close(io);
+ const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
+ defer gpa.free(path);
const overlap = 10;
var buf: [16]u8 = undefined;
- var file_writer = file.writer(&buf);
+ var file_writer = file.writer(io, &buf);
const writer = &file_writer.interface;
try writer.splatByteAll('a', std.heap.page_size_min - overlap);
try writer.writeByte('\n');
try writer.splatByteAll('a', overlap);
try writer.flush();
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
{
- const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
- defer file.close();
- const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
- defer allocator.free(path);
+ const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{});
+ defer file.close(io);
+ const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
+ defer gpa.free(path);
- var file_writer = file.writer(&.{});
+ var file_writer = file.writer(io, &.{});
const writer = &file_writer.interface;
try writer.splatByteAll('a', std.heap.page_size_max);
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
{
- const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
- defer file.close();
- const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
- defer allocator.free(path);
+ const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{});
+ defer file.close(io);
+ const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
+ defer gpa.free(path);
- var file_writer = file.writer(&.{});
+ var file_writer = file.writer(io, &.{});
const writer = &file_writer.interface;
try writer.splatByteAll('a', 3 * std.heap.page_size_max);
- try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
+ try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
aw.clearRetainingCapacity();
try writer.writeAll("a\na");
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings("a\n", aw.written());
aw.clearRetainingCapacity();
}
{
- const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
- defer file.close();
- const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
- defer allocator.free(path);
+ const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{});
+ defer file.close(io);
+ const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
+ defer gpa.free(path);
- var file_writer = file.writer(&.{});
+ var file_writer = file.writer(io, &.{});
const writer = &file_writer.interface;
const real_file_start = 3 * std.heap.page_size_min;
try writer.splatByteAll('\n', real_file_start);
try writer.writeAll("abc\ndef");
- try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
try expectEqualStrings("abc\n", aw.written());
aw.clearRetainingCapacity();
- try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
+ try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
try expectEqualStrings("def\n", aw.written());
aw.clearRetainingCapacity();
}
@@ -1563,19 +1540,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
_ = panicking.fetchAdd(1, .seq_cst);
trace: {
- const stderr, const tty_config = lockStderrWriter(&.{});
- defer unlockStderrWriter();
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
if (addr) |a| {
- stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
+ stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
} else {
- stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
+ stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
}
if (opt_ctx) |context| {
writeCurrentStackTrace(.{
.context = context,
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
- }, stderr, tty_config) catch break :trace;
+ }, stderr) catch break :trace;
}
}
},
@@ -1584,7 +1561,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
// A segfault happened while trying to print a previous panic message.
// We're still holding the mutex but that's fine as we're going to
// call abort().
- fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
+ const stderr = lockStderr(&.{}).terminal();
+ stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
},
else => {}, // Panicked while printing the recursive panic message.
}
@@ -1592,7 +1570,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
// We cannot allow the signal handler to return because when it runs the original instruction
// again, the memory may be mapped and undefined behavior would occur rather than repeating
// the segfault. So we simply abort here.
- posix.abort();
+ std.process.abort();
}
pub fn dumpStackPointerAddr(prefix: []const u8) void {
@@ -1616,20 +1594,14 @@ test "manage resources correctly" {
return @returnAddress();
}
};
- const gpa = std.testing.allocator;
- var threaded: Io.Threaded = .init_single_threaded;
- const io = threaded.ioBasic();
- var discarding: Io.Writer.Discarding = .init(&.{});
+ const gpa = testing.allocator;
+ const io = testing.io;
+
+ var discarding: Writer.Discarding = .init(&.{});
var di: SelfInfo = .init;
defer di.deinit(gpa);
- try printSourceAtAddress(
- gpa,
- io,
- &di,
- &discarding.writer,
- S.showMyTrace(),
- .no_color,
- );
+ const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };
+ try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace());
}
/// This API helps you track where a value originated and where it was mutated,
@@ -1690,21 +1662,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
pub fn dump(t: @This()) void {
if (!enabled) return;
- const stderr, const tty_config = lockStderrWriter(&.{});
- defer unlockStderrWriter();
+ const stderr = lockStderr(&.{}).terminal();
+ defer unlockStderr();
const end = @min(t.index, size);
for (t.addrs[0..end], 0..) |frames_array, i| {
- stderr.print("{s}:\n", .{t.notes[i]}) catch return;
+ stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
var frames_array_mutable = frames_array;
const frames = mem.sliceTo(frames_array_mutable[0..], 0);
const stack_trace: StackTrace = .{
.index = frames.len,
.instruction_addresses = frames,
};
- writeStackTrace(&stack_trace, stderr, tty_config) catch return;
+ writeStackTrace(&stack_trace, stderr) catch return;
}
if (t.index > end) {
- stderr.print("{d} more traces not shown; consider increasing trace size\n", .{
+ stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{
t.index - end,
}) catch return;
}