mirror of
https://github.com/ziglang/zig.git
synced 2024-11-27 15:42:49 +00:00
d060cbbec7
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`.
747 lines
20 KiB
Zig
747 lines
20 KiB
Zig
const std = @import("std");
|
|
const os = std.os;
|
|
const tests = @import("tests.zig");
|
|
|
|
pub fn addCases(cases: *tests.StackTracesContext) void {
|
|
cases.addCase(.{
|
|
.name = "return",
|
|
.source =
|
|
\\pub fn main() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in main (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
.linux, // defeated by aggressive inlining
|
|
},
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in main (test)
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
},
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in [function]
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
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",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ foo() catch {}; // should not affect error trace
|
|
\\ if (foo()) |_| {} else |_| {
|
|
\\ // should also not affect error trace
|
|
\\ }
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:10:5: [address] in main (test)
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
.linux, // defeated by aggressive inlining
|
|
},
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:10:5: [address] in [function]
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
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 {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in main (test)
|
|
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
.linux, // defeated by aggressive inlining
|
|
},
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in [function]
|
|
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "errors stored in var do not contribute to error trace",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ // Once an error is stored in a variable, it is popped from the trace
|
|
\\ var x = foo();
|
|
\\ x = {};
|
|
\\
|
|
\\ // As a result, this error trace will still be clean
|
|
\\ return error.SomethingUnrelatedWentWrong;
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: SomethingUnrelatedWentWrong
|
|
\\source.zig:11:5: [address] in main (test)
|
|
\\ return error.SomethingUnrelatedWentWrong;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
.linux, // defeated by aggressive inlining
|
|
},
|
|
.expect =
|
|
\\error: SomethingUnrelatedWentWrong
|
|
\\source.zig:11:5: [address] in [function]
|
|
\\ return error.SomethingUnrelatedWentWrong;
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: SomethingUnrelatedWentWrong
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: SomethingUnrelatedWentWrong
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
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 {
|
|
\\ actual_error catch |err| {
|
|
\\ if (err == expected_error) return {};
|
|
\\ };
|
|
\\ return error.TestExpectedError;
|
|
\\}
|
|
\\
|
|
\\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
|
|
\\fn foo() !void { return error.Foo; }
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
|
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
|
\\ try expectError(error.Foo, foo());
|
|
\\
|
|
\\ // Only the error trace for this failing check should appear:
|
|
\\ try expectError(error.Bar, foo());
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: TestExpectedError
|
|
\\source.zig:9:18: [address] in foo (test)
|
|
\\fn foo() !void { return error.Foo; }
|
|
\\ ^
|
|
\\source.zig:5:5: [address] in expectError (test)
|
|
\\ return error.TestExpectedError;
|
|
\\ ^
|
|
\\source.zig:17:5: [address] in main (test)
|
|
\\ try expectError(error.Bar, foo());
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
},
|
|
.expect =
|
|
\\error: TestExpectedError
|
|
\\source.zig:9:18: [address] in [function]
|
|
\\fn foo() !void { return error.Foo; }
|
|
\\ ^
|
|
\\source.zig:5:5: [address] in [function]
|
|
\\ return error.TestExpectedError;
|
|
\\ ^
|
|
\\source.zig:17:5: [address] in [function]
|
|
\\ try expectError(error.Bar, foo());
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: TestExpectedError
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: TestExpectedError
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return from within catch",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ foo() catch { // error trace should include foo()
|
|
\\ try bar();
|
|
\\ };
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar (test)
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in main (test)
|
|
\\ try bar();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
},
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in [function]
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in [function]
|
|
\\ try bar();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try return from within if-else",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
|
|
\\ try bar();
|
|
\\ }
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar (test)
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in main (test)
|
|
\\ try bar();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
},
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in [function]
|
|
\\ return error.AndMyCarIsOutOfGas;
|
|
\\ ^
|
|
\\source.zig:11:9: [address] in [function]
|
|
\\ try bar();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: AndMyCarIsOutOfGas
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.name = "try try return return",
|
|
.source =
|
|
\\fn foo() !void {
|
|
\\ try bar();
|
|
\\}
|
|
\\
|
|
\\fn bar() !void {
|
|
\\ return make_error();
|
|
\\}
|
|
\\
|
|
\\fn make_error() !void {
|
|
\\ return error.TheSkyIsFalling;
|
|
\\}
|
|
\\
|
|
\\pub fn main() !void {
|
|
\\ try foo();
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:10:5: [address] in make_error (test)
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in bar (test)
|
|
\\ return make_error();
|
|
\\ ^
|
|
\\source.zig:2:5: [address] in foo (test)
|
|
\\ try bar();
|
|
\\ ^
|
|
\\source.zig:14:5: [address] in main (test)
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSafe = .{
|
|
.exclude_os = .{
|
|
.windows, // TODO
|
|
},
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\source.zig:10:5: [address] in [function]
|
|
\\ return error.TheSkyIsFalling;
|
|
\\ ^
|
|
\\source.zig:6:5: [address] in [function]
|
|
\\ return make_error();
|
|
\\ ^
|
|
\\source.zig:2:5: [address] in [function]
|
|
\\ try bar();
|
|
\\ ^
|
|
\\source.zig:14:5: [address] in [function]
|
|
\\ try foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseFast = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
.ReleaseSmall = .{
|
|
.expect =
|
|
\\error: TheSkyIsFalling
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
|
|
cases.addCase(.{
|
|
.exclude_os = .{
|
|
.openbsd, // integer overflow
|
|
.windows, // TODO intermittent failures
|
|
},
|
|
.name = "dumpCurrentStackTrace",
|
|
.source =
|
|
\\const std = @import("std");
|
|
\\
|
|
\\fn bar() void {
|
|
\\ std.debug.dumpCurrentStackTrace(@returnAddress());
|
|
\\}
|
|
\\fn foo() void {
|
|
\\ bar();
|
|
\\}
|
|
\\pub fn main() u8 {
|
|
\\ foo();
|
|
\\ return 1;
|
|
\\}
|
|
,
|
|
.Debug = .{
|
|
.expect =
|
|
\\source.zig:7:8: [address] in foo (test)
|
|
\\ bar();
|
|
\\ ^
|
|
\\source.zig:10:8: [address] in main (test)
|
|
\\ foo();
|
|
\\ ^
|
|
\\
|
|
,
|
|
},
|
|
});
|
|
}
|