authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-09 22:10:12-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:09-08:00
logffcbd48a1220ce6d652ee762001d88baa385de49
treee1ea279b4cd0b8991df04d8a799589f72dbffd86
parent78d262d96ee6200c7a6bc0a41fe536d263c24d92

std: rework TTY detection and printing

This commit sketches an idea for how to deal with detection of file streams as being terminals. When a File stream is a terminal, writes through the stream should have their escapes stripped unless the programmer explicitly enables terminal escapes. Furthermore, the programmer needs a convenient API for intentionally outputting escapes into the stream. In particular it should be possible to set colors that are silently discarded when the stream is not a terminal. This commit makes `Io.File.Writer` track the terminal mode in the already-existing `mode` field, making it the appropriate place to implement escape stripping. `Io.lockStderrWriter` returns a `*Io.File.Writer` with terminal detection already done by default. This is a higher-level application layer stream for writing to stderr. Meanwhile, `std.debug.lockStderrWriter` also returns a `*Io.File.Writer` but a lower-level one that is hard-coded to use a static single-threaded `std.Io.Threaded` instance. This is the same instance that is used for collecting debug information and iterating the unwind info.

10 files changed, 448 insertions(+), 400 deletions(-)

lib/std/Io.zig+11-11
...@@ -82,8 +82,6 @@ pub const Limit = enum(usize) {...@@ -82,8 +82,6 @@ pub const Limit = enum(usize) {
82pub const Reader = @import("Io/Reader.zig");82pub const Reader = @import("Io/Reader.zig");
83pub const Writer = @import("Io/Writer.zig");83pub const Writer = @import("Io/Writer.zig");
8484
85pub const tty = @import("Io/tty.zig");
86
87pub fn poll(85pub fn poll(
88 gpa: Allocator,86 gpa: Allocator,
89 comptime StreamEnum: type,87 comptime StreamEnum: type,
...@@ -535,7 +533,6 @@ test {...@@ -535,7 +533,6 @@ test {
535 _ = net;533 _ = net;
536 _ = Reader;534 _ = Reader;
537 _ = Writer;535 _ = Writer;
538 _ = tty;
539 _ = Evented;536 _ = Evented;
540 _ = Threaded;537 _ = Threaded;
541 _ = @import("Io/test.zig");538 _ = @import("Io/test.zig");
...@@ -720,6 +717,9 @@ pub const VTable = struct {...@@ -720,6 +717,9 @@ pub const VTable = struct {
720717
721 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,718 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
722 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,719 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
720 lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer,
721 tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer,
722 unlockStderrWriter: *const fn (?*anyopaque) void,
723723
724 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,724 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
725 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,725 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
...@@ -740,10 +740,6 @@ pub const VTable = struct {...@@ -740,10 +740,6 @@ pub const VTable = struct {
740 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,740 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
741 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,741 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
742 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,742 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
743
744 lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer,
745 tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer,
746 unlockStderrWriter: *const fn (?*anyopaque) void,
747};743};
748744
749pub const Cancelable = error{745pub const Cancelable = error{
...@@ -2186,13 +2182,17 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {...@@ -2186,13 +2182,17 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2186///2182///
2187/// See also:2183/// See also:
2188/// * `tryLockStderrWriter`2184/// * `tryLockStderrWriter`
2189pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer {2185pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
2190 return io.vtable.lockStderrWriter(io.userdata, buffer);2186 const result = try io.vtable.lockStderrWriter(io.userdata, buffer);
2187 result.io = io;
2188 return result;
2191}2189}
21922190
2193/// Same as `lockStderrWriter` but uncancelable and non-blocking.2191/// Same as `lockStderrWriter` but uncancelable and non-blocking.
2194pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer {2192pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
2195 return io.vtable.tryLockStderrWriter(io.userdata, buffer);2193 const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null;
2194 result.io = io;
2195 return result;
2196}2196}
21972197
2198pub fn unlockStderrWriter(io: Io) void {2198pub fn unlockStderrWriter(io: Io) void {
lib/std/Io/File/Writer.zig+234-9
...@@ -1,4 +1,6 @@...@@ -1,4 +1,6 @@
1const Writer = @This();1const Writer = @This();
2const builtin = @import("builtin");
3const is_windows = builtin.os.tag == .windows;
24
3const std = @import("../../std.zig");5const std = @import("../../std.zig");
4const Io = std.Io;6const Io = std.Io;
...@@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null,...@@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null,
16seek_err: ?SeekError = null,18seek_err: ?SeekError = null,
17interface: Io.Writer,19interface: Io.Writer,
1820
19pub const Mode = File.Reader.Mode;21pub const Mode = union(enum) {
22 /// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal.
23 /// `setColor` does nothing.
24 streaming,
25 /// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal.
26 /// `setColor` does nothing.
27 positional,
28 /// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor`
29 /// does nothing.
30 streaming_simple,
31 /// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor`
32 /// does nothing.
33 positional_simple,
34 /// It's a terminal. Writes are escaped so as to strip escape sequences.
35 /// Color is enabled.
36 terminal_escaped,
37 /// It's a terminal. Colors are enabled via calling
38 /// SetConsoleTextAttribute. Writes are not escaped.
39 terminal_winapi: TerminalWinapi,
40 /// Indicates writing cannot continue because of a seek failure.
41 failure,
42
43 pub fn toStreaming(m: @This()) @This() {
44 return switch (m) {
45 .positional, .streaming => .streaming,
46 .positional_simple, .streaming_simple => .streaming_simple,
47 inline else => |_, x| x,
48 };
49 }
50
51 pub fn toSimple(m: @This()) @This() {
52 return switch (m) {
53 .positional, .positional_simple => .positional_simple,
54 .streaming, .streaming_simple => .streaming_simple,
55 inline else => |x| x,
56 };
57 }
58
59 pub fn toUnescaped(m: @This()) @This() {
60 return switch (m) {
61 .terminal_escaped => .streaming_simple,
62 inline else => |x| x,
63 };
64 }
65
66 pub const TerminalWinapi = if (!is_windows) noreturn else struct {
67 handle: File.Handle,
68 reset_attributes: u16,
69 };
70
71 /// Detect suitable TTY configuration options for the given file (commonly
72 /// stdout/stderr).
73 ///
74 /// Will attempt to enable ANSI escape code support if necessary/possible.
75 pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode {
76 if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback;
77
78 if (file.enableAnsiEscapeCodes(io)) |_| {
79 return .terminal_escaped;
80 } else |err| switch (err) {
81 error.Canceled => return error.Canceled,
82 error.NotTerminalDevice, error.Unexpected => {},
83 }
84
85 if (is_windows and file.isTty(io)) {
86 const windows = std.os.windows;
87 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
88 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) {
89 return .{ .terminal_winapi = .{
90 .handle = file.handle,
91 .reset_attributes = info.wAttributes,
92 } };
93 }
94 return .terminal_escaped;
95 }
96
97 return fallback;
98 }
99
100 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
101
102 pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) Mode.SetColorError!void {
103 switch (mode) {
104 .streaming, .positional, .streaming_simple, .positional_simple, .failure => return,
105 .terminal_escaped => {
106 const color_string = switch (color) {
107 .black => "\x1b[30m",
108 .red => "\x1b[31m",
109 .green => "\x1b[32m",
110 .yellow => "\x1b[33m",
111 .blue => "\x1b[34m",
112 .magenta => "\x1b[35m",
113 .cyan => "\x1b[36m",
114 .white => "\x1b[37m",
115 .bright_black => "\x1b[90m",
116 .bright_red => "\x1b[91m",
117 .bright_green => "\x1b[92m",
118 .bright_yellow => "\x1b[93m",
119 .bright_blue => "\x1b[94m",
120 .bright_magenta => "\x1b[95m",
121 .bright_cyan => "\x1b[96m",
122 .bright_white => "\x1b[97m",
123 .bold => "\x1b[1m",
124 .dim => "\x1b[2m",
125 .reset => "\x1b[0m",
126 };
127 try io_w.writeAll(color_string);
128 },
129 .terminal_winapi => |ctx| {
130 const windows = std.os.windows;
131 const attributes: windows.WORD = switch (color) {
132 .black => 0,
133 .red => windows.FOREGROUND_RED,
134 .green => windows.FOREGROUND_GREEN,
135 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
136 .blue => windows.FOREGROUND_BLUE,
137 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
138 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
139 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
140 .bright_black => windows.FOREGROUND_INTENSITY,
141 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
142 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
143 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
144 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
145 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
146 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
147 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
148 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
149 // This matches the old behavior of TTY.Color before the bright variants were added.
150 .dim => windows.FOREGROUND_INTENSITY,
151 .reset => ctx.reset_attributes,
152 };
153 try io_w.flush();
154 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
155 },
156 }
157 }
158};
20159
21pub const Error = error{160pub const Error = error{
22 DiskQuota,161 DiskQuota,
...@@ -74,6 +213,16 @@ pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer {...@@ -74,6 +213,16 @@ pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer {
74 };213 };
75}214}
76215
216/// Detects if `file` is terminal and sets the mode accordingly.
217pub fn initDetect(file: File, io: Io, buffer: []u8) Io.Cancelable!Writer {
218 return .{
219 .io = io,
220 .file = file,
221 .interface = initInterface(buffer),
222 .mode = try .detect(io, file, true, .positional),
223 };
224}
225
77pub fn initInterface(buffer: []u8) Io.Writer {226pub fn initInterface(buffer: []u8) Io.Writer {
78 return .{227 return .{
79 .vtable = &.{228 .vtable = &.{
...@@ -99,8 +248,9 @@ pub fn moveToReader(w: *Writer) File.Reader {...@@ -99,8 +248,9 @@ pub fn moveToReader(w: *Writer) File.Reader {
99pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {248pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
100 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));249 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
101 switch (w.mode) {250 switch (w.mode) {
102 .positional, .positional_reading => return drainPositional(w, data, splat),251 .positional, .positional_simple => return drainPositional(w, data, splat),
103 .streaming, .streaming_reading => return drainStreaming(w, data, splat),252 .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat),
253 .terminal_escaped => return drainEscaping(w, data, splat),
104 .failure => return error.WriteFailed,254 .failure => return error.WriteFailed,
105 }255 }
106}256}
...@@ -141,13 +291,38 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer....@@ -141,13 +291,38 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.
141 return w.interface.consume(n);291 return w.interface.consume(n);
142}292}
143293
294fn findTerminalEscape(buffer: []const u8) ?usize {
295 return std.mem.findScalar(u8, buffer, 0x1b);
296}
297
298fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
299 const io = w.io;
300 const header = w.interface.buffered();
301 if (findTerminalEscape(header)) |i| {
302 _ = i;
303 @panic("TODO strip terminal escape sequence");
304 }
305 for (data) |d| {
306 if (findTerminalEscape(d)) |i| {
307 _ = i;
308 @panic("TODO strip terminal escape sequence");
309 }
310 }
311 const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
312 w.err = err;
313 return error.WriteFailed;
314 };
315 w.pos += n;
316 return w.interface.consume(n);
317}
318
144pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {319pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
145 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));320 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
146 switch (w.mode) {321 switch (w.mode) {
147 .positional => return sendFilePositional(w, file_reader, limit),322 .positional => return sendFilePositional(w, file_reader, limit),
148 .positional_reading => return error.Unimplemented,323 .positional_simple => return error.Unimplemented,
149 .streaming => return sendFileStreaming(w, file_reader, limit),324 .streaming => return sendFileStreaming(w, file_reader, limit),
150 .streaming_reading => return error.Unimplemented,325 .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented,
151 .failure => return error.WriteFailed,326 .failure => return error.WriteFailed,
152 }327 }
153}328}
...@@ -214,10 +389,10 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {...@@ -214,10 +389,10 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {
214 assert(w.interface.buffered().len == 0);389 assert(w.interface.buffered().len == 0);
215 const io = w.io;390 const io = w.io;
216 switch (w.mode) {391 switch (w.mode) {
217 .positional, .positional_reading => {392 .positional, .positional_simple => {
218 w.pos = offset;393 w.pos = offset;
219 },394 },
220 .streaming, .streaming_reading => {395 .streaming, .streaming_simple, .terminal_escaped, .terminal_winapi => {
221 if (w.seek_err) |err| return err;396 if (w.seek_err) |err| return err;
222 io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {397 io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {
223 w.seek_err = err;398 w.seek_err = err;
...@@ -243,15 +418,65 @@ pub fn end(w: *Writer) EndError!void {...@@ -243,15 +418,65 @@ pub fn end(w: *Writer) EndError!void {
243 try w.interface.flush();418 try w.interface.flush();
244 switch (w.mode) {419 switch (w.mode) {
245 .positional,420 .positional,
246 .positional_reading,421 .positional_simple,
247 => w.file.setLength(io, w.pos) catch |err| switch (err) {422 => w.file.setLength(io, w.pos) catch |err| switch (err) {
248 error.NonResizable => return,423 error.NonResizable => return,
249 else => |e| return e,424 else => |e| return e,
250 },425 },
251426
252 .streaming,427 .streaming,
253 .streaming_reading,428 .streaming_simple,
254 .failure,429 .failure,
255 => {},430 => {},
256 }431 }
257}432}
433
434pub const Color = enum {
435 black,
436 red,
437 green,
438 yellow,
439 blue,
440 magenta,
441 cyan,
442 white,
443 bright_black,
444 bright_red,
445 bright_green,
446 bright_yellow,
447 bright_blue,
448 bright_magenta,
449 bright_cyan,
450 bright_white,
451 dim,
452 bold,
453 reset,
454};
455
456pub const SetColorError = Mode.SetColorError;
457
458pub fn setColor(w: *Writer, color: Color) SetColorError!void {
459 return w.mode.setColor(&w.interface, color);
460}
461
462pub fn disableEscape(w: *Writer) Mode {
463 const prev = w.mode;
464 w.mode = w.mode.toUnescaped();
465 return prev;
466}
467
468pub fn restoreEscape(w: *Writer, mode: Mode) void {
469 w.mode = mode;
470}
471
472pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Error!void {
473 const prev_mode = w.disableEscape();
474 defer w.restoreEscape(prev_mode);
475 return w.interface.writeAll(bytes);
476}
477
478pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Error!void {
479 const prev_mode = w.disableEscape();
480 defer w.restoreEscape(prev_mode);
481 return w.interface.print(fmt, args);
482}
lib/std/Io/Threaded.zig+34-13
...@@ -77,7 +77,13 @@ use_sendfile: UseSendfile = .default,...@@ -77,7 +77,13 @@ use_sendfile: UseSendfile = .default,
77use_copy_file_range: UseCopyFileRange = .default,77use_copy_file_range: UseCopyFileRange = .default,
78use_fcopyfile: UseFcopyfile = .default,78use_fcopyfile: UseFcopyfile = .default,
7979
80stderr_writer: Io.Writer,80stderr_writer: File.Writer = .{
81 .io = undefined,
82 .interface = Io.File.Writer.initInterface(&.{}),
83 .file = if (is_windows) undefined else .stderr(),
84 .mode = undefined,
85},
86stderr_writer_initialized: bool = false,
8187
82pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {88pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
83 enabled,89 enabled,
...@@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io {...@@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io {
737743
738 .processExecutableOpen = processExecutableOpen,744 .processExecutableOpen = processExecutableOpen,
739 .processExecutablePath = processExecutablePath,745 .processExecutablePath = processExecutablePath,
746 .lockStderrWriter = lockStderrWriter,
747 .tryLockStderrWriter = tryLockStderrWriter,
748 .unlockStderrWriter = unlockStderrWriter,
740749
741 .now = now,750 .now = now,
742 .sleep = sleep,751 .sleep = sleep,
...@@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io {
864873
865 .processExecutableOpen = processExecutableOpen,874 .processExecutableOpen = processExecutableOpen,
866 .processExecutablePath = processExecutablePath,875 .processExecutablePath = processExecutablePath,
876 .lockStderrWriter = lockStderrWriter,
877 .tryLockStderrWriter = tryLockStderrWriter,
878 .unlockStderrWriter = unlockStderrWriter,
867879
868 .now = now,880 .now = now,
869 .sleep = sleep,881 .sleep = sleep,
...@@ -9516,33 +9528,42 @@ fn netLookupFallible(...@@ -9516,33 +9528,42 @@ fn netLookupFallible(
9516 return error.OptionUnsupported;9528 return error.OptionUnsupported;
9517}9529}
95189530
9519fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer {9531fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer {
9520 const t: *Threaded = @ptrCast(@alignCast(userdata));9532 const t: *Threaded = @ptrCast(@alignCast(userdata));
9521 // Only global mutex since this is Threaded.9533 // Only global mutex since this is Threaded.
9522 Io.stderr_thread_mutex.lock();9534 Io.stderr_thread_mutex.lock();
9523 if (is_windows) t.stderr_writer.file = .stderr();9535 if (!t.stderr_writer_initialized) {
9536 if (is_windows) t.stderr_writer.file = .stderr();
9537 t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple);
9538 t.stderr_writer_initialized = true;
9539 }
9524 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};9540 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
9525 t.stderr_writer.flush() catch {};9541 t.stderr_writer.interface.flush() catch {};
9526 t.stderr_writer.buffer = buffer;9542 t.stderr_writer.interface.buffer = buffer;
9527 return &t.stderr_writer;9543 return &t.stderr_writer;
9528}9544}
95299545
9530fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer {9546fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
9531 const t: *Threaded = @ptrCast(@alignCast(userdata));9547 const t: *Threaded = @ptrCast(@alignCast(userdata));
9532 // Only global mutex since this is Threaded.9548 // Only global mutex since this is Threaded.
9533 if (!Io.stderr_thread_mutex.tryLock()) return null;9549 if (!Io.stderr_thread_mutex.tryLock()) return null;
9534 std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {};9550 if (!t.stderr_writer_initialized) {
9535 if (is_windows) t.stderr_writer.file = .stderr();9551 if (is_windows) t.stderr_writer.file = .stderr();
9536 t.stderr_writer.flush() catch {};9552 t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch
9537 t.stderr_writer.buffer = buffer;9553 return null;
9554 t.stderr_writer_initialized = true;
9555 }
9556 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
9557 t.stderr_writer.interface.flush() catch {};
9558 t.stderr_writer.interface.buffer = buffer;
9538 return &t.stderr_writer;9559 return &t.stderr_writer;
9539}9560}
95409561
9541fn unlockStderrWriter(userdata: ?*anyopaque) void {9562fn unlockStderrWriter(userdata: ?*anyopaque) void {
9542 const t: *Threaded = @ptrCast(@alignCast(userdata));9563 const t: *Threaded = @ptrCast(@alignCast(userdata));
9543 t.stderr_writer.flush() catch {};9564 t.stderr_writer.interface.flush() catch {};
9544 t.stderr_writer.end = 0;9565 t.stderr_writer.interface.end = 0;
9545 t.stderr_writer.buffer = &.{};9566 t.stderr_writer.interface.buffer = &.{};
9546 Io.stderr_thread_mutex.unlock();9567 Io.stderr_thread_mutex.unlock();
9547}9568}
95489569
lib/std/Io/tty.zig deleted-135
...@@ -1,135 +0,0 @@
1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std");
5const Io = std.Io;
6const File = std.Io.File;
7const process = std.process;
8const windows = std.os.windows;
9
10pub const Color = enum {
11 black,
12 red,
13 green,
14 yellow,
15 blue,
16 magenta,
17 cyan,
18 white,
19 bright_black,
20 bright_red,
21 bright_green,
22 bright_yellow,
23 bright_blue,
24 bright_magenta,
25 bright_cyan,
26 bright_white,
27 dim,
28 bold,
29 reset,
30};
31
32/// Provides simple functionality for manipulating the terminal in some way,
33/// such as coloring text, etc.
34pub const Config = union(enum) {
35 no_color,
36 escape_codes,
37 windows_api: if (native_os == .windows) WindowsContext else noreturn,
38
39 /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
40 /// This includes feature checks for ANSI escape codes and the Windows console API, as well as
41 /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
42 /// Will attempt to enable ANSI escape code support if necessary/possible.
43 pub fn detect(io: Io, file: File) Config {
44 const force_color: ?bool = if (builtin.os.tag == .wasi)
45 null // wasi does not support environment variables
46 else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
47 false
48 else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
49 true
50 else
51 null;
52
53 if (force_color == false) return .no_color;
54
55 if (file.enableAnsiEscapeCodes(io)) |_| {
56 return .escape_codes;
57 } else |_| {}
58
59 if (native_os == .windows and file.isTty()) {
60 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
61 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
62 return if (force_color == true) .escape_codes else .no_color;
63 }
64 return .{ .windows_api = .{
65 .handle = file.handle,
66 .reset_attributes = info.wAttributes,
67 } };
68 }
69
70 return if (force_color == true) .escape_codes else .no_color;
71 }
72
73 pub const WindowsContext = struct {
74 handle: File.Handle,
75 reset_attributes: u16,
76 };
77
78 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
79
80 pub fn setColor(conf: Config, w: *Io.Writer, color: Color) SetColorError!void {
81 nosuspend switch (conf) {
82 .no_color => return,
83 .escape_codes => {
84 const color_string = switch (color) {
85 .black => "\x1b[30m",
86 .red => "\x1b[31m",
87 .green => "\x1b[32m",
88 .yellow => "\x1b[33m",
89 .blue => "\x1b[34m",
90 .magenta => "\x1b[35m",
91 .cyan => "\x1b[36m",
92 .white => "\x1b[37m",
93 .bright_black => "\x1b[90m",
94 .bright_red => "\x1b[91m",
95 .bright_green => "\x1b[92m",
96 .bright_yellow => "\x1b[93m",
97 .bright_blue => "\x1b[94m",
98 .bright_magenta => "\x1b[95m",
99 .bright_cyan => "\x1b[96m",
100 .bright_white => "\x1b[97m",
101 .bold => "\x1b[1m",
102 .dim => "\x1b[2m",
103 .reset => "\x1b[0m",
104 };
105 try w.writeAll(color_string);
106 },
107 .windows_api => |ctx| {
108 const attributes = switch (color) {
109 .black => 0,
110 .red => windows.FOREGROUND_RED,
111 .green => windows.FOREGROUND_GREEN,
112 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
113 .blue => windows.FOREGROUND_BLUE,
114 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
115 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
116 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
117 .bright_black => windows.FOREGROUND_INTENSITY,
118 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
119 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
120 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
121 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
122 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
123 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
124 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
125 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
126 // This matches the old behavior of TTY.Color before the bright variants were added.
127 .dim => windows.FOREGROUND_INTENSITY,
128 .reset => ctx.reset_attributes,
129 };
130 try w.flush();
131 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
132 },
133 };
134 }
135};
lib/std/Progress.zig+2-3
...@@ -755,10 +755,9 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {...@@ -755,10 +755,9 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
755 }755 }
756}756}
757757
758fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void {758pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) anyerror!void {
759 if (noop_impl or !global_progress.need_clear) return;759 if (noop_impl or !global_progress.need_clear) return;
760760 try file_writer.interface.writeAllUnescaped(clear ++ progress_remove);
761 try w.writeAll(clear ++ progress_remove);
762 global_progress.need_clear = false;761 global_progress.need_clear = false;
763}762}
764763
lib/std/debug.zig+101-125
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const std = @import("std.zig");1const std = @import("std.zig");
2const Io = std.Io;2const Io = std.Io;
3const Writer = std.Io.Writer;3const Writer = std.Io.Writer;
4const tty = std.Io.tty;
5const math = std.math;4const math = std.math;
6const mem = std.mem;5const mem = std.mem;
7const posix = std.posix;6const posix = std.posix;
...@@ -262,6 +261,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -262,6 +261,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
262 else => true,261 else => true,
263};262};
264263
264/// This is used for debug information and debug printing. It is intentionally
265/// separate from the application's `Io` instance.
266var static_single_threaded_io: Io.Threaded = .init_single_threaded;
267
265/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.268/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
266///269///
267/// During the lock, any `std.Progress` information is cleared from the terminal.270/// During the lock, any `std.Progress` information is cleared from the terminal.
...@@ -279,18 +282,12 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -279,18 +282,12 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
279///282///
280/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with283/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
281/// the application's chosen `Io` implementation.284/// the application's chosen `Io` implementation.
282pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {285pub fn lockStderrWriter(buffer: []u8) *File.Writer {
283 Io.stderr_thread_mutex.lock();286 return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable;
284 const w = std.Progress.lockStderrWriter(buffer);
285 // The stderr lock also locks access to `global.conf`.
286 if (StderrWriter.singleton.tty_config == null) {
287 StderrWriter.singleton.tty_config = .detect(io, .stderr());
288 }
289 return .{ w, global.conf.? };
290}287}
291288
292pub fn unlockStderrWriter() void {289pub fn unlockStderrWriter() void {
293 std.Progress.unlockStderrWriter();290 static_single_threaded_io.ioBasic().unlockStderrWriter();
294}291}
295292
296/// Writes to stderr, ignoring errors.293/// Writes to stderr, ignoring errors.
...@@ -305,39 +302,13 @@ pub fn unlockStderrWriter() void {...@@ -305,39 +302,13 @@ pub fn unlockStderrWriter() void {
305/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to302/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
306/// integrate with the application's chosen `Io` implementation.303/// integrate with the application's chosen `Io` implementation.
307pub fn print(comptime fmt: []const u8, args: anytype) void {304pub fn print(comptime fmt: []const u8, args: anytype) void {
308 var buffer: [64]u8 = undefined;305 nosuspend {
309 const bw, _ = lockStderrWriter(&buffer);306 var buffer: [64]u8 = undefined;
310 defer unlockStderrWriter();307 const stderr = lockStderrWriter(&buffer);
311 nosuspend bw.print(fmt, args) catch return;308 defer unlockStderrWriter();
312}309 stderr.interface.print(fmt, args) catch return;
313
314const StderrWriter = struct {
315 interface: Writer,
316 tty_config: ?tty.Config,
317
318 var singleton: StderrWriter = .{
319 .interface = .{
320 .buffer = &.{},
321 .vtable = &.{ .drain = drain },
322 },
323 .tty_config = null,
324 };
325
326 fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
327 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
328 var n: usize = 0;
329 const header = w.interface.buffered();
330 if (header.len != 0) n += try std.Io.Threaded.debugWrite(header);
331 for (data[0 .. data.len - 1]) |d| {
332 if (d.len != 0) n += try std.Io.Threaded.debugWrite(d);
333 }
334 const pattern = data[data.len - 1];
335 if (pattern.len != 0) {
336 for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern);
337 }
338 return io_w.consume(n);
339 }310 }
340};311}
341312
342/// Marked `inline` to propagate a comptime-known error to callers.313/// Marked `inline` to propagate a comptime-known error to callers.
343pub inline fn getSelfDebugInfo() !*SelfInfo {314pub inline fn getSelfDebugInfo() !*SelfInfo {
...@@ -357,16 +328,16 @@ pub fn dumpHex(bytes: []const u8) void {...@@ -357,16 +328,16 @@ pub fn dumpHex(bytes: []const u8) void {
357}328}
358329
359/// Prints a hexadecimal view of the bytes, returning any error that occurs.330/// Prints a hexadecimal view of the bytes, returning any error that occurs.
360pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void {331pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void {
361 var chunks = mem.window(u8, bytes, 16, 16);332 var chunks = mem.window(u8, bytes, 16, 16);
362 while (chunks.next()) |window| {333 while (chunks.next()) |window| {
363 // 1. Print the address.334 // 1. Print the address.
364 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;335 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
365 try tty_config.setColor(bw, .dim);336 try fwm.setColor(bw, .dim);
366 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.337 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
367 // Also, make sure all lines are aligned by padding the address.338 // Also, make sure all lines are aligned by padding the address.
368 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });339 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
369 try tty_config.setColor(bw, .reset);340 try fwm.setColor(bw, .reset);
370341
371 // 2. Print the bytes.342 // 2. Print the bytes.
372 for (window, 0..) |byte, index| {343 for (window, 0..) |byte, index| {
...@@ -386,7 +357,7 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !...@@ -386,7 +357,7 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
386 try bw.writeByte(byte);357 try bw.writeByte(byte);
387 } else {358 } else {
388 // Related: https://github.com/ziglang/zig/issues/7600359 // Related: https://github.com/ziglang/zig/issues/7600
389 if (tty_config == .windows_api) {360 if (fwm == .terminal_winapi) {
390 try bw.writeByte('.');361 try bw.writeByte('.');
391 continue;362 continue;
392 }363 }
...@@ -408,11 +379,11 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !...@@ -408,11 +379,11 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
408379
409test dumpHexFallible {380test dumpHexFallible {
410 const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };381 const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
411 var aw: Writer.Allocating = .init(std.testing.allocator);382 var aw: Writer.Allocating = .init(testing.allocator);
412 defer aw.deinit();383 defer aw.deinit();
413384
414 try dumpHexFallible(&aw.writer, .no_color, bytes);385 try dumpHexFallible(&aw.writer, .no_color, bytes);
415 const expected = try std.fmt.allocPrint(std.testing.allocator,386 const expected = try std.fmt.allocPrint(testing.allocator,
416 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........387 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
417 \\{x:0>[2]} 01 12 13 ...388 \\{x:0>[2]} 01 12 13 ...
418 \\389 \\
...@@ -421,8 +392,8 @@ test dumpHexFallible {...@@ -421,8 +392,8 @@ test dumpHexFallible {
421 @intFromPtr(bytes.ptr) + 16,392 @intFromPtr(bytes.ptr) + 16,
422 @sizeOf(usize) * 2,393 @sizeOf(usize) * 2,
423 });394 });
424 defer std.testing.allocator.free(expected);395 defer testing.allocator.free(expected);
425 try std.testing.expectEqualStrings(expected, aw.written());396 try testing.expectEqualStrings(expected, aw.written());
426}397}
427398
428/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.399/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
...@@ -437,7 +408,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con...@@ -437,7 +408,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
437/// away, and in fact the optimizer is able to use the assertion in its408/// away, and in fact the optimizer is able to use the assertion in its
438/// heuristics.409/// heuristics.
439///410///
440/// Inside a test block, it is best to use the `std.testing` module rather than411/// Inside a test block, it is best to use the `testing` module rather than
441/// this function, because this function may not detect a test failure in412/// this function, because this function may not detect a test failure in
442/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert413/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
443/// function is the correct function to use.414/// function is the correct function to use.
...@@ -574,26 +545,26 @@ pub fn defaultPanic(...@@ -574,26 +545,26 @@ pub fn defaultPanic(
574 _ = panicking.fetchAdd(1, .seq_cst);545 _ = panicking.fetchAdd(1, .seq_cst);
575546
576 trace: {547 trace: {
577 const stderr, const tty_config = lockStderrWriter(&.{});548 const stderr = lockStderrWriter(&.{});
578 defer unlockStderrWriter();549 defer unlockStderrWriter();
579550
580 if (builtin.single_threaded) {551 if (builtin.single_threaded) {
581 stderr.print("panic: ", .{}) catch break :trace;552 stderr.interface.print("panic: ", .{}) catch break :trace;
582 } else {553 } else {
583 const current_thread_id = std.Thread.getCurrentId();554 const current_thread_id = std.Thread.getCurrentId();
584 stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;555 stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
585 }556 }
586 stderr.print("{s}\n", .{msg}) catch break :trace;557 stderr.interface.print("{s}\n", .{msg}) catch break :trace;
587558
588 if (@errorReturnTrace()) |t| if (t.index > 0) {559 if (@errorReturnTrace()) |t| if (t.index > 0) {
589 stderr.writeAll("error return context:\n") catch break :trace;560 stderr.interface.writeAll("error return context:\n") catch break :trace;
590 writeStackTrace(t, stderr, tty_config) catch break :trace;561 writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace;
591 stderr.writeAll("\nstack trace:\n") catch break :trace;562 stderr.interface.writeAll("\nstack trace:\n") catch break :trace;
592 };563 };
593 writeCurrentStackTrace(.{564 writeCurrentStackTrace(.{
594 .first_address = first_trace_addr orelse @returnAddress(),565 .first_address = first_trace_addr orelse @returnAddress(),
595 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!566 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
596 }, stderr, tty_config) catch break :trace;567 }, &stderr.interface, stderr.mode) catch break :trace;
597 }568 }
598569
599 waitForOtherThreadToFinishPanicking();570 waitForOtherThreadToFinishPanicking();
...@@ -603,8 +574,8 @@ pub fn defaultPanic(...@@ -603,8 +574,8 @@ pub fn defaultPanic(
603 // A panic happened while trying to print a previous panic message.574 // A panic happened while trying to print a previous panic message.
604 // We're still holding the mutex but that's fine as we're going to575 // We're still holding the mutex but that's fine as we're going to
605 // call abort().576 // call abort().
606 const stderr, _ = lockStderrWriter(&.{});577 const stderr = lockStderrWriter(&.{});
607 stderr.writeAll("aborting due to recursive panic\n") catch {};578 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
608 },579 },
609 else => {}, // Panicked while printing the recursive panic message.580 else => {}, // Panicked while printing the recursive panic message.
610 }581 }
...@@ -651,8 +622,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -651,8 +622,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
651 defer it.deinit();622 defer it.deinit();
652 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;623 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
653624
654 var threaded: Io.Threaded = .init_single_threaded;625 const io = static_single_threaded_io.ioBasic();
655 const io = threaded.ioBasic();
656626
657 var total_frames: usize = 0;627 var total_frames: usize = 0;
658 var index: usize = 0;628 var index: usize = 0;
...@@ -686,36 +656,34 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -686,36 +656,34 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
686/// Write the current stack trace to `writer`, annotated with source locations.656/// Write the current stack trace to `writer`, annotated with source locations.
687///657///
688/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.658/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
689pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {659pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
690 var threaded: Io.Threaded = .init_single_threaded;
691 const io = threaded.ioBasic();
692
693 if (!std.options.allow_stack_tracing) {660 if (!std.options.allow_stack_tracing) {
694 tty_config.setColor(writer, .dim) catch {};661 fwm.setColor(writer, .dim) catch {};
695 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});662 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
696 tty_config.setColor(writer, .reset) catch {};663 fwm.setColor(writer, .reset) catch {};
697 return;664 return;
698 }665 }
699 const di_gpa = getDebugInfoAllocator();666 const di_gpa = getDebugInfoAllocator();
700 const di = getSelfDebugInfo() catch |err| switch (err) {667 const di = getSelfDebugInfo() catch |err| switch (err) {
701 error.UnsupportedTarget => {668 error.UnsupportedTarget => {
702 tty_config.setColor(writer, .dim) catch {};669 fwm.setColor(writer, .dim) catch {};
703 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});670 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
704 tty_config.setColor(writer, .reset) catch {};671 fwm.setColor(writer, .reset) catch {};
705 return;672 return;
706 },673 },
707 };674 };
708 var it: StackIterator = .init(options.context);675 var it: StackIterator = .init(options.context);
709 defer it.deinit();676 defer it.deinit();
710 if (!it.stratOk(options.allow_unsafe_unwind)) {677 if (!it.stratOk(options.allow_unsafe_unwind)) {
711 tty_config.setColor(writer, .dim) catch {};678 fwm.setColor(writer, .dim) catch {};
712 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});679 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
713 tty_config.setColor(writer, .reset) catch {};680 fwm.setColor(writer, .reset) catch {};
714 return;681 return;
715 }682 }
716 var total_frames: usize = 0;683 var total_frames: usize = 0;
717 var wait_for = options.first_address;684 var wait_for = options.first_address;
718 var printed_any_frame = false;685 var printed_any_frame = false;
686 const io = static_single_threaded_io.ioBasic();
719 while (true) switch (it.next(io)) {687 while (true) switch (it.next(io)) {
720 .switch_to_fp => |unwind_error| {688 .switch_to_fp => |unwind_error| {
721 switch (StackIterator.fp_usability) {689 switch (StackIterator.fp_usability) {
...@@ -733,31 +701,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -733,31 +701,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
733 error.Unexpected => "unexpected error",701 error.Unexpected => "unexpected error",
734 };702 };
735 if (it.stratOk(options.allow_unsafe_unwind)) {703 if (it.stratOk(options.allow_unsafe_unwind)) {
736 tty_config.setColor(writer, .dim) catch {};704 fwm.setColor(writer, .dim) catch {};
737 try writer.print(705 try writer.print(
738 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",706 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
739 .{ module_name, unwind_error.address, caption },707 .{ module_name, unwind_error.address, caption },
740 );708 );
741 tty_config.setColor(writer, .reset) catch {};709 fwm.setColor(writer, .reset) catch {};
742 } else {710 } else {
743 tty_config.setColor(writer, .dim) catch {};711 fwm.setColor(writer, .dim) catch {};
744 try writer.print(712 try writer.print(
745 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",713 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
746 .{ module_name, unwind_error.address, caption },714 .{ module_name, unwind_error.address, caption },
747 );715 );
748 tty_config.setColor(writer, .reset) catch {};716 fwm.setColor(writer, .reset) catch {};
749 return;717 return;
750 }718 }
751 },719 },
752 .end => break,720 .end => break,
753 .frame => |ret_addr| {721 .frame => |ret_addr| {
754 if (total_frames > 10_000) {722 if (total_frames > 10_000) {
755 tty_config.setColor(writer, .dim) catch {};723 fwm.setColor(writer, .dim) catch {};
756 try writer.print(724 try writer.print(
757 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",725 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
758 .{total_frames},726 .{total_frames},
759 );727 );
760 tty_config.setColor(writer, .reset) catch {};728 fwm.setColor(writer, .reset) catch {};
761 return;729 return;
762 }730 }
763 total_frames += 1;731 total_frames += 1;
...@@ -767,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -767,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
767 }735 }
768 // `ret_addr` is the return address, which is *after* the function call.736 // `ret_addr` is the return address, which is *after* the function call.
769 // Subtract 1 to get an address *in* the function call for a better source location.737 // Subtract 1 to get an address *in* the function call for a better source location.
770 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);738 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
771 printed_any_frame = true;739 printed_any_frame = true;
772 },740 },
773 };741 };
...@@ -775,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -775,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
775}743}
776/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.744/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
777pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {745pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
778 const stderr, const tty_config = lockStderrWriter(&.{});746 const stderr = lockStderrWriter(&.{});
779 defer unlockStderrWriter();747 defer unlockStderrWriter();
780 writeCurrentStackTrace(.{748 writeCurrentStackTrace(.{
781 .first_address = a: {749 .first_address = a: {
...@@ -785,33 +753,40 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {...@@ -785,33 +753,40 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
785 },753 },
786 .context = options.context,754 .context = options.context,
787 .allow_unsafe_unwind = options.allow_unsafe_unwind,755 .allow_unsafe_unwind = options.allow_unsafe_unwind,
788 }, stderr, tty_config) catch |err| switch (err) {756 }, &stderr.interface, stderr.mode) catch |err| switch (err) {
789 error.WriteFailed => {},757 error.WriteFailed => {},
790 };758 };
791}759}
792760
793pub const FormatStackTrace = struct {761pub const FormatStackTrace = struct {
794 stack_trace: StackTrace,762 stack_trace: StackTrace,
795 tty_config: tty.Config,
796763
797 pub fn format(context: @This(), writer: *Writer) Writer.Error!void {764 pub const Decorated = struct {
798 try writer.writeAll("\n");765 stack_trace: StackTrace,
799 try writeStackTrace(&context.stack_trace, writer, context.tty_config);766 file_writer_mode: File.Writer.Mode,
767
768 pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void {
769 try writer.writeByte('\n');
770 try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode);
771 }
772 };
773
774 pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void {
775 return Decorated.format(.{
776 .stack_trace = context.stack_trace,
777 .file_writer_mode = .streaming,
778 }, writer);
800 }779 }
801};780};
802781
803/// Write a previously captured stack trace to `writer`, annotated with source locations.782/// Write a previously captured stack trace to `writer`, annotated with source locations.
804pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {783pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
805 if (!std.options.allow_stack_tracing) {784 if (!std.options.allow_stack_tracing) {
806 tty_config.setColor(writer, .dim) catch {};785 fwm.setColor(writer, .dim) catch {};
807 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});786 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
808 tty_config.setColor(writer, .reset) catch {};787 fwm.setColor(writer, .reset) catch {};
809 return;788 return;
810 }789 }
811 // We use an independent Io implementation here in case there was a problem
812 // with the application's Io implementation itself.
813 var threaded: Io.Threaded = .init_single_threaded;
814 const io = threaded.ioBasic();
815790
816 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if791 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
817 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.792 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
...@@ -820,22 +795,23 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C...@@ -820,22 +795,23 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C
820 const di_gpa = getDebugInfoAllocator();795 const di_gpa = getDebugInfoAllocator();
821 const di = getSelfDebugInfo() catch |err| switch (err) {796 const di = getSelfDebugInfo() catch |err| switch (err) {
822 error.UnsupportedTarget => {797 error.UnsupportedTarget => {
823 tty_config.setColor(writer, .dim) catch {};798 fwm.setColor(writer, .dim) catch {};
824 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});799 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
825 tty_config.setColor(writer, .reset) catch {};800 fwm.setColor(writer, .reset) catch {};
826 return;801 return;
827 },802 },
828 };803 };
804 const io = static_single_threaded_io.ioBasic();
829 const captured_frames = @min(n_frames, st.instruction_addresses.len);805 const captured_frames = @min(n_frames, st.instruction_addresses.len);
830 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {806 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
831 // `ret_addr` is the return address, which is *after* the function call.807 // `ret_addr` is the return address, which is *after* the function call.
832 // Subtract 1 to get an address *in* the function call for a better source location.808 // Subtract 1 to get an address *in* the function call for a better source location.
833 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);809 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
834 }810 }
835 if (n_frames > captured_frames) {811 if (n_frames > captured_frames) {
836 tty_config.setColor(writer, .bold) catch {};812 fwm.setColor(writer, .bold) catch {};
837 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});813 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
838 tty_config.setColor(writer, .reset) catch {};814 fwm.setColor(writer, .reset) catch {};
839 }815 }
840}816}
841/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.817/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
...@@ -1143,7 +1119,7 @@ fn printSourceAtAddress(...@@ -1143,7 +1119,7 @@ fn printSourceAtAddress(
1143 debug_info: *SelfInfo,1119 debug_info: *SelfInfo,
1144 writer: *Writer,1120 writer: *Writer,
1145 address: usize,1121 address: usize,
1146 tty_config: tty.Config,1122 fwm: File.Writer.Mode,
1147) Writer.Error!void {1123) Writer.Error!void {
1148 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {1124 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
1149 error.MissingDebugInfo,1125 error.MissingDebugInfo,
...@@ -1151,15 +1127,15 @@ fn printSourceAtAddress(...@@ -1151,15 +1127,15 @@ fn printSourceAtAddress(
1151 error.InvalidDebugInfo,1127 error.InvalidDebugInfo,
1152 => .unknown,1128 => .unknown,
1153 error.ReadFailed, error.Unexpected, error.Canceled => s: {1129 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1154 tty_config.setColor(writer, .dim) catch {};1130 fwm.setColor(writer, .dim) catch {};
1155 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});1131 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1156 tty_config.setColor(writer, .reset) catch {};1132 fwm.setColor(writer, .reset) catch {};
1157 break :s .unknown;1133 break :s .unknown;
1158 },1134 },
1159 error.OutOfMemory => s: {1135 error.OutOfMemory => s: {
1160 tty_config.setColor(writer, .dim) catch {};1136 fwm.setColor(writer, .dim) catch {};
1161 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});1137 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1162 tty_config.setColor(writer, .reset) catch {};1138 fwm.setColor(writer, .reset) catch {};
1163 break :s .unknown;1139 break :s .unknown;
1164 },1140 },
1165 };1141 };
...@@ -1171,7 +1147,7 @@ fn printSourceAtAddress(...@@ -1171,7 +1147,7 @@ fn printSourceAtAddress(
1171 address,1147 address,
1172 symbol.name orelse "???",1148 symbol.name orelse "???",
1173 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",1149 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1174 tty_config,1150 fwm,
1175 );1151 );
1176}1152}
1177fn printLineInfo(1153fn printLineInfo(
...@@ -1181,10 +1157,10 @@ fn printLineInfo(...@@ -1181,10 +1157,10 @@ fn printLineInfo(
1181 address: usize,1157 address: usize,
1182 symbol_name: []const u8,1158 symbol_name: []const u8,
1183 compile_unit_name: []const u8,1159 compile_unit_name: []const u8,
1184 tty_config: tty.Config,1160 fwm: File.Writer.Mode,
1185) Writer.Error!void {1161) Writer.Error!void {
1186 nosuspend {1162 nosuspend {
1187 tty_config.setColor(writer, .bold) catch {};1163 fwm.setColor(writer, .bold) catch {};
11881164
1189 if (source_location) |*sl| {1165 if (source_location) |*sl| {
1190 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });1166 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
...@@ -1192,11 +1168,11 @@ fn printLineInfo(...@@ -1192,11 +1168,11 @@ fn printLineInfo(
1192 try writer.writeAll("???:?:?");1168 try writer.writeAll("???:?:?");
1193 }1169 }
11941170
1195 tty_config.setColor(writer, .reset) catch {};1171 fwm.setColor(writer, .reset) catch {};
1196 try writer.writeAll(": ");1172 try writer.writeAll(": ");
1197 tty_config.setColor(writer, .dim) catch {};1173 fwm.setColor(writer, .dim) catch {};
1198 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });1174 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1199 tty_config.setColor(writer, .reset) catch {};1175 fwm.setColor(writer, .reset) catch {};
1200 try writer.writeAll("\n");1176 try writer.writeAll("\n");
12011177
1202 // Show the matching source code line if possible1178 // Show the matching source code line if possible
...@@ -1207,9 +1183,9 @@ fn printLineInfo(...@@ -1207,9 +1183,9 @@ fn printLineInfo(
1207 const space_needed = @as(usize, @intCast(sl.column - 1));1183 const space_needed = @as(usize, @intCast(sl.column - 1));
12081184
1209 try writer.splatByteAll(' ', space_needed);1185 try writer.splatByteAll(' ', space_needed);
1210 tty_config.setColor(writer, .green) catch {};1186 fwm.setColor(writer, .green) catch {};
1211 try writer.writeAll("^");1187 try writer.writeAll("^");
1212 tty_config.setColor(writer, .reset) catch {};1188 fwm.setColor(writer, .reset) catch {};
1213 }1189 }
1214 try writer.writeAll("\n");1190 try writer.writeAll("\n");
1215 } else |_| {1191 } else |_| {
...@@ -1250,18 +1226,18 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !...@@ -1250,18 +1226,18 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !
1250}1226}
12511227
1252test printLineFromFile {1228test printLineFromFile {
1253 const io = std.testing.io;1229 const io = testing.io;
1254 const gpa = std.testing.allocator;1230 const gpa = testing.allocator;
12551231
1256 var aw: Writer.Allocating = .init(gpa);1232 var aw: Writer.Allocating = .init(gpa);
1257 defer aw.deinit();1233 defer aw.deinit();
1258 const output_stream = &aw.writer;1234 const output_stream = &aw.writer;
12591235
1260 const join = std.fs.path.join;1236 const join = std.fs.path.join;
1261 const expectError = std.testing.expectError;1237 const expectError = testing.expectError;
1262 const expectEqualStrings = std.testing.expectEqualStrings;1238 const expectEqualStrings = testing.expectEqualStrings;
12631239
1264 var test_dir = std.testing.tmpDir(.{});1240 var test_dir = testing.tmpDir(.{});
1265 defer test_dir.cleanup();1241 defer test_dir.cleanup();
1266 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.1242 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
1267 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });1243 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
...@@ -1578,19 +1554,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1578,19 +1554,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1578 _ = panicking.fetchAdd(1, .seq_cst);1554 _ = panicking.fetchAdd(1, .seq_cst);
15791555
1580 trace: {1556 trace: {
1581 const stderr, const tty_config = lockStderrWriter(&.{});1557 const stderr = lockStderrWriter(&.{});
1582 defer unlockStderrWriter();1558 defer unlockStderrWriter();
15831559
1584 if (addr) |a| {1560 if (addr) |a| {
1585 stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;1561 stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1586 } else {1562 } else {
1587 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;1563 stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace;
1588 }1564 }
1589 if (opt_ctx) |context| {1565 if (opt_ctx) |context| {
1590 writeCurrentStackTrace(.{1566 writeCurrentStackTrace(.{
1591 .context = context,1567 .context = context,
1592 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!1568 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1593 }, stderr, tty_config) catch break :trace;1569 }, &stderr.interface, stderr.mode) catch break :trace;
1594 }1570 }
1595 }1571 }
1596 },1572 },
...@@ -1599,8 +1575,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1599,8 +1575,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1599 // A segfault happened while trying to print a previous panic message.1575 // A segfault happened while trying to print a previous panic message.
1600 // We're still holding the mutex but that's fine as we're going to1576 // We're still holding the mutex but that's fine as we're going to
1601 // call abort().1577 // call abort().
1602 const stderr, _ = lockStderrWriter(&.{});1578 const stderr = lockStderrWriter(&.{});
1603 stderr.writeAll("aborting due to recursive panic\n") catch {};1579 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
1604 },1580 },
1605 else => {}, // Panicked while printing the recursive panic message.1581 else => {}, // Panicked while printing the recursive panic message.
1606 }1582 }
...@@ -1632,9 +1608,9 @@ test "manage resources correctly" {...@@ -1632,9 +1608,9 @@ test "manage resources correctly" {
1632 return @returnAddress();1608 return @returnAddress();
1633 }1609 }
1634 };1610 };
1635 const gpa = std.testing.allocator;1611 const gpa = testing.allocator;
1636 var threaded: Io.Threaded = .init_single_threaded;1612 const io = testing.io;
1637 const io = threaded.ioBasic();1613
1638 var discarding: Writer.Discarding = .init(&.{});1614 var discarding: Writer.Discarding = .init(&.{});
1639 var di: SelfInfo = .init;1615 var di: SelfInfo = .init;
1640 defer di.deinit(gpa);1616 defer di.deinit(gpa);
lib/std/heap/debug_allocator.zig+17-79
...@@ -179,8 +179,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -179,8 +179,6 @@ pub fn DebugAllocator(comptime config: Config) type {
179 total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init,179 total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init,
180 requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init,180 requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init,
181 mutex: @TypeOf(mutex_init) = mutex_init,181 mutex: @TypeOf(mutex_init) = mutex_init,
182 /// Set this value differently to affect how errors and leaks are logged.
183 tty_config: std.Io.tty.Config = .no_color,
184182
185 const Self = @This();183 const Self = @This();
186184
...@@ -427,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -427,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {
427 bucket: *BucketHeader,425 bucket: *BucketHeader,
428 size_class_index: usize,426 size_class_index: usize,
429 used_bits_count: usize,427 used_bits_count: usize,
430 tty_config: std.Io.tty.Config,
431 ) usize {428 ) usize {
432 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));429 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
433 const slot_count = slot_counts[size_class_index];430 const slot_count = slot_counts[size_class_index];
...@@ -444,11 +441,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -444,11 +441,7 @@ pub fn DebugAllocator(comptime config: Config) type {
444 const page_addr = @intFromPtr(bucket) & ~(page_size - 1);441 const page_addr = @intFromPtr(bucket) & ~(page_size - 1);
445 const addr = page_addr + slot_index * size_class;442 const addr = page_addr + slot_index * size_class;
446 log.err("memory address 0x{x} leaked: {f}", .{443 log.err("memory address 0x{x} leaked: {f}", .{
447 addr,444 addr, std.debug.FormatStackTrace{ .stack_trace = stack_trace },
448 std.debug.FormatStackTrace{
449 .stack_trace = stack_trace,
450 .tty_config = tty_config,
451 },
452 });445 });
453 leaks += 1;446 leaks += 1;
454 }447 }
...@@ -460,8 +453,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -460,8 +453,6 @@ pub fn DebugAllocator(comptime config: Config) type {
460453
461 /// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected).454 /// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected).
462 pub fn detectLeaks(self: *Self) usize {455 pub fn detectLeaks(self: *Self) usize {
463 const tty_config = self.tty_config;
464
465 var leaks: usize = 0;456 var leaks: usize = 0;
466457
467 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {458 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
...@@ -469,7 +460,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -469,7 +460,7 @@ pub fn DebugAllocator(comptime config: Config) type {
469 const slot_count = slot_counts[size_class_index];460 const slot_count = slot_counts[size_class_index];
470 const used_bits_count = usedBitsCount(slot_count);461 const used_bits_count = usedBitsCount(slot_count);
471 while (optional_bucket) |bucket| {462 while (optional_bucket) |bucket| {
472 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config);463 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count);
473 optional_bucket = bucket.prev;464 optional_bucket = bucket.prev;
474 }465 }
475 }466 }
...@@ -480,10 +471,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -480,10 +471,7 @@ pub fn DebugAllocator(comptime config: Config) type {
480 const stack_trace = large_alloc.getStackTrace(.alloc);471 const stack_trace = large_alloc.getStackTrace(.alloc);
481 log.err("memory address 0x{x} leaked: {f}", .{472 log.err("memory address 0x{x} leaked: {f}", .{
482 @intFromPtr(large_alloc.bytes.ptr),473 @intFromPtr(large_alloc.bytes.ptr),
483 std.debug.FormatStackTrace{474 std.debug.FormatStackTrace{ .stack_trace = stack_trace },
484 .stack_trace = stack_trace,
485 .tty_config = tty_config,
486 },
487 });475 });
488 leaks += 1;476 leaks += 1;
489 }477 }
...@@ -535,28 +523,14 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -535,28 +523,14 @@ pub fn DebugAllocator(comptime config: Config) type {
535 @memset(addr_buf[@min(st.index, addr_buf.len)..], 0);523 @memset(addr_buf[@min(st.index, addr_buf.len)..], 0);
536 }524 }
537525
538 fn reportDoubleFree(526 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
539 tty_config: std.Io.tty.Config,
540 ret_addr: usize,
541 alloc_stack_trace: StackTrace,
542 free_stack_trace: StackTrace,
543 ) void {
544 @branchHint(.cold);527 @branchHint(.cold);
545 var addr_buf: [stack_n]usize = undefined;528 var addr_buf: [stack_n]usize = undefined;
546 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);529 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
547 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{530 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
548 std.debug.FormatStackTrace{531 std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace },
549 .stack_trace = alloc_stack_trace,532 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
550 .tty_config = tty_config,533 std.debug.FormatStackTrace{ .stack_trace = second_free_stack_trace },
551 },
552 std.debug.FormatStackTrace{
553 .stack_trace = free_stack_trace,
554 .tty_config = tty_config,
555 },
556 std.debug.FormatStackTrace{
557 .stack_trace = second_free_stack_trace,
558 .tty_config = tty_config,
559 },
560 });534 });
561 }535 }
562536
...@@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type {
587561
588 if (config.retain_metadata and entry.value_ptr.freed) {562 if (config.retain_metadata and entry.value_ptr.freed) {
589 if (config.safety) {563 if (config.safety) {
590 reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));564 reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
591 @panic("Unrecoverable double free");565 @panic("Unrecoverable double free");
592 } else {566 } else {
593 unreachable;567 unreachable;
...@@ -598,18 +572,11 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -598,18 +572,11 @@ pub fn DebugAllocator(comptime config: Config) type {
598 @branchHint(.cold);572 @branchHint(.cold);
599 var addr_buf: [stack_n]usize = undefined;573 var addr_buf: [stack_n]usize = undefined;
600 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);574 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
601 const tty_config = self.tty_config;
602 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{575 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
603 entry.value_ptr.bytes.len,576 entry.value_ptr.bytes.len,
604 old_mem.len,577 old_mem.len,
605 std.debug.FormatStackTrace{578 std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) },
606 .stack_trace = entry.value_ptr.getStackTrace(.alloc),579 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
607 .tty_config = tty_config,
608 },
609 std.debug.FormatStackTrace{
610 .stack_trace = free_stack_trace,
611 .tty_config = tty_config,
612 },
613 });580 });
614 }581 }
615582
...@@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type {
701668
702 if (config.retain_metadata and entry.value_ptr.freed) {669 if (config.retain_metadata and entry.value_ptr.freed) {
703 if (config.safety) {670 if (config.safety) {
704 reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));671 reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
705 return;672 return;
706 } else {673 } else {
707 unreachable;674 unreachable;
...@@ -712,18 +679,11 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -712,18 +679,11 @@ pub fn DebugAllocator(comptime config: Config) type {
712 @branchHint(.cold);679 @branchHint(.cold);
713 var addr_buf: [stack_n]usize = undefined;680 var addr_buf: [stack_n]usize = undefined;
714 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);681 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
715 const tty_config = self.tty_config;
716 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{682 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
717 entry.value_ptr.bytes.len,683 entry.value_ptr.bytes.len,
718 old_mem.len,684 old_mem.len,
719 std.debug.FormatStackTrace{685 std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) },
720 .stack_trace = entry.value_ptr.getStackTrace(.alloc),686 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
721 .tty_config = tty_config,
722 },
723 std.debug.FormatStackTrace{
724 .stack_trace = free_stack_trace,
725 .tty_config = tty_config,
726 },
727 });687 });
728 }688 }
729689
...@@ -924,7 +884,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -924,7 +884,6 @@ pub fn DebugAllocator(comptime config: Config) type {
924 if (!is_used) {884 if (!is_used) {
925 if (config.safety) {885 if (config.safety) {
926 reportDoubleFree(886 reportDoubleFree(
927 self.tty_config,
928 return_address,887 return_address,
929 bucketStackTrace(bucket, slot_count, slot_index, .alloc),888 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
930 bucketStackTrace(bucket, slot_count, slot_index, .free),889 bucketStackTrace(bucket, slot_count, slot_index, .free),
...@@ -946,34 +905,24 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -946,34 +905,24 @@ pub fn DebugAllocator(comptime config: Config) type {
946 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);905 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
947 if (old_memory.len != requested_size) {906 if (old_memory.len != requested_size) {
948 @branchHint(.cold);907 @branchHint(.cold);
949 const tty_config = self.tty_config;
950 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{908 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
951 requested_size,909 requested_size,
952 old_memory.len,910 old_memory.len,
953 std.debug.FormatStackTrace{911 std.debug.FormatStackTrace{
954 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),912 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
955 .tty_config = tty_config,
956 },
957 std.debug.FormatStackTrace{
958 .stack_trace = free_stack_trace,
959 .tty_config = tty_config,
960 },913 },
914 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
961 });915 });
962 }916 }
963 if (alignment != slot_alignment) {917 if (alignment != slot_alignment) {
964 @branchHint(.cold);918 @branchHint(.cold);
965 const tty_config = self.tty_config;
966 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{919 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
967 slot_alignment.toByteUnits(),920 slot_alignment.toByteUnits(),
968 alignment.toByteUnits(),921 alignment.toByteUnits(),
969 std.debug.FormatStackTrace{922 std.debug.FormatStackTrace{
970 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),923 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
971 .tty_config = tty_config,
972 },
973 std.debug.FormatStackTrace{
974 .stack_trace = free_stack_trace,
975 .tty_config = tty_config,
976 },924 },
925 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
977 });926 });
978 }927 }
979 }928 }
...@@ -1040,7 +989,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -1040,7 +989,6 @@ pub fn DebugAllocator(comptime config: Config) type {
1040 const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0;989 const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0;
1041 if (!is_used) {990 if (!is_used) {
1042 reportDoubleFree(991 reportDoubleFree(
1043 self.tty_config,
1044 return_address,992 return_address,
1045 bucketStackTrace(bucket, slot_count, slot_index, .alloc),993 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1046 bucketStackTrace(bucket, slot_count, slot_index, .free),994 bucketStackTrace(bucket, slot_count, slot_index, .free),
...@@ -1058,34 +1006,24 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -1058,34 +1006,24 @@ pub fn DebugAllocator(comptime config: Config) type {
1058 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);1006 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
1059 if (memory.len != requested_size) {1007 if (memory.len != requested_size) {
1060 @branchHint(.cold);1008 @branchHint(.cold);
1061 const tty_config = self.tty_config;
1062 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{1009 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
1063 requested_size,1010 requested_size,
1064 memory.len,1011 memory.len,
1065 std.debug.FormatStackTrace{1012 std.debug.FormatStackTrace{
1066 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),1013 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1067 .tty_config = tty_config,
1068 },
1069 std.debug.FormatStackTrace{
1070 .stack_trace = free_stack_trace,
1071 .tty_config = tty_config,
1072 },1014 },
1015 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
1073 });1016 });
1074 }1017 }
1075 if (alignment != slot_alignment) {1018 if (alignment != slot_alignment) {
1076 @branchHint(.cold);1019 @branchHint(.cold);
1077 const tty_config = self.tty_config;
1078 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{1020 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
1079 slot_alignment.toByteUnits(),1021 slot_alignment.toByteUnits(),
1080 alignment.toByteUnits(),1022 alignment.toByteUnits(),
1081 std.debug.FormatStackTrace{1023 std.debug.FormatStackTrace{
1082 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),1024 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1083 .tty_config = tty_config,
1084 },
1085 std.debug.FormatStackTrace{
1086 .stack_trace = free_stack_trace,
1087 .tty_config = tty_config,
1088 },1025 },
1026 std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
1089 });1027 });
1090 }1028 }
1091 }1029 }
lib/std/log.zig+45-19
...@@ -15,7 +15,7 @@...@@ -15,7 +15,7 @@
15//!15//!
16//! For an example implementation of the `logFn` function, see `defaultLog`,16//! For an example implementation of the `logFn` function, see `defaultLog`,
17//! which is the default implementation. It outputs to stderr, using color if17//! which is the default implementation. It outputs to stderr, using color if
18//! the detected `std.Io.tty.Config` supports it. Its output looks like this:18//! supported. Its output looks like this:
19//! ```19//! ```
20//! error: this is an error20//! error: this is an error
21//! error(scope): this is an error with a non-default scope21//! error(scope): this is an error with a non-default scope
...@@ -80,8 +80,6 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {...@@ -80,8 +80,6 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
81}81}
8282
83var static_threaded_io: std.Io.Threaded = .init_single_threaded;
84
85/// The default implementation for the log function. Custom log functions may83/// The default implementation for the log function. Custom log functions may
86/// forward log messages to this function.84/// forward log messages to this function.
87///85///
...@@ -93,36 +91,64 @@ pub fn defaultLog(...@@ -93,36 +91,64 @@ pub fn defaultLog(
93 comptime format: []const u8,91 comptime format: []const u8,
94 args: anytype,92 args: anytype,
95) void {93) void {
96 return defaultLogIo(level, scope, format, args, static_threaded_io.io());94 var buffer: [64]u8 = undefined;
95 const stderr = std.debug.lockStderrWriter(&buffer);
96 defer std.debug.unlockStderrWriter();
97 return defaultLogFileWriter(level, scope, format, args, stderr);
97}98}
9899
99pub fn defaultLogIo(100pub fn defaultLogFileWriter(
100 comptime level: Level,101 comptime level: Level,
101 comptime scope: @EnumLiteral(),102 comptime scope: @EnumLiteral(),
102 comptime format: []const u8,103 comptime format: []const u8,
103 args: anytype,104 args: anytype,
104 io: std.Io,105 fw: *std.Io.File.Writer,
105) void {106) void {
106 var buffer: [64]u8 = undefined;107 fw.setColor(switch (level) {
107 const stderr, const ttyconf = io.lockStderrWriter(&buffer);
108 defer io.unlockStderrWriter();
109 ttyconf.setColor(stderr, switch (level) {
110 .err => .red,108 .err => .red,
111 .warn => .yellow,109 .warn => .yellow,
112 .info => .green,110 .info => .green,
113 .debug => .magenta,111 .debug => .magenta,
114 }) catch {};112 }) catch {};
115 ttyconf.setColor(stderr, .bold) catch {};113 fw.setColor(.bold) catch {};
116 stderr.writeAll(level.asText()) catch return;114 fw.interface.writeAll(level.asText()) catch return;
117 ttyconf.setColor(stderr, .reset) catch {};115 fw.setColor(.reset) catch {};
118 ttyconf.setColor(stderr, .dim) catch {};116 fw.setColor(.dim) catch {};
119 ttyconf.setColor(stderr, .bold) catch {};117 fw.setColor(.bold) catch {};
120 if (scope != .default) {118 if (scope != .default) {
121 stderr.print("({s})", .{@tagName(scope)}) catch return;119 fw.interface.print("({s})", .{@tagName(scope)}) catch return;
120 }
121 fw.interface.writeAll(": ") catch return;
122 fw.setColor(.reset) catch {};
123 fw.interface.print(format ++ "\n", decorateArgs(args, fw.mode)) catch return;
124}
125
126fn DecorateArgs(comptime Args: type) type {
127 const fields = @typeInfo(Args).@"struct".fields;
128 var new_fields: [fields.len]type = undefined;
129 for (fields, &new_fields) |old, *new| {
130 if (old.type == std.debug.FormatStackTrace) {
131 new.* = std.debug.FormatStackTrace.Decorated;
132 } else {
133 new.* = old.type;
134 }
135 }
136 return @Tuple(&new_fields);
137}
138
139fn decorateArgs(args: anytype, file_writer_mode: std.Io.File.Writer.Mode) DecorateArgs(@TypeOf(args)) {
140 var new_args: DecorateArgs(@TypeOf(args)) = undefined;
141 inline for (args, &new_args) |old, *new| {
142 if (@TypeOf(old) == std.debug.FormatStackTrace) {
143 new.* = .{
144 .stack_trace = old.stack_trace,
145 .file_writer_mode = file_writer_mode,
146 };
147 } else {
148 new.* = old;
149 }
122 }150 }
123 stderr.writeAll(": ") catch return;151 return new_args;
124 ttyconf.setColor(stderr, .reset) catch {};
125 stderr.print(format ++ "\n", args) catch return;
126}152}
127153
128/// Returns a scoped logging namespace that logs all messages using the scope154/// Returns a scoped logging namespace that logs all messages using the scope
lib/std/process.zig+4-4
...@@ -439,25 +439,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -439,25 +439,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
439}439}
440440
441/// On Windows, `key` must be valid WTF-8.441/// On Windows, `key` must be valid WTF-8.
442pub fn hasEnvVarConstant(comptime key: []const u8) bool {442pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
443 if (native_os == .windows) {443 if (native_os == .windows) {
444 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);444 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
445 return getenvW(key_w) != null;445 return getenvW(key_w) != null;
446 } else if (native_os == .wasi and !builtin.link_libc) {446 } else if (native_os == .wasi and !builtin.link_libc) {
447 @compileError("hasEnvVarConstant is not supported for WASI without libc");447 return false;
448 } else {448 } else {
449 return posix.getenv(key) != null;449 return posix.getenv(key) != null;
450 }450 }
451}451}
452452
453/// On Windows, `key` must be valid WTF-8.453/// On Windows, `key` must be valid WTF-8.
454pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {454pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
455 if (native_os == .windows) {455 if (native_os == .windows) {
456 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);456 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
457 const value = getenvW(key_w) orelse return false;457 const value = getenvW(key_w) orelse return false;
458 return value.len != 0;458 return value.len != 0;
459 } else if (native_os == .wasi and !builtin.link_libc) {459 } else if (native_os == .wasi and !builtin.link_libc) {
460 @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");460 return false;
461 } else {461 } else {
462 const value = posix.getenv(key) orelse return false;462 const value = posix.getenv(key) orelse return false;
463 return value.len != 0;463 return value.len != 0;
src/main.zig-2
...@@ -247,8 +247,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -247,8 +247,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
247 threaded.stack_size = thread_stack_size;247 threaded.stack_size = thread_stack_size;
248 const io = threaded.io();248 const io = threaded.io();
249249
250 debug_allocator.tty_config = .detect(io, .stderr());
251
252 const cmd = args[1];250 const cmd = args[1];
253 const cmd_args = args[2..];251 const cmd_args = args[2..];
254 if (mem.eql(u8, cmd, "build-exe")) {252 if (mem.eql(u8, cmd, "build-exe")) {