aboutsummaryrefslogtreecommitdiff
path: root/test/stack_traces.zig
diff options
context:
space:
mode:
authorCody Tapscott <topolarity@tapscott.me>2022-09-25 19:51:38 -0700
committerCody Tapscott <topolarity@tapscott.me>2022-10-21 12:40:29 -0700
commitd060cbbec75ac7b0204c706e4dfdfb38f1b24dfd (patch)
tree3504dbae6e338daddf97eddf892557f05ca1d186 /test/stack_traces.zig
parent597ead5318421befba3619fed389820d241ecc78 (diff)
downloadzig-d060cbbec75ac7b0204c706e4dfdfb38f1b24dfd.tar.gz
zig-d060cbbec75ac7b0204c706e4dfdfb38f1b24dfd.zip
stage2: Keep error return traces alive when storing to `const`
This change extends the "lifetime" of the error return trace associated with an error to continue throughout the block of a `const` variable that it is assigned to. This is necessary to support patterns like this one in test_runner.zig: ```zig const result = foo(); if (result) |_| { // ... success logic } else |err| { // `foo()` should be included in the error trace here return error.TestFailed; } ``` To make this happen, the majority of the error return trace popping logic needed to move into Sema, since `const x = foo();` cannot be examined syntactically to determine whether it modifies the error return trace. We also have to make sure not to delete pertinent block information before it makes it to Sema, so that Sema can pop/restore around blocks correctly. * Why do this only for `const` and not `var`? * There is room to relax things for `var`, but only a little bit. We could do the same thing we do for const and keep the error trace alive for the remainder of the block where the *assignment* happens. Any wider scope would violate the stack discipline for traces, so it's not viable. In the end, I decided the most consistent behavior for the user is just to kill all error return traces assigned to a mutable `var`.
Diffstat (limited to 'test/stack_traces.zig')
-rw-r--r--test/stack_traces.zig184
1 files changed, 183 insertions, 1 deletions
diff --git a/test/stack_traces.zig b/test/stack_traces.zig
index 24c2b16373..ebd910563b 100644
--- a/test/stack_traces.zig
+++ b/test/stack_traces.zig
@@ -97,6 +97,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
,
},
});
+ cases.addCase(.{
+ .name = "non-error return pops error trace",
+ .source =
+ \\fn bar() !void {
+ \\ return error.UhOh;
+ \\}
+ \\
+ \\fn foo() !void {
+ \\ bar() catch {
+ \\ return; // non-error result: success
+ \\ };
+ \\}
+ \\
+ \\pub fn main() !void {
+ \\ try foo();
+ \\ return error.UnrelatedError;
+ \\}
+ ,
+ .Debug = .{
+ .expect =
+ \\error: UnrelatedError
+ \\source.zig:13:5: [address] in main (test)
+ \\ return error.UnrelatedError;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude_os = .{
+ .windows, // TODO
+ .linux, // defeated by aggressive inlining
+ },
+ .expect =
+ \\error: UnrelatedError
+ \\source.zig:13:5: [address] in [function]
+ \\ return error.UnrelatedError;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: UnrelatedError
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: UnrelatedError
+ \\
+ ,
+ },
+ });
cases.addCase(.{
.name = "try return + handled catch/if-else",
@@ -156,6 +209,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
});
cases.addCase(.{
+ .name = "break from inline loop pops error return trace",
+ .source =
+ \\fn foo() !void { return error.FooBar; }
+ \\
+ \\pub fn main() !void {
+ \\ comptime var i: usize = 0;
+ \\ b: inline while (i < 5) : (i += 1) {
+ \\ foo() catch {
+ \\ break :b; // non-error break, success
+ \\ };
+ \\ }
+ \\ // foo() was successfully handled, should not appear in trace
+ \\
+ \\ return error.BadTime;
+ \\}
+ ,
+ .Debug = .{
+ .expect =
+ \\error: BadTime
+ \\source.zig:12:5: [address] in main (test)
+ \\ return error.BadTime;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude_os = .{
+ .windows, // TODO
+ .linux, // defeated by aggressive inlining
+ },
+ .expect =
+ \\error: BadTime
+ \\source.zig:12:5: [address] in [function]
+ \\ return error.BadTime;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: BadTime
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: BadTime
+ \\
+ ,
+ },
+ });
+
+ cases.addCase(.{
.name = "catch and re-throw error",
.source =
\\fn foo() !void {
@@ -209,7 +315,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
});
cases.addCase(.{
- .name = "stored errors do not contribute to error trace",
+ .name = "errors stored in var do not contribute to error trace",
.source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
@@ -261,6 +367,82 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
});
cases.addCase(.{
+ .name = "error stored in const has trace preserved for duration of block",
+ .source =
+ \\fn foo() !void { return error.TheSkyIsFalling; }
+ \\fn bar() !void { return error.InternalError; }
+ \\fn baz() !void { return error.UnexpectedReality; }
+ \\
+ \\pub fn main() !void {
+ \\ const x = foo();
+ \\ const y = b: {
+ \\ if (true)
+ \\ break :b bar();
+ \\
+ \\ break :b {};
+ \\ };
+ \\ x catch {};
+ \\ y catch {};
+ \\ // foo()/bar() error traces not popped until end of block
+ \\
+ \\ {
+ \\ const z = baz();
+ \\ z catch {};
+ \\ // baz() error trace still alive here
+ \\ }
+ \\ // baz() error trace popped, foo(), bar() still alive
+ \\ return error.StillUnresolved;
+ \\}
+ ,
+ .Debug = .{
+ .expect =
+ \\error: StillUnresolved
+ \\source.zig:1:18: [address] in foo (test)
+ \\fn foo() !void { return error.TheSkyIsFalling; }
+ \\ ^
+ \\source.zig:2:18: [address] in bar (test)
+ \\fn bar() !void { return error.InternalError; }
+ \\ ^
+ \\source.zig:23:5: [address] in main (test)
+ \\ return error.StillUnresolved;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude_os = .{
+ .windows, // TODO
+ .linux, // defeated by aggressive inlining
+ },
+ .expect =
+ \\error: StillUnresolved
+ \\source.zig:1:18: [address] in [function]
+ \\fn foo() !void { return error.TheSkyIsFalling; }
+ \\ ^
+ \\source.zig:2:18: [address] in [function]
+ \\fn bar() !void { return error.InternalError; }
+ \\ ^
+ \\source.zig:23:5: [address] in [function]
+ \\ return error.StillUnresolved;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: StillUnresolved
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: StillUnresolved
+ \\
+ ,
+ },
+ });
+
+ cases.addCase(.{
.name = "error passed to function has its trace preserved for duration of the call",
.source =
\\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {