mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 04:24:33 +01:00
std: rework TTY detection and printing
This commit sketches an idea for how to deal with detection of file streams as being terminals. When a File stream is a terminal, writes through the stream should have their escapes stripped unless the programmer explicitly enables terminal escapes. Furthermore, the programmer needs a convenient API for intentionally outputting escapes into the stream. In particular it should be possible to set colors that are silently discarded when the stream is not a terminal. This commit makes `Io.File.Writer` track the terminal mode in the already-existing `mode` field, making it the appropriate place to implement escape stripping. `Io.lockStderrWriter` returns a `*Io.File.Writer` with terminal detection already done by default. This is a higher-level application layer stream for writing to stderr. Meanwhile, `std.debug.lockStderrWriter` also returns a `*Io.File.Writer` but a lower-level one that is hard-coded to use a static single-threaded `std.Io.Threaded` instance. This is the same instance that is used for collecting debug information and iterating the unwind info.
This commit is contained in:
parent
78d262d96e
commit
ffcbd48a12
10 changed files with 448 additions and 400 deletions
|
|
@ -82,8 +82,6 @@ pub const Limit = enum(usize) {
|
|||
pub const Reader = @import("Io/Reader.zig");
|
||||
pub const Writer = @import("Io/Writer.zig");
|
||||
|
||||
pub const tty = @import("Io/tty.zig");
|
||||
|
||||
pub fn poll(
|
||||
gpa: Allocator,
|
||||
comptime StreamEnum: type,
|
||||
|
|
@ -535,7 +533,6 @@ test {
|
|||
_ = net;
|
||||
_ = Reader;
|
||||
_ = Writer;
|
||||
_ = tty;
|
||||
_ = Evented;
|
||||
_ = Threaded;
|
||||
_ = @import("Io/test.zig");
|
||||
|
|
@ -720,6 +717,9 @@ pub const VTable = struct {
|
|||
|
||||
processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
|
||||
processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
|
||||
lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer,
|
||||
tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer,
|
||||
unlockStderrWriter: *const fn (?*anyopaque) void,
|
||||
|
||||
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
|
||||
sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
|
||||
|
|
@ -740,10 +740,6 @@ pub const VTable = struct {
|
|||
netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
|
||||
netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
|
||||
netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
|
||||
|
||||
lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer,
|
||||
tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer,
|
||||
unlockStderrWriter: *const fn (?*anyopaque) void,
|
||||
};
|
||||
|
||||
pub const Cancelable = error{
|
||||
|
|
@ -2186,13 +2182,17 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
|
|||
///
|
||||
/// See also:
|
||||
/// * `tryLockStderrWriter`
|
||||
pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer {
|
||||
return io.vtable.lockStderrWriter(io.userdata, buffer);
|
||||
pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
|
||||
const result = try io.vtable.lockStderrWriter(io.userdata, buffer);
|
||||
result.io = io;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Same as `lockStderrWriter` but uncancelable and non-blocking.
|
||||
pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer {
|
||||
return io.vtable.tryLockStderrWriter(io.userdata, buffer);
|
||||
pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
|
||||
const result = io.vtable.tryLockStderrWriter(io.userdata, buffer) orelse return null;
|
||||
result.io = io;
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter(io: Io) void {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const Writer = @This();
|
||||
const builtin = @import("builtin");
|
||||
const is_windows = builtin.os.tag == .windows;
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const Io = std.Io;
|
||||
|
|
@ -16,7 +18,144 @@ write_file_err: ?WriteFileError = null,
|
|||
seek_err: ?SeekError = null,
|
||||
interface: Io.Writer,
|
||||
|
||||
pub const Mode = File.Reader.Mode;
|
||||
pub const Mode = union(enum) {
|
||||
/// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal.
|
||||
/// `setColor` does nothing.
|
||||
streaming,
|
||||
/// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal.
|
||||
/// `setColor` does nothing.
|
||||
positional,
|
||||
/// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor`
|
||||
/// does nothing.
|
||||
streaming_simple,
|
||||
/// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor`
|
||||
/// does nothing.
|
||||
positional_simple,
|
||||
/// It's a terminal. Writes are escaped so as to strip escape sequences.
|
||||
/// Color is enabled.
|
||||
terminal_escaped,
|
||||
/// It's a terminal. Colors are enabled via calling
|
||||
/// SetConsoleTextAttribute. Writes are not escaped.
|
||||
terminal_winapi: TerminalWinapi,
|
||||
/// Indicates writing cannot continue because of a seek failure.
|
||||
failure,
|
||||
|
||||
pub fn toStreaming(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .streaming => .streaming,
|
||||
.positional_simple, .streaming_simple => .streaming_simple,
|
||||
inline else => |_, x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toSimple(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .positional_simple => .positional_simple,
|
||||
.streaming, .streaming_simple => .streaming_simple,
|
||||
inline else => |x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toUnescaped(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.terminal_escaped => .streaming_simple,
|
||||
inline else => |x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub const TerminalWinapi = if (!is_windows) noreturn else struct {
|
||||
handle: File.Handle,
|
||||
reset_attributes: u16,
|
||||
};
|
||||
|
||||
/// Detect suitable TTY configuration options for the given file (commonly
|
||||
/// stdout/stderr).
|
||||
///
|
||||
/// Will attempt to enable ANSI escape code support if necessary/possible.
|
||||
pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode {
|
||||
if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback;
|
||||
|
||||
if (file.enableAnsiEscapeCodes(io)) |_| {
|
||||
return .terminal_escaped;
|
||||
} else |err| switch (err) {
|
||||
error.Canceled => return error.Canceled,
|
||||
error.NotTerminalDevice, error.Unexpected => {},
|
||||
}
|
||||
|
||||
if (is_windows and file.isTty(io)) {
|
||||
const windows = std.os.windows;
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) {
|
||||
return .{ .terminal_winapi = .{
|
||||
.handle = file.handle,
|
||||
.reset_attributes = info.wAttributes,
|
||||
} };
|
||||
}
|
||||
return .terminal_escaped;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
|
||||
|
||||
pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) Mode.SetColorError!void {
|
||||
switch (mode) {
|
||||
.streaming, .positional, .streaming_simple, .positional_simple, .failure => return,
|
||||
.terminal_escaped => {
|
||||
const color_string = switch (color) {
|
||||
.black => "\x1b[30m",
|
||||
.red => "\x1b[31m",
|
||||
.green => "\x1b[32m",
|
||||
.yellow => "\x1b[33m",
|
||||
.blue => "\x1b[34m",
|
||||
.magenta => "\x1b[35m",
|
||||
.cyan => "\x1b[36m",
|
||||
.white => "\x1b[37m",
|
||||
.bright_black => "\x1b[90m",
|
||||
.bright_red => "\x1b[91m",
|
||||
.bright_green => "\x1b[92m",
|
||||
.bright_yellow => "\x1b[93m",
|
||||
.bright_blue => "\x1b[94m",
|
||||
.bright_magenta => "\x1b[95m",
|
||||
.bright_cyan => "\x1b[96m",
|
||||
.bright_white => "\x1b[97m",
|
||||
.bold => "\x1b[1m",
|
||||
.dim => "\x1b[2m",
|
||||
.reset => "\x1b[0m",
|
||||
};
|
||||
try io_w.writeAll(color_string);
|
||||
},
|
||||
.terminal_winapi => |ctx| {
|
||||
const windows = std.os.windows;
|
||||
const attributes: windows.WORD = switch (color) {
|
||||
.black => 0,
|
||||
.red => windows.FOREGROUND_RED,
|
||||
.green => windows.FOREGROUND_GREEN,
|
||||
.yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
|
||||
.blue => windows.FOREGROUND_BLUE,
|
||||
.magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
|
||||
.cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
|
||||
.white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
|
||||
.bright_black => windows.FOREGROUND_INTENSITY,
|
||||
.bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
|
||||
.bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
|
||||
.bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
|
||||
.bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
// "dim" is not supported using basic character attributes, but let's still make it do *something*.
|
||||
// This matches the old behavior of TTY.Color before the bright variants were added.
|
||||
.dim => windows.FOREGROUND_INTENSITY,
|
||||
.reset => ctx.reset_attributes,
|
||||
};
|
||||
try io_w.flush();
|
||||
try windows.SetConsoleTextAttribute(ctx.handle, attributes);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Error = error{
|
||||
DiskQuota,
|
||||
|
|
@ -74,6 +213,16 @@ pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer {
|
|||
};
|
||||
}
|
||||
|
||||
/// Detects if `file` is terminal and sets the mode accordingly.
|
||||
pub fn initDetect(file: File, io: Io, buffer: []u8) Io.Cancelable!Writer {
|
||||
return .{
|
||||
.io = io,
|
||||
.file = file,
|
||||
.interface = initInterface(buffer),
|
||||
.mode = try .detect(io, file, true, .positional),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initInterface(buffer: []u8) Io.Writer {
|
||||
return .{
|
||||
.vtable = &.{
|
||||
|
|
@ -99,8 +248,9 @@ pub fn moveToReader(w: *Writer) File.Reader {
|
|||
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
switch (w.mode) {
|
||||
.positional, .positional_reading => return drainPositional(w, data, splat),
|
||||
.streaming, .streaming_reading => return drainStreaming(w, data, splat),
|
||||
.positional, .positional_simple => return drainPositional(w, data, splat),
|
||||
.streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat),
|
||||
.terminal_escaped => return drainEscaping(w, data, splat),
|
||||
.failure => return error.WriteFailed,
|
||||
}
|
||||
}
|
||||
|
|
@ -141,13 +291,38 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.
|
|||
return w.interface.consume(n);
|
||||
}
|
||||
|
||||
fn findTerminalEscape(buffer: []const u8) ?usize {
|
||||
return std.mem.findScalar(u8, buffer, 0x1b);
|
||||
}
|
||||
|
||||
fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
|
||||
const io = w.io;
|
||||
const header = w.interface.buffered();
|
||||
if (findTerminalEscape(header)) |i| {
|
||||
_ = i;
|
||||
@panic("TODO strip terminal escape sequence");
|
||||
}
|
||||
for (data) |d| {
|
||||
if (findTerminalEscape(d)) |i| {
|
||||
_ = i;
|
||||
@panic("TODO strip terminal escape sequence");
|
||||
}
|
||||
}
|
||||
const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return w.interface.consume(n);
|
||||
}
|
||||
|
||||
pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
switch (w.mode) {
|
||||
.positional => return sendFilePositional(w, file_reader, limit),
|
||||
.positional_reading => return error.Unimplemented,
|
||||
.positional_simple => return error.Unimplemented,
|
||||
.streaming => return sendFileStreaming(w, file_reader, limit),
|
||||
.streaming_reading => return error.Unimplemented,
|
||||
.streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented,
|
||||
.failure => return error.WriteFailed,
|
||||
}
|
||||
}
|
||||
|
|
@ -214,10 +389,10 @@ pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {
|
|||
assert(w.interface.buffered().len == 0);
|
||||
const io = w.io;
|
||||
switch (w.mode) {
|
||||
.positional, .positional_reading => {
|
||||
.positional, .positional_simple => {
|
||||
w.pos = offset;
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
.streaming, .streaming_simple, .terminal_escaped, .terminal_winapi => {
|
||||
if (w.seek_err) |err| return err;
|
||||
io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {
|
||||
w.seek_err = err;
|
||||
|
|
@ -243,15 +418,65 @@ pub fn end(w: *Writer) EndError!void {
|
|||
try w.interface.flush();
|
||||
switch (w.mode) {
|
||||
.positional,
|
||||
.positional_reading,
|
||||
.positional_simple,
|
||||
=> w.file.setLength(io, w.pos) catch |err| switch (err) {
|
||||
error.NonResizable => return,
|
||||
else => |e| return e,
|
||||
},
|
||||
|
||||
.streaming,
|
||||
.streaming_reading,
|
||||
.streaming_simple,
|
||||
.failure,
|
||||
=> {},
|
||||
}
|
||||
}
|
||||
|
||||
pub const Color = enum {
|
||||
black,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white,
|
||||
dim,
|
||||
bold,
|
||||
reset,
|
||||
};
|
||||
|
||||
pub const SetColorError = Mode.SetColorError;
|
||||
|
||||
pub fn setColor(w: *Writer, color: Color) SetColorError!void {
|
||||
return w.mode.setColor(&w.interface, color);
|
||||
}
|
||||
|
||||
pub fn disableEscape(w: *Writer) Mode {
|
||||
const prev = w.mode;
|
||||
w.mode = w.mode.toUnescaped();
|
||||
return prev;
|
||||
}
|
||||
|
||||
pub fn restoreEscape(w: *Writer, mode: Mode) void {
|
||||
w.mode = mode;
|
||||
}
|
||||
|
||||
pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Error!void {
|
||||
const prev_mode = w.disableEscape();
|
||||
defer w.restoreEscape(prev_mode);
|
||||
return w.interface.writeAll(bytes);
|
||||
}
|
||||
|
||||
pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Error!void {
|
||||
const prev_mode = w.disableEscape();
|
||||
defer w.restoreEscape(prev_mode);
|
||||
return w.interface.print(fmt, args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,13 @@ use_sendfile: UseSendfile = .default,
|
|||
use_copy_file_range: UseCopyFileRange = .default,
|
||||
use_fcopyfile: UseFcopyfile = .default,
|
||||
|
||||
stderr_writer: Io.Writer,
|
||||
stderr_writer: File.Writer = .{
|
||||
.io = undefined,
|
||||
.interface = Io.File.Writer.initInterface(&.{}),
|
||||
.file = if (is_windows) undefined else .stderr(),
|
||||
.mode = undefined,
|
||||
},
|
||||
stderr_writer_initialized: bool = false,
|
||||
|
||||
pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
|
||||
enabled,
|
||||
|
|
@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io {
|
|||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
.lockStderrWriter = lockStderrWriter,
|
||||
.tryLockStderrWriter = tryLockStderrWriter,
|
||||
.unlockStderrWriter = unlockStderrWriter,
|
||||
|
||||
.now = now,
|
||||
.sleep = sleep,
|
||||
|
|
@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io {
|
|||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
.lockStderrWriter = lockStderrWriter,
|
||||
.tryLockStderrWriter = tryLockStderrWriter,
|
||||
.unlockStderrWriter = unlockStderrWriter,
|
||||
|
||||
.now = now,
|
||||
.sleep = sleep,
|
||||
|
|
@ -9516,33 +9528,42 @@ fn netLookupFallible(
|
|||
return error.OptionUnsupported;
|
||||
}
|
||||
|
||||
fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer {
|
||||
fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
Io.stderr_thread_mutex.lock();
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
if (!t.stderr_writer_initialized) {
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple);
|
||||
t.stderr_writer_initialized = true;
|
||||
}
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.buffer = buffer;
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
t.stderr_writer.interface.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
}
|
||||
|
||||
fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer {
|
||||
fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
if (!Io.stderr_thread_mutex.tryLock()) return null;
|
||||
std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {};
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.buffer = buffer;
|
||||
if (!t.stderr_writer_initialized) {
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple) catch
|
||||
return null;
|
||||
t.stderr_writer_initialized = true;
|
||||
}
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
t.stderr_writer.interface.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
}
|
||||
|
||||
fn unlockStderrWriter(userdata: ?*anyopaque) void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.end = 0;
|
||||
t.stderr_writer.buffer = &.{};
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
t.stderr_writer.interface.end = 0;
|
||||
t.stderr_writer.interface.buffer = &.{};
|
||||
Io.stderr_thread_mutex.unlock();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
const builtin = @import("builtin");
|
||||
const native_os = builtin.os.tag;
|
||||
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = std.Io.File;
|
||||
const process = std.process;
|
||||
const windows = std.os.windows;
|
||||
|
||||
pub const Color = enum {
|
||||
black,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white,
|
||||
dim,
|
||||
bold,
|
||||
reset,
|
||||
};
|
||||
|
||||
/// Provides simple functionality for manipulating the terminal in some way,
|
||||
/// such as coloring text, etc.
|
||||
pub const Config = union(enum) {
|
||||
no_color,
|
||||
escape_codes,
|
||||
windows_api: if (native_os == .windows) WindowsContext else noreturn,
|
||||
|
||||
/// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
|
||||
/// This includes feature checks for ANSI escape codes and the Windows console API, as well as
|
||||
/// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
|
||||
/// Will attempt to enable ANSI escape code support if necessary/possible.
|
||||
pub fn detect(io: Io, file: File) Config {
|
||||
const force_color: ?bool = if (builtin.os.tag == .wasi)
|
||||
null // wasi does not support environment variables
|
||||
else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
|
||||
false
|
||||
else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
|
||||
true
|
||||
else
|
||||
null;
|
||||
|
||||
if (force_color == false) return .no_color;
|
||||
|
||||
if (file.enableAnsiEscapeCodes(io)) |_| {
|
||||
return .escape_codes;
|
||||
} else |_| {}
|
||||
|
||||
if (native_os == .windows and file.isTty()) {
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
|
||||
return if (force_color == true) .escape_codes else .no_color;
|
||||
}
|
||||
return .{ .windows_api = .{
|
||||
.handle = file.handle,
|
||||
.reset_attributes = info.wAttributes,
|
||||
} };
|
||||
}
|
||||
|
||||
return if (force_color == true) .escape_codes else .no_color;
|
||||
}
|
||||
|
||||
pub const WindowsContext = struct {
|
||||
handle: File.Handle,
|
||||
reset_attributes: u16,
|
||||
};
|
||||
|
||||
pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
|
||||
|
||||
pub fn setColor(conf: Config, w: *Io.Writer, color: Color) SetColorError!void {
|
||||
nosuspend switch (conf) {
|
||||
.no_color => return,
|
||||
.escape_codes => {
|
||||
const color_string = switch (color) {
|
||||
.black => "\x1b[30m",
|
||||
.red => "\x1b[31m",
|
||||
.green => "\x1b[32m",
|
||||
.yellow => "\x1b[33m",
|
||||
.blue => "\x1b[34m",
|
||||
.magenta => "\x1b[35m",
|
||||
.cyan => "\x1b[36m",
|
||||
.white => "\x1b[37m",
|
||||
.bright_black => "\x1b[90m",
|
||||
.bright_red => "\x1b[91m",
|
||||
.bright_green => "\x1b[92m",
|
||||
.bright_yellow => "\x1b[93m",
|
||||
.bright_blue => "\x1b[94m",
|
||||
.bright_magenta => "\x1b[95m",
|
||||
.bright_cyan => "\x1b[96m",
|
||||
.bright_white => "\x1b[97m",
|
||||
.bold => "\x1b[1m",
|
||||
.dim => "\x1b[2m",
|
||||
.reset => "\x1b[0m",
|
||||
};
|
||||
try w.writeAll(color_string);
|
||||
},
|
||||
.windows_api => |ctx| {
|
||||
const attributes = switch (color) {
|
||||
.black => 0,
|
||||
.red => windows.FOREGROUND_RED,
|
||||
.green => windows.FOREGROUND_GREEN,
|
||||
.yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
|
||||
.blue => windows.FOREGROUND_BLUE,
|
||||
.magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
|
||||
.cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
|
||||
.white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
|
||||
.bright_black => windows.FOREGROUND_INTENSITY,
|
||||
.bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
|
||||
.bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
|
||||
.bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
|
||||
.bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
.bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||
// "dim" is not supported using basic character attributes, but let's still make it do *something*.
|
||||
// This matches the old behavior of TTY.Color before the bright variants were added.
|
||||
.dim => windows.FOREGROUND_INTENSITY,
|
||||
.reset => ctx.reset_attributes,
|
||||
};
|
||||
try w.flush();
|
||||
try windows.SetConsoleTextAttribute(ctx.handle, attributes);
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -755,10 +755,9 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
|
|||
}
|
||||
}
|
||||
|
||||
fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void {
|
||||
pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) anyerror!void {
|
||||
if (noop_impl or !global_progress.need_clear) return;
|
||||
|
||||
try w.writeAll(clear ++ progress_remove);
|
||||
try file_writer.interface.writeAllUnescaped(clear ++ progress_remove);
|
||||
global_progress.need_clear = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const std = @import("std.zig");
|
||||
const Io = std.Io;
|
||||
const Writer = std.Io.Writer;
|
||||
const tty = std.Io.tty;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const posix = std.posix;
|
||||
|
|
@ -262,6 +261,10 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
|
|||
else => true,
|
||||
};
|
||||
|
||||
/// This is used for debug information and debug printing. It is intentionally
|
||||
/// separate from the application's `Io` instance.
|
||||
var static_single_threaded_io: Io.Threaded = .init_single_threaded;
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
|
||||
///
|
||||
/// 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) {
|
|||
///
|
||||
/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
|
||||
/// the application's chosen `Io` implementation.
|
||||
pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
|
||||
Io.stderr_thread_mutex.lock();
|
||||
const w = std.Progress.lockStderrWriter(buffer);
|
||||
// The stderr lock also locks access to `global.conf`.
|
||||
if (StderrWriter.singleton.tty_config == null) {
|
||||
StderrWriter.singleton.tty_config = .detect(io, .stderr());
|
||||
}
|
||||
return .{ w, global.conf.? };
|
||||
pub fn lockStderrWriter(buffer: []u8) *File.Writer {
|
||||
return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter() void {
|
||||
std.Progress.unlockStderrWriter();
|
||||
static_single_threaded_io.ioBasic().unlockStderrWriter();
|
||||
}
|
||||
|
||||
/// Writes to stderr, ignoring errors.
|
||||
|
|
@ -305,39 +302,13 @@ pub fn unlockStderrWriter() void {
|
|||
/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
|
||||
/// integrate with the application's chosen `Io` implementation.
|
||||
pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const bw, _ = lockStderrWriter(&buffer);
|
||||
defer unlockStderrWriter();
|
||||
nosuspend bw.print(fmt, args) catch return;
|
||||
}
|
||||
|
||||
const StderrWriter = struct {
|
||||
interface: Writer,
|
||||
tty_config: ?tty.Config,
|
||||
|
||||
var singleton: StderrWriter = .{
|
||||
.interface = .{
|
||||
.buffer = &.{},
|
||||
.vtable = &.{ .drain = drain },
|
||||
},
|
||||
.tty_config = null,
|
||||
};
|
||||
|
||||
fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
var n: usize = 0;
|
||||
const header = w.interface.buffered();
|
||||
if (header.len != 0) n += try std.Io.Threaded.debugWrite(header);
|
||||
for (data[0 .. data.len - 1]) |d| {
|
||||
if (d.len != 0) n += try std.Io.Threaded.debugWrite(d);
|
||||
}
|
||||
const pattern = data[data.len - 1];
|
||||
if (pattern.len != 0) {
|
||||
for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern);
|
||||
}
|
||||
return io_w.consume(n);
|
||||
nosuspend {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr = lockStderrWriter(&buffer);
|
||||
defer unlockStderrWriter();
|
||||
stderr.interface.print(fmt, args) catch return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Marked `inline` to propagate a comptime-known error to callers.
|
||||
pub inline fn getSelfDebugInfo() !*SelfInfo {
|
||||
|
|
@ -357,16 +328,16 @@ pub fn dumpHex(bytes: []const u8) void {
|
|||
}
|
||||
|
||||
/// Prints a hexadecimal view of the bytes, returning any error that occurs.
|
||||
pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void {
|
||||
pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void {
|
||||
var chunks = mem.window(u8, bytes, 16, 16);
|
||||
while (chunks.next()) |window| {
|
||||
// 1. Print the address.
|
||||
const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
|
||||
try tty_config.setColor(bw, .dim);
|
||||
try fwm.setColor(bw, .dim);
|
||||
// We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
|
||||
// Also, make sure all lines are aligned by padding the address.
|
||||
try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
|
||||
try tty_config.setColor(bw, .reset);
|
||||
try fwm.setColor(bw, .reset);
|
||||
|
||||
// 2. Print the bytes.
|
||||
for (window, 0..) |byte, index| {
|
||||
|
|
@ -386,7 +357,7 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
|
|||
try bw.writeByte(byte);
|
||||
} else {
|
||||
// Related: https://github.com/ziglang/zig/issues/7600
|
||||
if (tty_config == .windows_api) {
|
||||
if (fwm == .terminal_winapi) {
|
||||
try bw.writeByte('.');
|
||||
continue;
|
||||
}
|
||||
|
|
@ -408,11 +379,11 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
|
|||
|
||||
test dumpHexFallible {
|
||||
const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
|
||||
var aw: Writer.Allocating = .init(std.testing.allocator);
|
||||
var aw: Writer.Allocating = .init(testing.allocator);
|
||||
defer aw.deinit();
|
||||
|
||||
try dumpHexFallible(&aw.writer, .no_color, bytes);
|
||||
const expected = try std.fmt.allocPrint(std.testing.allocator,
|
||||
const expected = try std.fmt.allocPrint(testing.allocator,
|
||||
\\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
|
||||
\\{x:0>[2]} 01 12 13 ...
|
||||
\\
|
||||
|
|
@ -421,8 +392,8 @@ test dumpHexFallible {
|
|||
@intFromPtr(bytes.ptr) + 16,
|
||||
@sizeOf(usize) * 2,
|
||||
});
|
||||
defer std.testing.allocator.free(expected);
|
||||
try std.testing.expectEqualStrings(expected, aw.written());
|
||||
defer testing.allocator.free(expected);
|
||||
try testing.expectEqualStrings(expected, aw.written());
|
||||
}
|
||||
|
||||
/// 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
|
|||
/// away, and in fact the optimizer is able to use the assertion in its
|
||||
/// heuristics.
|
||||
///
|
||||
/// Inside a test block, it is best to use the `std.testing` module rather than
|
||||
/// Inside a test block, it is best to use the `testing` module rather than
|
||||
/// this function, because this function may not detect a test failure in
|
||||
/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
|
||||
/// function is the correct function to use.
|
||||
|
|
@ -574,26 +545,26 @@ pub fn defaultPanic(
|
|||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
trace: {
|
||||
const stderr, const tty_config = lockStderrWriter(&.{});
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
|
||||
if (builtin.single_threaded) {
|
||||
stderr.print("panic: ", .{}) catch break :trace;
|
||||
stderr.interface.print("panic: ", .{}) catch break :trace;
|
||||
} else {
|
||||
const current_thread_id = std.Thread.getCurrentId();
|
||||
stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
|
||||
stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
|
||||
}
|
||||
stderr.print("{s}\n", .{msg}) catch break :trace;
|
||||
stderr.interface.print("{s}\n", .{msg}) catch break :trace;
|
||||
|
||||
if (@errorReturnTrace()) |t| if (t.index > 0) {
|
||||
stderr.writeAll("error return context:\n") catch break :trace;
|
||||
writeStackTrace(t, stderr, tty_config) catch break :trace;
|
||||
stderr.writeAll("\nstack trace:\n") catch break :trace;
|
||||
stderr.interface.writeAll("error return context:\n") catch break :trace;
|
||||
writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace;
|
||||
stderr.interface.writeAll("\nstack trace:\n") catch break :trace;
|
||||
};
|
||||
writeCurrentStackTrace(.{
|
||||
.first_address = first_trace_addr orelse @returnAddress(),
|
||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||
}, stderr, tty_config) catch break :trace;
|
||||
}, &stderr.interface, stderr.mode) catch break :trace;
|
||||
}
|
||||
|
||||
waitForOtherThreadToFinishPanicking();
|
||||
|
|
@ -603,8 +574,8 @@ pub fn defaultPanic(
|
|||
// A panic happened while trying to print a previous panic message.
|
||||
// We're still holding the mutex but that's fine as we're going to
|
||||
// call abort().
|
||||
const stderr, _ = lockStderrWriter(&.{});
|
||||
stderr.writeAll("aborting due to recursive panic\n") catch {};
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
|
||||
},
|
||||
else => {}, // Panicked while printing the recursive panic message.
|
||||
}
|
||||
|
|
@ -651,8 +622,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
|
|||
defer it.deinit();
|
||||
if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
|
||||
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
const io = static_single_threaded_io.ioBasic();
|
||||
|
||||
var total_frames: usize = 0;
|
||||
var index: usize = 0;
|
||||
|
|
@ -686,36 +656,34 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
|
|||
/// Write the current stack trace to `writer`, annotated with source locations.
|
||||
///
|
||||
/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
|
||||
pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
|
||||
pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
|
||||
if (!std.options.allow_stack_tracing) {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
}
|
||||
const di_gpa = getDebugInfoAllocator();
|
||||
const di = getSelfDebugInfo() catch |err| switch (err) {
|
||||
error.UnsupportedTarget => {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
},
|
||||
};
|
||||
var it: StackIterator = .init(options.context);
|
||||
defer it.deinit();
|
||||
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
}
|
||||
var total_frames: usize = 0;
|
||||
var wait_for = options.first_address;
|
||||
var printed_any_frame = false;
|
||||
const io = static_single_threaded_io.ioBasic();
|
||||
while (true) switch (it.next(io)) {
|
||||
.switch_to_fp => |unwind_error| {
|
||||
switch (StackIterator.fp_usability) {
|
||||
|
|
@ -733,31 +701,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
error.Unexpected => "unexpected error",
|
||||
};
|
||||
if (it.stratOk(options.allow_unsafe_unwind)) {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print(
|
||||
"Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
|
||||
.{ module_name, unwind_error.address, caption },
|
||||
);
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
} else {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print(
|
||||
"Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
|
||||
.{ module_name, unwind_error.address, caption },
|
||||
);
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
}
|
||||
},
|
||||
.end => break,
|
||||
.frame => |ret_addr| {
|
||||
if (total_frames > 10_000) {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print(
|
||||
"Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
|
||||
.{total_frames},
|
||||
);
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
}
|
||||
total_frames += 1;
|
||||
|
|
@ -767,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
}
|
||||
// `ret_addr` is the return address, which is *after* the function call.
|
||||
// Subtract 1 to get an address *in* the function call for a better source location.
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
|
||||
printed_any_frame = true;
|
||||
},
|
||||
};
|
||||
|
|
@ -775,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
}
|
||||
/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
|
||||
pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
|
||||
const stderr, const tty_config = lockStderrWriter(&.{});
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
writeCurrentStackTrace(.{
|
||||
.first_address = a: {
|
||||
|
|
@ -785,33 +753,40 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
|
|||
},
|
||||
.context = options.context,
|
||||
.allow_unsafe_unwind = options.allow_unsafe_unwind,
|
||||
}, stderr, tty_config) catch |err| switch (err) {
|
||||
}, &stderr.interface, stderr.mode) catch |err| switch (err) {
|
||||
error.WriteFailed => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub const FormatStackTrace = struct {
|
||||
stack_trace: StackTrace,
|
||||
tty_config: tty.Config,
|
||||
|
||||
pub fn format(context: @This(), writer: *Writer) Writer.Error!void {
|
||||
try writer.writeAll("\n");
|
||||
try writeStackTrace(&context.stack_trace, writer, context.tty_config);
|
||||
pub const Decorated = struct {
|
||||
stack_trace: StackTrace,
|
||||
file_writer_mode: File.Writer.Mode,
|
||||
|
||||
pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void {
|
||||
try writer.writeByte('\n');
|
||||
try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void {
|
||||
return Decorated.format(.{
|
||||
.stack_trace = context.stack_trace,
|
||||
.file_writer_mode = .streaming,
|
||||
}, writer);
|
||||
}
|
||||
};
|
||||
|
||||
/// Write a previously captured stack trace to `writer`, annotated with source locations.
|
||||
pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
|
||||
pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
|
||||
if (!std.options.allow_stack_tracing) {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
}
|
||||
// We use an independent Io implementation here in case there was a problem
|
||||
// with the application's Io implementation itself.
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
|
||||
// Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
|
||||
// `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
|
||||
|
|
@ -820,22 +795,23 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C
|
|||
const di_gpa = getDebugInfoAllocator();
|
||||
const di = getSelfDebugInfo() catch |err| switch (err) {
|
||||
error.UnsupportedTarget => {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
return;
|
||||
},
|
||||
};
|
||||
const io = static_single_threaded_io.ioBasic();
|
||||
const captured_frames = @min(n_frames, st.instruction_addresses.len);
|
||||
for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
|
||||
// `ret_addr` is the return address, which is *after* the function call.
|
||||
// Subtract 1 to get an address *in* the function call for a better source location.
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
|
||||
}
|
||||
if (n_frames > captured_frames) {
|
||||
tty_config.setColor(writer, .bold) catch {};
|
||||
fwm.setColor(writer, .bold) catch {};
|
||||
try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
}
|
||||
}
|
||||
/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
|
||||
|
|
@ -1143,7 +1119,7 @@ fn printSourceAtAddress(
|
|||
debug_info: *SelfInfo,
|
||||
writer: *Writer,
|
||||
address: usize,
|
||||
tty_config: tty.Config,
|
||||
fwm: File.Writer.Mode,
|
||||
) Writer.Error!void {
|
||||
const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo,
|
||||
|
|
@ -1151,15 +1127,15 @@ fn printSourceAtAddress(
|
|||
error.InvalidDebugInfo,
|
||||
=> .unknown,
|
||||
error.ReadFailed, error.Unexpected, error.Canceled => s: {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
break :s .unknown;
|
||||
},
|
||||
error.OutOfMemory => s: {
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
break :s .unknown;
|
||||
},
|
||||
};
|
||||
|
|
@ -1171,7 +1147,7 @@ fn printSourceAtAddress(
|
|||
address,
|
||||
symbol.name orelse "???",
|
||||
symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
|
||||
tty_config,
|
||||
fwm,
|
||||
);
|
||||
}
|
||||
fn printLineInfo(
|
||||
|
|
@ -1181,10 +1157,10 @@ fn printLineInfo(
|
|||
address: usize,
|
||||
symbol_name: []const u8,
|
||||
compile_unit_name: []const u8,
|
||||
tty_config: tty.Config,
|
||||
fwm: File.Writer.Mode,
|
||||
) Writer.Error!void {
|
||||
nosuspend {
|
||||
tty_config.setColor(writer, .bold) catch {};
|
||||
fwm.setColor(writer, .bold) catch {};
|
||||
|
||||
if (source_location) |*sl| {
|
||||
try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
|
||||
|
|
@ -1192,11 +1168,11 @@ fn printLineInfo(
|
|||
try writer.writeAll("???:?:?");
|
||||
}
|
||||
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
try writer.writeAll(": ");
|
||||
tty_config.setColor(writer, .dim) catch {};
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
try writer.writeAll("\n");
|
||||
|
||||
// Show the matching source code line if possible
|
||||
|
|
@ -1207,9 +1183,9 @@ fn printLineInfo(
|
|||
const space_needed = @as(usize, @intCast(sl.column - 1));
|
||||
|
||||
try writer.splatByteAll(' ', space_needed);
|
||||
tty_config.setColor(writer, .green) catch {};
|
||||
fwm.setColor(writer, .green) catch {};
|
||||
try writer.writeAll("^");
|
||||
tty_config.setColor(writer, .reset) catch {};
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
}
|
||||
try writer.writeAll("\n");
|
||||
} else |_| {
|
||||
|
|
@ -1250,18 +1226,18 @@ fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !
|
|||
}
|
||||
|
||||
test printLineFromFile {
|
||||
const io = std.testing.io;
|
||||
const gpa = std.testing.allocator;
|
||||
const io = testing.io;
|
||||
const gpa = testing.allocator;
|
||||
|
||||
var aw: Writer.Allocating = .init(gpa);
|
||||
defer aw.deinit();
|
||||
const output_stream = &aw.writer;
|
||||
|
||||
const join = std.fs.path.join;
|
||||
const expectError = std.testing.expectError;
|
||||
const expectEqualStrings = std.testing.expectEqualStrings;
|
||||
const expectError = testing.expectError;
|
||||
const expectEqualStrings = testing.expectEqualStrings;
|
||||
|
||||
var test_dir = std.testing.tmpDir(.{});
|
||||
var test_dir = testing.tmpDir(.{});
|
||||
defer test_dir.cleanup();
|
||||
// Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
|
||||
const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
|
||||
|
|
@ -1578,19 +1554,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
|
|||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
trace: {
|
||||
const stderr, const tty_config = lockStderrWriter(&.{});
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
|
||||
if (addr) |a| {
|
||||
stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||
stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||
} else {
|
||||
stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||
stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||
}
|
||||
if (opt_ctx) |context| {
|
||||
writeCurrentStackTrace(.{
|
||||
.context = context,
|
||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||
}, stderr, tty_config) catch break :trace;
|
||||
}, &stderr.interface, stderr.mode) catch break :trace;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1599,8 +1575,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
|
|||
// A segfault happened while trying to print a previous panic message.
|
||||
// We're still holding the mutex but that's fine as we're going to
|
||||
// call abort().
|
||||
const stderr, _ = lockStderrWriter(&.{});
|
||||
stderr.writeAll("aborting due to recursive panic\n") catch {};
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
|
||||
},
|
||||
else => {}, // Panicked while printing the recursive panic message.
|
||||
}
|
||||
|
|
@ -1632,9 +1608,9 @@ test "manage resources correctly" {
|
|||
return @returnAddress();
|
||||
}
|
||||
};
|
||||
const gpa = std.testing.allocator;
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
const gpa = testing.allocator;
|
||||
const io = testing.io;
|
||||
|
||||
var discarding: Writer.Discarding = .init(&.{});
|
||||
var di: SelfInfo = .init;
|
||||
defer di.deinit(gpa);
|
||||
|
|
|
|||
|
|
@ -179,8 +179,6 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
total_requested_bytes: @TypeOf(total_requested_bytes_init) = total_requested_bytes_init,
|
||||
requested_memory_limit: @TypeOf(requested_memory_limit_init) = requested_memory_limit_init,
|
||||
mutex: @TypeOf(mutex_init) = mutex_init,
|
||||
/// Set this value differently to affect how errors and leaks are logged.
|
||||
tty_config: std.Io.tty.Config = .no_color,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
|
@ -427,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
bucket: *BucketHeader,
|
||||
size_class_index: usize,
|
||||
used_bits_count: usize,
|
||||
tty_config: std.Io.tty.Config,
|
||||
) usize {
|
||||
const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
|
||||
const slot_count = slot_counts[size_class_index];
|
||||
|
|
@ -444,11 +441,7 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const page_addr = @intFromPtr(bucket) & ~(page_size - 1);
|
||||
const addr = page_addr + slot_index * size_class;
|
||||
log.err("memory address 0x{x} leaked: {f}", .{
|
||||
addr,
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
addr, std.debug.FormatStackTrace{ .stack_trace = stack_trace },
|
||||
});
|
||||
leaks += 1;
|
||||
}
|
||||
|
|
@ -460,8 +453,6 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
|
||||
/// Emits log messages for leaks and then returns the number of detected leaks (0 if no leaks were detected).
|
||||
pub fn detectLeaks(self: *Self) usize {
|
||||
const tty_config = self.tty_config;
|
||||
|
||||
var leaks: usize = 0;
|
||||
|
||||
for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
|
||||
|
|
@ -469,7 +460,7 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const slot_count = slot_counts[size_class_index];
|
||||
const used_bits_count = usedBitsCount(slot_count);
|
||||
while (optional_bucket) |bucket| {
|
||||
leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config);
|
||||
leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count);
|
||||
optional_bucket = bucket.prev;
|
||||
}
|
||||
}
|
||||
|
|
@ -480,10 +471,7 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const stack_trace = large_alloc.getStackTrace(.alloc);
|
||||
log.err("memory address 0x{x} leaked: {f}", .{
|
||||
@intFromPtr(large_alloc.bytes.ptr),
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = stack_trace },
|
||||
});
|
||||
leaks += 1;
|
||||
}
|
||||
|
|
@ -535,28 +523,14 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
@memset(addr_buf[@min(st.index, addr_buf.len)..], 0);
|
||||
}
|
||||
|
||||
fn reportDoubleFree(
|
||||
tty_config: std.Io.tty.Config,
|
||||
ret_addr: usize,
|
||||
alloc_stack_trace: StackTrace,
|
||||
free_stack_trace: StackTrace,
|
||||
) void {
|
||||
fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
|
||||
@branchHint(.cold);
|
||||
var addr_buf: [stack_n]usize = undefined;
|
||||
const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
|
||||
log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = alloc_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = second_free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = alloc_stack_trace },
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
std.debug.FormatStackTrace{ .stack_trace = second_free_stack_trace },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -587,7 +561,7 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
|
||||
if (config.retain_metadata and entry.value_ptr.freed) {
|
||||
if (config.safety) {
|
||||
reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
|
||||
reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
|
||||
@panic("Unrecoverable double free");
|
||||
} else {
|
||||
unreachable;
|
||||
|
|
@ -598,18 +572,11 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
@branchHint(.cold);
|
||||
var addr_buf: [stack_n]usize = undefined;
|
||||
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
|
||||
entry.value_ptr.bytes.len,
|
||||
old_mem.len,
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = entry.value_ptr.getStackTrace(.alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) },
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -701,7 +668,7 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
|
||||
if (config.retain_metadata and entry.value_ptr.freed) {
|
||||
if (config.safety) {
|
||||
reportDoubleFree(self.tty_config, ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
|
||||
reportDoubleFree(ret_addr, entry.value_ptr.getStackTrace(.alloc), entry.value_ptr.getStackTrace(.free));
|
||||
return;
|
||||
} else {
|
||||
unreachable;
|
||||
|
|
@ -712,18 +679,11 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
@branchHint(.cold);
|
||||
var addr_buf: [stack_n]usize = undefined;
|
||||
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
|
||||
entry.value_ptr.bytes.len,
|
||||
old_mem.len,
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = entry.value_ptr.getStackTrace(.alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = entry.value_ptr.getStackTrace(.alloc) },
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -924,7 +884,6 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
if (!is_used) {
|
||||
if (config.safety) {
|
||||
reportDoubleFree(
|
||||
self.tty_config,
|
||||
return_address,
|
||||
bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
bucketStackTrace(bucket, slot_count, slot_index, .free),
|
||||
|
|
@ -946,34 +905,24 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
|
||||
if (old_memory.len != requested_size) {
|
||||
@branchHint(.cold);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
|
||||
requested_size,
|
||||
old_memory.len,
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
if (alignment != slot_alignment) {
|
||||
@branchHint(.cold);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
|
||||
slot_alignment.toByteUnits(),
|
||||
alignment.toByteUnits(),
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1040,7 +989,6 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const is_used = @as(u1, @truncate(used_byte.* >> used_bit_index)) != 0;
|
||||
if (!is_used) {
|
||||
reportDoubleFree(
|
||||
self.tty_config,
|
||||
return_address,
|
||||
bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
bucketStackTrace(bucket, slot_count, slot_index, .free),
|
||||
|
|
@ -1058,34 +1006,24 @@ pub fn DebugAllocator(comptime config: Config) type {
|
|||
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
|
||||
if (memory.len != requested_size) {
|
||||
@branchHint(.cold);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
|
||||
requested_size,
|
||||
memory.len,
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
if (alignment != slot_alignment) {
|
||||
@branchHint(.cold);
|
||||
const tty_config = self.tty_config;
|
||||
log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
|
||||
slot_alignment.toByteUnits(),
|
||||
alignment.toByteUnits(),
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{
|
||||
.stack_trace = free_stack_trace,
|
||||
.tty_config = tty_config,
|
||||
},
|
||||
std.debug.FormatStackTrace{ .stack_trace = free_stack_trace },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
//!
|
||||
//! For an example implementation of the `logFn` function, see `defaultLog`,
|
||||
//! which is the default implementation. It outputs to stderr, using color if
|
||||
//! the detected `std.Io.tty.Config` supports it. Its output looks like this:
|
||||
//! supported. Its output looks like this:
|
||||
//! ```
|
||||
//! error: this is an error
|
||||
//! 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 {
|
|||
return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
|
||||
}
|
||||
|
||||
var static_threaded_io: std.Io.Threaded = .init_single_threaded;
|
||||
|
||||
/// The default implementation for the log function. Custom log functions may
|
||||
/// forward log messages to this function.
|
||||
///
|
||||
|
|
@ -93,36 +91,64 @@ pub fn defaultLog(
|
|||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
return defaultLogIo(level, scope, format, args, static_threaded_io.io());
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr = std.debug.lockStderrWriter(&buffer);
|
||||
defer std.debug.unlockStderrWriter();
|
||||
return defaultLogFileWriter(level, scope, format, args, stderr);
|
||||
}
|
||||
|
||||
pub fn defaultLogIo(
|
||||
pub fn defaultLogFileWriter(
|
||||
comptime level: Level,
|
||||
comptime scope: @EnumLiteral(),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
io: std.Io,
|
||||
fw: *std.Io.File.Writer,
|
||||
) void {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr, const ttyconf = io.lockStderrWriter(&buffer);
|
||||
defer io.unlockStderrWriter();
|
||||
ttyconf.setColor(stderr, switch (level) {
|
||||
fw.setColor(switch (level) {
|
||||
.err => .red,
|
||||
.warn => .yellow,
|
||||
.info => .green,
|
||||
.debug => .magenta,
|
||||
}) catch {};
|
||||
ttyconf.setColor(stderr, .bold) catch {};
|
||||
stderr.writeAll(level.asText()) catch return;
|
||||
ttyconf.setColor(stderr, .reset) catch {};
|
||||
ttyconf.setColor(stderr, .dim) catch {};
|
||||
ttyconf.setColor(stderr, .bold) catch {};
|
||||
fw.setColor(.bold) catch {};
|
||||
fw.interface.writeAll(level.asText()) catch return;
|
||||
fw.setColor(.reset) catch {};
|
||||
fw.setColor(.dim) catch {};
|
||||
fw.setColor(.bold) catch {};
|
||||
if (scope != .default) {
|
||||
stderr.print("({s})", .{@tagName(scope)}) catch return;
|
||||
fw.interface.print("({s})", .{@tagName(scope)}) catch return;
|
||||
}
|
||||
stderr.writeAll(": ") catch return;
|
||||
ttyconf.setColor(stderr, .reset) catch {};
|
||||
stderr.print(format ++ "\n", args) catch return;
|
||||
fw.interface.writeAll(": ") catch return;
|
||||
fw.setColor(.reset) catch {};
|
||||
fw.interface.print(format ++ "\n", decorateArgs(args, fw.mode)) catch return;
|
||||
}
|
||||
|
||||
fn DecorateArgs(comptime Args: type) type {
|
||||
const fields = @typeInfo(Args).@"struct".fields;
|
||||
var new_fields: [fields.len]type = undefined;
|
||||
for (fields, &new_fields) |old, *new| {
|
||||
if (old.type == std.debug.FormatStackTrace) {
|
||||
new.* = std.debug.FormatStackTrace.Decorated;
|
||||
} else {
|
||||
new.* = old.type;
|
||||
}
|
||||
}
|
||||
return @Tuple(&new_fields);
|
||||
}
|
||||
|
||||
fn decorateArgs(args: anytype, file_writer_mode: std.Io.File.Writer.Mode) DecorateArgs(@TypeOf(args)) {
|
||||
var new_args: DecorateArgs(@TypeOf(args)) = undefined;
|
||||
inline for (args, &new_args) |old, *new| {
|
||||
if (@TypeOf(old) == std.debug.FormatStackTrace) {
|
||||
new.* = .{
|
||||
.stack_trace = old.stack_trace,
|
||||
.file_writer_mode = file_writer_mode,
|
||||
};
|
||||
} else {
|
||||
new.* = old;
|
||||
}
|
||||
}
|
||||
return new_args;
|
||||
}
|
||||
|
||||
/// Returns a scoped logging namespace that logs all messages using the scope
|
||||
|
|
|
|||
|
|
@ -439,25 +439,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
|
|||
}
|
||||
|
||||
/// On Windows, `key` must be valid WTF-8.
|
||||
pub fn hasEnvVarConstant(comptime key: []const u8) bool {
|
||||
pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
|
||||
if (native_os == .windows) {
|
||||
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
|
||||
return getenvW(key_w) != null;
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@compileError("hasEnvVarConstant is not supported for WASI without libc");
|
||||
return false;
|
||||
} else {
|
||||
return posix.getenv(key) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// On Windows, `key` must be valid WTF-8.
|
||||
pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
|
||||
pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
|
||||
if (native_os == .windows) {
|
||||
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
|
||||
const value = getenvW(key_w) orelse return false;
|
||||
return value.len != 0;
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");
|
||||
return false;
|
||||
} else {
|
||||
const value = posix.getenv(key) orelse return false;
|
||||
return value.len != 0;
|
||||
|
|
|
|||
|
|
@ -247,8 +247,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
|
|||
threaded.stack_size = thread_stack_size;
|
||||
const io = threaded.io();
|
||||
|
||||
debug_allocator.tty_config = .detect(io, .stderr());
|
||||
|
||||
const cmd = args[1];
|
||||
const cmd_args = args[2..];
|
||||
if (mem.eql(u8, cmd, "build-exe")) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue