std: fix compilation errors on Windows

This commit is contained in:
Andrew Kelley 2025-10-19 14:08:21 -07:00
parent b215f8667a
commit 10b1eef2d3
17 changed files with 186 additions and 101 deletions

View file

@ -148,7 +148,7 @@ fn mainServer() !void {
error.SkipZigTest => .skip, error.SkipZigTest => .skip,
else => s: { else => s: {
if (@errorReturnTrace()) |trace| { if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace); std.debug.dumpStackTrace(trace.*);
} }
break :s .fail; break :s .fail;
}, },
@ -269,7 +269,7 @@ fn mainTerminal() void {
std.debug.print("FAIL ({t})\n", .{err}); std.debug.print("FAIL ({t})\n", .{err});
} }
if (@errorReturnTrace()) |trace| { if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace); std.debug.dumpStackTrace(trace.*);
} }
test_node.end(); test_node.end();
}, },

View file

@ -332,7 +332,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void { pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void {
if (step.debug_stack_trace.instruction_addresses.len > 0) { if (step.debug_stack_trace.instruction_addresses.len > 0) {
w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {}; std.debug.writeStackTrace(step.debug_stack_trace, w, tty_config) catch {};
} else { } else {
const field = "debug_stack_frames_count"; const field = "debug_stack_frames_count";
comptime assert(@hasField(Build, field)); comptime assert(@hasField(Build, field));

View file

@ -31,7 +31,7 @@ const max_iovecs_len = 8;
const splat_buffer_size = 64; const splat_buffer_size = 64;
comptime { comptime {
assert(max_iovecs_len <= posix.IOV_MAX); if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
} }
const Closure = struct { const Closure = struct {
@ -91,9 +91,7 @@ pub fn init(
/// Statically initialize such that any call to the following functions will /// Statically initialize such that any call to the following functions will
/// fail with `error.OutOfMemory`: /// fail with `error.OutOfMemory`:
/// * `Io.VTable.async`
/// * `Io.VTable.concurrent` /// * `Io.VTable.concurrent`
/// * `Io.VTable.groupAsync`
/// When initialized this way, `deinit` is safe, but unnecessary to call. /// When initialized this way, `deinit` is safe, but unnecessary to call.
pub const init_single_threaded: Threaded = .{ pub const init_single_threaded: Threaded = .{
.allocator = .failing, .allocator = .failing,

View file

@ -221,7 +221,7 @@ pub fn connect(
defer { defer {
connect_many.cancel(io); connect_many.cancel(io);
if (!saw_end) while (true) switch (connect_many_queue.getOneUncancelable(io)) { if (!saw_end) while (true) switch (connect_many_queue.getOneUncancelable(io)) {
.connection => |loser| if (loser) |s| s.closeConst(io) else |_| continue, .connection => |loser| if (loser) |s| s.close(io) else |_| continue,
.end => break, .end => break,
}; };
} }

View file

@ -577,7 +577,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
@call(.auto, f, args) catch |err| { @call(.auto, f, args) catch |err| {
std.debug.print("error: {s}\n", .{@errorName(err)}); std.debug.print("error: {s}\n", .{@errorName(err)});
if (@errorReturnTrace()) |trace| { if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace); std.debug.dumpStackTrace(trace.*);
} }
}; };

View file

@ -37,19 +37,6 @@ pub const subsystem: ?std.Target.SubSystem = blk: {
pub const StackTrace = struct { pub const StackTrace = struct {
index: usize, index: usize,
instruction_addresses: []usize, instruction_addresses: []usize,
pub fn format(st: *const StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void {
// TODO: re-evaluate whether to use format() methods at all.
// Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly
// where it tries to call detectTTYConfig here.
if (builtin.os.tag == .freestanding) return;
// TODO: why on earth are we using stderr's ttyconfig?
// If we want colored output, we should just make a formatter out of `writeStackTrace`.
const tty_config = std.Io.tty.detectConfig(.stderr());
try writer.writeAll("\n");
try std.debug.writeStackTrace(st, writer, tty_config);
}
}; };
/// This data structure is used by the Zig language code generation and /// This data structure is used by the Zig language code generation and

View file

@ -1,4 +1,7 @@
const std = @import("std.zig"); const std = @import("std.zig");
const Io = std.Io;
const Writer = std.Io.Writer;
const tty = std.Io.tty;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;
const posix = std.posix; const posix = std.posix;
@ -7,12 +10,11 @@ const testing = std.testing;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const File = std.fs.File; const File = std.fs.File;
const windows = std.os.windows; const windows = std.os.windows;
const Writer = std.Io.Writer;
const tty = std.Io.tty;
const builtin = @import("builtin"); const builtin = @import("builtin");
const native_arch = builtin.cpu.arch; const native_arch = builtin.cpu.arch;
const native_os = builtin.os.tag; const native_os = builtin.os.tag;
const StackTrace = std.builtin.StackTrace;
const root = @import("root"); const root = @import("root");
@ -545,13 +547,13 @@ pub fn defaultPanic(
stderr.print("panic: ", .{}) catch break :trace; stderr.print("panic: ", .{}) catch break :trace;
} else { } else {
const current_thread_id = std.Thread.getCurrentId(); const current_thread_id = std.Thread.getCurrentId();
stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace; stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
} }
stderr.print("{s}\n", .{msg}) catch break :trace; stderr.print("{s}\n", .{msg}) catch break :trace;
if (@errorReturnTrace()) |t| if (t.index > 0) { if (@errorReturnTrace()) |t| if (t.index > 0) {
stderr.writeAll("error return context:\n") catch break :trace; stderr.writeAll("error return context:\n") catch break :trace;
writeStackTrace(t, stderr, tty_config) catch break :trace; writeStackTrace(t.*, stderr, tty_config) catch break :trace;
stderr.writeAll("\nstack trace:\n") catch break :trace; stderr.writeAll("\nstack trace:\n") catch break :trace;
}; };
writeCurrentStackTrace(.{ writeCurrentStackTrace(.{
@ -607,8 +609,8 @@ pub const StackUnwindOptions = struct {
/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`. /// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
/// ///
/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
const empty_trace: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &.{} }; const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} };
if (!std.options.allow_stack_tracing) return empty_trace; if (!std.options.allow_stack_tracing) return empty_trace;
var it = StackIterator.init(options.context) catch return empty_trace; var it = StackIterator.init(options.context) catch return empty_trace;
defer it.deinit(); defer it.deinit();
@ -646,6 +648,9 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
/// ///
/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. /// 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 { 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.io();
if (!std.options.allow_stack_tracing) { if (!std.options.allow_stack_tracing) {
tty_config.setColor(writer, .dim) catch {}; tty_config.setColor(writer, .dim) catch {};
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
@ -730,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
} }
// `ret_addr` is the return address, which is *after* the function call. // `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. // Subtract 1 to get an address *in* the function call for a better source location.
try printSourceAtAddress(di_gpa, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config); try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
printed_any_frame = true; printed_any_frame = true;
}, },
}; };
@ -754,14 +759,29 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
}; };
} }
pub const FormatStackTrace = struct {
stack_trace: StackTrace,
tty_config: tty.Config,
pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
try writer.writeAll("\n");
try writeStackTrace(context.stack_trace, writer, context.tty_config);
}
};
/// Write a previously captured stack trace to `writer`, annotated with source locations. /// Write a previously captured stack trace to `writer`, annotated with source locations.
pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { pub fn writeStackTrace(st: StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
if (!std.options.allow_stack_tracing) { if (!std.options.allow_stack_tracing) {
tty_config.setColor(writer, .dim) catch {}; tty_config.setColor(writer, .dim) catch {};
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
tty_config.setColor(writer, .reset) catch {}; tty_config.setColor(writer, .reset) catch {};
return; 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.io();
// Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if // 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. // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
const n_frames = st.index; const n_frames = st.index;
@ -779,7 +799,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
for (st.instruction_addresses[0..captured_frames]) |ret_addr| { for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
// `ret_addr` is the return address, which is *after* the function call. // `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. // Subtract 1 to get an address *in* the function call for a better source location.
try printSourceAtAddress(di_gpa, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config); try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
} }
if (n_frames > captured_frames) { if (n_frames > captured_frames) {
tty_config.setColor(writer, .bold) catch {}; tty_config.setColor(writer, .bold) catch {};
@ -788,7 +808,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
} }
} }
/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { pub fn dumpStackTrace(st: StackTrace) void {
const tty_config = tty.detectConfig(.stderr()); const tty_config = tty.detectConfig(.stderr());
const stderr = lockStderrWriter(&.{}); const stderr = lockStderrWriter(&.{});
defer unlockStderrWriter(); defer unlockStderrWriter();
@ -1075,8 +1095,8 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
return ptr; return ptr;
} }
fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) { const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
error.MissingDebugInfo, error.MissingDebugInfo,
error.UnsupportedDebugInfo, error.UnsupportedDebugInfo,
error.InvalidDebugInfo, error.InvalidDebugInfo,
@ -1581,11 +1601,14 @@ test "manage resources correctly" {
} }
}; };
const gpa = std.testing.allocator; const gpa = std.testing.allocator;
var discarding: std.Io.Writer.Discarding = .init(&.{}); var threaded: Io.Threaded = .init_single_threaded;
const io = threaded.io();
var discarding: Io.Writer.Discarding = .init(&.{});
var di: SelfInfo = .init; var di: SelfInfo = .init;
defer di.deinit(gpa); defer di.deinit(gpa);
try printSourceAtAddress( try printSourceAtAddress(
gpa, gpa,
io,
&di, &di,
&discarding.writer, &discarding.writer,
S.showMyTrace(), S.showMyTrace(),
@ -1659,11 +1682,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
stderr.print("{s}:\n", .{t.notes[i]}) catch return; stderr.print("{s}:\n", .{t.notes[i]}) catch return;
var frames_array_mutable = frames_array; var frames_array_mutable = frames_array;
const frames = mem.sliceTo(frames_array_mutable[0..], 0); const frames = mem.sliceTo(frames_array_mutable[0..], 0);
const stack_trace: std.builtin.StackTrace = .{ const stack_trace: StackTrace = .{
.index = frames.len, .index = frames.len,
.instruction_addresses = frames, .instruction_addresses = frames,
}; };
writeStackTrace(&stack_trace, stderr, tty_config) catch return; writeStackTrace(stack_trace, stderr, tty_config) catch return;
} }
if (t.index > end) { if (t.index > end) {
stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ stderr.print("{d} more traces not shown; consider increasing trace size\n", .{

View file

@ -30,7 +30,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
si.ofiles.deinit(gpa); si.ofiles.deinit(gpa);
} }
pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
_ = io;
const module = try si.findModule(gpa, address); const module = try si.findModule(gpa, address);
defer si.mutex.unlock(); defer si.mutex.unlock();
@ -970,6 +971,7 @@ fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
} }
const std = @import("std"); const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Dwarf = std.debug.Dwarf; const Dwarf = std.debug.Dwarf;
const Error = std.debug.SelfInfoError; const Error = std.debug.SelfInfoError;

View file

@ -474,7 +474,7 @@ const Module = struct {
break :pdb pdb; break :pdb pdb;
}; };
errdefer if (opt_pdb) |*pdb| { errdefer if (opt_pdb) |*pdb| {
pdb.file_reader.file.close(); pdb.file_reader.file.close(io);
pdb.deinit(); pdb.deinit();
}; };
@ -484,6 +484,7 @@ const Module = struct {
return .{ return .{
.arena = arena_instance.state, .arena = arena_instance.state,
.io = io,
.coff_image_base = coff_image_base, .coff_image_base = coff_image_base,
.mapped_file = mapped_file, .mapped_file = mapped_file,
.dwarf = opt_dwarf, .dwarf = opt_dwarf,

View file

@ -1062,7 +1062,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptio
w.SYNCHRONIZE | w.FILE_TRAVERSE | w.SYNCHRONIZE | w.FILE_TRAVERSE |
(if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0)); (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow); return self.makeOpenPathAccessMaskW(sub_path, base_flags, !open_dir_options.follow_symlinks);
}, },
else => { else => {
return self.openDir(sub_path, open_dir_options) catch |err| switch (err) { return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
@ -1575,8 +1575,7 @@ pub fn symLink(
// when converting to an NT namespaced path. CreateSymbolicLink in // when converting to an NT namespaced path. CreateSymbolicLink in
// symLinkW will handle the necessary conversion. // symLinkW will handle the necessary conversion.
var target_path_w: windows.PathSpace = undefined; var target_path_w: windows.PathSpace = undefined;
try windows.checkWtf8ToWtf16LeOverflow(target_path, &target_path_w.data); target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
target_path_w.data[target_path_w.len] = 0; target_path_w.data[target_path_w.len] = 0;
// However, we need to canonicalize any path separators to `\`, since if // However, we need to canonicalize any path separators to `\`, since if
// the target path is relative, then it must use `\` as the path separator. // the target path is relative, then it must use `\` as the path separator.

View file

@ -564,8 +564,8 @@ pub fn updateTimes(
mtime: Io.Timestamp, mtime: Io.Timestamp,
) UpdateTimesError!void { ) UpdateTimesError!void {
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
const atime_ft = windows.nanoSecondsToFileTime(atime.nanoseconds); const atime_ft = windows.nanoSecondsToFileTime(atime);
const mtime_ft = windows.nanoSecondsToFileTime(mtime.nanoseconds); const mtime_ft = windows.nanoSecondsToFileTime(mtime);
return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
} }
const times = [2]posix.timespec{ const times = [2]posix.timespec{

View file

@ -80,15 +80,15 @@
//! //!
//! Resizing and remapping are forwarded directly to the backing allocator, //! Resizing and remapping are forwarded directly to the backing allocator,
//! except where such operations would change the category from large to small. //! except where such operations would change the category from large to small.
const builtin = @import("builtin");
const StackTrace = std.builtin.StackTrace;
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const log = std.log.scoped(.gpa); const log = std.log.scoped(.gpa);
const math = std.math; const math = std.math;
const assert = std.debug.assert; const assert = std.debug.assert;
const mem = std.mem; const mem = std.mem;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const StackTrace = std.builtin.StackTrace;
const default_page_size: usize = switch (builtin.os.tag) { const default_page_size: usize = switch (builtin.os.tag) {
// Makes `std.heap.PageAllocator` take the happy path. // Makes `std.heap.PageAllocator` take the happy path.
@ -421,7 +421,12 @@ pub fn DebugAllocator(comptime config: Config) type {
return usedBitsCount(slot_count) * @sizeOf(usize); return usedBitsCount(slot_count) * @sizeOf(usize);
} }
fn detectLeaksInBucket(bucket: *BucketHeader, size_class_index: usize, used_bits_count: usize) usize { fn detectLeaksInBucket(
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 size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
const slot_count = slot_counts[size_class_index]; const slot_count = slot_counts[size_class_index];
var leaks: usize = 0; var leaks: usize = 0;
@ -436,7 +441,13 @@ pub fn DebugAllocator(comptime config: Config) type {
const stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc); const stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc);
const page_addr = @intFromPtr(bucket) & ~(page_size - 1); const page_addr = @intFromPtr(bucket) & ~(page_size - 1);
const addr = page_addr + slot_index * size_class; const addr = page_addr + slot_index * size_class;
log.err("memory address 0x{x} leaked: {f}", .{ addr, stack_trace }); log.err("memory address 0x{x} leaked: {f}", .{
addr,
std.debug.FormatStackTrace{
.stack_trace = stack_trace,
.tty_config = tty_config,
},
});
leaks += 1; leaks += 1;
} }
} }
@ -449,12 +460,14 @@ pub fn DebugAllocator(comptime config: Config) type {
pub fn detectLeaks(self: *Self) usize { pub fn detectLeaks(self: *Self) usize {
var leaks: usize = 0; var leaks: usize = 0;
const tty_config = std.Io.tty.detectConfig(.stderr());
for (self.buckets, 0..) |init_optional_bucket, size_class_index| { for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
var optional_bucket = init_optional_bucket; var optional_bucket = init_optional_bucket;
const slot_count = slot_counts[size_class_index]; const slot_count = slot_counts[size_class_index];
const used_bits_count = usedBitsCount(slot_count); const used_bits_count = usedBitsCount(slot_count);
while (optional_bucket) |bucket| { while (optional_bucket) |bucket| {
leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count); leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config);
optional_bucket = bucket.prev; optional_bucket = bucket.prev;
} }
} }
@ -464,7 +477,11 @@ pub fn DebugAllocator(comptime config: Config) type {
if (config.retain_metadata and large_alloc.freed) continue; if (config.retain_metadata and large_alloc.freed) continue;
const stack_trace = large_alloc.getStackTrace(.alloc); const stack_trace = large_alloc.getStackTrace(.alloc);
log.err("memory address 0x{x} leaked: {f}", .{ log.err("memory address 0x{x} leaked: {f}", .{
@intFromPtr(large_alloc.bytes.ptr), stack_trace, @intFromPtr(large_alloc.bytes.ptr),
std.debug.FormatStackTrace{
.stack_trace = stack_trace,
.tty_config = tty_config,
},
}); });
leaks += 1; leaks += 1;
} }
@ -519,8 +536,20 @@ pub fn DebugAllocator(comptime config: Config) type {
fn reportDoubleFree(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 {
var addr_buf: [stack_n]usize = undefined; var addr_buf: [stack_n]usize = undefined;
const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
alloc_stack_trace, free_stack_trace, second_free_stack_trace, 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,
},
}); });
} }
@ -561,11 +590,18 @@ pub fn DebugAllocator(comptime config: Config) type {
if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
var addr_buf: [stack_n]usize = undefined; var addr_buf: [stack_n]usize = undefined;
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
entry.value_ptr.bytes.len, entry.value_ptr.bytes.len,
old_mem.len, old_mem.len,
entry.value_ptr.getStackTrace(.alloc), std.debug.FormatStackTrace{
free_stack_trace, .stack_trace = entry.value_ptr.getStackTrace(.alloc),
.tty_config = tty_config,
},
std.debug.FormatStackTrace{
.stack_trace = free_stack_trace,
.tty_config = tty_config,
},
}); });
} }
@ -667,11 +703,18 @@ pub fn DebugAllocator(comptime config: Config) type {
if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
var addr_buf: [stack_n]usize = undefined; var addr_buf: [stack_n]usize = undefined;
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
entry.value_ptr.bytes.len, entry.value_ptr.bytes.len,
old_mem.len, old_mem.len,
entry.value_ptr.getStackTrace(.alloc), std.debug.FormatStackTrace{
free_stack_trace, .stack_trace = entry.value_ptr.getStackTrace(.alloc),
.tty_config = tty_config,
},
std.debug.FormatStackTrace{
.stack_trace = free_stack_trace,
.tty_config = tty_config,
},
}); });
} }
@ -892,19 +935,33 @@ pub fn DebugAllocator(comptime config: Config) type {
var addr_buf: [stack_n]usize = undefined; var addr_buf: [stack_n]usize = undefined;
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
if (old_memory.len != requested_size) { if (old_memory.len != requested_size) {
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
requested_size, requested_size,
old_memory.len, old_memory.len,
bucketStackTrace(bucket, slot_count, slot_index, .alloc), std.debug.FormatStackTrace{
free_stack_trace, .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,
},
}); });
} }
if (alignment != slot_alignment) { if (alignment != slot_alignment) {
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
slot_alignment.toByteUnits(), slot_alignment.toByteUnits(),
alignment.toByteUnits(), alignment.toByteUnits(),
bucketStackTrace(bucket, slot_count, slot_index, .alloc), std.debug.FormatStackTrace{
free_stack_trace, .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,
},
}); });
} }
} }
@ -987,19 +1044,33 @@ pub fn DebugAllocator(comptime config: Config) type {
var addr_buf: [stack_n]usize = undefined; var addr_buf: [stack_n]usize = undefined;
const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
if (memory.len != requested_size) { if (memory.len != requested_size) {
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
requested_size, requested_size,
memory.len, memory.len,
bucketStackTrace(bucket, slot_count, slot_index, .alloc), std.debug.FormatStackTrace{
free_stack_trace, .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,
},
}); });
} }
if (alignment != slot_alignment) { if (alignment != slot_alignment) {
const tty_config = std.Io.tty.detectConfig(.stderr());
log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{ log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
slot_alignment.toByteUnits(), slot_alignment.toByteUnits(),
alignment.toByteUnits(), alignment.toByteUnits(),
bucketStackTrace(bucket, slot_count, slot_index, .alloc), std.debug.FormatStackTrace{
free_stack_trace, .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,
},
}); });
} }
} }

