authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-09 19:01:20-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-04-12 04:01:30-07:00
log825ba5a350cab21fff5531d645d18cd9d2facd8f
tree493b190f3c996073092b358682ea277b6c01983b
parentdcdb562c154c950ed06a23a3a267fa8cdc172dda

Adds tests for inline traces


5 files changed, 292 insertions(+), 106 deletions(-)

test/error_traces.zig+53-50
...@@ -1,4 +1,6 @@...@@ -1,4 +1,6 @@
1pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {1const std = @import("std");
2
3pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.Os.Tag) void {
2 cases.addCase(.{4 cases.addCase(.{
3 .name = "return",5 .name = "return",
4 .source =6 .source =
...@@ -450,53 +452,54 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {...@@ -450,53 +452,54 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
450 },452 },
451 });453 });
452454
453 cases.addCase(.{455 // TODO: the standard library has a bug in PDB parsing where given an address corresponding
454 .name = "trace through inline call",456 // to an inline call, the frame we see will be for the *caller*, not the *callee*. As a
455 .source =457 // result this test gives bogus results on Windows right now.
456 \\pub fn main() !void {458 // This is a part of https://codeberg.org/ziglang/zig/issues/30847.
457 \\ try foo();459 if (os != .windows) {
458 \\}460 cases.addCase(.{
459 \\inline fn foo() !void {461 .name = "trace through inline call",
460 \\ try bar();462 .source =
461 \\}463 \\pub fn main() !void {
462 \\fn bar() !void {464 \\ try foo();
463 \\ return error.ThisIsSoSad;465 \\}
464 \\}466 \\inline fn foo() !void {
465 ,467 \\ try bar();
466 .expect_error = "ThisIsSoSad",468 \\}
467 .expect_trace =469 \\fn bar() !void {
468 \\source.zig:8:5: [address] in bar470 \\ return error.ThisIsSoSad;
469 \\ return error.ThisIsSoSad;471 \\}
470 \\ ^472 ,
471 \\source.zig:5:5: [address] in foo473 .expect_error = "ThisIsSoSad",
472 \\ try bar();474 .expect_trace =
473 \\ ^475 \\source.zig:8:5: [address] in bar
474 \\source.zig:2:5: [address] in main476 \\ return error.ThisIsSoSad;
475 \\ try foo();477 \\ ^
476 \\ ^478 \\source.zig:5:5: [address] in foo
477 ,479 \\ try bar();
478 .disable_trace_optimized = &.{480 \\ ^
479 .{ .x86_64, .freebsd },481 \\source.zig:2:5: [address] in main
480 .{ .x86_64, .netbsd },482 \\ try foo();
481 .{ .x86_64, .linux },483 \\ ^
482 .{ .x86, .linux },484 ,
483 .{ .aarch64, .freebsd },485 .disable_trace_optimized = &.{
484 .{ .aarch64, .netbsd },486 .{ .x86_64, .freebsd },
485 .{ .aarch64, .linux },487 .{ .x86_64, .netbsd },
486 .{ .loongarch64, .linux },488 .{ .x86_64, .linux },
487 .{ .powerpc64le, .linux },489 .{ .x86, .linux },
488 .{ .riscv64, .linux },490 .{ .aarch64, .freebsd },
489 .{ .s390x, .linux },491 .{ .aarch64, .netbsd },
490 .{ .x86_64, .openbsd },492 .{ .aarch64, .linux },
491 .{ .x86_64, .windows },493 .{ .loongarch64, .linux },
492 .{ .x86, .windows },494 .{ .powerpc64le, .linux },
493 .{ .x86_64, .macos },495 .{ .riscv64, .linux },
494 .{ .aarch64, .macos },496 .{ .s390x, .linux },
495 },497 .{ .x86_64, .openbsd },
496 // TODO: the standard library has a bug in PDB parsing where given an address corresponding498 .{ .x86_64, .windows },
497 // to an inline call, the frame we see will be for the *caller*, not the *callee*. As a499 .{ .x86, .windows },
498 // result this test gives bogus results on Windows right now.500 .{ .x86_64, .macos },
499 // This is a part of https://codeberg.org/ziglang/zig/issues/30847.501 .{ .aarch64, .macos },
500 .disable_trace_pdb = true,502 },
501 });503 });
504 }
502}505}
test/src/ErrorTrace.zig-3
...@@ -17,8 +17,6 @@ pub const Case = struct {...@@ -17,8 +17,6 @@ pub const Case = struct {
17 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it17 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it
18 /// seems that LLVM is particularly good at optimizing traces away in those.18 /// seems that LLVM is particularly good at optimizing traces away in those.
19 disable_trace_optimized: []const DisableConfig = &.{},19 disable_trace_optimized: []const DisableConfig = &.{},
20 /// If `true` then we will not test the error trace on Windows due to bugs in PDB handling.
21 disable_trace_pdb: bool = false,
2220
23 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };21 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
24 pub const Backend = enum { llvm, selfhosted };22 pub const Backend = enum { llvm, selfhosted };
...@@ -62,7 +60,6 @@ fn addCaseConfig(...@@ -62,7 +60,6 @@ fn addCaseConfig(
62 const b = self.b;60 const b = self.b;
6361
64 const error_tracing: bool = tracing: {62 const error_tracing: bool = tracing: {
65 if (target.result.os.tag == .windows and case.disable_trace_pdb) break :tracing false;
66 if (optimize == .Debug) break :tracing true;63 if (optimize == .Debug) break :tracing true;
67 if (backend != .llvm) break :tracing true;64 if (backend != .llvm) break :tracing true;
68 if (optimize == .ReleaseSmall) break :tracing false;65 if (optimize == .ReleaseSmall) break :tracing false;
test/src/convert-stack-trace.zig+7-8
...@@ -52,20 +52,19 @@ pub fn main(init: std.process.Init) !void {...@@ -52,20 +52,19 @@ pub fn main(init: std.process.Init) !void {
52 continue;52 continue;
53 }53 }
5454
55 const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {55 // If both the row and column are present, this it he column end. Otherwise it's the line end.
56 const src_pos_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {
56 try w.writeAll(in_line);57 try w.writeAll(in_line);
57 continue;58 continue;
58 };59 };
59 const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse {60 const src_row_or_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_pos_end], ':') orelse {
60 try w.writeAll(in_line);
61 continue;
62 };
63 const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse {
64 try w.writeAll(in_line);61 try w.writeAll(in_line);
65 continue;62 continue;
66 };63 };
64 const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_or_path_end], ':')
65 orelse src_row_or_path_end;
6766
68 const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse {67 const addr_end = std.mem.indexOfPos(u8, in_line, src_pos_end, " in ") orelse {
69 try w.writeAll(in_line);68 try w.writeAll(in_line);
70 continue;69 continue;
71 };70 };
...@@ -91,7 +90,7 @@ pub fn main(init: std.process.Init) !void {...@@ -91,7 +90,7 @@ pub fn main(init: std.process.Init) !void {
91 const src_path = in_line[0..src_path_end];90 const src_path = in_line[0..src_path_end];
92 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;91 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;
93 const symbol_start = addr_end + " in ".len;92 const symbol_start = addr_end + " in ".len;
94 try w.writeAll(in_line[basename_start..src_col_end]);93 try w.writeAll(in_line[basename_start..src_pos_end]);
95 try w.writeAll(": [address] in ");94 try w.writeAll(": [address] in ");
96 try w.writeAll(in_line[symbol_start..symbol_end]);95 try w.writeAll(in_line[symbol_start..symbol_end]);
97 try w.writeByte('\n');96 try w.writeByte('\n');
test/stack_traces.zig+117-1
...@@ -1,4 +1,6 @@...@@ -1,4 +1,6 @@
1pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {1const std = @import("std");
2
3pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.Os.Tag) void {
2 cases.addCase(.{4 cases.addCase(.{
3 .name = "simple panic",5 .name = "simple panic",
4 .source =6 .source =
...@@ -221,4 +223,118 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {...@@ -221,4 +223,118 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
221 \\223 \\
222 ,224 ,
223 });225 });
226
227 cases.addCase(.{
228 .name = "simple inline panic",
229 .source =
230 \\pub fn main() void {
231 \\ foo();
232 \\}
233 \\inline fn foo() void {
234 \\ @panic("oh no");
235 \\}
236 \\
237 ,
238 .unwind = .any,
239 .expect_panic = true,
240 .expect = switch (os) {
241 // We use the information present in PDBs to resolve inlines when dumping stack traces
242 // on Windows. Column numbers are missing as LLVM doesn't emit column info in the PDBs
243 // for inline functions.
244 .windows =>
245 \\panic: oh no
246 \\source.zig:5: [address] in foo
247 \\ @panic("oh no");
248 \\
249 \\source.zig:2:8: [address] in main
250 \\ foo();
251 \\ ^
252 \\
253 ,
254 // We don't yet resolve inlines on other platforms.
255 else =>
256 \\panic: oh no
257 \\source.zig:5:5: [address] in foo
258 \\ @panic("oh no");
259 \\ ^
260 ,
261 },
262 .expect_strip = switch (os) {
263 .windows =>
264 \\panic: oh no
265 \\???:?:?: [address] in source.foo
266 \\???:?:?: [address] in source.main
267 \\
268 ,
269 else =>
270 \\panic: oh no
271 \\???:?:?: [address] in source.foo
272 \\
273 ,
274 },
275 });
276
277 // Make sure all inline calls are resolved and in the right order!
278 cases.addCase(.{
279 .name = "nested inline panic",
280 .source =
281 \\pub fn main() void {
282 \\ foo();
283 \\}
284 \\inline fn foo() void {
285 \\ bar();
286 \\}
287 \\inline fn bar() void {
288 \\ baz();
289 \\}
290 \\inline fn baz() void {
291 \\ @panic("oh no");
292 \\}
293 \\
294 ,
295 .unwind = .any,
296 .expect_panic = true,
297 .expect = switch (os) {
298 // Similarly to "inline panic", we can resolve inlines from PDBs but LLVM doesn't emit
299 // column info for them.
300 .windows =>
301 \\panic: oh no
302 \\source.zig:11: [address] in baz
303 \\ @panic("oh no");
304 \\
305 \\source.zig:8: [address] in bar
306 \\ baz();
307 \\
308 \\source.zig:5: [address] in foo
309 \\ bar();
310 \\
311 \\source.zig:2:8: [address] in main
312 \\ foo();
313 \\ ^
314 \\
315 ,
316 // Similarly to "inline panic", we don't yet resolve inlines on other platforms.
317 else =>
318 \\panic: oh no
319 \\source.zig:11:5: [address] in baz
320 \\ @panic("oh no");
321 \\ ^
322 ,
323 },
324 .expect_strip = switch (os) {
325 .windows =>
326 \\panic: oh no
327 \\???:?:?: [address] in baz
328 \\???:?:?: [address] in bar
329 \\???:?:?: [address] in foo
330 \\???:?:?: [address] in main
331 \\
332 ,
333 else =>
334 \\panic: oh no
335 \\???:?:?: [address] in baz
336 \\
337 ,
338 },
339 });
224}340}
test/tests.zig+115-44
...@@ -1989,56 +1989,75 @@ const c_abi_targets = blk: {...@@ -1989,56 +1989,75 @@ const c_abi_targets = blk: {
1989 };1989 };
1990};1990};
19911991
1992/// For stack trace tests, we only test native, because external executors are pretty unreliable at1992fn compatible32bitArch(b: *std.Build) ?std.Target.Cpu.Arch {
1993/// stack tracing. However, if there's a 32-bit equivalent target which the host can trivially run,
1994/// we may as well at least test that!
1995fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
1996 const host = b.graph.host.result;1993 const host = b.graph.host.result;
1997 const only_native = (&b.graph.host)[0..1];1994 return switch (host.os.tag) {
1998 if (skip_non_native) return only_native;
1999 const arch32: std.Target.Cpu.Arch = switch (host.os.tag) {
2000 .windows => switch (host.cpu.arch) {1995 .windows => switch (host.cpu.arch) {
2001 .x86_64 => .x86,1996 .x86_64 => .x86,
2002 .aarch64 => .thumb,1997 .aarch64 => .thumb,
2003 .aarch64_be => .thumbeb,1998 .aarch64_be => .thumbeb,
2004 else => return only_native,1999 else => null,
2005 },2000 },
2006 .freebsd => switch (host.cpu.arch) {2001 .freebsd => switch (host.cpu.arch) {
2007 .aarch64 => .arm,2002 .aarch64 => .arm,
2008 .aarch64_be => .armeb,2003 .aarch64_be => .armeb,
2009 else => return only_native,2004 else => null,
2010 },2005 },
2011 .linux, .netbsd => switch (host.cpu.arch) {2006 .linux, .netbsd => switch (host.cpu.arch) {
2012 .x86_64 => .x86,2007 .x86_64 => .x86,
2013 .aarch64 => .arm,2008 .aarch64 => .arm,
2014 .aarch64_be => .armeb,2009 .aarch64_be => .armeb,
2015 else => return only_native,2010 else => null,
2016 },2011 },
2017 else => return only_native,2012 else => null,
2018 };2013 };
2019 var targets = std.ArrayList(std.Build.ResolvedTarget).initCapacity(b.graph.arena, 2)2014}
2020 catch @panic("OOM");2015
2021 targets.appendAssumeCapacity(b.graph.host);2016/// For stack trace tests, we only test native by default, because external executors are pretty
2022 targets.appendAssumeCapacity(b.resolveTargetQuery(.{2017/// unreliable at stack tracing. However, if there's a 32-bit equivalent target which the host can
2023 .cpu_arch = arch32,2018/// trivially run, we may as well at least test that!
2024 .os_tag = host.os.tag,2019fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2025 }));2020 const host = b.graph.host.result;
2026 if (b.enable_wine and b.graph.host.result.os.tag != .windows) {2021 const only_native = (&b.graph.host)[0..1];
2027 targets.append(b.graph.arena, b.resolveTargetQuery(.{2022 if (skip_non_native) return only_native;
2028 .cpu_arch = host.cpu.arch,2023 const arch32 = compatible32bitArch(b) orelse return only_native;
2029 .os_tag = .windows,2024 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{
2030 })) catch @panic("OOM");2025 b.graph.host,
2031 targets.append(b.graph.arena, b.resolveTargetQuery(.{2026 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),
2032 .cpu_arch = arch32,2027 }) catch @panic("OOM");
2033 .os_tag = .windows,2028}
2034 })) catch @panic("OOM");2029
2035 }2030fn wineAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2036 if (b.enable_darling and b.graph.host.result.os.tag != .macos) {2031 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2037 targets.append(b.graph.arena, b.resolveTargetQuery(.{2032
2038 .cpu_arch = host.cpu.arch,2033 const host = b.graph.host.result;
2039 .os_tag = .macos,2034
2040 })) catch @panic("OOM");2035 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2036 .cpu_arch = host.cpu.arch,
2037 .os_tag = .windows,
2038 })) catch @panic("OOM");
2039 if (!skip_non_native) {
2040 if (compatible32bitArch(b)) |arch| {
2041 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2042 .cpu_arch = arch,
2043 .os_tag = .windows,
2044 })) catch @panic("OOM");
2045 }
2041 }2046 }
2047
2048 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2049}
2050
2051fn darlingTargets(b: *std.Build) []const std.Build.ResolvedTarget {
2052 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2053
2054 const host = b.graph.host.result;
2055
2056 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2057 .cpu_arch = host.cpu.arch,
2058 .os_tag = .macos,
2059 })) catch @panic("OOM");
2060
2042 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");2061 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2043}2062}
20442063
...@@ -2047,6 +2066,8 @@ pub fn addStackTraceTests(...@@ -2047,6 +2066,8 @@ pub fn addStackTraceTests(
2047 test_filters: []const []const u8,2066 test_filters: []const []const u8,
2048 skip_non_native: bool,2067 skip_non_native: bool,
2049) *Step {2068) *Step {
2069 const step = b.step("test-stack-traces", "Run the stack trace tests");
2070
2050 const convert_exe = b.addExecutable(.{2071 const convert_exe = b.addExecutable(.{
2051 .name = "convert-stack-trace",2072 .name = "convert-stack-trace",
2052 .root_module = b.createModule(.{2073 .root_module = b.createModule(.{
...@@ -2056,19 +2077,41 @@ pub fn addStackTraceTests(...@@ -2056,19 +2077,41 @@ pub fn addStackTraceTests(
2056 }),2077 }),
2057 });2078 });
20582079
2059 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");2080 const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
20602081 host_cases.* = .{
2061 cases.* = .{
2062 .b = b,2082 .b = b,
2063 .step = b.step("test-stack-traces", "Run the stack trace tests"),2083 .step = step,
2064 .test_filters = test_filters,2084 .test_filters = test_filters,
2065 .targets = nativeAndCompatible32bit(b, skip_non_native),2085 .targets = nativeAndCompatible32bit(b, skip_non_native),
2066 .convert_exe = convert_exe,2086 .convert_exe = convert_exe,
2067 };2087 };
2088 stack_traces.addCases(host_cases, b.graph.host.result.os.tag);
20682089
2069 stack_traces.addCases(cases);2090 if (b.enable_wine) {
2091 const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2092 wine_cases.* = .{
2093 .b = b,
2094 .step = step,
2095 .test_filters = test_filters,
2096 .targets = wineAndCompatible32bit(b, skip_non_native),
2097 .convert_exe = convert_exe,
2098 };
2099 stack_traces.addCases(wine_cases, .windows);
2100 }
2101
2102 if (b.enable_darling) {
2103 const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2104 darling_cases.* = .{
2105 .b = b,
2106 .step = step,
2107 .test_filters = test_filters,
2108 .targets = darlingTargets(b),
2109 .convert_exe = convert_exe,
2110 };
2111 stack_traces.addCases(darling_cases, .macos);
2112 }
20702113
2071 return cases.step;2114 return step;
2072}2115}
20732116
2074pub fn addErrorTraceTests(2117pub fn addErrorTraceTests(
...@@ -2077,6 +2120,8 @@ pub fn addErrorTraceTests(...@@ -2077,6 +2120,8 @@ pub fn addErrorTraceTests(
2077 optimize_modes: []const OptimizeMode,2120 optimize_modes: []const OptimizeMode,
2078 skip_non_native: bool,2121 skip_non_native: bool,
2079) *Step {2122) *Step {
2123 const step = b.step("test-error-traces", "Run the error trace tests");
2124
2080 const convert_exe = b.addExecutable(.{2125 const convert_exe = b.addExecutable(.{
2081 .name = "convert-stack-trace",2126 .name = "convert-stack-trace",
2082 .root_module = b.createModule(.{2127 .root_module = b.createModule(.{
...@@ -2086,19 +2131,45 @@ pub fn addErrorTraceTests(...@@ -2086,19 +2131,45 @@ pub fn addErrorTraceTests(
2086 }),2131 }),
2087 });2132 });
20882133
2089 const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");2134 const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2090 cases.* = .{2135 host_cases.* = .{
2091 .b = b,2136 .b = b,
2092 .step = b.step("test-error-traces", "Run the error trace tests"),2137 .step = step,
2093 .test_filters = test_filters,2138 .test_filters = test_filters,
2094 .targets = nativeAndCompatible32bit(b, skip_non_native),2139 .targets = nativeAndCompatible32bit(b, skip_non_native),
2095 .optimize_modes = optimize_modes,2140 .optimize_modes = optimize_modes,
2096 .convert_exe = convert_exe,2141 .convert_exe = convert_exe,
2097 };2142 };
2143 error_traces.addCases(host_cases, b.graph.host.result.os.tag);
20982144
2099 error_traces.addCases(cases);2145 if (b.enable_wine) {
2146 const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2147 wine_cases.* = .{
2148 .b = b,
2149 .step = step,
2150 .test_filters = test_filters,
2151 .targets = wineAndCompatible32bit(b, skip_non_native),
2152 .optimize_modes = optimize_modes,
2153 .convert_exe = convert_exe,
2154 };
2155 error_traces.addCases(wine_cases, .windows);
2156 }
21002157
2101 return cases.step;2158 if (b.enable_darling) {
2159 const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2160 darling_cases.* = .{
2161 .b = b,
2162 .step = step,
2163 .test_filters = test_filters,
2164 .targets = darlingTargets(b),
2165 .optimize_modes = optimize_modes,
2166 .convert_exe = convert_exe,
2167 };
2168 error_traces.addCases(darling_cases, .macos);
2169 }
2170
2171
2172 return step;
2102}2173}
21032174
2104fn compilerHasPackageManager(b: *std.Build) bool {2175fn compilerHasPackageManager(b: *std.Build) bool {