authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-09 22:45:39+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:53+01:00
log1a8a8c610d9d5256df25090b0c8ca47cbe94ef1b
tree1a1eeb15ca47cd237a344e86b5e521623fe695f4
parentcedd9de64f183e4ce088654f8a9ed978cbdc8962
signaturelock-open Commit is signed but in an unrecognized format.

tests: split up and enhance stack trace tests

Previously, the `test-stack-traces` step was essentially just testing error traces, and even there we didn't have much coverage. This commit solves that by splitting the "stack trace" tests into two separate harnesses: the "stack trace" tests are for actual stack traces (i.e. involving stack unwinding), while the "error trace" tests are specifically for error return traces. The "stack trace" tests will test different configurations of: * `-lc` * `-fPIE` * `-fomit-frame-pointer` * `-fllvm` * unwind tables (currently disabled) * strip debug info (currently disabled) The main goal there is to test *stack unwinding* under different conditions. Meanwhile, the "error trace" tests will test different configurations of `-O` and `-fllvm`; the main goal here, aside from checking that error traces themselves do not miscompile, is to check whether debug info is still working even in optimized builds. Of course, aggressive optimizations *can* thwart debug info no matter what, so as before, there is a way to disable cases for specific targets / optimize modes. The program which converts stack traces into a more validatable format by removing things like addresses (previously `check-stack-trace.zig`, now `convert-stack-trace.zig`) has been rewritten and simplified. Also, thanks to various fixes in this branch, several workarounds have become unnecessary: for instance, we don't need to ignore the function name printed in stack traces in release modes, because `std.debug.Dwarf` now uses the correct DIE for inlined functions! Neither `test-stack-traces` nor `test-error-traces` does general foreign architecture testing, because it seems that (at least for now) external executors often aren't particularly good at handling stack tracing correctly (looking at you, Wine). Generally, they just test the native target (this matches the old behavior of `test-stack-traces`). However, there is one exception: when on an x86_64 or aarch64 host, we will also test the 32-bit version (x86 or arm) if the OS supports it, because such executables can be trivially tested without an external executor. Oh, also, I wrote a bunch of stack trace tests. Previously there was, erm, *one* test in `test-stack-traces` which wasn't for error traces. Now there are a good few!

9 files changed, 1032 insertions(+), 960 deletions(-)

