| author | |
| committer | |
| log | ffcbd48a1220ce6d652ee762001d88baa385de49 |
| tree | e1ea279b4cd0b8991df04d8a799589f72dbffd86 |
| parent | 78d262d96ee6200c7a6bc0a41fe536d263c24d92 |
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) { |
| 82 | pub const Reader = @import("Io/Reader.zig"); | 82 | pub const Reader = @import("Io/Reader.zig"); |
| 83 | pub const Writer = @import("Io/Writer.zig"); | 83 | pub const Writer = @import("Io/Writer.zig"); |
| 84 | 84 | ||
| 85 | pub const tty = @import("Io/tty.zig"); | ||
| 86 | |||
| 87 | pub fn poll( | 85 | pub 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 { |
| 720 | 717 | ||
| 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, | ||
| 723 | 723 | ||
| 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 | }; |
| 748 | 744 | ||
| 749 | pub const Cancelable = error{ | 745 | pub 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` |
| 2189 | pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer { | 2185 | pub 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 | } |
| 2192 | 2190 | ||
| 2193 | /// Same as `lockStderrWriter` but uncancelable and non-blocking. | 2191 | /// Same as `lockStderrWriter` but uncancelable and non-blocking. |
| 2194 | pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer { | 2192 | pub 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 | } |
| 2197 | 2197 | ||
| 2198 | pub fn unlockStderrWriter(io: Io) void { | 2198 | pub fn unlockStderrWriter(io: Io) void { |
lib/std/Io/File/Writer.zig+234-9| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | const Writer = @This(); | 1 | const Writer = @This(); |
| 2 | const builtin = @import("builtin"); | ||
| 3 | const is_windows = builtin.os.tag == .windows; | ||
| 2 | 4 | ||
| 3 | const std = @import("../../std.zig"); | 5 | const std = @import("../../std.zig"); |
| 4 | const Io = std.Io; | 6 | const Io = std.Io; |
| ... | @@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null, | ... | @@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null, |
| 16 | seek_err: ?SeekError = null, | 18 | seek_err: ?SeekError = null, |
| 17 | interface: Io.Writer, | 19 | interface: Io.Writer, |
| 18 | 20 | ||
| 19 | pub const Mode = File.Reader.Mode; | 21 | pub 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 | }; | ||
| 20 | 159 | ||
| 21 | pub const Error = error{ | 160 | pub 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 | } |
| 76 | 215 | ||
| 216 | /// Detects if `file` is terminal and sets the mode accordingly. | ||
| 217 | pub 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 | |||
| 77 | pub fn initInterface(buffer: []u8) Io.Writer { | 226 | pub 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 { |
| 99 | pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { | 248 | pub 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 | } |
| 143 | 293 | ||
| 294 | fn findTerminalEscape(buffer: []const u8) ?usize { | ||
| 295 | return std.mem.findScalar(u8, buffer, 0x1b); | ||
| 296 | } | ||
| 297 | |||
| 298 | fn 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 | |||
| 144 | pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { | 319 | pub 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 | }, |
| 251 | 426 | ||
| 252 | .streaming, | 427 | .streaming, |
| 253 | .streaming_reading, | 428 | .streaming_simple, |
| 254 | .failure, | 429 | .failure, |
| 255 | => {}, | 430 | => {}, |
| 256 | } | 431 | } |
| 257 | } | 432 | } |
| 433 | |||
| 434 | pub 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 | |||
| 456 | pub const SetColorError = Mode.SetColorError; | ||
| 457 | |||
| 458 | pub fn setColor(w: *Writer, color: Color) SetColorError!void { | ||
| 459 | return w.mode.setColor(&w.interface, color); | ||
| 460 | } | ||
| 461 | |||
| 462 | pub fn disableEscape(w: *Writer) Mode { | ||
| 463 | const prev = w.mode; | ||
| 464 | w.mode = w.mode.toUnescaped(); | ||
| 465 | return prev; | ||
| 466 | } | ||
| 467 | |||
| 468 | pub fn restoreEscape(w: *Writer, mode: Mode) void { | ||
| 469 | w.mode = mode; | ||
| 470 | } | ||
| 471 | |||
| 472 | pub 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 | |||
| 478 | pub 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, |
| 77 | use_copy_file_range: UseCopyFileRange = .default, | 77 | use_copy_file_range: UseCopyFileRange = .default, |
| 78 | use_fcopyfile: UseFcopyfile = .default, | 78 | use_fcopyfile: UseFcopyfile = .default, |
| 79 | 79 | ||
| 80 | stderr_writer: Io.Writer, | 80 | stderr_writer: File.Writer = .{ |
| 81 | .io = undefined, | ||
| 82 | .interface = Io.File.Writer.initInterface(&.{}), | ||
| 83 | .file = if (is_windows) undefined else .stderr(), | ||
| 84 | .mode = undefined, | ||
| 85 | }, | ||
| 86 | stderr_writer_initialized: bool = false, | ||
| 81 | 87 | ||
| 82 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { | 88 | pub 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 { |
| 737 | 743 | ||
| 738 | .processExecutableOpen = processExecutableOpen, | 744 | .processExecutableOpen = processExecutableOpen, |
| 739 | .processExecutablePath = processExecutablePath, | 745 | .processExecutablePath = processExecutablePath, |
| 746 | .lockStderrWriter = lockStderrWriter, | ||
| 747 | .tryLockStderrWriter = tryLockStderrWriter, | ||
| 748 | .unlockStderrWriter = unlockStderrWriter, | ||
| 740 | 749 | ||
| 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 { |
| 864 | 873 | ||
| 865 | .processExecutableOpen = processExecutableOpen, | 874 | .processExecutableOpen = processExecutableOpen, |
| 866 | .processExecutablePath = processExecutablePath, | 875 | .processExecutablePath = processExecutablePath, |
| 876 | .lockStderrWriter = lockStderrWriter, | ||
| 877 | .tryLockStderrWriter = tryLockStderrWriter, | ||
| 878 | .unlockStderrWriter = unlockStderrWriter, | ||
| 867 | 879 | ||
| 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 | } |
| 9518 | 9530 | ||
| 9519 | fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer { | 9531 | fn 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 | } |
| 9529 | 9545 | ||
| 9530 | fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer { | 9546 | fn 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 | } |
| 9540 | 9561 | ||
| 9541 | fn unlockStderrWriter(userdata: ?*anyopaque) void { | 9562 | fn 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 | } |
| 9548 | 9569 |
lib/std/Io/tty.zig deleted-135| ... | @@ -1,135 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const native_os = builtin.os.tag; | ||
| 3 | |||
| 4 | const std = @import("std"); | ||
| 5 | const Io = std.Io; | ||
| 6 | const File = std.Io.File; | ||
| 7 | const process = std.process; | ||
| 8 | const windows = std.os.windows; | ||
| 9 | |||
| 10 | pub 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. | ||
| 34 | pub 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 | } |
| 757 | 757 | ||
| 758 | fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void { | 758 | pub 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; |
| 760 | 760 | 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 | } |
| 764 | 763 |
lib/std/debug.zig+101-125| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | const std = @import("std.zig"); | 1 | const std = @import("std.zig"); |
| 2 | const Io = std.Io; | 2 | const Io = std.Io; |
| 3 | const Writer = std.Io.Writer; | 3 | const Writer = std.Io.Writer; |
| 4 | const tty = std.Io.tty; | ||
| 5 | const math = std.math; | 4 | const math = std.math; |
| 6 | const mem = std.mem; | 5 | const mem = std.mem; |
| 7 | const posix = std.posix; | 6 | const 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 | }; |
| 264 | 263 | ||
| 264 | /// This is used for debug information and debug printing. It is intentionally | ||
| 265 | /// separate from the application's `Io` instance. | ||
| 266 | var 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 with | 283 | /// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with |
| 281 | /// the application's chosen `Io` implementation. | 284 | /// the application's chosen `Io` implementation. |
| 282 | pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { | 285 | pub 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 | } |
| 291 | 288 | ||
| 292 | pub fn unlockStderrWriter() void { | 289 | pub fn unlockStderrWriter() void { |
| 293 | std.Progress.unlockStderrWriter(); | 290 | static_single_threaded_io.ioBasic().unlockStderrWriter(); |
| 294 | } | 291 | } |
| 295 | 292 | ||
| 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` to | 302 | /// 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. |
| 307 | pub fn print(comptime fmt: []const u8, args: anytype) void { | 304 | pub 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 | |||
| 314 | const 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 | } |
| 341 | 312 | ||
| 342 | /// Marked `inline` to propagate a comptime-known error to callers. | 313 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 343 | pub inline fn getSelfDebugInfo() !*SelfInfo { | 314 | pub 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 | } |
| 358 | 329 | ||
| 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. |
| 360 | pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void { | 331 | pub 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); |
| 370 | 341 | ||
| 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/7600 | 359 | // 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) ! |
| 408 | 379 | ||
| 409 | test dumpHexFallible { | 380 | test 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(); |
| 413 | 384 | ||
| 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 | } |
| 427 | 398 | ||
| 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 its | 408 | /// 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 than | 411 | /// 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 in | 412 | /// this function, because this function may not detect a test failure in |
| 442 | /// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert | 413 | /// 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); |
| 575 | 546 | ||
| 576 | trace: { | 547 | trace: { |
| 577 | const stderr, const tty_config = lockStderrWriter(&.{}); | 548 | const stderr = lockStderrWriter(&.{}); |
| 578 | defer unlockStderrWriter(); | 549 | defer unlockStderrWriter(); |
| 579 | 550 | ||
| 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; |
| 587 | 558 | ||
| 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 | } |
| 598 | 569 | ||
| 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 to | 575 | // 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; |
| 653 | 624 | ||
| 654 | var threaded: Io.Threaded = .init_single_threaded; | 625 | const io = static_single_threaded_io.ioBasic(); |
| 655 | const io = threaded.ioBasic(); | ||
| 656 | 626 | ||
| 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. |
| 689 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | 659 | pub 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. |
| 777 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { | 745 | pub 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 | } |
| 792 | 760 | ||
| 793 | pub const FormatStackTrace = struct { | 761 | pub const FormatStackTrace = struct { |
| 794 | stack_trace: StackTrace, | 762 | stack_trace: StackTrace, |
| 795 | tty_config: tty.Config, | ||
| 796 | 763 | ||
| 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 | }; |
| 802 | 781 | ||
| 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. |
| 804 | pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | 783 | pub 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(); | ||
| 815 | 790 | ||
| 816 | // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if | 791 | // 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 | } |
| 1177 | fn printLineInfo( | 1153 | fn 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 {}; |
| 1188 | 1164 | ||
| 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 | } |
| 1194 | 1170 | ||
| 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"); |
| 1201 | 1177 | ||
| 1202 | // Show the matching source code line if possible | 1178 | // 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)); |
| 1208 | 1184 | ||
| 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 | } |
| 1251 | 1227 | ||
| 1252 | test printLineFromFile { | 1228 | test printLineFromFile { |
| 1253 | const io = std.testing.io; | 1229 | const io = testing.io; |
| 1254 | const gpa = std.testing.allocator; | 1230 | const gpa = testing.allocator; |
| 1255 | 1231 | ||
| 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; |
| 1259 | 1235 | ||
| 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; |
| 1263 | 1239 | ||
| 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); |
| 1579 | 1555 | ||
| 1580 | trace: { | 1556 | trace: { |
| 1581 | const stderr, const tty_config = lockStderrWriter(&.{}); | 1557 | const stderr = lockStderrWriter(&.{}); |
| 1582 | defer unlockStderrWriter(); | 1558 | defer unlockStderrWriter(); |
| 1583 | 1559 | ||
| 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 to | 1576 | // 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, | ||
| 184 | 182 | ||
| 185 | const Self = @This(); | 183 | const Self = @This(); |
| 186 | 184 | ||
| ... | @@ -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 { |
| 460 | 453 | ||
| 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; |
| 466 | 457 | ||
| 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 | } |
| 537 | 525 | ||
| 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 | } |
| 562 | 536 | ||
| ... | @@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type { | ... | @@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 587 | 561 | ||
| 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 | } |
| 615 | 582 | ||
| ... | @@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type { | ... | @@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 701 | 668 | ||
| 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 | } |
| 729 | 689 | ||
| ... | @@ -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 if | 17 | //! 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 error | 20 | //! error: this is an error |
| 21 | //! error(scope): this is an error with a non-default scope | 21 | //! 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 | } |
| 82 | 82 | ||
| 83 | var static_threaded_io: std.Io.Threaded = .init_single_threaded; | ||
| 84 | |||
| 85 | /// The default implementation for the log function. Custom log functions may | 83 | /// 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 | } |
| 98 | 99 | ||
| 99 | pub fn defaultLogIo( | 100 | pub 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 | |||
| 126 | fn 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 | |||
| 139 | fn 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 | } |
| 127 | 153 | ||
| 128 | /// Returns a scoped logging namespace that logs all messages using the scope | 154 | /// 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 | } |
| 440 | 440 | ||
| 441 | /// On Windows, `key` must be valid WTF-8. | 441 | /// On Windows, `key` must be valid WTF-8. |
| 442 | pub fn hasEnvVarConstant(comptime key: []const u8) bool { | 442 | pub 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 | } |
| 452 | 452 | ||
| 453 | /// On Windows, `key` must be valid WTF-8. | 453 | /// On Windows, `key` must be valid WTF-8. |
| 454 | pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | 454 | pub 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(); |
| 249 | 249 | ||
| 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")) { |