diff --git a/test/error_traces.zig b/test/error_traces.zig index 6c9cdc7166..d033cf741b 100644 --- a/test/error_traces.zig +++ b/test/error_traces.zig @@ -1,4 +1,6 @@ -pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void { +const std = @import("std"); + +pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.Os.Tag) void { cases.addCase(.{ .name = "return", .source = @@ -450,53 +452,54 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void { }, }); - cases.addCase(.{ - .name = "trace through inline call", - .source = - \\pub fn main() !void { - \\ try foo(); - \\} - \\inline fn foo() !void { - \\ try bar(); - \\} - \\fn bar() !void { - \\ return error.ThisIsSoSad; - \\} - , - .expect_error = "ThisIsSoSad", - .expect_trace = - \\source.zig:8:5: [address] in bar - \\ return error.ThisIsSoSad; - \\ ^ - \\source.zig:5:5: [address] in foo - \\ try bar(); - \\ ^ - \\source.zig:2:5: [address] in main - \\ try foo(); - \\ ^ - , - .disable_trace_optimized = &.{ - .{ .x86_64, .freebsd }, - .{ .x86_64, .netbsd }, - .{ .x86_64, .linux }, - .{ .x86, .linux }, - .{ .aarch64, .freebsd }, - .{ .aarch64, .netbsd }, - .{ .aarch64, .linux }, - .{ .loongarch64, .linux }, - .{ .powerpc64le, .linux }, - .{ .riscv64, .linux }, - .{ .s390x, .linux }, - .{ .x86_64, .openbsd }, - .{ .x86_64, .windows }, - .{ .x86, .windows }, - .{ .x86_64, .macos }, - .{ .aarch64, .macos }, - }, - // TODO: the standard library has a bug in PDB parsing where given an address corresponding - // to an inline call, the frame we see will be for the *caller*, not the *callee*. As a - // result this test gives bogus results on Windows right now. - // This is a part of https://codeberg.org/ziglang/zig/issues/30847. - .disable_trace_pdb = true, - }); + // TODO: the standard library has a bug in PDB parsing where given an address corresponding + // to an inline call, the frame we see will be for the *caller*, not the *callee*. As a + // result this test gives bogus results on Windows right now. + // This is a part of https://codeberg.org/ziglang/zig/issues/30847. + if (os != .windows) { + cases.addCase(.{ + .name = "trace through inline call", + .source = + \\pub fn main() !void { + \\ try foo(); + \\} + \\inline fn foo() !void { + \\ try bar(); + \\} + \\fn bar() !void { + \\ return error.ThisIsSoSad; + \\} + , + .expect_error = "ThisIsSoSad", + .expect_trace = + \\source.zig:8:5: [address] in bar + \\ return error.ThisIsSoSad; + \\ ^ + \\source.zig:5:5: [address] in foo + \\ try bar(); + \\ ^ + \\source.zig:2:5: [address] in main + \\ try foo(); + \\ ^ + , + .disable_trace_optimized = &.{ + .{ .x86_64, .freebsd }, + .{ .x86_64, .netbsd }, + .{ .x86_64, .linux }, + .{ .x86, .linux }, + .{ .aarch64, .freebsd }, + .{ .aarch64, .netbsd }, + .{ .aarch64, .linux }, + .{ .loongarch64, .linux }, + .{ .powerpc64le, .linux }, + .{ .riscv64, .linux }, + .{ .s390x, .linux }, + .{ .x86_64, .openbsd }, + .{ .x86_64, .windows }, + .{ .x86, .windows }, + .{ .x86_64, .macos }, + .{ .aarch64, .macos }, + }, + }); + } } diff --git a/test/src/ErrorTrace.zig b/test/src/ErrorTrace.zig index ac93f3a57d..f6f80f8b13 100644 --- a/test/src/ErrorTrace.zig +++ b/test/src/ErrorTrace.zig @@ -17,8 +17,6 @@ pub const Case = struct { /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it /// seems that LLVM is particularly good at optimizing traces away in those. disable_trace_optimized: []const DisableConfig = &.{}, - /// If `true` then we will not test the error trace on Windows due to bugs in PDB handling. - disable_trace_pdb: bool = false, pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag }; pub const Backend = enum { llvm, selfhosted }; @@ -62,7 +60,6 @@ fn addCaseConfig( const b = self.b; const error_tracing: bool = tracing: { - if (target.result.os.tag == .windows and case.disable_trace_pdb) break :tracing false; if (optimize == .Debug) break :tracing true; if (backend != .llvm) break :tracing true; if (optimize == .ReleaseSmall) break :tracing false; diff --git a/test/src/convert-stack-trace.zig b/test/src/convert-stack-trace.zig index 272c43e311..259e34ae59 100644 --- a/test/src/convert-stack-trace.zig +++ b/test/src/convert-stack-trace.zig @@ -52,20 +52,19 @@ pub fn main(init: std.process.Init) !void { continue; } - const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse { + // If both the row and column are present, this it he column end. Otherwise it's the line end. + const src_pos_end = std.mem.indexOf(u8, in_line, ": 0x") orelse { try w.writeAll(in_line); continue; }; - const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse { - try w.writeAll(in_line); - continue; - }; - const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse { + const src_row_or_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_pos_end], ':') orelse { try w.writeAll(in_line); continue; }; + const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_or_path_end], ':') + orelse src_row_or_path_end; - const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse { + const addr_end = std.mem.indexOfPos(u8, in_line, src_pos_end, " in ") orelse { try w.writeAll(in_line); continue; }; @@ -91,7 +90,7 @@ pub fn main(init: std.process.Init) !void { const src_path = in_line[0..src_path_end]; const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0; const symbol_start = addr_end + " in ".len; - try w.writeAll(in_line[basename_start..src_col_end]); + try w.writeAll(in_line[basename_start..src_pos_end]); try w.writeAll(": [address] in "); try w.writeAll(in_line[symbol_start..symbol_end]); try w.writeByte('\n'); diff --git a/test/stack_traces.zig b/test/stack_traces.zig index ec3102975e..3b5ed8d541 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -1,4 +1,6 @@ -pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void { +const std = @import("std"); + +pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.Os.Tag) void { cases.addCase(.{ .name = "simple panic", .source = @@ -221,4 +223,118 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void { \\ , }); + + cases.addCase(.{ + .name = "simple inline panic", + .source = + \\pub fn main() void { + \\ foo(); + \\} + \\inline fn foo() void { + \\ @panic("oh no"); + \\} + \\ + , + .unwind = .any, + .expect_panic = true, + .expect = switch (os) { + // We use the information present in PDBs to resolve inlines when dumping stack traces + // on Windows. Column numbers are missing as LLVM doesn't emit column info in the PDBs + // for inline functions. + .windows => + \\panic: oh no + \\source.zig:5: [address] in foo + \\ @panic("oh no"); + \\ + \\source.zig:2:8: [address] in main + \\ foo(); + \\ ^ + \\ + , + // We don't yet resolve inlines on other platforms. + else => + \\panic: oh no + \\source.zig:5:5: [address] in foo + \\ @panic("oh no"); + \\ ^ + , + }, + .expect_strip = switch (os) { + .windows => + \\panic: oh no + \\???:?:?: [address] in source.foo + \\???:?:?: [address] in source.main + \\ + , + else => + \\panic: oh no + \\???:?:?: [address] in source.foo + \\ + , + }, + }); + + // Make sure all inline calls are resolved and in the right order! + cases.addCase(.{ + .name = "nested inline panic", + .source = + \\pub fn main() void { + \\ foo(); + \\} + \\inline fn foo() void { + \\ bar(); + \\} + \\inline fn bar() void { + \\ baz(); + \\} + \\inline fn baz() void { + \\ @panic("oh no"); + \\} + \\ + , + .unwind = .any, + .expect_panic = true, + .expect = switch (os) { + // Similarly to "inline panic", we can resolve inlines from PDBs but LLVM doesn't emit + // column info for them. + .windows => + \\panic: oh no + \\source.zig:11: [address] in baz + \\ @panic("oh no"); + \\ + \\source.zig:8: [address] in bar + \\ baz(); + \\ + \\source.zig:5: [address] in foo + \\ bar(); + \\ + \\source.zig:2:8: [address] in main + \\ foo(); + \\ ^ + \\ + , + // Similarly to "inline panic", we don't yet resolve inlines on other platforms. + else => + \\panic: oh no + \\source.zig:11:5: [address] in baz + \\ @panic("oh no"); + \\ ^ + , + }, + .expect_strip = switch (os) { + .windows => + \\panic: oh no + \\???:?:?: [address] in baz + \\???:?:?: [address] in bar + \\???:?:?: [address] in foo + \\???:?:?: [address] in main + \\ + , + else => + \\panic: oh no + \\???:?:?: [address] in baz + \\ + , + }, + }); } diff --git a/test/tests.zig b/test/tests.zig index a3667385fa..511846dd31 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1989,56 +1989,75 @@ const c_abi_targets = blk: { }; }; -/// For stack trace tests, we only test native, because external executors are pretty unreliable at -/// stack tracing. However, if there's a 32-bit equivalent target which the host can trivially run, -/// we may as well at least test that! -fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { +fn compatible32bitArch(b: *std.Build) ?std.Target.Cpu.Arch { const host = b.graph.host.result; - const only_native = (&b.graph.host)[0..1]; - if (skip_non_native) return only_native; - const arch32: std.Target.Cpu.Arch = switch (host.os.tag) { + return switch (host.os.tag) { .windows => switch (host.cpu.arch) { .x86_64 => .x86, .aarch64 => .thumb, .aarch64_be => .thumbeb, - else => return only_native, + else => null, }, .freebsd => switch (host.cpu.arch) { .aarch64 => .arm, .aarch64_be => .armeb, - else => return only_native, + else => null, }, .linux, .netbsd => switch (host.cpu.arch) { .x86_64 => .x86, .aarch64 => .arm, .aarch64_be => .armeb, - else => return only_native, + else => null, }, - else => return only_native, + else => null, }; - var targets = std.ArrayList(std.Build.ResolvedTarget).initCapacity(b.graph.arena, 2) - catch @panic("OOM"); - targets.appendAssumeCapacity(b.graph.host); - targets.appendAssumeCapacity(b.resolveTargetQuery(.{ - .cpu_arch = arch32, - .os_tag = host.os.tag, - })); - if (b.enable_wine and b.graph.host.result.os.tag != .windows) { - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = host.cpu.arch, - .os_tag = .windows, - })) catch @panic("OOM"); - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = arch32, - .os_tag = .windows, - })) catch @panic("OOM"); - } - if (b.enable_darling and b.graph.host.result.os.tag != .macos) { - targets.append(b.graph.arena, b.resolveTargetQuery(.{ - .cpu_arch = host.cpu.arch, - .os_tag = .macos, - })) catch @panic("OOM"); +} + +/// For stack trace tests, we only test native by default, because external executors are pretty +/// unreliable at stack tracing. However, if there's a 32-bit equivalent target which the host can +/// trivially run, we may as well at least test that! +fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { + const host = b.graph.host.result; + const only_native = (&b.graph.host)[0..1]; + if (skip_non_native) return only_native; + const arch32 = compatible32bitArch(b) orelse return only_native; + return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{ + b.graph.host, + b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }), + }) catch @panic("OOM"); +} + +fn wineAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { + var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty; + + const host = b.graph.host.result; + + targets.append(b.graph.arena, b.resolveTargetQuery(.{ + .cpu_arch = host.cpu.arch, + .os_tag = .windows, + })) catch @panic("OOM"); + if (!skip_non_native) { + if (compatible32bitArch(b)) |arch| { + targets.append(b.graph.arena, b.resolveTargetQuery(.{ + .cpu_arch = arch, + .os_tag = .windows, + })) catch @panic("OOM"); + } } + + return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM"); +} + +fn darlingTargets(b: *std.Build) []const std.Build.ResolvedTarget { + var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty; + + const host = b.graph.host.result; + + targets.append(b.graph.arena, b.resolveTargetQuery(.{ + .cpu_arch = host.cpu.arch, + .os_tag = .macos, + })) catch @panic("OOM"); + return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM"); } @@ -2047,6 +2066,8 @@ pub fn addStackTraceTests( test_filters: []const []const u8, skip_non_native: bool, ) *Step { + const step = b.step("test-stack-traces", "Run the stack trace tests"); + const convert_exe = b.addExecutable(.{ .name = "convert-stack-trace", .root_module = b.createModule(.{ @@ -2056,19 +2077,41 @@ pub fn addStackTraceTests( }), }); - const cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); - - cases.* = .{ + const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + host_cases.* = .{ .b = b, - .step = b.step("test-stack-traces", "Run the stack trace tests"), + .step = step, .test_filters = test_filters, .targets = nativeAndCompatible32bit(b, skip_non_native), .convert_exe = convert_exe, }; + stack_traces.addCases(host_cases, b.graph.host.result.os.tag); - stack_traces.addCases(cases); + if (b.enable_wine) { + const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + wine_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = wineAndCompatible32bit(b, skip_non_native), + .convert_exe = convert_exe, + }; + stack_traces.addCases(wine_cases, .windows); + } - return cases.step; + if (b.enable_darling) { + const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + darling_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = darlingTargets(b), + .convert_exe = convert_exe, + }; + stack_traces.addCases(darling_cases, .macos); + } + + return step; } pub fn addErrorTraceTests( @@ -2077,6 +2120,8 @@ pub fn addErrorTraceTests( optimize_modes: []const OptimizeMode, skip_non_native: bool, ) *Step { + const step = b.step("test-error-traces", "Run the error trace tests"); + const convert_exe = b.addExecutable(.{ .name = "convert-stack-trace", .root_module = b.createModule(.{ @@ -2086,19 +2131,45 @@ pub fn addErrorTraceTests( }), }); - const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); - cases.* = .{ + const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + host_cases.* = .{ .b = b, - .step = b.step("test-error-traces", "Run the error trace tests"), + .step = step, .test_filters = test_filters, .targets = nativeAndCompatible32bit(b, skip_non_native), .optimize_modes = optimize_modes, .convert_exe = convert_exe, }; + error_traces.addCases(host_cases, b.graph.host.result.os.tag); - error_traces.addCases(cases); + if (b.enable_wine) { + const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + wine_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = wineAndCompatible32bit(b, skip_non_native), + .optimize_modes = optimize_modes, + .convert_exe = convert_exe, + }; + error_traces.addCases(wine_cases, .windows); + } - return cases.step; + if (b.enable_darling) { + const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); + darling_cases.* = .{ + .b = b, + .step = step, + .test_filters = test_filters, + .targets = darlingTargets(b), + .optimize_modes = optimize_modes, + .convert_exe = convert_exe, + }; + error_traces.addCases(darling_cases, .macos); + } + + + return step; } fn compilerHasPackageManager(b: *std.Build) bool {