View file

@ -5,12 +5,14 @@
//! slices as well as APIs which accept null-terminated WTF16LE byte buffers. //! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
const builtin = @import("builtin"); const builtin = @import("builtin");
const native_arch = builtin.cpu.arch;
const std = @import("../std.zig"); const std = @import("../std.zig");
const Io = std.Io;
const mem = std.mem; const mem = std.mem;
const assert = std.debug.assert; const assert = std.debug.assert;
const math = std.math; const math = std.math;
const maxInt = std.math.maxInt; const maxInt = std.math.maxInt;
const native_arch = builtin.cpu.arch;
const UnexpectedError = std.posix.UnexpectedError; const UnexpectedError = std.posix.UnexpectedError;
test { test {
@ -2219,25 +2221,25 @@ pub fn peb() *PEB {
/// Universal Time (UTC). /// Universal Time (UTC).
/// This function returns the number of nanoseconds since the canonical epoch, /// This function returns the number of nanoseconds since the canonical epoch,
/// which is the POSIX one (Jan 01, 1970 AD). /// which is the POSIX one (Jan 01, 1970 AD).
pub fn fromSysTime(hns: i64) i128 { pub fn fromSysTime(hns: i64) Io.Timestamp {
const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100); const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100);
return adjusted_epoch * 100; return .fromNanoseconds(@intCast(adjusted_epoch * 100));
} }
pub fn toSysTime(ns: i128) i64 { pub fn toSysTime(ns: Io.Timestamp) i64 {
const hns = @divFloor(ns, 100); const hns = @divFloor(ns.nanoseconds, 100);
return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100); return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100);
} }
pub fn fileTimeToNanoSeconds(ft: FILETIME) i128 { pub fn fileTimeToNanoSeconds(ft: FILETIME) Io.Timestamp {
const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
return fromSysTime(hns); return fromSysTime(hns);
} }
/// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME. /// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME.
pub fn nanoSecondsToFileTime(ns: i128) FILETIME { pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
const adjusted: u64 = @bitCast(toSysTime(ns)); const adjusted: u64 = @bitCast(toSysTime(ns));
return FILETIME{ return .{
.dwHighDateTime = @as(u32, @truncate(adjusted >> 32)), .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)),
.dwLowDateTime = @as(u32, @truncate(adjusted)), .dwLowDateTime = @as(u32, @truncate(adjusted)),
}; };
@ -5740,11 +5742,15 @@ pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
return ppeb.ImageBaseAddress; return ppeb.ImageBaseAddress;
} }
pub fn checkWtf8ToWtf16LeOverflow(wtf8: []const u8, wtf16le: []const u16) error{ BadPathName, NameTooLong }!void { pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{ BadPathName, NameTooLong }!usize {
// Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE. // Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE.
if (wtf16le.len >= wtf8.len) return; if (wtf16le.len < wtf8.len) {
const utf16_len = std.unicode.calcUtf16LeLenImpl(wtf8, .can_encode_surrogate_half) catch const utf16_len = std.unicode.calcUtf16LeLenImpl(wtf8, .can_encode_surrogate_half) catch
return error.BadPathName; return error.BadPathName;
if (utf16_len > wtf16le.len) if (utf16_len > wtf16le.len)
return error.NameTooLong; return error.NameTooLong;
}
return std.unicode.wtf8ToWtf16Le(wtf16le, wtf8) catch |err| switch (err) {
error.InvalidWtf8 => return error.BadPathName,
};
} }

