Skip to content

Show error return trace whenever catch block reaches unreachable #14807

@codethief

Description

@codethief

Zig Version

0.11.0-dev.1836+28364166e

Steps to Reproduce and Observed Output

This suggestion is similar to #12807 but goes a bit further: While the example from that pull requests yields an error return trace these days, the following code snippet does not:

const std = @import("std");

fn foo() !void {
    return error.Foo;
}
fn bar() !void {
    try foo();
}
fn baz() void {
    bar() catch |err| {
        std.debug.print("\nSome additional debug information: [...] Error: {any}\n", .{err});
        unreachable;
    };
}
test {
    baz();
}

This yields:

$ zig test test.zig
Test [1/1] test_0...
Some additional debug information: [...] Error: error.Foo
thread 205313 panic: reached unreachable code
/home/user/zig-test/test.zig:12:9: 0x20bef3 in baz (test)
        unreachable;
        ^
/home/user/zig-test/test.zig:16:8: 0x20be78 in test_0 (test)
    baz();
       ^
/home/user/opt/zig/lib/test_runner.zig:66:28: 0x20d85e in main (test)
        } else test_fn.func();
                           ^
/home/user/opt/zig/lib/std/start.zig:607:22: 0x20c7b5 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/user/opt/zig/lib/std/start.zig:376:5: 0x20c261 in _start (test)
    @call(.never_inline, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
/home/user/zig-test/zig-cache/o/2fb76e24211cebd8013e87fd8c71dd2f/test

i.e. no error return trace.

Expected Output

I would expect the output to contain an error return trace, like so:

$ zig test test.zig
Test [1/1] test_0... thread 209915 panic: attempt to unwrap error: Foo
/home/user/zig-test/test.zig:4:5: 0x20c6c8 in foo (test)
    return error.Foo;
    ^
/home/user/zig-test/test.zig:7:5: 0x20c7d3 in bar (test)
    try foo();
    ^
/home/user/zig-test/test.zig:12:9: 0x20bdd0 in baz (test)
        unreachable;
        ^
/home/user/zig-test/test.zig:13:8: 0x20bd78 in test_0 (test)
    baz();
       ^
/home/user/opt/zig/lib/test_runner.zig:66:28: 0x20d6ce in main (test)
        } else test_fn.func();
                           ^
/home/user/opt/zig/lib/std/start.zig:607:22: 0x20c695 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/user/opt/zig/lib/std/start.zig:376:5: 0x20c141 in _start (test)
    @call(.never_inline, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
/home/user/zig-test/zig-cache/o/2fb76e24211cebd8013e87fd8c71dd2f/test

I would expect a similar output if I don't even capture the error (i.e. catch { … } instead of catch |err| { … }).

Interestingly, if in the code snippet I replace unreachable; with switch (err) { else => unreachable, } I do get an error return trace, which is probably thanks to the aforementioned pull request.

[Add-on] What about if unreachable is invoked only indirectly?

Ideally, outputting an error return trace would also work if I outsource the catch block to a noreturn function which then calls unreachable: E.g.

const std = @import("std");

fn foo() !void {
    return error.Foo;
}
fn bar() !void {
    try foo();
}
fn baz() void {
    bar() catch |err| failWithDebugInfo(err);
}
test {
    baz();
}
fn failWithDebugInfo(err: anyerror) noreturn {
    std.debug.print("\nSome additional debug information: [...]\nError: {any}", .{err});
    unreachable; // or @panic("…")
}

This would be really helpful if one wants to wrap unreachable or @panic in a custom function that, like above, outputs additional information. I'm not sure if this possible, though, so please consider this an optional add-on to my feature request. FWIW, I tried using the aforementioned workaround of employing a switch statement:

const std = @import("std");

fn foo() !void {
    return error.Foo;
}
fn bar() !void {
    try foo();
}
fn baz() void {
    bar() catch |err| failWithDebugInfo(err);
}
test {
    baz();
}
fn failWithDebugInfo(err: anyerror) noreturn {
    std.debug.print("\nSome additional debug information: [...]\nError: {any}", .{err});
    switch (err) {
        else => unreachable,
    }
}

But this yields an incomplete error return trace (it doesn't tell me where error.Foo came from):

$ zig test test.zig
Test [1/1] test_0...
Some additional debug information: [...]
Error: error.Foothread 239739 panic: attempt to unwrap error: Foo
/home/user/zigfish-test/test.zig:18:17: 0x20c92f in failWithDebugInfo (test)
        else => unreachable,
                ^
/home/user/zigfish-test/test.zig:10:40: 0x20bedc in baz (test)
    bar() catch |err| failWithDebugInfo(err);
                                       ^
/home/user/zigfish-test/test.zig:13:8: 0x20be88 in test_0 (test)
    baz();
       ^
/home/user/opt/zig/lib/test_runner.zig:66:28: 0x20d7be in main (test)
        } else test_fn.func();
                           ^
/home/user/opt/zig/lib/std/start.zig:607:22: 0x20c7a5 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/user/opt/zig/lib/std/start.zig:376:5: 0x20c251 in _start (test)
    @call(.never_inline, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
/home/user/zigfish-test/zig-cache/o/2fb76e24211cebd8013e87fd8c71dd2f/test

[Add-on 2] Allow for @panic instead of unreachable

I.e. all the code snippets from above should also yield an error return trace when unreachable is replaced with @panic("Foo").

Again, I'm not familiar with Zig internals, so I can't speak to the feasibility of implementing this behavior but it's at least what I would expect from a user perspective.

Metadata

Metadata

Assignees

No one assigned

    Labels

    error messageThis issue points out an error message that is unhelpful and should be improved.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions