aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/Io.zig22
-rw-r--r--lib/std/Io/File/Writer.zig243
-rw-r--r--lib/std/Io/Threaded.zig47
-rw-r--r--lib/std/Io/tty.zig135
-rw-r--r--lib/std/Progress.zig5
-rw-r--r--lib/std/debug.zig226
-rw-r--r--lib/std/heap/debug_allocator.zig96
-rw-r--r--lib/std/log.zig64
-rw-r--r--lib/std/process.zig8
9 files changed, 448 insertions, 398 deletions
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
index 833cc4ec0f..fdd813536c 100644
--- a/lib/std/Io.zig
+++ b/lib/std/Io.zig
@@ -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 {
diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig
index ec58824c06..0e995908c4 100644
--- a/lib/std/Io/File/Writer.zig
+++ b/lib/std/Io/File/Writer.zig
@@ -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);
+}
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
index 9d6d9f979e..c2a3e15f15 100644
--- a/lib/std/Io/Threaded.zig
+++ b/lib/std/Io/Threaded.zig
@@ -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();
}
diff --git a/lib/std/Io/tty.zig b/lib/std/Io/tty.zig
deleted file mode 100644
index 65f1b7dad5..0000000000
--- a/lib/std/Io/tty.zig
+++ /dev/null
@@ -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);
- },
- };
- }
-};
diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig
index 5528591620..799e3f9c9b 100644
--- a/lib/std/Progress.zig
+++ b/lib/std/Progress.zig
@@ -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;
}
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index 7e10c5af32..347dbf85ec 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -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);
diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig
index 27b1b9179f..66ff59a711 100644
--- a/lib/std/heap/debug_allocator.zig
+++ b/lib/std/heap/debug_allocator.zig
@@ -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 },
});
}
}
diff --git a/lib/std/log.zig b/lib/std/log.zig
index 461dfca36e..029f724f44 100644
--- a/lib/std/log.zig
+++ b/lib/std/log.zig
@@ -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;
+ }
+ 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;
+ }
}
- stderr.writeAll(": ") catch return;
- ttyconf.setColor(stderr, .reset) catch {};
- stderr.print(format ++ "\n", args) catch return;
+ return new_args;
}
/// Returns a scoped logging namespace that logs all messages using the scope
diff --git a/lib/std/process.zig b/lib/std/process.zig
index f7ecf5fdc2..5c6e6f89eb 100644
--- a/lib/std/process.zig
+++ b/lib/std/process.zig
@@ -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;