1const std = @import("std");
2const Context = @import("tests.zig").ErrorTracesContext;
3
4pub fn addCases(cases: *Context, params: *const Context.CaseParameters, target: *const std.Target) void {
5 cases.addCase(.{
6 .params = params,
7 .target = target,
8 .name = "return",
9 .source =
10 \\pub fn main() !void {
11 \\ return error.TheSkyIsFalling;
12 \\}
13 ,
14 .expect_error = "TheSkyIsFalling",
15 .expect_trace =
16 \\source.zig:2:5: [address] in main
17 \\ return error.TheSkyIsFalling;
18 \\ ^
19 ,
20 });
21
22 cases.addCase(.{
23 .params = params,
24 .target = target,
25 .name = "try return",
26 .source =
27 \\fn foo() !void {
28 \\ return error.TheSkyIsFalling;
29 \\}
30 \\
31 \\pub fn main() !void {
32 \\ try foo();
33 \\}
34 ,
35 .expect_error = "TheSkyIsFalling",
36 .expect_trace =
37 \\source.zig:2:5: [address] in foo
38 \\ return error.TheSkyIsFalling;
39 \\ ^
40 \\source.zig:6:5: [address] in main
41 \\ try foo();
42 \\ ^
43 ,
44 .disable_trace_optimized = &.{
45 .{ .x86_64, .windows },
46 .{ .x86, .windows },
47 .{ .x86_64, .macos },
48 .{ .aarch64, .macos },
49 },
50 });
51 cases.addCase(.{
52 .params = params,
53 .target = target,
54 .name = "non-error return pops error trace",
55 .source =
56 \\fn bar() !void {
57 \\ return error.UhOh;
58 \\}
59 \\
60 \\fn foo() !void {
61 \\ bar() catch {
62 \\ return; // non-error result: success
63 \\ };
64 \\}
65 \\
66 \\pub fn main() !void {
67 \\ try foo();
68 \\ return error.UnrelatedError;
69 \\}
70 ,
71 .expect_error = "UnrelatedError",
72 .expect_trace =
73 \\source.zig:13:5: [address] in main
74 \\ return error.UnrelatedError;
75 \\ ^
76 ,
77 });
78
79 cases.addCase(.{
80 .params = params,
81 .target = target,
82 .name = "continue in while loop",
83 .source =
84 \\fn foo() !void {
85 \\ return error.UhOh;
86 \\}
87 \\
88 \\pub fn main() !void {
89 \\ var i: usize = 0;
90 \\ while (i < 3) : (i += 1) {
91 \\ foo() catch continue;
92 \\ }
93 \\ return error.UnrelatedError;
94 \\}
95 ,
96 .expect_error = "UnrelatedError",
97 .expect_trace =
98 \\source.zig:10:5: [address] in main
99 \\ return error.UnrelatedError;
100 \\ ^
101 ,
102 });
103
104 cases.addCase(.{
105 .params = params,
106 .target = target,
107 .name = "for loop pops error return trace",
108 .source =
109 \\fn foo() !void { return error.FooError; }
110 \\
111 \\pub fn main() !void {
112 \\ for (0..2) |_| {
113 \\ const f = foo();
114 \\ f catch {};
115 \\ } else {
116 \\ const f = foo();
117 \\ f catch {};
118 \\ }
119 \\ return error.Stop;
120 \\}
121 ,
122 .expect_error = "Stop",
123 .expect_trace =
124 \\source.zig:11:5: [address] in main
125 \\ return error.Stop;
126 \\ ^
127 ,
128 .disable_trace_optimized = &.{
129 .{ .x86_64, .windows },
130 .{ .x86, .windows },
131 .{ .x86_64, .macos },
132 .{ .aarch64, .macos },
133 },
134 });
135
136 cases.addCase(.{
137 .params = params,
138 .target = target,
139 .name = "implicit continue in for loop pops stale error return trace",
140 .source =
141 \\fn foo() !void { return error.FooError; }
142 \\
143 \\pub fn main() !void {
144 \\ for (0..2) |i| {
145 \\ const f = foo();
146 \\ f catch {};
147 \\
148 \\ if (i == 1) return error.Stop;
149 \\ }
150 \\}
151 ,
152 .expect_error = "Stop",
153 .expect_trace =
154 \\source.zig:1:18: [address] in foo
155 \\fn foo() !void { return error.FooError; }
156 \\ ^
157 \\source.zig:8:21: [address] in main
158 \\ if (i == 1) return error.Stop;
159 \\ ^
160 ,
161 .disable_trace_optimized = &.{
162 .{ .x86_64, .windows },
163 .{ .x86, .windows },
164 .{ .x86_64, .macos },
165 .{ .aarch64, .macos },
166 },
167 });
168
169 cases.addCase(.{
170 .params = params,
171 .target = target,
172 .name = "while loop pops error return trace",
173 .source =
174 \\fn foo() !void { return error.FooError; }
175 \\
176 \\pub fn main() !void {
177 \\ var i: usize = 0;
178 \\ while (i < 2) {
179 \\ const f = foo();
180 \\ f catch {};
181 \\ i += 1;
182 \\ } else {
183 \\ const f = foo();
184 \\ f catch {};
185 \\ }
186 \\ return error.Stop;
187 \\}
188 ,
189 .expect_error = "Stop",
190 .expect_trace =
191 \\source.zig:13:5: [address] in main
192 \\ return error.Stop;
193 \\ ^
194 ,
195 .disable_trace_optimized = &.{
196 .{ .x86_64, .windows },
197 .{ .x86, .windows },
198 .{ .x86_64, .macos },
199 .{ .aarch64, .macos },
200 },
201 });
202
203 cases.addCase(.{
204 .params = params,
205 .target = target,
206 .name = "implicit continue in while loop pops stale error return trace",
207 .source =
208 \\fn foo() !void { return error.FooError; }
209 \\
210 \\pub fn main() !void {
211 \\ var i: usize = 0;
212 \\ while (i < 2) {
213 \\ const f = foo();
214 \\ f catch {};
215 \\
216 \\ if (i == 1) return error.Stop;
217 \\ i += 1;
218 \\ }
219 \\}
220 ,
221 .expect_error = "Stop",
222 .expect_trace =
223 \\source.zig:1:18: [address] in foo
224 \\fn foo() !void { return error.FooError; }
225 \\ ^
226 \\source.zig:9:21: [address] in main
227 \\ if (i == 1) return error.Stop;
228 \\ ^
229 ,
230 .disable_trace_optimized = &.{
231 .{ .x86_64, .windows },
232 .{ .x86, .windows },
233 .{ .x86_64, .macos },
234 .{ .aarch64, .macos },
235 },
236 });
237
238 cases.addCase(.{
239 .params = params,
240 .target = target,
241 .name = "try return + handled catch/if-else",
242 .source =
243 \\fn foo() !void {
244 \\ return error.TheSkyIsFalling;
245 \\}
246 \\
247 \\pub fn main() !void {
248 \\ foo() catch {}; // should not affect error trace
249 \\ if (foo()) |_| {} else |_| {
250 \\ // should also not affect error trace
251 \\ }
252 \\ try foo();
253 \\}
254 ,
255 .expect_error = "TheSkyIsFalling",
256 .expect_trace =
257 \\source.zig:2:5: [address] in foo
258 \\ return error.TheSkyIsFalling;
259 \\ ^
260 \\source.zig:10:5: [address] in main
261 \\ try foo();
262 \\ ^
263 ,
264 .disable_trace_optimized = &.{
265 .{ .x86_64, .windows },
266 .{ .x86, .windows },
267 .{ .x86_64, .macos },
268 .{ .aarch64, .macos },
269 },
270 });
271
272 cases.addCase(.{
273 .params = params,
274 .target = target,
275 .name = "break from inline loop pops error return trace",
276 .source =
277 \\fn foo() !void { return error.FooBar; }
278 \\
279 \\pub fn main() !void {
280 \\ comptime var i: usize = 0;
281 \\ b: inline while (i < 5) : (i += 1) {
282 \\ foo() catch {
283 \\ break :b; // non-error break, success
284 \\ };
285 \\ }
286 \\ // foo() was successfully handled, should not appear in trace
287 \\
288 \\ return error.BadTime;
289 \\}
290 ,
291 .expect_error = "BadTime",
292 .expect_trace =
293 \\source.zig:12:5: [address] in main
294 \\ return error.BadTime;
295 \\ ^
296 ,
297 });
298
299 cases.addCase(.{
300 .params = params,
301 .target = target,
302 .name = "catch and re-throw error",
303 .source =
304 \\fn foo() !void {
305 \\ return error.TheSkyIsFalling;
306 \\}
307 \\
308 \\pub fn main() !void {
309 \\ return foo() catch error.AndMyCarIsOutOfGas;
310 \\}
311 ,
312 .expect_error = "AndMyCarIsOutOfGas",
313 .expect_trace =
314 \\source.zig:2:5: [address] in foo
315 \\ return error.TheSkyIsFalling;
316 \\ ^
317 \\source.zig:6:5: [address] in main
318 \\ return foo() catch error.AndMyCarIsOutOfGas;
319 \\ ^
320 ,
321 .disable_trace_optimized = &.{
322 .{ .x86_64, .windows },
323 .{ .x86, .windows },
324 .{ .x86_64, .macos },
325 .{ .aarch64, .macos },
326 },
327 });
328
329 cases.addCase(.{
330 .params = params,
331 .target = target,
332 .name = "errors stored in var do not contribute to error trace",
333 .source =
334 \\fn foo() !void {
335 \\ return error.TheSkyIsFalling;
336 \\}
337 \\
338 \\pub fn main() !void {
339 \\ // Once an error is stored in a variable, it is popped from the trace
340 \\ var x = foo();
341 \\ x = {};
342 \\
343 \\ // As a result, this error trace will still be clean
344 \\ return error.SomethingUnrelatedWentWrong;
345 \\}
346 ,
347 .expect_error = "SomethingUnrelatedWentWrong",
348 .expect_trace =
349 \\source.zig:11:5: [address] in main
350 \\ return error.SomethingUnrelatedWentWrong;
351 \\ ^
352 ,
353 });
354
355 cases.addCase(.{
356 .params = params,
357 .target = target,
358 .name = "error stored in const has trace preserved for duration of block",
359 .source =
360 \\fn foo() !void { return error.TheSkyIsFalling; }
361 \\fn bar() !void { return error.InternalError; }
362 \\fn baz() !void { return error.UnexpectedReality; }
363 \\
364 \\pub fn main() !void {
365 \\ const x = foo();
366 \\ const y = b: {
367 \\ if (true)
368 \\ break :b bar();
369 \\
370 \\ break :b {};
371 \\ };
372 \\ x catch {};
373 \\ y catch {};
374 \\ // foo()/bar() error traces not popped until end of block
375 \\
376 \\ {
377 \\ const z = baz();
378 \\ z catch {};
379 \\ // baz() error trace still alive here
380 \\ }
381 \\ // baz() error trace popped, foo(), bar() still alive
382 \\ return error.StillUnresolved;
383 \\}
384 ,
385 .expect_error = "StillUnresolved",
386 .expect_trace =
387 \\source.zig:1:18: [address] in foo
388 \\fn foo() !void { return error.TheSkyIsFalling; }
389 \\ ^
390 \\source.zig:2:18: [address] in bar
391 \\fn bar() !void { return error.InternalError; }
392 \\ ^
393 \\source.zig:23:5: [address] in main
394 \\ return error.StillUnresolved;
395 \\ ^
396 ,
397 .disable_trace_optimized = &.{
398 .{ .x86_64, .windows },
399 .{ .x86, .windows },
400 .{ .x86_64, .macos },
401 .{ .aarch64, .macos },
402 },
403 });
404
405 cases.addCase(.{
406 .params = params,
407 .target = target,
408 .name = "error passed to function has its trace preserved for duration of the call",
409 .source =
410 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
411 \\ actual_error catch |err| {
412 \\ if (err == expected_error) return {};
413 \\ };
414 \\ return error.TestExpectedError;
415 \\}
416 \\
417 \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
418 \\fn foo() !void { return error.Foo; }
419 \\
420 \\pub fn main() !void {
421 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
422 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
423 \\ try expectError(error.Foo, foo());
424 \\
425 \\ // Only the error trace for this failing check should appear:
426 \\ try expectError(error.Bar, foo());
427 \\}
428 ,
429 .expect_error = "TestExpectedError",
430 .expect_trace =
431 \\source.zig:9:18: [address] in foo
432 \\fn foo() !void { return error.Foo; }
433 \\ ^
434 \\source.zig:5:5: [address] in expectError
435 \\ return error.TestExpectedError;
436 \\ ^
437 \\source.zig:17:5: [address] in main
438 \\ try expectError(error.Bar, foo());
439 \\ ^
440 ,
441 .disable_trace_optimized = &.{
442 .{ .x86_64, .windows },
443 .{ .x86, .windows },
444 .{ .x86_64, .macos },
445 .{ .aarch64, .macos },
446 },
447 });
448
449 cases.addCase(.{
450 .params = params,
451 .target = target,
452 .name = "try return from within catch",
453 .source =
454 \\fn foo() !void {
455 \\ return error.TheSkyIsFalling;
456 \\}
457 \\
458 \\fn bar() !void {
459 \\ return error.AndMyCarIsOutOfGas;
460 \\}
461 \\
462 \\pub fn main() !void {
463 \\ foo() catch { // error trace should include foo()
464 \\ try bar();
465 \\ };
466 \\}
467 ,
468 .expect_error = "AndMyCarIsOutOfGas",
469 .expect_trace =
470 \\source.zig:2:5: [address] in foo
471 \\ return error.TheSkyIsFalling;
472 \\ ^
473 \\source.zig:6:5: [address] in bar
474 \\ return error.AndMyCarIsOutOfGas;
475 \\ ^
476 \\source.zig:11:9: [address] in main
477 \\ try bar();
478 \\ ^
479 ,
480 .disable_trace_optimized = &.{
481 .{ .x86_64, .windows },
482 .{ .x86, .windows },
483 .{ .x86_64, .macos },
484 .{ .aarch64, .macos },
485 },
486 });
487
488 cases.addCase(.{
489 .params = params,
490 .target = target,
491 .name = "try return from within if-else",
492 .source =
493 \\fn foo() !void {
494 \\ return error.TheSkyIsFalling;
495 \\}
496 \\
497 \\fn bar() !void {
498 \\ return error.AndMyCarIsOutOfGas;
499 \\}
500 \\
501 \\pub fn main() !void {
502 \\ if (foo()) |_| {} else |_| { // error trace should include foo()
503 \\ try bar();
504 \\ }
505 \\}
506 ,
507 .expect_error = "AndMyCarIsOutOfGas",
508 .expect_trace =
509 \\source.zig:2:5: [address] in foo
510 \\ return error.TheSkyIsFalling;
511 \\ ^
512 \\source.zig:6:5: [address] in bar
513 \\ return error.AndMyCarIsOutOfGas;
514 \\ ^
515 \\source.zig:11:9: [address] in main
516 \\ try bar();
517 \\ ^
518 ,
519 .disable_trace_optimized = &.{
520 .{ .x86_64, .windows },
521 .{ .x86, .windows },
522 .{ .x86_64, .macos },
523 .{ .aarch64, .macos },
524 },
525 });
526
527 cases.addCase(.{
528 .params = params,
529 .target = target,
530 .name = "try try return return",
531 .source =
532 \\fn foo() !void {
533 \\ try bar();
534 \\}
535 \\
536 \\fn bar() !void {
537 \\ return make_error();
538 \\}
539 \\
540 \\fn make_error() !void {
541 \\ return error.TheSkyIsFalling;
542 \\}
543 \\
544 \\pub fn main() !void {
545 \\ try foo();
546 \\}
547 ,
548 .expect_error = "TheSkyIsFalling",
549 .expect_trace =
550 \\source.zig:10:5: [address] in make_error
551 \\ return error.TheSkyIsFalling;
552 \\ ^
553 \\source.zig:6:5: [address] in bar
554 \\ return make_error();
555 \\ ^
556 \\source.zig:2:5: [address] in foo
557 \\ try bar();
558 \\ ^
559 \\source.zig:14:5: [address] in main
560 \\ try foo();
561 \\ ^
562 ,
563 .disable_trace_optimized = &.{
564 .{ .x86_64, .windows },
565 .{ .x86, .windows },
566 .{ .x86_64, .macos },
567 .{ .aarch64, .macos },
568 },
569 });
570
571 cases.addCase(.{
572 .params = params,
573 .target = target,
574 .name = "error union switch with call operand",
575 .source =
576 \\pub fn main() !void {
577 \\ try foo();
578 \\ return error.TheSkyIsFalling;
579 \\}
580 \\
581 \\noinline fn failure() error{ Fatal, NonFatal }!void {
582 \\ return error.NonFatal;
583 \\}
584 \\
585 \\fn foo() error{Fatal}!void {
586 \\ return failure() catch |err| switch (err) {
587 \\ error.Fatal => return error.Fatal,
588 \\ error.NonFatal => return,
589 \\ };
590 \\}
591 ,
592 .expect_error = "TheSkyIsFalling",
593 .expect_trace =
594 \\source.zig:3:5: [address] in main
595 \\ return error.TheSkyIsFalling;
596 \\ ^
597 ,
598 .disable_trace_optimized = &.{
599 .{ .x86_64, .freebsd },
600 .{ .x86_64, .netbsd },
601 .{ .x86_64, .linux },
602 .{ .x86, .linux },
603 .{ .aarch64, .freebsd },
604 .{ .aarch64, .netbsd },
605 .{ .aarch64, .linux },
606 .{ .loongarch64, .linux },
607 .{ .powerpc64le, .linux },
608 .{ .riscv64, .linux },
609 .{ .s390x, .linux },
610 .{ .x86_64, .openbsd },
611 .{ .x86_64, .windows },
612 .{ .x86, .windows },
613 .{ .x86_64, .macos },
614 .{ .aarch64, .macos },
615 },
616 });
617
618 cases.addCase(.{
619 .params = params,
620 .target = target,
621 .name = "trace through inline call",
622 // The main function has two inline calls to ensure
623 // that inlinees in PDBs are properly deduplicated.
624 .source =
625 \\pub fn main() !void {
626 \\ try foo(false);
627 \\ try foo(true);
628 \\}
629 \\inline fn foo(b: bool) !void {
630 \\ if (b) try bar();
631 \\}
632 \\fn bar() !void {
633 \\ return error.ThisIsSoSad;
634 \\}
635 ,
636 .expect_error = "ThisIsSoSad",
637 .expect_trace = switch (target.os.tag) {
638 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
639 // so our expected result is slightly different for Windows than on other operating
640 // systems.
641 .windows =>
642 \\source.zig:9:5: [address] in bar
643 \\ return error.ThisIsSoSad;
644 \\ ^
645 \\source.zig:6: [address] in foo
646 \\ if (b) try bar();
647 \\
648 \\source.zig:3:5: [address] in main
649 \\ try foo(true);
650 \\ ^
651 ,
652 else =>
653 \\source.zig:9:5: [address] in bar
654 \\ return error.ThisIsSoSad;
655 \\ ^
656 \\source.zig:6:12: [address] in foo
657 \\ if (b) try bar();
658 \\ ^
659 \\source.zig:3:5: [address] in main
660 \\ try foo(true);
661 \\ ^
662 ,
663 },
664 .disable_trace_optimized = &.{
665 .{ .x86_64, .freebsd },
666 .{ .x86_64, .netbsd },
667 .{ .x86_64, .linux },
668 .{ .x86, .linux },
669 .{ .aarch64, .freebsd },
670 .{ .aarch64, .netbsd },
671 .{ .aarch64, .linux },
672 .{ .loongarch64, .linux },
673 .{ .powerpc64le, .linux },
674 .{ .riscv64, .linux },
675 .{ .s390x, .linux },
676 .{ .x86_64, .openbsd },
677 .{ .x86_64, .windows },
678 .{ .x86, .windows },
679 .{ .x86_64, .macos },
680 .{ .aarch64, .macos },
681 },
682 });
683}