build.zig+2-1
...@@ -563,7 +563,8 @@ pub fn build(b: *std.Build) !void {...@@ -563,7 +563,8 @@ pub fn build(b: *std.Build) !void {
563 .skip_release = skip_release,563 .skip_release = skip_release,
564 }));564 }));
565 test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows));565 test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows));
566 test_step.dependOn(tests.addStackTraceTests(b, test_filters, optimization_modes));566 test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native));
567 test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimization_modes, skip_non_native));
567 test_step.dependOn(tests.addCliTests(b));568 test_step.dependOn(tests.addCliTests(b));
568 if (tests.addDebuggerTests(b, .{569 if (tests.addDebuggerTests(b, .{
569 .test_filters = test_filters,570 .test_filters = test_filters,
lib/std/debug/SelfInfo.zig+1-1
...@@ -355,7 +355,7 @@ pub const DwarfUnwindContext = struct {...@@ -355,7 +355,7 @@ pub const DwarfUnwindContext = struct {
355 context.reg_context.eh_frame = cie.version != 4;355 context.reg_context.eh_frame = cie.version != 4;
356 context.reg_context.is_macho = native_os.isDarwin();356 context.reg_context.is_macho = native_os.isDarwin();
357357
358 const row = try context.vm.runTo(gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);358 const row = try context.vm.runTo(gpa, pc_vaddr, cie, fde, @sizeOf(usize), native_endian);
359 context.cfa = switch (row.cfa.rule) {359 context.cfa = switch (row.cfa.rule) {
360 .val_offset => |offset| blk: {360 .val_offset => |offset| blk: {
361 const register = row.cfa.register orelse return error.InvalidCFARule;361 const register = row.cfa.register orelse return error.InvalidCFARule;
test/error_traces.zig created+430
...@@ -0,0 +1,430 @@
1pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
2 cases.addCase(.{
3 .name = "return",
4 .source =
5 \\pub fn main() !void {
6 \\ return error.TheSkyIsFalling;
7 \\}
8 ,
9 .expect_error = "TheSkyIsFalling",
10 .expect_trace =
11 \\source.zig:2:5: [address] in main
12 \\ return error.TheSkyIsFalling;
13 \\ ^
14 ,
15 });
16
17 cases.addCase(.{
18 .name = "try return",
19 .source =
20 \\fn foo() !void {
21 \\ return error.TheSkyIsFalling;
22 \\}
23 \\
24 \\pub fn main() !void {
25 \\ try foo();
26 \\}
27 ,
28 .expect_error = "TheSkyIsFalling",
29 .expect_trace =
30 \\source.zig:2:5: [address] in foo
31 \\ return error.TheSkyIsFalling;
32 \\ ^
33 \\source.zig:6:5: [address] in main
34 \\ try foo();
35 \\ ^
36 ,
37 .disable_trace_optimized = &.{
38 .{ .x86_64, .windows },
39 .{ .x86, .windows },
40 },
41 });
42 cases.addCase(.{
43 .name = "non-error return pops error trace",
44 .source =
45 \\fn bar() !void {
46 \\ return error.UhOh;
47 \\}
48 \\
49 \\fn foo() !void {
50 \\ bar() catch {
51 \\ return; // non-error result: success
52 \\ };
53 \\}
54 \\
55 \\pub fn main() !void {
56 \\ try foo();
57 \\ return error.UnrelatedError;
58 \\}
59 ,
60 .expect_error = "UnrelatedError",
61 .expect_trace =
62 \\source.zig:13:5: [address] in main
63 \\ return error.UnrelatedError;
64 \\ ^
65 ,
66 });
67
68 cases.addCase(.{
69 .name = "continue in while loop",
70 .source =
71 \\fn foo() !void {
72 \\ return error.UhOh;
73 \\}
74 \\
75 \\pub fn main() !void {
76 \\ var i: usize = 0;
77 \\ while (i < 3) : (i += 1) {
78 \\ foo() catch continue;
79 \\ }
80 \\ return error.UnrelatedError;
81 \\}
82 ,
83 .expect_error = "UnrelatedError",
84 .expect_trace =
85 \\source.zig:10:5: [address] in main
86 \\ return error.UnrelatedError;
87 \\ ^
88 ,
89 .disable_trace_optimized = &.{
90 .{ .x86_64, .linux },
91 .{ .x86, .linux },
92 .{ .x86_64, .windows },
93 .{ .x86, .windows },
94 },
95 });
96
97 cases.addCase(.{
98 .name = "try return + handled catch/if-else",
99 .source =
100 \\fn foo() !void {
101 \\ return error.TheSkyIsFalling;
102 \\}
103 \\
104 \\pub fn main() !void {
105 \\ foo() catch {}; // should not affect error trace
106 \\ if (foo()) |_| {} else |_| {
107 \\ // should also not affect error trace
108 \\ }
109 \\ try foo();
110 \\}
111 ,
112 .expect_error = "TheSkyIsFalling",
113 .expect_trace =
114 \\source.zig:2:5: [address] in foo
115 \\ return error.TheSkyIsFalling;
116 \\ ^
117 \\source.zig:10:5: [address] in main
118 \\ try foo();
119 \\ ^
120 ,
121 .disable_trace_optimized = &.{
122 .{ .x86_64, .windows },
123 .{ .x86, .windows },
124 },
125 });
126
127 cases.addCase(.{
128 .name = "break from inline loop pops error return trace",
129 .source =
130 \\fn foo() !void { return error.FooBar; }
131 \\
132 \\pub fn main() !void {
133 \\ comptime var i: usize = 0;
134 \\ b: inline while (i < 5) : (i += 1) {
135 \\ foo() catch {
136 \\ break :b; // non-error break, success
137 \\ };
138 \\ }
139 \\ // foo() was successfully handled, should not appear in trace
140 \\
141 \\ return error.BadTime;
142 \\}
143 ,
144 .expect_error = "BadTime",
145 .expect_trace =
146 \\source.zig:12:5: [address] in main
147 \\ return error.BadTime;
148 \\ ^
149 ,
150 });
151
152 cases.addCase(.{
153 .name = "catch and re-throw error",
154 .source =
155 \\fn foo() !void {
156 \\ return error.TheSkyIsFalling;
157 \\}
158 \\
159 \\pub fn main() !void {
160 \\ return foo() catch error.AndMyCarIsOutOfGas;
161 \\}
162 ,
163 .expect_error = "AndMyCarIsOutOfGas",
164 .expect_trace =
165 \\source.zig:2:5: [address] in foo
166 \\ return error.TheSkyIsFalling;
167 \\ ^
168 \\source.zig:6:5: [address] in main
169 \\ return foo() catch error.AndMyCarIsOutOfGas;
170 \\ ^
171 ,
172 .disable_trace_optimized = &.{
173 .{ .x86_64, .windows },
174 .{ .x86, .windows },
175 },
176 });
177
178 cases.addCase(.{
179 .name = "errors stored in var do not contribute to error trace",
180 .source =
181 \\fn foo() !void {
182 \\ return error.TheSkyIsFalling;
183 \\}
184 \\
185 \\pub fn main() !void {
186 \\ // Once an error is stored in a variable, it is popped from the trace
187 \\ var x = foo();
188 \\ x = {};
189 \\
190 \\ // As a result, this error trace will still be clean
191 \\ return error.SomethingUnrelatedWentWrong;
192 \\}
193 ,
194 .expect_error = "SomethingUnrelatedWentWrong",
195 .expect_trace =
196 \\source.zig:11:5: [address] in main
197 \\ return error.SomethingUnrelatedWentWrong;
198 \\ ^
199 ,
200 });
201
202 cases.addCase(.{
203 .name = "error stored in const has trace preserved for duration of block",
204 .source =
205 \\fn foo() !void { return error.TheSkyIsFalling; }
206 \\fn bar() !void { return error.InternalError; }
207 \\fn baz() !void { return error.UnexpectedReality; }
208 \\
209 \\pub fn main() !void {
210 \\ const x = foo();
211 \\ const y = b: {
212 \\ if (true)
213 \\ break :b bar();
214 \\
215 \\ break :b {};
216 \\ };
217 \\ x catch {};
218 \\ y catch {};
219 \\ // foo()/bar() error traces not popped until end of block
220 \\
221 \\ {
222 \\ const z = baz();
223 \\ z catch {};
224 \\ // baz() error trace still alive here
225 \\ }
226 \\ // baz() error trace popped, foo(), bar() still alive
227 \\ return error.StillUnresolved;
228 \\}
229 ,
230 .expect_error = "StillUnresolved",
231 .expect_trace =
232 \\source.zig:1:18: [address] in foo
233 \\fn foo() !void { return error.TheSkyIsFalling; }
234 \\ ^
235 \\source.zig:2:18: [address] in bar
236 \\fn bar() !void { return error.InternalError; }
237 \\ ^
238 \\source.zig:23:5: [address] in main
239 \\ return error.StillUnresolved;
240 \\ ^
241 ,
242 .disable_trace_optimized = &.{
243 .{ .x86_64, .windows },
244 .{ .x86, .windows },
245 },
246 });
247
248 cases.addCase(.{
249 .name = "error passed to function has its trace preserved for duration of the call",
250 .source =
251 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
252 \\ actual_error catch |err| {
253 \\ if (err == expected_error) return {};
254 \\ };
255 \\ return error.TestExpectedError;
256 \\}
257 \\
258 \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
259 \\fn foo() !void { return error.Foo; }
260 \\
261 \\pub fn main() !void {
262 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
263 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
264 \\ try expectError(error.Foo, foo());
265 \\
266 \\ // Only the error trace for this failing check should appear:
267 \\ try expectError(error.Bar, foo());
268 \\}
269 ,
270 .expect_error = "TestExpectedError",
271 .expect_trace =
272 \\source.zig:9:18: [address] in foo
273 \\fn foo() !void { return error.Foo; }
274 \\ ^
275 \\source.zig:5:5: [address] in expectError
276 \\ return error.TestExpectedError;
277 \\ ^
278 \\source.zig:17:5: [address] in main
279 \\ try expectError(error.Bar, foo());
280 \\ ^
281 ,
282 .disable_trace_optimized = &.{
283 .{ .x86_64, .windows },
284 .{ .x86, .windows },
285 },
286 });
287
288 cases.addCase(.{
289 .name = "try return from within catch",
290 .source =
291 \\fn foo() !void {
292 \\ return error.TheSkyIsFalling;
293 \\}
294 \\
295 \\fn bar() !void {
296 \\ return error.AndMyCarIsOutOfGas;
297 \\}
298 \\
299 \\pub fn main() !void {
300 \\ foo() catch { // error trace should include foo()
301 \\ try bar();
302 \\ };
303 \\}
304 ,
305 .expect_error = "AndMyCarIsOutOfGas",
306 .expect_trace =
307 \\source.zig:2:5: [address] in foo
308 \\ return error.TheSkyIsFalling;
309 \\ ^
310 \\source.zig:6:5: [address] in bar
311 \\ return error.AndMyCarIsOutOfGas;
312 \\ ^
313 \\source.zig:11:9: [address] in main
314 \\ try bar();
315 \\ ^
316 ,
317 .disable_trace_optimized = &.{
318 .{ .x86_64, .windows },
319 .{ .x86, .windows },
320 },
321 });
322
323 cases.addCase(.{
324 .name = "try return from within if-else",
325 .source =
326 \\fn foo() !void {
327 \\ return error.TheSkyIsFalling;
328 \\}
329 \\
330 \\fn bar() !void {
331 \\ return error.AndMyCarIsOutOfGas;
332 \\}
333 \\
334 \\pub fn main() !void {
335 \\ if (foo()) |_| {} else |_| { // error trace should include foo()
336 \\ try bar();
337 \\ }
338 \\}
339 ,
340 .expect_error = "AndMyCarIsOutOfGas",
341 .expect_trace =
342 \\source.zig:2:5: [address] in foo
343 \\ return error.TheSkyIsFalling;
344 \\ ^
345 \\source.zig:6:5: [address] in bar
346 \\ return error.AndMyCarIsOutOfGas;
347 \\ ^
348 \\source.zig:11:9: [address] in main
349 \\ try bar();
350 \\ ^
351 ,
352 .disable_trace_optimized = &.{
353 .{ .x86_64, .windows },
354 .{ .x86, .windows },
355 },
356 });
357
358 cases.addCase(.{
359 .name = "try try return return",
360 .source =
361 \\fn foo() !void {
362 \\ try bar();
363 \\}
364 \\
365 \\fn bar() !void {
366 \\ return make_error();
367 \\}
368 \\
369 \\fn make_error() !void {
370 \\ return error.TheSkyIsFalling;
371 \\}
372 \\
373 \\pub fn main() !void {
374 \\ try foo();
375 \\}
376 ,
377 .expect_error = "TheSkyIsFalling",
378 .expect_trace =
379 \\source.zig:10:5: [address] in make_error
380 \\ return error.TheSkyIsFalling;
381 \\ ^
382 \\source.zig:6:5: [address] in bar
383 \\ return make_error();
384 \\ ^
385 \\source.zig:2:5: [address] in foo
386 \\ try bar();
387 \\ ^
388 \\source.zig:14:5: [address] in main
389 \\ try foo();
390 \\ ^
391 ,
392 .disable_trace_optimized = &.{
393 .{ .x86_64, .windows },
394 .{ .x86, .windows },
395 },
396 });
397
398 cases.addCase(.{
399 .name = "error union switch with call operand",
400 .source =
401 \\pub fn main() !void {
402 \\ try foo();
403 \\ return error.TheSkyIsFalling;
404 \\}
405 \\
406 \\noinline fn failure() error{ Fatal, NonFatal }!void {
407 \\ return error.NonFatal;
408 \\}
409 \\
410 \\fn foo() error{Fatal}!void {
411 \\ return failure() catch |err| switch (err) {
412 \\ error.Fatal => return error.Fatal,
413 \\ error.NonFatal => return,
414 \\ };
415 \\}
416 ,
417 .expect_error = "TheSkyIsFalling",
418 .expect_trace =
419 \\source.zig:3:5: [address] in main
420 \\ return error.TheSkyIsFalling;
421 \\ ^
422 ,
423 .disable_trace_optimized = &.{
424 .{ .x86_64, .linux },
425 .{ .x86, .linux },
426 .{ .x86_64, .windows },
427 .{ .x86, .windows },
428 },
429 });
430}
test/src/ErrorTrace.zig created+126
...@@ -0,0 +1,126 @@
1b: *std.Build,
2step: *Step,
3test_filters: []const []const u8,
4targets: []const std.Build.ResolvedTarget,
5optimize_modes: []const OptimizeMode,
6convert_exe: *std.Build.Step.Compile,
7
8pub const Case = struct {
9 name: []const u8,
10 source: []const u8,
11 expect_error: []const u8,
12 expect_trace: []const u8,
13 /// On these arch/OS pairs we will not test the error trace on optimized LLVM builds because the
14 /// optimizations break the error trace. We will test the binary with error tracing disabled,
15 /// just to ensure that the expected error is still returned from `main`.
16 disable_trace_optimized: []const DisableConfig = &.{},
17
18 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
19 pub const Backend = enum { llvm, selfhosted };
20};
21
22pub fn addCase(self: *ErrorTrace, case: Case) void {
23 for (self.targets) |*target| {
24 const triple: ?[]const u8 = if (target.query.isNative()) null else t: {
25 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");
26 };
27 for (self.optimize_modes) |optimize| {
28 self.addCaseConfig(case, target, triple, optimize, .llvm);
29 }
30 if (shouldTestNonLlvm(&target.result)) {
31 for (self.optimize_modes) |optimize| {
32 self.addCaseConfig(case, target, triple, optimize, .selfhosted);
33 }
34 }
35 }
36}
37
38fn shouldTestNonLlvm(target: *const std.Target) bool {
39 return switch (target.cpu.arch) {
40 .x86_64 => switch (target.ofmt) {
41 .elf => true,
42 else => false,
43 },
44 else => false,
45 };
46}
47
48fn addCaseConfig(
49 self: *ErrorTrace,
50 case: Case,
51 target: *const std.Build.ResolvedTarget,
52 triple: ?[]const u8,
53 optimize: OptimizeMode,
54 backend: Case.Backend,
55) void {
56 const b = self.b;
57
58 const error_tracing: bool = tracing: {
59 if (optimize == .Debug) break :tracing true;
60 if (backend != .llvm) break :tracing true;
61 for (case.disable_trace_optimized) |disable| {
62 const d_arch, const d_os = disable;
63 if (target.result.cpu.arch == d_arch and target.result.os.tag == d_os) {
64 // This particular configuration cannot do error tracing in optimized LLVM builds.
65 break :tracing false;
66 }
67 }
68 break :tracing true;
69 };
70
71 const annotated_case_name = b.fmt("check {s} ({s}{s}{s} {s})", .{
72 case.name,
73 triple orelse "",
74 if (triple != null) " " else "",
75 @tagName(optimize),
76 @tagName(backend),
77 });
78 if (self.test_filters.len > 0) {
79 for (self.test_filters) |test_filter| {
80 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
81 } else return;
82 }
83
84 const write_files = b.addWriteFiles();
85 const source_zig = write_files.add("source.zig", case.source);
86 const exe = b.addExecutable(.{
87 .name = "test",
88 .root_module = b.createModule(.{
89 .root_source_file = source_zig,
90 .optimize = optimize,
91 .target = target.*,
92 .error_tracing = error_tracing,
93 .strip = false,
94 }),
95 .use_llvm = switch (backend) {
96 .llvm => true,
97 .selfhosted => false,
98 },
99 });
100 exe.bundle_ubsan_rt = false;
101
102 const run = b.addRunArtifact(exe);
103 run.removeEnvironmentVariable("CLICOLOR_FORCE");
104 run.setEnvironmentVariable("NO_COLOR", "1");
105 run.expectExitCode(1);
106 run.expectStdOutEqual("");
107
108 const expected_stderr = switch (error_tracing) {
109 true => b.fmt("error: {s}\n{s}\n", .{ case.expect_error, case.expect_trace }),
110 false => b.fmt("error: {s}\n", .{case.expect_error}),
111 };
112
113 const check_run = b.addRunArtifact(self.convert_exe);
114 check_run.setName(annotated_case_name);
115 check_run.addFileArg(run.captureStdErr(.{}));
116 check_run.expectStdOutEqual(expected_stderr);
117
118 self.step.dependOn(&check_run.step);
119}
120
121const ErrorTrace = @This();
122const std = @import("std");
123const builtin = @import("builtin");
124const Step = std.Build.Step;
125const OptimizeMode = std.builtin.OptimizeMode;
126const mem = std.mem;
test/src/StackTrace.zig+152-56
...@@ -1,75 +1,164 @@...@@ -1,75 +1,164 @@
1b: *std.Build,1b: *std.Build,
2step: *Step,2step: *Step,
3test_index: usize,
4test_filters: []const []const u8,3test_filters: []const []const u8,
5optimize_modes: []const OptimizeMode,4targets: []const std.Build.ResolvedTarget,
6check_exe: *std.Build.Step.Compile,5convert_exe: *std.Build.Step.Compile,
76
8const Config = struct {7const Config = struct {
9 name: []const u8,8 name: []const u8,
10 source: []const u8,9 source: []const u8,
11 Debug: ?PerMode = null,10 /// Whether this test case expects to have unwind tables / frame pointers.
12 ReleaseSmall: ?PerMode = null,11 unwind: enum {
13 ReleaseSafe: ?PerMode = null,12 /// This case assumes that some unwind strategy, safe or unsafe, is available.
14 ReleaseFast: ?PerMode = null,13 any,
1514 /// This case assumes that no unwinding strategy is available.
16 const PerMode = struct {15 none,
17 expect: []const u8,16 /// This case assumes that a safe unwind strategy, like DWARF unwinding, is available.
18 exclude_arch: []const std.Target.Cpu.Arch = &.{},17 safe,
19 exclude_os: []const std.Target.Os.Tag = &.{},18 /// This case assumes that at most, unsafe FP unwinding is available.
20 error_tracing: ?bool = null,19 no_safe,
21 };20 },
21 /// If `true`, the expected exit code is that of the default panic handler, rather than 0.
22 expect_panic: bool,
23 /// When debug info is not stripped, stdout is expected to **contain** (not equal!) this string.
24 expect: []const u8,
25 /// When debug info *is* stripped, stdout is expected to **contain** (not equal!) this string.
26 expect_strip: []const u8,
22};27};
2328
24pub fn addCase(self: *StackTrace, config: Config) void {29pub fn addCase(self: *StackTrace, config: Config) void {
25 self.addCaseInner(config, true);30 for (self.targets) |*target| {
26 if (shouldTestNonLlvm(&self.b.graph.host.result)) {31 addCaseTarget(
27 self.addCaseInner(config, false);32 self,
33 config,
34 target,
35 if (target.query.isNative()) null else t: {
36 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");
37 },
38 );
28 }39 }
29}40}
3041fn addCaseTarget(
31fn addCaseInner(self: *StackTrace, config: Config, use_llvm: bool) void {42 self: *StackTrace,
32 if (config.Debug) |per_mode|43 config: Config,
33 self.addExpect(config.name, config.source, .Debug, use_llvm, per_mode);44 target: *const std.Build.ResolvedTarget,
3445 triple: ?[]const u8,
35 if (config.ReleaseSmall) |per_mode|46) void {
36 self.addExpect(config.name, config.source, .ReleaseSmall, use_llvm, per_mode);47 const both_backends = switch (target.result.cpu.arch) {
3748 .x86_64 => switch (target.result.ofmt) {
38 if (config.ReleaseFast) |per_mode|49 .elf => true,
39 self.addExpect(config.name, config.source, .ReleaseFast, use_llvm, per_mode);
40
41 if (config.ReleaseSafe) |per_mode|
42 self.addExpect(config.name, config.source, .ReleaseSafe, use_llvm, per_mode);
43}
44
45fn shouldTestNonLlvm(target: *const std.Target) bool {
46 return switch (target.cpu.arch) {
47 .x86_64 => switch (target.ofmt) {
48 .elf => !target.os.tag.isBSD(),
49 else => false,50 else => false,
50 },51 },
51 else => false,52 else => false,
52 };53 };
54 const both_pie = switch (target.result.os.tag) {
55 .fuchsia, .openbsd => false,
56 else => true,
57 };
58 const both_libc = switch (target.result.os.tag) {
59 .freebsd, .netbsd => false,
60 else => !target.result.requiresLibC(),
61 };
62
63 // On aarch64-macos, FP unwinding is blessed by Apple to always be reliable, and std.debug knows this.
64 const fp_unwind_is_safe = target.result.cpu.arch == .aarch64 and target.result.os.tag.isDarwin();
65
66 const use_llvm_vals: []const bool = if (both_backends) &.{ true, false } else &.{true};
67 const pie_vals: []const ?bool = if (both_pie) &.{ true, false } else &.{null};
68 const link_libc_vals: []const ?bool = if (both_libc) &.{ true, false } else &.{null};
69 const strip_debug_vals: []const bool = &.{ true, false };
70
71 const UnwindInfo = packed struct(u2) {
72 tables: bool,
73 fp: bool,
74 const none: @This() = .{ .tables = false, .fp = false };
75 const both: @This() = .{ .tables = true, .fp = true };
76 const only_tables: @This() = .{ .tables = true, .fp = false };
77 const only_fp: @This() = .{ .tables = false, .fp = true };
78 };
79 const unwind_info_vals: []const UnwindInfo = switch (config.unwind) {
80 .none => &.{.none},
81 .any => &.{ .only_tables, .only_fp, .both },
82 .safe => if (fp_unwind_is_safe) &.{ .only_tables, .only_fp, .both } else &.{ .only_tables, .both },
83 .no_safe => if (fp_unwind_is_safe) &.{.none} else &.{ .none, .only_fp },
84 };
85
86 for (use_llvm_vals) |use_llvm| {
87 for (pie_vals) |pie| {
88 for (link_libc_vals) |link_libc| {
89 for (strip_debug_vals) |strip_debug| {
90 for (unwind_info_vals) |unwind_info| {
91 self.addCaseInstance(
92 target,
93 triple,
94 config.name,
95 config.source,
96 use_llvm,
97 pie,
98 link_libc,
99 strip_debug,
100 !unwind_info.tables,
101 !unwind_info.fp,
102 config.expect_panic,
103 if (strip_debug) config.expect_strip else config.expect,
104 );
105 }
106 }
107 }
108 }
109 }
53}110}
54111
55fn addExpect(112fn addCaseInstance(
56 self: *StackTrace,113 self: *StackTrace,
114 target: *const std.Build.ResolvedTarget,
115 triple: ?[]const u8,
57 name: []const u8,116 name: []const u8,
58 source: []const u8,117 source: []const u8,
59 optimize_mode: OptimizeMode,
60 use_llvm: bool,118 use_llvm: bool,
61 mode_config: Config.PerMode,119 pie: ?bool,
120 link_libc: ?bool,
121 strip_debug: bool,
122 strip_unwind: bool,
123 omit_frame_pointer: bool,
124 expect_panic: bool,
125 expect_stderr: []const u8,
62) void {126) void {
63 for (mode_config.exclude_arch) |tag| if (tag == builtin.cpu.arch) return;
64 for (mode_config.exclude_os) |tag| if (tag == builtin.os.tag) return;
65
66 const b = self.b;127 const b = self.b;
67 const annotated_case_name = b.fmt("check {s} ({s} {s})", .{128
68 name, @tagName(optimize_mode), if (use_llvm) "llvm" else "selfhosted",129 if (strip_debug) {
130 // To enable this coverage, one of two things needs to happen:
131 // * The compiler needs to gain the ability to strip only debug info (not symbols)
132 // * `std.Build.Step.ObjCopy` needs to be un-regressed
133 return;
134 }
135
136 if (strip_unwind) {
137 // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the
138 // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it
139 // does not prevent `.debug_frame` from being emitted. If we could, we would remove the
140 // following sections:
141 // * `.eh_frame`, `.eh_frame_hdr`, `.debug_frame` (Linux)
142 // * `__TEXT,__eh_frame`, `__TEXT,__unwind_info` (macOS)
143 return;
144 }
145
146 const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{
147 name,
148 triple orelse "",
149 if (triple != null) " " else "",
150 if (use_llvm) "llvm" else "selfhosted",
151 if (pie == true) " pie" else "",
152 if (link_libc == true) " libc" else "",
153 if (strip_debug) " strip" else "",
154 if (strip_unwind) " no_unwind" else "",
155 if (omit_frame_pointer) " no_fp" else "",
69 });156 });
70 for (self.test_filters) |test_filter| {157 if (self.test_filters.len > 0) {
71 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;158 for (self.test_filters) |test_filter| {
72 } else if (self.test_filters.len > 0) return;159 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
160 } else return;
161 }
73162
74 const write_files = b.addWriteFiles();163 const write_files = b.addWriteFiles();
75 const source_zig = write_files.add("source.zig", source);164 const source_zig = write_files.add("source.zig", source);
...@@ -77,27 +166,34 @@ fn addExpect(...@@ -77,27 +166,34 @@ fn addExpect(
77 .name = "test",166 .name = "test",
78 .root_module = b.createModule(.{167 .root_module = b.createModule(.{
79 .root_source_file = source_zig,168 .root_source_file = source_zig,
80 .optimize = optimize_mode,169 .optimize = .Debug,
81 .target = b.graph.host,170 .target = target.*,
82 .error_tracing = mode_config.error_tracing,171 .omit_frame_pointer = omit_frame_pointer,
172 .link_libc = link_libc,
173 .unwind_tables = if (strip_unwind) .none else null,
174 // make panics single-threaded so that they don't include a thread ID
175 .single_threaded = expect_panic,
83 }),176 }),
84 .use_llvm = use_llvm,177 .use_llvm = use_llvm,
85 });178 });
179 exe.pie = pie;
86 exe.bundle_ubsan_rt = false;180 exe.bundle_ubsan_rt = false;
87181
88 const run = b.addRunArtifact(exe);182 const run = b.addRunArtifact(exe);
89 run.removeEnvironmentVariable("CLICOLOR_FORCE");183 run.removeEnvironmentVariable("CLICOLOR_FORCE");
90 run.setEnvironmentVariable("NO_COLOR", "1");184 run.setEnvironmentVariable("NO_COLOR", "1");
91 run.expectExitCode(1);185 run.addCheck(.{ .expect_term = term: {
186 if (!expect_panic) break :term .{ .Exited = 0 };
187 if (target.result.os.tag == .windows) break :term .{ .Exited = 3 };
188 break :term .{ .Signal = 6 };
189 } });
92 run.expectStdOutEqual("");190 run.expectStdOutEqual("");
93191
94 const check_run = b.addRunArtifact(self.check_exe);192 const check_run = b.addRunArtifact(self.convert_exe);
95 check_run.setName(annotated_case_name);193 check_run.setName(annotated_case_name);
96 check_run.addFileArg(run.captureStdErr(.{}));194 check_run.addFileArg(run.captureStdErr(.{}));
97 check_run.addArgs(&.{195 check_run.expectExitCode(0);
98 @tagName(optimize_mode),196 check_run.addCheck(.{ .expect_stdout_match = expect_stderr });
99 });
100 check_run.expectStdOutEqual(mode_config.expect);
101197
102 self.step.dependOn(&check_run.step);198 self.step.dependOn(&check_run.step);
103}199}
test/src/check-stack-trace.zig deleted-88
...@@ -1,88 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5
6pub fn main() !void {
7 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
8 defer arena_instance.deinit();
9 const arena = arena_instance.allocator();
10
11 const args = try std.process.argsAlloc(arena);
12
13 const input_path = args[1];
14 const optimize_mode_text = args[2];
15
16 const input_bytes = try std.fs.cwd().readFileAlloc(input_path, arena, .limited(5 * 1024 * 1024));
17 const optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, optimize_mode_text).?;
18
19 var stderr = input_bytes;
20
21 // process result
22 // - keep only basename of source file path
23 // - replace address with symbolic string
24 // - replace function name with symbolic string when optimize_mode != .Debug
25 // - skip empty lines
26 const got: []const u8 = got_result: {
27 var buf = std.array_list.Managed(u8).init(arena);
28 defer buf.deinit();
29 if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1];
30 var it = mem.splitScalar(u8, stderr, '\n');
31 process_lines: while (it.next()) |line| {
32 if (line.len == 0) continue;
33
34 // offset search past `[drive]:` on windows
35 var pos: usize = if (builtin.os.tag == .windows) 2 else 0;
36 // locate delims/anchor
37 const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" };
38 var marks = [_]usize{0} ** delims.len;
39 for (delims, 0..) |delim, i| {
40 marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse {
41 // unexpected pattern: emit raw line and cont
42 try buf.appendSlice(line);
43 try buf.appendSlice("\n");
44 continue :process_lines;
45 };
46 pos = marks[i] + delim.len;
47 }
48 // locate source basename
49 pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse {
50 // unexpected pattern: emit raw line and cont
51 try buf.appendSlice(line);
52 try buf.appendSlice("\n");
53 continue :process_lines;
54 };
55 // end processing if source basename changes
56 if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break;
57 // emit substituted line
58 try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]);
59 try buf.appendSlice(" [address]");
60 if (optimize_mode == .Debug) {
61 try buf.appendSlice(line[marks[3] .. marks[4] + delims[4].len]);
62
63 const file_name = line[marks[4] + delims[4].len .. marks[5]];
64 // The LLVM backend currently uses the object file name in the debug info here.
65 // This actually violates the DWARF specification (DWARF5 § 3.1.1, lines 24-27).
66 // The self-hosted backend uses the root Zig source file of the module (in compilance with the spec).
67 if (std.mem.eql(u8, file_name, "test") or
68 std.mem.eql(u8, file_name, "test_zcu.obj") or
69 std.mem.endsWith(u8, file_name, ".zig"))
70 {
71 try buf.appendSlice("[main_file]");
72 } else {
73 // Something unexpected; include it verbatim.
74 try buf.appendSlice(file_name);
75 }
76
77 try buf.appendSlice(line[marks[5]..]);
78 } else {
79 try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]);
80 try buf.appendSlice("[function]");
81 }
82 try buf.appendSlice("\n");
83 }
84 break :got_result try buf.toOwnedSlice();
85 };
86
87 try std.fs.File.stdout().writeAll(got);
88}
test/src/convert-stack-trace.zig created+104
...@@ -0,0 +1,104 @@
1//! Accepts a stack trace in a file (whose path is given as argv[1]), and removes all
2//! non-reproducible information from it, including addresses, module names, and file
3//! paths. All module names are removed, file paths become just their basename, and
4//! addresses are replaced with a fixed string. So, lines like this:
5//!
6//! /something/foo.zig:1:5: 0x12345678 in bar (main.o)
7//! doThing();
8//! ^
9//! ???:?:?: 0x12345678 in qux (other.o)
10//! ???:?:?: 0x12345678 in ??? (???)
11//!
12//! ...are turned into lines like this:
13//!
14//! foo.zig:1:5: [address] in bar
15//! doThing();
16//! ^
17//! ???:?:?: [address] in qux
18//! ???:?:?: [address] in ???
19//!
20//! Additionally, lines reporting unwind errors are removed:
21//!
22//! Unwind error at address `/proc/self/exe:0x1016533` (unwind info unavailable), remaining frames may be incorrect
23//!
24//! With these transformations, the test harness can safely do string comparisons.
25
26pub fn main() !void {
27 var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
28 defer arena_instance.deinit();
29 const arena = arena_instance.allocator();
30
31 const args = try std.process.argsAlloc(arena);
32 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});
33
34 var read_buf: [1024]u8 = undefined;
35 var write_buf: [1024]u8 = undefined;
36
37 const in_file = try std.fs.cwd().openFile(args[1], .{});
38 defer in_file.close();
39
40 const out_file: std.fs.File = .stdout();
41
42 var in_fr = in_file.reader(&read_buf);
43 var out_fw = out_file.writer(&write_buf);
44
45 const w = &out_fw.interface;
46
47 while (in_fr.interface.takeDelimiterInclusive('\n')) |in_line| {
48 if (std.mem.startsWith(u8, in_line, "Unwind error at address `")) {
49 // Remove these lines from the output.
50 continue;
51 }
52
53 const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {
54 try w.writeAll(in_line);
55 continue;
56 };
57 const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse {
58 try w.writeAll(in_line);
59 continue;
60 };
61 const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse {
62 try w.writeAll(in_line);
63 continue;
64 };
65
66 const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse {
67 try w.writeAll(in_line);
68 continue;
69 };
70 const symbol_end = std.mem.indexOfPos(u8, in_line, addr_end, " (") orelse {
71 try w.writeAll(in_line);
72 continue;
73 };
74 if (!std.mem.endsWith(u8, std.mem.trimEnd(u8, in_line, "\n"), ")")) {
75 try w.writeAll(in_line);
76 continue;
77 }
78
79 // Where '_' is a placeholder for an arbitrary string, we now know the line looks like:
80 //
81 // _:_:_: 0x_ in _ (_)
82 //
83 // That seems good enough to assume it's a stack trace frame! We'll rewrite it to:
84 //
85 // _:_:_: [address] in _
86 //
87 // ...with that first '_' being replaced by its basename.
88
89 const src_path = in_line[0..src_path_end];
90 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;
91 const symbol_start = addr_end + " in ".len;
92 try w.writeAll(in_line[basename_start..src_col_end]);
93 try w.writeAll(": [address] in ");
94 try w.writeAll(in_line[symbol_start..symbol_end]);
95 try w.writeByte('\n');
96 } else |err| switch (err) {
97 error.EndOfStream => {},
98 else => |e| return e,
99 }
100
101 try w.flush();
102}
103
104const std = @import("std");
test/stack_traces.zig+153-807
...@@ -1,878 +1,224 @@...@@ -1,878 +1,224 @@
1const std = @import("std");1pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
2const os = std.os;
3const tests = @import("tests.zig");
4
5pub fn addCases(cases: *tests.StackTracesContext) void {
6 cases.addCase(.{2 cases.addCase(.{
7 .name = "return",3 .name = "simple panic",
8 .source =4 .source =
9 \\pub fn main() !void {5 \\pub fn main() void {
10 \\ return error.TheSkyIsFalling;6 \\ foo();
11 \\}7 \\}
12 ,8 \\fn foo() void {
13 .Debug = .{9 \\ @panic("oh no");
14 .expect =
15 \\error: TheSkyIsFalling
16 \\source.zig:2:5: [address] in main ([main_file])
17 \\ return error.TheSkyIsFalling;
18 \\ ^
19 \\
20 ,
21 },
22 .ReleaseSafe = .{
23 .exclude_os = &.{
24 .windows, // TODO
25 .linux, // defeated by aggressive inlining
26 },
27 .expect =
28 \\error: TheSkyIsFalling
29 \\source.zig:2:5: [address] in [function]
30 \\ return error.TheSkyIsFalling;
31 \\ ^
32 \\
33 ,
34 .error_tracing = true,
35 },
36 .ReleaseFast = .{
37 .expect =
38 \\error: TheSkyIsFalling
39 \\
40 ,
41 },
42 .ReleaseSmall = .{
43 .expect =
44 \\error: TheSkyIsFalling
45 \\
46 ,
47 },
48 });
49
50 cases.addCase(.{
51 .name = "try return",
52 .source =
53 \\fn foo() !void {
54 \\ return error.TheSkyIsFalling;
55 \\}10 \\}
56 \\11 \\
57 \\pub fn main() !void {
58 \\ try foo();
59 \\}
60 ,12 ,
61 .Debug = .{13 .unwind = .any,
62 .expect =14 .expect_panic = true,
63 \\error: TheSkyIsFalling15 .expect =
64 \\source.zig:2:5: [address] in foo ([main_file])16 \\panic: oh no
65 \\ return error.TheSkyIsFalling;17 \\source.zig:5:5: [address] in foo
66 \\ ^18 \\ @panic("oh no");
67 \\source.zig:6:5: [address] in main ([main_file])19 \\ ^
68 \\ try foo();20 \\source.zig:2:8: [address] in main
69 \\ ^21 \\ foo();
70 \\22 \\ ^
71 ,
72 },
73 .ReleaseSafe = .{
74 .exclude_os = &.{
75 .windows, // TODO
76 },
77 .expect =
78 \\error: TheSkyIsFalling
79 \\source.zig:2:5: [address] in [function]
80 \\ return error.TheSkyIsFalling;
81 \\ ^
82 \\source.zig:6:5: [address] in [function]
83 \\ try foo();
84 \\ ^
85 \\
86 ,
87 .error_tracing = true,
88 },
89 .ReleaseFast = .{
90 .expect =
91 \\error: TheSkyIsFalling
92 \\
93 ,
94 },
95 .ReleaseSmall = .{
96 .expect =
97 \\error: TheSkyIsFalling
98 \\
99 ,
100 },
101 });
102 cases.addCase(.{
103 .name = "non-error return pops error trace",
104 .source =
105 \\fn bar() !void {
106 \\ return error.UhOh;
107 \\}
108 \\23 \\
109 \\fn foo() !void {24 ,
110 \\ bar() catch {25 .expect_strip =
111 \\ return; // non-error result: success26 \\panic: oh no
112 \\ };27 \\???:?:?: [address] in source.foo
113 \\}28 \\???:?:?: [address] in source.main
114 \\29 \\
115 \\pub fn main() !void {
116 \\ try foo();
117 \\ return error.UnrelatedError;
118 \\}
119 ,30 ,
120 .Debug = .{
121 .expect =
122 \\error: UnrelatedError
123 \\source.zig:13:5: [address] in main ([main_file])
124 \\ return error.UnrelatedError;
125 \\ ^
126 \\
127 ,
128 },
129 .ReleaseSafe = .{
130 .exclude_os = &.{
131 .windows, // TODO
132 .linux, // defeated by aggressive inlining
133 },
134 .expect =
135 \\error: UnrelatedError
136 \\source.zig:13:5: [address] in [function]
137 \\ return error.UnrelatedError;
138 \\ ^
139 \\
140 ,
141 .error_tracing = true,
142 },
143 .ReleaseFast = .{
144 .expect =
145 \\error: UnrelatedError
146 \\
147 ,
148 },
149 .ReleaseSmall = .{
150 .expect =
151 \\error: UnrelatedError
152 \\
153 ,
154 },
155 });31 });
15632
157 cases.addCase(.{33 cases.addCase(.{
158 .name = "continue in while loop",34 .name = "simple panic with no unwind strategy",
159 .source =35 .source =
160 \\fn foo() !void {36 \\pub fn main() void {
161 \\ return error.UhOh;37 \\ foo();
162 \\}38 \\}
163 \\39 \\fn foo() void {
164 \\pub fn main() !void {40 \\ @panic("oh no");
165 \\ var i: usize = 0;
166 \\ while (i < 3) : (i += 1) {
167 \\ foo() catch continue;
168 \\ }
169 \\ return error.UnrelatedError;
170 \\}41 \\}
42 \\
171 ,43 ,
172 .Debug = .{44 .unwind = .none,
173 .expect =45 .expect_panic = true,
174 \\error: UnrelatedError46 .expect = "panic: oh no",
175 \\source.zig:10:5: [address] in main ([main_file])47 .expect_strip = "panic: oh no",
176 \\ return error.UnrelatedError;
177 \\ ^
178 \\
179 ,
180 },
181 .ReleaseSafe = .{
182 .exclude_os = &.{
183 .windows, // TODO
184 .linux, // defeated by aggressive inlining
185 },
186 .expect =
187 \\error: UnrelatedError
188 \\source.zig:10:5: [address] in [function]
189 \\ return error.UnrelatedError;
190 \\ ^
191 \\
192 ,
193 .error_tracing = true,
194 },
195 .ReleaseFast = .{
196 .expect =
197 \\error: UnrelatedError
198 \\
199 ,
200 },
201 .ReleaseSmall = .{
202 .expect =
203 \\error: UnrelatedError
204 \\
205 ,
206 },
207 });48 });
20849
209 cases.addCase(.{50 cases.addCase(.{
210 .name = "try return + handled catch/if-else",51 .name = "dump current trace",
211 .source =52 .source =
212 \\fn foo() !void {53 \\pub fn main() void {
213 \\ return error.TheSkyIsFalling;54 \\ foo(bar());
214 \\}55 \\}
215 \\56 \\fn bar() void {
216 \\pub fn main() !void {57 \\ qux(123);
217 \\ foo() catch {}; // should not affect error trace
218 \\ if (foo()) |_| {} else |_| {
219 \\ // should also not affect error trace
220 \\ }
221 \\ try foo();
222 \\}58 \\}
59 \\fn foo(_: void) void {}
60 \\fn qux(x: u32) void {
61 \\ std.debug.dumpCurrentStackTrace(.{});
62 \\ _ = x;
63 \\}
64 \\const std = @import("std");
65 \\
223 ,66 ,
224 .Debug = .{67 .unwind = .safe,
225 .expect =68 .expect_panic = false,
226 \\error: TheSkyIsFalling69 .expect =
227 \\source.zig:2:5: [address] in foo ([main_file])70 \\source.zig:9:36: [address] in qux
228 \\ return error.TheSkyIsFalling;71 \\ std.debug.dumpCurrentStackTrace(.{});
229 \\ ^72 \\ ^
230 \\source.zig:10:5: [address] in main ([main_file])73 \\source.zig:5:8: [address] in bar
231 \\ try foo();74 \\ qux(123);
232 \\ ^75 \\ ^
233 \\76 \\source.zig:2:12: [address] in main
234 ,77 \\ foo(bar());
235 },78 \\ ^
236 .ReleaseSafe = .{
237 .exclude_os = &.{
238 .windows, // TODO
239 .linux, // defeated by aggressive inlining
240 },
241 .expect =
242 \\error: TheSkyIsFalling
243 \\source.zig:2:5: [address] in [function]
244 \\ return error.TheSkyIsFalling;
245 \\ ^
246 \\source.zig:10:5: [address] in [function]
247 \\ try foo();
248 \\ ^
249 \\
250 ,
251 .error_tracing = true,
252 },
253 .ReleaseFast = .{
254 .expect =
255 \\error: TheSkyIsFalling
256 \\
257 ,
258 },
259 .ReleaseSmall = .{
260 .expect =
261 \\error: TheSkyIsFalling
262 \\
263 ,
264 },
265 });
266
267 cases.addCase(.{
268 .name = "break from inline loop pops error return trace",
269 .source =
270 \\fn foo() !void { return error.FooBar; }
271 \\79 \\
272 \\pub fn main() !void {80 ,
273 \\ comptime var i: usize = 0;81 .expect_strip =
274 \\ b: inline while (i < 5) : (i += 1) {82 \\???:?:?: [address] in source.qux
275 \\ foo() catch {83 \\???:?:?: [address] in source.bar
276 \\ break :b; // non-error break, success84 \\???:?:?: [address] in source.main
277 \\ };
278 \\ }
279 \\ // foo() was successfully handled, should not appear in trace
280 \\85 \\
281 \\ return error.BadTime;
282 \\}
283 ,86 ,
284 .Debug = .{
285 .expect =
286 \\error: BadTime
287 \\source.zig:12:5: [address] in main ([main_file])
288 \\ return error.BadTime;
289 \\ ^
290 \\
291 ,
292 },
293 .ReleaseSafe = .{
294 .exclude_os = &.{
295 .windows, // TODO
296 .linux, // defeated by aggressive inlining
297 },
298 .expect =
299 \\error: BadTime
300 \\source.zig:12:5: [address] in [function]
301 \\ return error.BadTime;
302 \\ ^
303 \\
304 ,
305 .error_tracing = true,
306 },
307 .ReleaseFast = .{
308 .expect =
309 \\error: BadTime
310 \\
311 ,
312 },
313 .ReleaseSmall = .{
314 .expect =
315 \\error: BadTime
316 \\
317 ,
318 },
319 });87 });
32088
321 cases.addCase(.{89 cases.addCase(.{
322 .name = "catch and re-throw error",90 .name = "dump current trace with no unwind strategy",
323 .source =91 .source =
324 \\fn foo() !void {92 \\pub fn main() void {
325 \\ return error.TheSkyIsFalling;93 \\ foo(bar());
326 \\}94 \\}
327 \\95 \\fn bar() void {
328 \\pub fn main() !void {96 \\ qux(123);
329 \\ return foo() catch error.AndMyCarIsOutOfGas;
330 \\}97 \\}
331 ,98 \\fn foo(_: void) void {}
332 .Debug = .{99 \\fn qux(x: u32) void {
333 .expect =100 \\ std.debug.print("pre\n", .{});
334 \\error: AndMyCarIsOutOfGas101 \\ std.debug.dumpCurrentStackTrace(.{});
335 \\source.zig:2:5: [address] in foo ([main_file])102 \\ std.debug.print("post\n", .{});
336 \\ return error.TheSkyIsFalling;103 \\ _ = x;
337 \\ ^
338 \\source.zig:6:5: [address] in main ([main_file])
339 \\ return foo() catch error.AndMyCarIsOutOfGas;
340 \\ ^
341 \\
342 ,
343 },
344 .ReleaseSafe = .{
345 .exclude_os = &.{
346 .windows, // TODO
347 .linux, // defeated by aggressive inlining
348 },
349 .expect =
350 \\error: AndMyCarIsOutOfGas
351 \\source.zig:2:5: [address] in [function]
352 \\ return error.TheSkyIsFalling;
353 \\ ^
354 \\source.zig:6:5: [address] in [function]
355 \\ return foo() catch error.AndMyCarIsOutOfGas;
356 \\ ^
357 \\
358 ,
359 .error_tracing = true,
360 },
361 .ReleaseFast = .{
362 .expect =
363 \\error: AndMyCarIsOutOfGas
364 \\
365 ,
366 },
367 .ReleaseSmall = .{
368 .expect =
369 \\error: AndMyCarIsOutOfGas
370 \\
371 ,
372 },
373 });
374
375 cases.addCase(.{
376 .name = "errors stored in var do not contribute to error trace",
377 .source =
378 \\fn foo() !void {
379 \\ return error.TheSkyIsFalling;
380 \\}104 \\}
105 \\const std = @import("std");
381 \\106 \\
382 \\pub fn main() !void {
383 \\ // Once an error is stored in a variable, it is popped from the trace
384 \\ var x = foo();
385 \\ x = {};
386 \\
387 \\ // As a result, this error trace will still be clean
388 \\ return error.SomethingUnrelatedWentWrong;
389 \\}
390 ,107 ,
391 .Debug = .{108 .unwind = .no_safe,
392 .expect =109 .expect_panic = false,
393 \\error: SomethingUnrelatedWentWrong110 .expect = "pre\npost\n",
394 \\source.zig:11:5: [address] in main ([main_file])111 .expect_strip = "pre\npost\n",
395 \\ return error.SomethingUnrelatedWentWrong;
396 \\ ^
397 \\
398 ,
399 },
400 .ReleaseSafe = .{
401 .exclude_os = &.{
402 .windows, // TODO
403 .linux, // defeated by aggressive inlining
404 },
405 .expect =
406 \\error: SomethingUnrelatedWentWrong
407 \\source.zig:11:5: [address] in [function]
408 \\ return error.SomethingUnrelatedWentWrong;
409 \\ ^
410 \\
411 ,
412 .error_tracing = true,
413 },
414 .ReleaseFast = .{
415 .expect =
416 \\error: SomethingUnrelatedWentWrong
417 \\
418 ,
419 },
420 .ReleaseSmall = .{
421 .expect =
422 \\error: SomethingUnrelatedWentWrong
423 \\
424 ,
425 },
426 });112 });
427113
428 cases.addCase(.{114 cases.addCase(.{
429 .name = "error stored in const has trace preserved for duration of block",115 .name = "dump captured trace",
430 .source =116 .source =
431 \\fn foo() !void { return error.TheSkyIsFalling; }117 \\pub fn main() void {
432 \\fn bar() !void { return error.InternalError; }118 \\ var stack_trace_buf: [8]usize = undefined;
433 \\fn baz() !void { return error.UnexpectedReality; }119 \\ dumpIt(&captureIt(&stack_trace_buf));
434 \\
435 \\pub fn main() !void {
436 \\ const x = foo();
437 \\ const y = b: {
438 \\ if (true)
439 \\ break :b bar();
440 \\
441 \\ break :b {};
442 \\ };
443 \\ x catch {};
444 \\ y catch {};
445 \\ // foo()/bar() error traces not popped until end of block
446 \\
447 \\ {
448 \\ const z = baz();
449 \\ z catch {};
450 \\ // baz() error trace still alive here
451 \\ }
452 \\ // baz() error trace popped, foo(), bar() still alive
453 \\ return error.StillUnresolved;
454 \\}120 \\}
455 ,121 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
456 .Debug = .{122 \\ return captureItInner(buf);
457 .expect =
458 \\error: StillUnresolved
459 \\source.zig:1:18: [address] in foo ([main_file])
460 \\fn foo() !void { return error.TheSkyIsFalling; }
461 \\ ^
462 \\source.zig:2:18: [address] in bar ([main_file])
463 \\fn bar() !void { return error.InternalError; }
464 \\ ^
465 \\source.zig:23:5: [address] in main ([main_file])
466 \\ return error.StillUnresolved;
467 \\ ^
468 \\
469 ,
470 },
471 .ReleaseSafe = .{
472 .exclude_os = &.{
473 .windows, // TODO
474 .linux, // defeated by aggressive inlining
475 },
476 .expect =
477 \\error: StillUnresolved
478 \\source.zig:1:18: [address] in [function]
479 \\fn foo() !void { return error.TheSkyIsFalling; }
480 \\ ^
481 \\source.zig:2:18: [address] in [function]
482 \\fn bar() !void { return error.InternalError; }
483 \\ ^
484 \\source.zig:23:5: [address] in [function]
485 \\ return error.StillUnresolved;
486 \\ ^
487 \\
488 ,
489 .error_tracing = true,
490 },
491 .ReleaseFast = .{
492 .expect =
493 \\error: StillUnresolved
494 \\
495 ,
496 },
497 .ReleaseSmall = .{
498 .expect =
499 \\error: StillUnresolved
500 \\
501 ,
502 },
503 });
504
505 cases.addCase(.{
506 .name = "error passed to function has its trace preserved for duration of the call",
507 .source =
508 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
509 \\ actual_error catch |err| {
510 \\ if (err == expected_error) return {};
511 \\ };
512 \\ return error.TestExpectedError;
513 \\}123 \\}
124 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
125 \\ std.debug.dumpStackTrace(st);
126 \\}
127 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
128 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
129 \\}
130 \\const std = @import("std");
514 \\131 \\
515 \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }132 ,
516 \\fn foo() !void { return error.Foo; }133 .unwind = .safe,
134 .expect_panic = false,
135 .expect =
136 \\source.zig:12:46: [address] in captureItInner
137 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
138 \\ ^
139 \\source.zig:6:26: [address] in captureIt
140 \\ return captureItInner(buf);
141 \\ ^
142 \\source.zig:3:22: [address] in main
143 \\ dumpIt(&captureIt(&stack_trace_buf));
144 \\ ^
517 \\145 \\
518 \\pub fn main() !void {146 ,
519 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());147 .expect_strip =
520 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());148 \\???:?:?: [address] in source.captureItInner
521 \\ try expectError(error.Foo, foo());149 \\???:?:?: [address] in source.captureIt
150 \\???:?:?: [address] in source.main
522 \\151 \\
523 \\ // Only the error trace for this failing check should appear:
524 \\ try expectError(error.Bar, foo());
525 \\}
526 ,152 ,
527 .Debug = .{
528 .expect =
529 \\error: TestExpectedError
530 \\source.zig:9:18: [address] in foo ([main_file])
531 \\fn foo() !void { return error.Foo; }
532 \\ ^
533 \\source.zig:5:5: [address] in expectError ([main_file])
534 \\ return error.TestExpectedError;
535 \\ ^
536 \\source.zig:17:5: [address] in main ([main_file])
537 \\ try expectError(error.Bar, foo());
538 \\ ^
539 \\
540 ,
541 },
542 .ReleaseSafe = .{
543 .exclude_os = &.{
544 .windows, // TODO
545 },
546 .expect =
547 \\error: TestExpectedError
548 \\source.zig:9:18: [address] in [function]
549 \\fn foo() !void { return error.Foo; }
550 \\ ^
551 \\source.zig:5:5: [address] in [function]
552 \\ return error.TestExpectedError;
553 \\ ^
554 \\source.zig:17:5: [address] in [function]
555 \\ try expectError(error.Bar, foo());
556 \\ ^
557 \\
558 ,
559 .error_tracing = true,
560 },
561 .ReleaseFast = .{
562 .expect =
563 \\error: TestExpectedError
564 \\
565 ,
566 },
567 .ReleaseSmall = .{
568 .expect =
569 \\error: TestExpectedError
570 \\
571 ,
572 },
573 });153 });
574154
575 cases.addCase(.{155 cases.addCase(.{
576 .name = "try return from within catch",156 .name = "dump captured trace with no unwind strategy",
577 .source =157 .source =
578 \\fn foo() !void {158 \\pub fn main() void {
579 \\ return error.TheSkyIsFalling;159 \\ var stack_trace_buf: [8]usize = undefined;
160 \\ dumpIt(&captureIt(&stack_trace_buf));
580 \\}161 \\}
581 \\162 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
582 \\fn bar() !void {163 \\ return captureItInner(buf);
583 \\ return error.AndMyCarIsOutOfGas;
584 \\}164 \\}
585 \\165 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
586 \\pub fn main() !void {166 \\ std.debug.dumpStackTrace(st);
587 \\ foo() catch { // error trace should include foo()
588 \\ try bar();
589 \\ };
590 \\}167 \\}
591 ,168 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
592 .Debug = .{169 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
593 .expect =
594 \\error: AndMyCarIsOutOfGas
595 \\source.zig:2:5: [address] in foo ([main_file])
596 \\ return error.TheSkyIsFalling;
597 \\ ^
598 \\source.zig:6:5: [address] in bar ([main_file])
599 \\ return error.AndMyCarIsOutOfGas;
600 \\ ^
601 \\source.zig:11:9: [address] in main ([main_file])
602 \\ try bar();
603 \\ ^
604 \\
605 ,
606 },
607 .ReleaseSafe = .{
608 .exclude_os = &.{
609 .windows, // TODO
610 },
611 .expect =
612 \\error: AndMyCarIsOutOfGas
613 \\source.zig:2:5: [address] in [function]
614 \\ return error.TheSkyIsFalling;
615 \\ ^
616 \\source.zig:6:5: [address] in [function]
617 \\ return error.AndMyCarIsOutOfGas;
618 \\ ^
619 \\source.zig:11:9: [address] in [function]
620 \\ try bar();
621 \\ ^
622 \\
623 ,
624 .error_tracing = true,
625 },
626 .ReleaseFast = .{
627 .expect =
628 \\error: AndMyCarIsOutOfGas
629 \\
630 ,
631 },
632 .ReleaseSmall = .{
633 .expect =
634 \\error: AndMyCarIsOutOfGas
635 \\
636 ,
637 },
638 });
639
640 cases.addCase(.{
641 .name = "try return from within if-else",
642 .source =
643 \\fn foo() !void {
644 \\ return error.TheSkyIsFalling;
645 \\}
646 \\
647 \\fn bar() !void {
648 \\ return error.AndMyCarIsOutOfGas;
649 \\}170 \\}
171 \\const std = @import("std");
650 \\172 \\
651 \\pub fn main() !void {
652 \\ if (foo()) |_| {} else |_| { // error trace should include foo()
653 \\ try bar();
654 \\ }
655 \\}
656 ,173 ,
657 .Debug = .{174 .unwind = .no_safe,
658 .expect =175 .expect_panic = false,
659 \\error: AndMyCarIsOutOfGas176 .expect = "(empty stack trace)\n",
660 \\source.zig:2:5: [address] in foo ([main_file])177 .expect_strip = "(empty stack trace)\n",
661 \\ return error.TheSkyIsFalling;
662 \\ ^
663 \\source.zig:6:5: [address] in bar ([main_file])
664 \\ return error.AndMyCarIsOutOfGas;
665 \\ ^
666 \\source.zig:11:9: [address] in main ([main_file])
667 \\ try bar();
668 \\ ^
669 \\
670 ,
671 },
672 .ReleaseSafe = .{
673 .exclude_os = &.{
674 .windows, // TODO
675 },
676 .expect =
677 \\error: AndMyCarIsOutOfGas
678 \\source.zig:2:5: [address] in [function]
679 \\ return error.TheSkyIsFalling;
680 \\ ^
681 \\source.zig:6:5: [address] in [function]
682 \\ return error.AndMyCarIsOutOfGas;
683 \\ ^
684 \\source.zig:11:9: [address] in [function]
685 \\ try bar();
686 \\ ^
687 \\
688 ,
689 .error_tracing = true,
690 },
691 .ReleaseFast = .{
692 .expect =
693 \\error: AndMyCarIsOutOfGas
694 \\
695 ,
696 },
697 .ReleaseSmall = .{
698 .expect =
699 \\error: AndMyCarIsOutOfGas
700 \\
701 ,
702 },
703 });178 });
704179
705 cases.addCase(.{180 cases.addCase(.{
706 .name = "try try return return",181 .name = "dump captured trace on thread",
707 .source =182 .source =
708 \\fn foo() !void {183 \\pub fn main() !void {
709 \\ try bar();184 \\ var stack_trace_buf: [8]usize = undefined;
185 \\ const t = try std.Thread.spawn(.{}, threadMain, .{&stack_trace_buf});
186 \\ t.join();
710 \\}187 \\}
711 \\188 \\fn threadMain(stack_trace_buf: []usize) void {
712 \\fn bar() !void {189 \\ dumpIt(&captureIt(stack_trace_buf));
713 \\ return make_error();
714 \\}190 \\}
715 \\191 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
716 \\fn make_error() !void {192 \\ return captureItInner(buf);
717 \\ return error.TheSkyIsFalling;
718 \\}193 \\}
719 \\194 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
720 \\pub fn main() !void {195 \\ std.debug.dumpStackTrace(st);
721 \\ try foo();196 \\}
197 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
198 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
722 \\}199 \\}
723 ,
724 .Debug = .{
725 .expect =
726 \\error: TheSkyIsFalling
727 \\source.zig:10:5: [address] in make_error ([main_file])
728 \\ return error.TheSkyIsFalling;
729 \\ ^
730 \\source.zig:6:5: [address] in bar ([main_file])
731 \\ return make_error();
732 \\ ^
733 \\source.zig:2:5: [address] in foo ([main_file])
734 \\ try bar();
735 \\ ^
736 \\source.zig:14:5: [address] in main ([main_file])
737 \\ try foo();
738 \\ ^
739 \\
740 ,
741 },
742 .ReleaseSafe = .{
743 .exclude_os = &.{
744 .windows, // TODO
745 },
746 .expect =
747 \\error: TheSkyIsFalling
748 \\source.zig:10:5: [address] in [function]
749 \\ return error.TheSkyIsFalling;
750 \\ ^
751 \\source.zig:6:5: [address] in [function]
752 \\ return make_error();
753 \\ ^
754 \\source.zig:2:5: [address] in [function]
755 \\ try bar();
756 \\ ^
757 \\source.zig:14:5: [address] in [function]
758 \\ try foo();
759 \\ ^
760 \\
761 ,
762 .error_tracing = true,
763 },
764 .ReleaseFast = .{
765 .expect =
766 \\error: TheSkyIsFalling
767 \\
768 ,
769 },
770 .ReleaseSmall = .{
771 .expect =
772 \\error: TheSkyIsFalling
773 \\
774 ,
775 },
776 });
777
778 cases.addCase(.{
779 .name = "dumpCurrentStackTrace",
780 .source =
781 \\const std = @import("std");200 \\const std = @import("std");
782 \\201 \\
783 \\fn bar() void {
784 \\ std.debug.dumpCurrentStackTrace(@returnAddress());
785 \\}
786 \\fn foo() void {
787 \\ bar();
788 \\}
789 \\pub fn main() u8 {
790 \\ foo();
791 \\ return 1;
792 \\}
793 ,202 ,
794 .Debug = .{203 .unwind = .safe,
795 // std.debug.sys_can_stack_trace204 .expect_panic = false,
796 .exclude_arch = &.{205 .expect =
797 .loongarch32,206 \\source.zig:16:46: [address] in captureItInner
798 .loongarch64,207 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
799 .mips,208 \\ ^
800 .mipsel,209 \\source.zig:10:26: [address] in captureIt
801 .mips64,210 \\ return captureItInner(buf);
802 .mips64el,211 \\ ^
803 .s390x,212 \\source.zig:7:22: [address] in threadMain
804 },213 \\ dumpIt(&captureIt(stack_trace_buf));
805 .exclude_os = &.{214 \\ ^
806 .freebsd,
807 .openbsd, // integer overflow
808 .windows, // TODO intermittent failures
809 },
810 .expect =
811 \\source.zig:7:8: [address] in foo ([main_file])
812 \\ bar();
813 \\ ^
814 \\source.zig:10:8: [address] in main ([main_file])
815 \\ foo();
816 \\ ^
817 \\
818 ,
819 },
820 });
821 cases.addCase(.{
822 .name = "error union switch with call operand",
823 .source =
824 \\pub fn main() !void {
825 \\ try foo();
826 \\ return error.TheSkyIsFalling;
827 \\}
828 \\215 \\
829 \\noinline fn failure() error{ Fatal, NonFatal }!void {216 ,
830 \\ return error.NonFatal;217 .expect_strip =
831 \\}218 \\???:?:?: [address] in source.captureItInner
219 \\???:?:?: [address] in source.captureIt
220 \\???:?:?: [address] in source.threadMain
832 \\221 \\
833 \\fn foo() error{Fatal}!void {
834 \\ return failure() catch |err| switch (err) {
835 \\ error.Fatal => return error.Fatal,
836 \\ error.NonFatal => return,
837 \\ };
838 \\}
839 ,222 ,
840 .Debug = .{
841 .expect =
842 \\error: TheSkyIsFalling
843 \\source.zig:3:5: [address] in main ([main_file])
844 \\ return error.TheSkyIsFalling;
845 \\ ^
846 \\
847 ,
848 },
849 .ReleaseSafe = .{
850 .exclude_os = &.{
851 .freebsd,
852 .windows, // TODO
853 .linux, // defeated by aggressive inlining
854 .macos, // Broken in LLVM 20.
855 },
856 .expect =
857 \\error: TheSkyIsFalling
858 \\source.zig:3:5: [address] in [function]
859 \\ return error.TheSkyIsFalling;
860 \\ ^
861 \\
862 ,
863 .error_tracing = true,
864 },
865 .ReleaseFast = .{
866 .expect =
867 \\error: TheSkyIsFalling
868 \\
869 ,
870 },
871 .ReleaseSmall = .{
872 .expect =
873 \\error: TheSkyIsFalling
874 \\
875 ,
876 },
877 });223 });
878}224}
test/tests.zig+64-7
...@@ -6,11 +6,13 @@ const OptimizeMode = std.builtin.OptimizeMode;...@@ -6,11 +6,13 @@ const OptimizeMode = std.builtin.OptimizeMode;
6const Step = std.Build.Step;6const Step = std.Build.Step;
77
8// Cases8// Cases
9const error_traces = @import("error_traces.zig");
9const stack_traces = @import("stack_traces.zig");10const stack_traces = @import("stack_traces.zig");
10const llvm_ir = @import("llvm_ir.zig");11const llvm_ir = @import("llvm_ir.zig");
11const libc = @import("libc.zig");12const libc = @import("libc.zig");
1213
13// Implementations14// Implementations
15pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
14pub const StackTracesContext = @import("src/StackTrace.zig");16pub const StackTracesContext = @import("src/StackTrace.zig");
15pub const DebuggerContext = @import("src/Debugger.zig");17pub const DebuggerContext = @import("src/Debugger.zig");
16pub const LlvmIrContext = @import("src/LlvmIr.zig");18pub const LlvmIrContext = @import("src/LlvmIr.zig");
...@@ -1857,28 +1859,53 @@ const c_abi_targets = blk: {...@@ -1857,28 +1859,53 @@ const c_abi_targets = blk: {
1857 };1859 };
1858};1860};
18591861
1862/// For stack trace tests, we only test native, because external executors are pretty unreliable at
1863/// stack tracing. However, if there's a 32-bit equivalent target which the host can trivially run,
1864/// we may as well at least test that!
1865fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
1866 const host = b.graph.host.result;
1867 const only_native = (&b.graph.host)[0..1];
1868 if (skip_non_native) return only_native;
1869 const arch32: std.Target.Cpu.Arch = switch (host.cpu.arch) {
1870 .x86_64 => .x86,
1871 .aarch64 => .arm,
1872 .aarch64_be => .armeb,
1873 else => return only_native,
1874 };
1875 switch (host.os.tag) {
1876 .windows => if (arch32.isArm()) return only_native,
1877 .macos, .freebsd => if (arch32 == .x86) return only_native,
1878 .linux, .netbsd => {},
1879 else => return only_native,
1880 }
1881 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{
1882 b.graph.host,
1883 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),
1884 }) catch @panic("OOM");
1885}
1886
1860pub fn addStackTraceTests(1887pub fn addStackTraceTests(
1861 b: *std.Build,1888 b: *std.Build,
1862 test_filters: []const []const u8,1889 test_filters: []const []const u8,
1863 optimize_modes: []const OptimizeMode,1890 skip_non_native: bool,
1864) *Step {1891) *Step {
1865 const check_exe = b.addExecutable(.{1892 const convert_exe = b.addExecutable(.{
1866 .name = "check-stack-trace",1893 .name = "convert-stack-trace",
1867 .root_module = b.createModule(.{1894 .root_module = b.createModule(.{
1868 .root_source_file = b.path("test/src/check-stack-trace.zig"),1895 .root_source_file = b.path("test/src/convert-stack-trace.zig"),
1869 .target = b.graph.host,1896 .target = b.graph.host,
1870 .optimize = .Debug,1897 .optimize = .Debug,
1871 }),1898 }),
1872 });1899 });
18731900
1874 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");1901 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
1902
1875 cases.* = .{1903 cases.* = .{
1876 .b = b,1904 .b = b,
1877 .step = b.step("test-stack-traces", "Run the stack trace tests"),1905 .step = b.step("test-stack-traces", "Run the stack trace tests"),
1878 .test_index = 0,
1879 .test_filters = test_filters,1906 .test_filters = test_filters,
1880 .optimize_modes = optimize_modes,1907 .targets = nativeAndCompatible32bit(b, skip_non_native),
1881 .check_exe = check_exe,1908 .convert_exe = convert_exe,
1882 };1909 };
18831910
1884 stack_traces.addCases(cases);1911 stack_traces.addCases(cases);
...@@ -1886,6 +1913,36 @@ pub fn addStackTraceTests(...@@ -1886,6 +1913,36 @@ pub fn addStackTraceTests(
1886 return cases.step;1913 return cases.step;
1887}1914}
18881915
1916pub fn addErrorTraceTests(
1917 b: *std.Build,
1918 test_filters: []const []const u8,
1919 optimize_modes: []const OptimizeMode,
1920 skip_non_native: bool,
1921) *Step {
1922 const convert_exe = b.addExecutable(.{
1923 .name = "convert-stack-trace",
1924 .root_module = b.createModule(.{
1925 .root_source_file = b.path("test/src/convert-stack-trace.zig"),
1926 .target = b.graph.host,
1927 .optimize = .Debug,
1928 }),
1929 });
1930
1931 const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
1932 cases.* = .{
1933 .b = b,
1934 .step = b.step("test-error-traces", "Run the error trace tests"),
1935 .test_filters = test_filters,
1936 .targets = nativeAndCompatible32bit(b, skip_non_native),
1937 .optimize_modes = optimize_modes,
1938 .convert_exe = convert_exe,
1939 };
1940
1941 error_traces.addCases(cases);
1942
1943 return cases.step;
1944}
1945
1889fn compilerHasPackageManager(b: *std.Build) bool {1946fn compilerHasPackageManager(b: *std.Build) bool {
1890 // We can only use dependencies if the compiler was built with support for package management.1947 // We can only use dependencies if the compiler was built with support for package management.
1891 // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.)1948 // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.)