| 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 | 82 | pub const Reader = @import("Io/Reader.zig"); |
| 83 | 83 | pub const Writer = @import("Io/Writer.zig"); |
| 84 | 84 | |
| 85 | pub const tty = @import("Io/tty.zig"); | |
| 86 | ||
| 87 | 85 | pub fn poll( |
| 88 | 86 | gpa: Allocator, |
| 89 | 87 | comptime StreamEnum: type, |
| ... | ... | @@ -535,7 +533,6 @@ test { |
| 535 | 533 | _ = net; |
| 536 | 534 | _ = Reader; |
| 537 | 535 | _ = Writer; |
| 538 | _ = tty; | |
| 539 | 536 | _ = Evented; |
| 540 | 537 | _ = Threaded; |
| 541 | 538 | _ = @import("Io/test.zig"); |
| ... | ... | @@ -720,6 +717,9 @@ pub const VTable = struct { |
| 720 | 717 | |
| 721 | 718 | processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, |
| 722 | 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 | 724 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 725 | 725 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
| ... | ... | @@ -740,10 +740,6 @@ pub const VTable = struct { |
| 740 | 740 | netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, |
| 741 | 741 | netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, |
| 742 | 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 | 745 | pub const Cancelable = error{ |
| ... | ... | @@ -2186,13 +2182,17 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { |
| 2186 | 2182 | /// |
| 2187 | 2183 | /// See also: |
| 2188 | 2184 | /// * `tryLockStderrWriter` |
| 2189 | pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer { | |
| 2190 | return io.vtable.lockStderrWriter(io.userdata, buffer); | |
| 2185 | pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer { | |
| 2186 | const result = try io.vtable.lockStderrWriter(io.userdata, buffer); | |
| 2187 | result.io = io; | |
| 2188 | return result; | |
| 2191 | 2189 | } |
| 2192 | 2190 | |
| 2193 | 2191 | /// Same as `lockStderrWriter` but uncancelable and non-blocking. |
| 2194 | pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer { | |
| 2195 | return io.vtable.tryLockStderrWriter(io.userdata, buffer); | |
| 2192 | pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer { | |
| 2193 | const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null; | |
| 2194 | result.io = io; | |
| 2195 | return result; | |
| 2196 | 2196 | } |
| 2197 | 2197 | |
| 2198 | 2198 | pub fn unlockStderrWriter(io: Io) void { |
lib/std/Io/File/Writer.zig+234-9| ... | ... | @@ -1,4 +1,6 @@ |
| 1 | 1 | const Writer = @This(); |
| 2 | const builtin = @import("builtin"); | |
| 3 | const is_windows = builtin.os.tag == .windows; | |
| 2 | 4 | |
| 3 | 5 | const std = @import("../../std.zig"); |
| 4 | 6 | const Io = std.Io; |
| ... | ... | @@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null, |
| 16 | 18 | seek_err: ?SeekError = null, |
| 17 | 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 | 160 | pub const Error = error{ |
| 22 | 161 | DiskQuota, |
| ... | ... | @@ -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 | 226 | pub fn initInterface(buffer: []u8) Io.Writer { |
| 78 | 227 | return .{ |
| 79 | 228 | .vtable = &.{ |
| ... | ... | @@ -99,8 +248,9 @@ pub fn moveToReader(w: *Writer) File.Reader { |
| 99 | 248 | pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { |
| 100 | 249 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 101 | 250 | switch (w.mode) { |
| 102 | .positional, .positional_reading => return drainPositional(w, data, splat), | |
| 103 | .streaming, .streaming_reading => return drainStreaming(w, data, splat), | |
| 251 | .positional, .positional_simple => return drainPositional(w, data, splat), | |
| 252 | .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat), | |
| 253 | .terminal_escaped => return drainEscaping(w, data, splat), | |
| 104 | 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 | 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 | 319 | pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { |
| 145 | 320 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 146 | 321 | switch (w.mode) { |
| 147 | 322 | .positional => return sendFilePositional(w, file_reader, limit), |
| 148 | .positional_reading => return error.Unimplemented, | |
| 323 | .positional_simple => return error.Unimplemented, | |
| 149 | 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 | 326 | .failure => return error.WriteFailed, |
| 152 | 327 | } |
| 153 | 328 | } |
| ... | ... | @@ -214,10 +389,10 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void { |
| 214 | 389 | assert(w.interface.buffered().len == 0); |
| 215 | 390 | const io = w.io; |
| 216 | 391 | switch (w.mode) { |
| 217 | .positional, .positional_reading => { | |
| 392 | .positional, .positional_simple => { | |
| 218 | 393 | w.pos = offset; |
| 219 | 394 | }, |
| 220 | .streaming, .streaming_reading => { | |
| 395 | .streaming, .streaming_simple, .terminal_escaped, .terminal_winapi => { | |
| 221 | 396 | if (w.seek_err) |err| return err; |
| 222 | 397 | io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| { |
| 223 | 398 | w.seek_err = err; |
| ... | ... | @@ -243,15 +418,65 @@ pub fn end(w: *Writer) EndError!void { |
| 243 | 418 | try w.interface.flush(); |
| 244 | 419 | switch (w.mode) { |
| 245 | 420 | .positional, |
| 246 | .positional_reading, | |
| 421 | .positional_simple, | |
| 247 | 422 | => w.file.setLength(io, w.pos) catch |err| switch (err) { |
| 248 | 423 | error.NonResizable => return, |
| 249 | 424 | else => |e| return e, |
| 250 | 425 | }, |
| 251 | 426 | |
| 252 | 427 | .streaming, |
| 253 | .streaming_reading, | |
| 428 | .streaming_simple, | |
| 254 | 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 | 77 | use_copy_file_range: UseCopyFileRange = .default, |
| 78 | 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 | 88 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| 83 | 89 | enabled, |
| ... | ... | @@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io { |
| 737 | 743 | |
| 738 | 744 | .processExecutableOpen = processExecutableOpen, |
| 739 | 745 | .processExecutablePath = processExecutablePath, |
| 746 | .lockStderrWriter = lockStderrWriter, | |
| 747 | .tryLockStderrWriter = tryLockStderrWriter, | |
| 748 | .unlockStderrWriter = unlockStderrWriter, | |
| 740 | 749 | |
| 741 | 750 | .now = now, |
| 742 | 751 | .sleep = sleep, |
| ... | ... | @@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io { |
| 864 | 873 | |
| 865 | 874 | .processExecutableOpen = processExecutableOpen, |
| 866 | 875 | .processExecutablePath = processExecutablePath, |
| 876 | .lockStderrWriter = lockStderrWriter, | |
| 877 | .tryLockStderrWriter = tryLockStderrWriter, | |
| 878 | .unlockStderrWriter = unlockStderrWriter, | |
| 867 | 879 | |
| 868 | 880 | .now = now, |
| 869 | 881 | .sleep = sleep, |
| ... | ... | @@ -9516,33 +9528,42 @@ fn netLookupFallible( |
| 9516 | 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 | 9532 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9521 | 9533 | // Only global mutex since this is Threaded. |
| 9522 | 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 | 9540 | std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; |
| 9525 | t.stderr_writer.flush() catch {}; | |
| 9526 | t.stderr_writer.buffer = buffer; | |
| 9541 | t.stderr_writer.interface.flush() catch {}; | |
| 9542 | t.stderr_writer.interface.buffer = buffer; | |
| 9527 | 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 | 9547 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9532 | 9548 | // Only global mutex since this is Threaded. |
| 9533 | 9549 | if (!Io.stderr_thread_mutex.tryLock()) return null; |
| 9534 | std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {}; | |
| 9535 | if (is_windows) t.stderr_writer.file = .stderr(); | |
| 9536 | t.stderr_writer.flush() catch {}; | |
| 9537 | t.stderr_writer.buffer = buffer; | |
| 9550 | if (!t.stderr_writer_initialized) { | |
| 9551 | if (is_windows) t.stderr_writer.file = .stderr(); | |
| 9552 | t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch | |
| 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 | 9559 | return &t.stderr_writer; |
| 9539 | 9560 | } |
| 9540 | 9561 | |
| 9541 | 9562 | fn unlockStderrWriter(userdata: ?*anyopaque) void { |
| 9542 | 9563 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9543 | t.stderr_writer.flush() catch {}; | |
| 9544 | t.stderr_writer.end = 0; | |
| 9545 | t.stderr_writer.buffer = &.{}; | |
| 9564 | t.stderr_writer.interface.flush() catch {}; | |
| 9565 | t.stderr_writer.interface.end = 0; | |
| 9566 | t.stderr_writer.interface.buffer = &.{}; | |
| 9546 | 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 | 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 | 759 | if (noop_impl or !global_progress.need_clear) return; |
| 760 | ||
| 761 | try w.writeAll(clear ++ progress_remove); | |
| 760 | try file_writer.interface.writeAllUnescaped(clear ++ progress_remove); | |
| 762 | 761 | global_progress.need_clear = false; |
| 763 | 762 | } |
| 764 | 763 |
lib/std/debug.zig+101-125| ... | ... | @@ -1,7 +1,6 @@ |
| 1 | 1 | const std = @import("std.zig"); |
| 2 | 2 | const Io = std.Io; |
| 3 | 3 | const Writer = std.Io.Writer; |
| 4 | const tty = std.Io.tty; | |
| 5 | 4 | const math = std.math; |
| 6 | 5 | const mem = std.mem; |
| 7 | 6 | const posix = std.posix; |
| ... | ... | @@ -262,6 +261,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 262 | 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 | 268 | /// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. |
| 266 | 269 | /// |
| 267 | 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 | 282 | /// |
| 280 | 283 | /// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with |
| 281 | 284 | /// the application's chosen `Io` implementation. |
| 282 | pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { | |
| 283 | Io.stderr_thread_mutex.lock(); | |
| 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.? }; | |
| 285 | pub fn lockStderrWriter(buffer: []u8) *File.Writer { | |
| 286 | return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable; | |
| 290 | 287 | } |
| 291 | 288 | |
| 292 | 289 | pub fn unlockStderrWriter() void { |
| 293 | std.Progress.unlockStderrWriter(); | |
| 290 | static_single_threaded_io.ioBasic().unlockStderrWriter(); | |
| 294 | 291 | } |
| 295 | 292 | |
| 296 | 293 | /// Writes to stderr, ignoring errors. |
| ... | ... | @@ -305,39 +302,13 @@ pub fn unlockStderrWriter() void { |
| 305 | 302 | /// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to |
| 306 | 303 | /// integrate with the application's chosen `Io` implementation. |
| 307 | 304 | pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 308 | var buffer: [64]u8 = undefined; | |
| 309 | const bw, _ = lockStderrWriter(&buffer); | |
| 310 | defer unlockStderrWriter(); | |
| 311 | nosuspend bw.print(fmt, args) catch return; | |
| 312 | } | |
| 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); | |
| 305 | nosuspend { | |
| 306 | var buffer: [64]u8 = undefined; | |
| 307 | const stderr = lockStderrWriter(&buffer); | |
| 308 | defer unlockStderrWriter(); | |
| 309 | stderr.interface.print(fmt, args) catch return; | |
| 339 | 310 | } |
| 340 | }; | |
| 311 | } | |
| 341 | 312 | |
| 342 | 313 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 343 | 314 | pub inline fn getSelfDebugInfo() !*SelfInfo { |
| ... | ... | @@ -357,16 +328,16 @@ pub fn dumpHex(bytes: []const u8) void { |
| 357 | 328 | } |
| 358 | 329 | |
| 359 | 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 | 332 | var chunks = mem.window(u8, bytes, 16, 16); |
| 362 | 333 | while (chunks.next()) |window| { |
| 363 | 334 | // 1. Print the address. |
| 364 | 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 | 337 | // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. |
| 367 | 338 | // Also, make sure all lines are aligned by padding the address. |
| 368 | 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 | 342 | // 2. Print the bytes. |
| 372 | 343 | for (window, 0..) |byte, index| { |
| ... | ... | @@ -386,7 +357,7 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) ! |
| 386 | 357 | try bw.writeByte(byte); |
| 387 | 358 | } else { |
| 388 | 359 | // Related: https://github.com/ziglang/zig/issues/7600 |
| 389 | if (tty_config == .windows_api) { | |
| 360 | if (fwm == .terminal_winapi) { | |
| 390 | 361 | try bw.writeByte('.'); |
| 391 | 362 | continue; |
| 392 | 363 | } |
| ... | ... | @@ -408,11 +379,11 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) ! |
| 408 | 379 | |
| 409 | 380 | test dumpHexFallible { |
| 410 | 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 | 383 | defer aw.deinit(); |
| 413 | 384 | |
| 414 | 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 | 387 | \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ |
| 417 | 388 | \\{x:0>[2]} 01 12 13 ... |
| 418 | 389 | \\ |
| ... | ... | @@ -421,8 +392,8 @@ test dumpHexFallible { |
| 421 | 392 | @intFromPtr(bytes.ptr) + 16, |
| 422 | 393 | @sizeOf(usize) * 2, |
| 423 | 394 | }); |
| 424 | defer std.testing.allocator.free(expected); | |
| 425 | try std.testing.expectEqualStrings(expected, aw.written()); | |
| 395 | defer testing.allocator.free(expected); | |
| 396 | try testing.expectEqualStrings(expected, aw.written()); | |
| 426 | 397 | } |
| 427 | 398 | |
| 428 | 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 | 408 | /// away, and in fact the optimizer is able to use the assertion in its |
| 438 | 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 | 412 | /// this function, because this function may not detect a test failure in |
| 442 | 413 | /// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert |
| 443 | 414 | /// function is the correct function to use. |
| ... | ... | @@ -574,26 +545,26 @@ pub fn defaultPanic( |
| 574 | 545 | _ = panicking.fetchAdd(1, .seq_cst); |
| 575 | 546 | |
| 576 | 547 | trace: { |
| 577 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 548 | const stderr = lockStderrWriter(&.{}); | |
| 578 | 549 | defer unlockStderrWriter(); |
| 579 | 550 | |
| 580 | 551 | if (builtin.single_threaded) { |
| 581 | stderr.print("panic: ", .{}) catch break :trace; | |
| 552 | stderr.interface.print("panic: ", .{}) catch break :trace; | |
| 582 | 553 | } else { |
| 583 | 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 | 559 | if (@errorReturnTrace()) |t| if (t.index > 0) { |
| 589 | stderr.writeAll("error return context:\n") catch break :trace; | |
| 590 | writeStackTrace(t, stderr, tty_config) catch break :trace; | |
| 591 | stderr.writeAll("\nstack trace:\n") catch break :trace; | |
| 560 | stderr.interface.writeAll("error return context:\n") catch break :trace; | |
| 561 | writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace; | |
| 562 | stderr.interface.writeAll("\nstack trace:\n") catch break :trace; | |
| 592 | 563 | }; |
| 593 | 564 | writeCurrentStackTrace(.{ |
| 594 | 565 | .first_address = first_trace_addr orelse @returnAddress(), |
| 595 | 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 | 570 | waitForOtherThreadToFinishPanicking(); |
| ... | ... | @@ -603,8 +574,8 @@ pub fn defaultPanic( |
| 603 | 574 | // A panic happened while trying to print a previous panic message. |
| 604 | 575 | // We're still holding the mutex but that's fine as we're going to |
| 605 | 576 | // call abort(). |
| 606 | const stderr, _ = lockStderrWriter(&.{}); | |
| 607 | stderr.writeAll("aborting due to recursive panic\n") catch {}; | |
| 577 | const stderr = lockStderrWriter(&.{}); | |
| 578 | stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; | |
| 608 | 579 | }, |
| 609 | 580 | else => {}, // Panicked while printing the recursive panic message. |
| 610 | 581 | } |
| ... | ... | @@ -651,8 +622,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 651 | 622 | defer it.deinit(); |
| 652 | 623 | if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; |
| 653 | 624 | |
| 654 | var threaded: Io.Threaded = .init_single_threaded; | |
| 655 | const io = threaded.ioBasic(); | |
| 625 | const io = static_single_threaded_io.ioBasic(); | |
| 656 | 626 | |
| 657 | 627 | var total_frames: usize = 0; |
| 658 | 628 | var index: usize = 0; |
| ... | ... | @@ -686,36 +656,34 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 686 | 656 | /// Write the current stack trace to `writer`, annotated with source locations. |
| 687 | 657 | /// |
| 688 | 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 { | |
| 690 | var threaded: Io.Threaded = .init_single_threaded; | |
| 691 | const io = threaded.ioBasic(); | |
| 692 | ||
| 659 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { | |
| 693 | 660 | if (!std.options.allow_stack_tracing) { |
| 694 | tty_config.setColor(writer, .dim) catch {}; | |
| 661 | fwm.setColor(writer, .dim) catch {}; | |
| 695 | 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 | 664 | return; |
| 698 | 665 | } |
| 699 | 666 | const di_gpa = getDebugInfoAllocator(); |
| 700 | 667 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 701 | 668 | error.UnsupportedTarget => { |
| 702 | tty_config.setColor(writer, .dim) catch {}; | |
| 669 | fwm.setColor(writer, .dim) catch {}; | |
| 703 | 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 | 672 | return; |
| 706 | 673 | }, |
| 707 | 674 | }; |
| 708 | 675 | var it: StackIterator = .init(options.context); |
| 709 | 676 | defer it.deinit(); |
| 710 | 677 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 711 | tty_config.setColor(writer, .dim) catch {}; | |
| 678 | fwm.setColor(writer, .dim) catch {}; | |
| 712 | 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 | 681 | return; |
| 715 | 682 | } |
| 716 | 683 | var total_frames: usize = 0; |
| 717 | 684 | var wait_for = options.first_address; |
| 718 | 685 | var printed_any_frame = false; |
| 686 | const io = static_single_threaded_io.ioBasic(); | |
| 719 | 687 | while (true) switch (it.next(io)) { |
| 720 | 688 | .switch_to_fp => |unwind_error| { |
| 721 | 689 | switch (StackIterator.fp_usability) { |
| ... | ... | @@ -733,31 +701,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 733 | 701 | error.Unexpected => "unexpected error", |
| 734 | 702 | }; |
| 735 | 703 | if (it.stratOk(options.allow_unsafe_unwind)) { |
| 736 | tty_config.setColor(writer, .dim) catch {}; | |
| 704 | fwm.setColor(writer, .dim) catch {}; | |
| 737 | 705 | try writer.print( |
| 738 | 706 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", |
| 739 | 707 | .{ module_name, unwind_error.address, caption }, |
| 740 | 708 | ); |
| 741 | tty_config.setColor(writer, .reset) catch {}; | |
| 709 | fwm.setColor(writer, .reset) catch {}; | |
| 742 | 710 | } else { |
| 743 | tty_config.setColor(writer, .dim) catch {}; | |
| 711 | fwm.setColor(writer, .dim) catch {}; | |
| 744 | 712 | try writer.print( |
| 745 | 713 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", |
| 746 | 714 | .{ module_name, unwind_error.address, caption }, |
| 747 | 715 | ); |
| 748 | tty_config.setColor(writer, .reset) catch {}; | |
| 716 | fwm.setColor(writer, .reset) catch {}; | |
| 749 | 717 | return; |
| 750 | 718 | } |
| 751 | 719 | }, |
| 752 | 720 | .end => break, |
| 753 | 721 | .frame => |ret_addr| { |
| 754 | 722 | if (total_frames > 10_000) { |
| 755 | tty_config.setColor(writer, .dim) catch {}; | |
| 723 | fwm.setColor(writer, .dim) catch {}; | |
| 756 | 724 | try writer.print( |
| 757 | 725 | "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", |
| 758 | 726 | .{total_frames}, |
| 759 | 727 | ); |
| 760 | tty_config.setColor(writer, .reset) catch {}; | |
| 728 | fwm.setColor(writer, .reset) catch {}; | |
| 761 | 729 | return; |
| 762 | 730 | } |
| 763 | 731 | total_frames += 1; |
| ... | ... | @@ -767,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 767 | 735 | } |
| 768 | 736 | // `ret_addr` is the return address, which is *after* the function call. |
| 769 | 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 | 739 | printed_any_frame = true; |
| 772 | 740 | }, |
| 773 | 741 | }; |
| ... | ... | @@ -775,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 775 | 743 | } |
| 776 | 744 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. |
| 777 | 745 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 778 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 746 | const stderr = lockStderrWriter(&.{}); | |
| 779 | 747 | defer unlockStderrWriter(); |
| 780 | 748 | writeCurrentStackTrace(.{ |
| 781 | 749 | .first_address = a: { |
| ... | ... | @@ -785,33 +753,40 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 785 | 753 | }, |
| 786 | 754 | .context = options.context, |
| 787 | 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 | 757 | error.WriteFailed => {}, |
| 790 | 758 | }; |
| 791 | 759 | } |
| 792 | 760 | |
| 793 | 761 | pub const FormatStackTrace = struct { |
| 794 | 762 | stack_trace: StackTrace, |
| 795 | tty_config: tty.Config, | |
| 796 | 763 | |
| 797 | pub fn format(context: @This(), writer: *Writer) Writer.Error!void { | |
| 798 | try writer.writeAll("\n"); | |
| 799 | try writeStackTrace(&context.stack_trace, writer, context.tty_config); | |
| 764 | pub const Decorated = struct { | |
| 765 | stack_trace: StackTrace, | |
| 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 | 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 | 784 | if (!std.options.allow_stack_tracing) { |
| 806 | tty_config.setColor(writer, .dim) catch {}; | |
| 785 | fwm.setColor(writer, .dim) catch {}; | |
| 807 | 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 | 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 | 791 | // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if |
| 817 | 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 | 795 | const di_gpa = getDebugInfoAllocator(); |
| 821 | 796 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 822 | 797 | error.UnsupportedTarget => { |
| 823 | tty_config.setColor(writer, .dim) catch {}; | |
| 798 | fwm.setColor(writer, .dim) catch {}; | |
| 824 | 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 | 801 | return; |
| 827 | 802 | }, |
| 828 | 803 | }; |
| 804 | const io = static_single_threaded_io.ioBasic(); | |
| 829 | 805 | const captured_frames = @min(n_frames, st.instruction_addresses.len); |
| 830 | 806 | for (st.instruction_addresses[0..captured_frames]) |ret_addr| { |
| 831 | 807 | // `ret_addr` is the return address, which is *after* the function call. |
| 832 | 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 | 811 | if (n_frames > captured_frames) { |
| 836 | tty_config.setColor(writer, .bold) catch {}; | |
| 812 | fwm.setColor(writer, .bold) catch {}; | |
| 837 | 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 | 817 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| ... | ... | @@ -1143,7 +1119,7 @@ fn printSourceAtAddress( |
| 1143 | 1119 | debug_info: *SelfInfo, |
| 1144 | 1120 | writer: *Writer, |
| 1145 | 1121 | address: usize, |
| 1146 | tty_config: tty.Config, | |
| 1122 | fwm: File.Writer.Mode, | |
| 1147 | 1123 | ) Writer.Error!void { |
| 1148 | 1124 | const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { |
| 1149 | 1125 | error.MissingDebugInfo, |
| ... | ... | @@ -1151,15 +1127,15 @@ fn printSourceAtAddress( |
| 1151 | 1127 | error.InvalidDebugInfo, |
| 1152 | 1128 | => .unknown, |
| 1153 | 1129 | error.ReadFailed, error.Unexpected, error.Canceled => s: { |
| 1154 | tty_config.setColor(writer, .dim) catch {}; | |
| 1130 | fwm.setColor(writer, .dim) catch {}; | |
| 1155 | 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 | 1133 | break :s .unknown; |
| 1158 | 1134 | }, |
| 1159 | 1135 | error.OutOfMemory => s: { |
| 1160 | tty_config.setColor(writer, .dim) catch {}; | |
| 1136 | fwm.setColor(writer, .dim) catch {}; | |
| 1161 | 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 | 1139 | break :s .unknown; |
| 1164 | 1140 | }, |
| 1165 | 1141 | }; |
| ... | ... | @@ -1171,7 +1147,7 @@ fn printSourceAtAddress( |
| 1171 | 1147 | address, |
| 1172 | 1148 | symbol.name orelse "???", |
| 1173 | 1149 | symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", |
| 1174 | tty_config, | |
| 1150 | fwm, | |
| 1175 | 1151 | ); |
| 1176 | 1152 | } |
| 1177 | 1153 | fn printLineInfo( |
| ... | ... | @@ -1181,10 +1157,10 @@ fn printLineInfo( |
| 1181 | 1157 | address: usize, |
| 1182 | 1158 | symbol_name: []const u8, |
| 1183 | 1159 | compile_unit_name: []const u8, |
| 1184 | tty_config: tty.Config, | |
| 1160 | fwm: File.Writer.Mode, | |
| 1185 | 1161 | ) Writer.Error!void { |
| 1186 | 1162 | nosuspend { |
| 1187 | tty_config.setColor(writer, .bold) catch {}; | |
| 1163 | fwm.setColor(writer, .bold) catch {}; | |
| 1188 | 1164 | |
| 1189 | 1165 | if (source_location) |*sl| { |
| 1190 | 1166 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); |
| ... | ... | @@ -1192,11 +1168,11 @@ fn printLineInfo( |
| 1192 | 1168 | try writer.writeAll("???:?:?"); |
| 1193 | 1169 | } |
| 1194 | 1170 | |
| 1195 | tty_config.setColor(writer, .reset) catch {}; | |
| 1171 | fwm.setColor(writer, .reset) catch {}; | |
| 1196 | 1172 | try writer.writeAll(": "); |
| 1197 | tty_config.setColor(writer, .dim) catch {}; | |
| 1173 | fwm.setColor(writer, .dim) catch {}; | |
| 1198 | 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 | 1176 | try writer.writeAll("\n"); |
| 1201 | 1177 | |
| 1202 | 1178 | // Show the matching source code line if possible |
| ... | ... | @@ -1207,9 +1183,9 @@ fn printLineInfo( |
| 1207 | 1183 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| 1208 | 1184 | |
| 1209 | 1185 | try writer.splatByteAll(' ', space_needed); |
| 1210 | tty_config.setColor(writer, .green) catch {}; | |
| 1186 | fwm.setColor(writer, .green) catch {}; | |
| 1211 | 1187 | try writer.writeAll("^"); |
| 1212 | tty_config.setColor(writer, .reset) catch {}; | |
| 1188 | fwm.setColor(writer, .reset) catch {}; | |
| 1213 | 1189 | } |
| 1214 | 1190 | try writer.writeAll("\n"); |
| 1215 | 1191 | } else |_| { |
| ... | ... | @@ -1250,18 +1226,18 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) ! |
| 1250 | 1226 | } |
| 1251 | 1227 | |
| 1252 | 1228 | test printLineFromFile { |
| 1253 | const io = std.testing.io; | |
| 1254 | const gpa = std.testing.allocator; | |
| 1229 | const io = testing.io; | |
| 1230 | const gpa = testing.allocator; | |
| 1255 | 1231 | |
| 1256 | 1232 | var aw: Writer.Allocating = .init(gpa); |
| 1257 | 1233 | defer aw.deinit(); |
| 1258 | 1234 | const output_stream = &aw.writer; |
| 1259 | 1235 | |
| 1260 | 1236 | const join = std.fs.path.join; |
| 1261 | const expectError = std.testing.expectError; | |
| 1262 | const expectEqualStrings = std.testing.expectEqualStrings; | |
| 1237 | const expectError = testing.expectError; | |
| 1238 | const expectEqualStrings = testing.expectEqualStrings; | |
| 1263 | 1239 | |
| 1264 | var test_dir = std.testing.tmpDir(.{}); | |
| 1240 | var test_dir = testing.tmpDir(.{}); | |
| 1265 | 1241 | defer test_dir.cleanup(); |
| 1266 | 1242 | // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. |
| 1267 | 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 | 1554 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1579 | 1555 | |
| 1580 | 1556 | trace: { |
| 1581 | const stderr, const tty_config = lockStderrWriter(&.{}); | |
| 1557 | const stderr = lockStderrWriter(&.{}); | |
| 1582 | 1558 | defer unlockStderrWriter(); |
| 1583 | 1559 | |
| 1584 | 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 | 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 | 1565 | if (opt_ctx) |context| { |
| 1590 | 1566 | writeCurrentStackTrace(.{ |
| 1591 | 1567 | .context = context, |
| 1592 | 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 | 1575 | // A segfault happened while trying to print a previous panic message. |
| 1600 | 1576 | // We're still holding the mutex but that's fine as we're going to |
| 1601 | 1577 | // call abort(). |
| 1602 | const stderr, _ = lockStderrWriter(&.{}); | |
| 1603 | stderr.writeAll("aborting due to recursive panic\n") catch {}; | |
| 1578 | const stderr = lockStderrWriter(&.{}); | |
| 1579 | stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; | |
| 1604 | 1580 | }, |
| 1605 | 1581 | else => {}, // Panicked while printing the recursive panic message. |
| 1606 | 1582 | } |
| ... | ... | @@ -1632,9 +1608,9 @@ test "manage resources correctly" { |
| 1632 | 1608 | return @returnAddress(); |
| 1633 | 1609 | } |
| 1634 | 1610 | }; |
| 1635 | const gpa = std.testing.allocator; | |
| 1636 | var threaded: Io.Threaded = .init_single_threaded; | |
| 1637 | const io = threaded.ioBasic(); | |
| 1611 | const gpa = testing.allocator; | |
| 1612 | const io = testing.io; | |
| 1613 | ||
| 1638 | 1614 | var discarding: Writer.Discarding = .init(&.{}); |
| 1639 | 1615 | var di: SelfInfo = .init; |
| 1640 | 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 | 179 | total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init, |
| 180 | 180 | requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init, |
| 181 | 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 | 183 | const Self = @This(); |
| 186 | 184 | |
| ... | ... | @@ -427,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 427 | 425 | bucket: *BucketHeader, |
| 428 | 426 | size_class_index: usize, |
| 429 | 427 | used_bits_count: usize, |
| 430 | tty_config: std.Io.tty.Config, | |
| 431 | 428 | ) usize { |
| 432 | 429 | const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index)); |
| 433 | 430 | const slot_count = slot_counts[size_class_index]; |
| ... | ... | @@ -444,11 +441,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 444 | 441 | const page_addr = @intFromPtr(bucket) & ~(page_size - 1); |
| 445 | 442 | const addr = page_addr + slot_index * size_class; |
| 446 | 443 | log.err("memory address 0x{x} leaked: {f}", .{ |
| 447 | addr, | |
| 448 | std.debug.FormatStackTrace{ | |
| 449 | .stack_trace = stack_trace, | |
| 450 | .tty_config = tty_config, | |
| 451 | }, | |
| 444 | addr, std.debug.FormatStackTrace{ .stack_trace = stack_trace }, | |
| 452 | 445 | }); |
| 453 | 446 | leaks += 1; |
| 454 | 447 | } |
| ... | ... | @@ -460,8 +453,6 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 460 | 453 | |
| 461 | 454 | /// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected). |
| 462 | 455 | pub fn detectLeaks(self: *Self) usize { |
| 463 | const tty_config = self.tty_config; | |
| 464 | ||
| 465 | 456 | var leaks: usize = 0; |
| 466 | 457 | |
| 467 | 458 | for (self.buckets, 0..) |init_optional_bucket, size_class_index| { |
| ... | ... | @@ -469,7 +460,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 469 | 460 | const slot_count = slot_counts[size_class_index]; |
| 470 | 461 | const used_bits_count = usedBitsCount(slot_count); |
| 471 | 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 | 464 | optional_bucket = bucket.prev; |
| 474 | 465 | } |
| 475 | 466 | } |
| ... | ... | @@ -480,10 +471,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 480 | 471 | const stack_trace = large_alloc.getStackTrace(.alloc); |
| 481 | 472 | log.err("memory address 0x{x} leaked: {f}", .{ |
| 482 | 473 | @intFromPtr(large_alloc.bytes.ptr), |
| 483 | std.debug.FormatStackTrace{ | |
| 484 | .stack_trace = stack_trace, | |
| 485 | .tty_config = tty_config, | |
| 486 | }, | |
| 474 | std.debug.FormatStackTrace{ .stack_trace = stack_trace }, | |
| 487 | 475 | }); |
| 488 | 476 | leaks += 1; |
| 489 | 477 | } |
| ... | ... | @@ -535,28 +523,14 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 535 | 523 | @memset(addr_buf[@min(st.index, addr_buf.len)..], 0); |
| 536 | 524 | } |
| 537 | 525 | |
| 538 | fn reportDoubleFree( | |
| 539 | tty_config: std.Io.tty.Config, | |
| 540 | ret_addr: usize, | |
| 541 | alloc_stack_trace: StackTrace, | |
| 542 | free_stack_trace: StackTrace, | |
| 543 | ) void { | |
| 526 | fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { | |
| 544 | 527 | @branchHint(.cold); |
| 545 | 528 | var addr_buf: [stack_n]usize = undefined; |
| 546 | 529 | const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); |
| 547 | 530 | log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ |
| 548 | std.debug.FormatStackTrace{ | |
| 549 | .stack_trace = alloc_stack_trace, | |
| 550 | .tty_config = tty_config, | |
| 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 | }, | |
| 531 | std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace }, | |
| 532 | std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, | |
| 533 | std.debug.FormatStackTrace{ .stack_trace = second_free_stack_trace }, | |
| 560 | 534 | }); |
| 561 | 535 | } |
| 562 | 536 | |
| ... | ... | @@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 587 | 561 | |
| 588 | 562 | if (config.retain_metadata and entry.value_ptr.freed) { |
| 589 | 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 | 565 | @panic("Unrecoverable double free"); |
| 592 | 566 | } else { |
| 593 | 567 | unreachable; |
| ... | ... | @@ -598,18 +572,11 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 598 | 572 | @branchHint(.cold); |
| 599 | 573 | var addr_buf: [stack_n]usize = undefined; |
| 600 | 574 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); |
| 601 | const tty_config = self.tty_config; | |
| 602 | 575 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 603 | 576 | entry.value_ptr.bytes.len, |
| 604 | 577 | old_mem.len, |
| 605 | std.debug.FormatStackTrace{ | |
| 606 | .stack_trace = entry.value_ptr.getStackTrace(.alloc), | |
| 607 | .tty_config = tty_config, | |
| 608 | }, | |
| 609 | std.debug.FormatStackTrace{ | |
| 610 | .stack_trace = free_stack_trace, | |
| 611 | .tty_config = tty_config, | |
| 612 | }, | |
| 578 | std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, | |
| 579 | std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, | |
| 613 | 580 | }); |
| 614 | 581 | } |
| 615 | 582 | |
| ... | ... | @@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 701 | 668 | |
| 702 | 669 | if (config.retain_metadata and entry.value_ptr.freed) { |
| 703 | 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 | 672 | return; |
| 706 | 673 | } else { |
| 707 | 674 | unreachable; |
| ... | ... | @@ -712,18 +679,11 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 712 | 679 | @branchHint(.cold); |
| 713 | 680 | var addr_buf: [stack_n]usize = undefined; |
| 714 | 681 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); |
| 715 | const tty_config = self.tty_config; | |
| 716 | 682 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 717 | 683 | entry.value_ptr.bytes.len, |
| 718 | 684 | old_mem.len, |
| 719 | std.debug.FormatStackTrace{ | |
| 720 | .stack_trace = entry.value_ptr.getStackTrace(.alloc), | |
| 721 | .tty_config = tty_config, | |
| 722 | }, | |
| 723 | std.debug.FormatStackTrace{ | |
| 724 | .stack_trace = free_stack_trace, | |
| 725 | .tty_config = tty_config, | |
| 726 | }, | |
| 685 | std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) }, | |
| 686 | std.debug.FormatStackTrace{ .stack_trace = free_stack_trace }, | |
| 727 | 687 | }); |
| 728 | 688 | } |
| 729 | 689 | |
| ... | ... | @@ -924,7 +884,6 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 924 | 884 | if (!is_used) { |
| 925 | 885 | if (config.safety) { |
| 926 | 886 | reportDoubleFree( |
| 927 | self.tty_config, | |
| 928 | 887 | return_address, |
| 929 | 888 | bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 930 | 889 | bucketStackTrace(bucket, slot_count, slot_index, .free), |
| ... | ... | @@ -946,34 +905,24 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 946 | 905 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); |
| 947 | 906 | if (old_memory.len != requested_size) { |
| 948 | 907 | @branchHint(.cold); |
| 949 | const tty_config = self.tty_config; | |
| 950 | 908 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 951 | 909 | requested_size, |
| 952 | 910 | old_memory.len, |
| 953 | 911 | std.debug.FormatStackTrace{ |
| 954 | 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 | 917 | if (alignment != slot_alignment) { |
| 964 | 918 | @branchHint(.cold); |
| 965 | const tty_config = self.tty_config; | |
| 966 | 919 | log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ |
| 967 | 920 | slot_alignment.toByteUnits(), |
| 968 | 921 | alignment.toByteUnits(), |
| 969 | 922 | std.debug.FormatStackTrace{ |
| 970 | 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 | 989 | const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0; |
| 1041 | 990 | if (!is_used) { |
| 1042 | 991 | reportDoubleFree( |
| 1043 | self.tty_config, | |
| 1044 | 992 | return_address, |
| 1045 | 993 | bucketStackTrace(bucket, slot_count, slot_index, .alloc), |
| 1046 | 994 | bucketStackTrace(bucket, slot_count, slot_index, .free), |
| ... | ... | @@ -1058,34 +1006,24 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 1058 | 1006 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); |
| 1059 | 1007 | if (memory.len != requested_size) { |
| 1060 | 1008 | @branchHint(.cold); |
| 1061 | const tty_config = self.tty_config; | |
| 1062 | 1009 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 1063 | 1010 | requested_size, |
| 1064 | 1011 | memory.len, |
| 1065 | 1012 | std.debug.FormatStackTrace{ |
| 1066 | 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 | 1018 | if (alignment != slot_alignment) { |
| 1076 | 1019 | @branchHint(.cold); |
| 1077 | const tty_config = self.tty_config; | |
| 1078 | 1020 | log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ |
| 1079 | 1021 | slot_alignment.toByteUnits(), |
| 1080 | 1022 | alignment.toByteUnits(), |
| 1081 | 1023 | std.debug.FormatStackTrace{ |
| 1082 | 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 | 15 | //! |
| 16 | 16 | //! For an example implementation of the `logFn` function, see `defaultLog`, |
| 17 | 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 | 20 | //! error: this is an error |
| 21 | 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 | 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 | 83 | /// The default implementation for the log function. Custom log functions may |
| 86 | 84 | /// forward log messages to this function. |
| 87 | 85 | /// |
| ... | ... | @@ -93,36 +91,64 @@ pub fn defaultLog( |
| 93 | 91 | comptime format: []const u8, |
| 94 | 92 | args: anytype, |
| 95 | 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 | 101 | comptime level: Level, |
| 101 | 102 | comptime scope: @EnumLiteral(), |
| 102 | 103 | comptime format: []const u8, |
| 103 | 104 | args: anytype, |
| 104 | io: std.Io, | |
| 105 | fw: *std.Io.File.Writer, | |
| 105 | 106 | ) void { |
| 106 | var buffer: [64]u8 = undefined; | |
| 107 | const stderr, const ttyconf = io.lockStderrWriter(&buffer); | |
| 108 | defer io.unlockStderrWriter(); | |
| 109 | ttyconf.setColor(stderr, switch (level) { | |
| 107 | fw.setColor(switch (level) { | |
| 110 | 108 | .err => .red, |
| 111 | 109 | .warn => .yellow, |
| 112 | 110 | .info => .green, |
| 113 | 111 | .debug => .magenta, |
| 114 | 112 | }) catch {}; |
| 115 | ttyconf.setColor(stderr, .bold) catch {}; | |
| 116 | stderr.writeAll(level.asText()) catch return; | |
| 117 | ttyconf.setColor(stderr, .reset) catch {}; | |
| 118 | ttyconf.setColor(stderr, .dim) catch {}; | |
| 119 | ttyconf.setColor(stderr, .bold) catch {}; | |
| 113 | fw.setColor(.bold) catch {}; | |
| 114 | fw.interface.writeAll(level.asText()) catch return; | |
| 115 | fw.setColor(.reset) catch {}; | |
| 116 | fw.setColor(.dim) catch {}; | |
| 117 | fw.setColor(.bold) catch {}; | |
| 120 | 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; | |
| 124 | ttyconf.setColor(stderr, .reset) catch {}; | |
| 125 | stderr.print(format ++ "\n", args) catch return; | |
| 151 | return new_args; | |
| 126 | 152 | } |
| 127 | 153 | |
| 128 | 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 | 439 | } |
| 440 | 440 | |
| 441 | 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 | 443 | if (native_os == .windows) { |
| 444 | 444 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 445 | 445 | return getenvW(key_w) != null; |
| 446 | 446 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 447 | @compileError("hasEnvVarConstant is not supported for WASI without libc"); | |
| 447 | return false; | |
| 448 | 448 | } else { |
| 449 | 449 | return posix.getenv(key) != null; |
| 450 | 450 | } |
| 451 | 451 | } |
| 452 | 452 | |
| 453 | 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 | 455 | if (native_os == .windows) { |
| 456 | 456 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 457 | 457 | const value = getenvW(key_w) orelse return false; |
| 458 | 458 | return value.len != 0; |
| 459 | 459 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 460 | @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc"); | |
| 460 | return false; | |
| 461 | 461 | } else { |
| 462 | 462 | const value = posix.getenv(key) orelse return false; |
| 463 | 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 | 247 | threaded.stack_size = thread_stack_size; |
| 248 | 248 | const io = threaded.io(); |
| 249 | 249 | |
| 250 | debug_allocator.tty_config = .detect(io, .stderr()); | |
| 251 | ||
| 252 | 250 | const cmd = args[1]; |
| 253 | 251 | const cmd_args = args[2..]; |
| 254 | 252 | if (mem.eql(u8, cmd, "build-exe")) { |