View file

@ -821,6 +821,9 @@ pub const ReadError = std.Io.File.ReadStreamingError;
/// The corresponding POSIX limit is `maxInt(isize)`. /// The corresponding POSIX limit is `maxInt(isize)`.
pub fn read(fd: fd_t, buf: []u8) ReadError!usize { pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
if (buf.len == 0) return 0; if (buf.len == 0) return 0;
if (native_os == .windows) {
return windows.ReadFile(fd, buf, null);
}
if (native_os == .wasi and !builtin.link_libc) { if (native_os == .wasi and !builtin.link_libc) {
const iovs = [1]iovec{iovec{ const iovs = [1]iovec{iovec{
.base = buf.ptr, .base = buf.ptr,
@ -2918,8 +2921,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
@compileError("WASI does not support os.chdir"); @compileError("WASI does not support os.chdir");
} else if (native_os == .windows) { } else if (native_os == .windows) {
var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
try windows.checkWtf8ToWtf16LeOverflow(dir_path, &wtf16_dir_path); const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
return chdirW(wtf16_dir_path[0..len]); return chdirW(wtf16_dir_path[0..len]);
} else { } else {
const dir_path_c = try toPosixPath(dir_path); const dir_path_c = try toPosixPath(dir_path);
@ -2935,8 +2937,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
if (native_os == .windows) { if (native_os == .windows) {
const dir_path_span = mem.span(dir_path); const dir_path_span = mem.span(dir_path);
var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
try windows.checkWtf8ToWtf16LeOverflow(dir_path_span, &wtf16_dir_path); const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
return chdirW(wtf16_dir_path[0..len]); return chdirW(wtf16_dir_path[0..len]);
} else if (native_os == .wasi and !builtin.link_libc) { } else if (native_os == .wasi and !builtin.link_libc) {
return chdir(mem.span(dir_path)); return chdir(mem.span(dir_path));

View file

@ -862,20 +862,6 @@ test "isatty" {
try expectEqual(posix.isatty(file.handle), false); try expectEqual(posix.isatty(file.handle), false);
} }
test "read with empty buffer" {
var tmp = tmpDir(.{});
defer tmp.cleanup();
var file = try tmp.dir.createFile("read_empty", .{ .read = true });
defer file.close();
const bytes = try a.alloc(u8, 0);
defer a.free(bytes);
const rc = try posix.read(file.handle, bytes);
try expectEqual(rc, 0);
}
test "pread with empty buffer" { test "pread with empty buffer" {
var tmp = tmpDir(.{}); var tmp = tmpDir(.{});
defer tmp.cleanup(); defer tmp.cleanup();

View file

@ -1148,6 +1148,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
} else |err| switch (err) { } else |err| switch (err) {
error.OutOfMemory => { error.OutOfMemory => {
if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) { if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
const tty_config = std.Io.tty.detectConfig(.stderr());
print( print(
"\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}", "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
.{ .{
@ -1157,7 +1158,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
failing_allocator_inst.freed_bytes, failing_allocator_inst.freed_bytes,
failing_allocator_inst.allocations, failing_allocator_inst.allocations,
failing_allocator_inst.deallocations, failing_allocator_inst.deallocations,
failing_allocator_inst.getStackTrace(), std.debug.FormatStackTrace{
.stack_trace = failing_allocator_inst.getStackTrace(),
.tty_config = tty_config,
},
}, },
); );
return error.MemoryLeakDetected; return error.MemoryLeakDetected;

View file

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Cache = std.Build.Cache; const Cache = std.Build.Cache;
@ -11,6 +12,12 @@ pub fn main() !void {
defer arena_instance.deinit(); defer arena_instance.deinit();
const arena = arena_instance.allocator(); const arena = arena_instance.allocator();
const gpa = arena;
var threaded: Io.Threaded = .init(gpa);
defer threaded.deinit();
const io = threaded.io();
var opt_zig_exe: ?[]const u8 = null; var opt_zig_exe: ?[]const u8 = null;
var opt_input_file_name: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null;
var opt_lib_dir: ?[]const u8 = null; var opt_lib_dir: ?[]const u8 = null;
@ -53,7 +60,7 @@ pub fn main() !void {
const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage}); const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});
const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32))); const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32)));
const case = try Case.parse(arena, input_file_bytes); const case = try Case.parse(arena, io, input_file_bytes);
// Check now: if there are any targets using the `cbe` backend, we need the lib dir. // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
if (opt_lib_dir == null) { if (opt_lib_dir == null) {
@ -86,7 +93,7 @@ pub fn main() !void {
else else
null; null;
const host = try std.zig.system.resolveTargetQuery(.{}); const host = try std.zig.system.resolveTargetQuery(io, .{});
const debug_log_verbose = debug_zcu or debug_dwarf or debug_link; const debug_log_verbose = debug_zcu or debug_dwarf or debug_link;
@ -186,7 +193,7 @@ pub fn main() !void {
try child.spawn(); try child.spawn();
var poller = std.Io.poll(arena, Eval.StreamEnum, .{ var poller = Io.poll(arena, Eval.StreamEnum, .{
.stdout = child.stdout.?, .stdout = child.stdout.?,
.stderr = child.stderr.?, .stderr = child.stderr.?,
}); });
@ -226,7 +233,7 @@ const Eval = struct {
cc_child_args: *std.ArrayListUnmanaged([]const u8), cc_child_args: *std.ArrayListUnmanaged([]const u8),
const StreamEnum = enum { stdout, stderr }; const StreamEnum = enum { stdout, stderr };
const Poller = std.Io.Poller(StreamEnum); const Poller = Io.Poller(StreamEnum);
/// Currently this function assumes the previous updates have already been written. /// Currently this function assumes the previous updates have already been written.
fn write(eval: *Eval, update: Case.Update) void { fn write(eval: *Eval, update: Case.Update) void {
@ -647,7 +654,7 @@ const Case = struct {
msg: []const u8, msg: []const u8,
}; };
fn parse(arena: Allocator, bytes: []const u8) !Case { fn parse(arena: Allocator, io: Io, bytes: []const u8) !Case {
const fatal = std.process.fatal; const fatal = std.process.fatal;
var targets: std.ArrayListUnmanaged(Target) = .empty; var targets: std.ArrayListUnmanaged(Target) = .empty;
@ -683,7 +690,7 @@ const Case = struct {
}, },
}) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query }); }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });
const resolved = try std.zig.system.resolveTargetQuery(parsed_query); const resolved = try std.zig.system.resolveTargetQuery(io, parsed_query);
try targets.append(arena, .{ try targets.append(arena, .{
.query = query, .query = query,