Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/std/Thread.zig
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
@call(.auto, f, args) catch |err| {
std.debug.print("error: {s}\n", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
if (builtin.zig_backend != .stage1)
trace.index -= 1; // exclude this error-handling block
Comment on lines +428 to +429
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example you gave, the extra line pointing at catch is nice. However, the introduction of this code makes me think this change is not worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

My main argument for the change is that it can be confusing to understand the "U-turn" taken by an error return trace when handling an error. It also may not be terribly obvious to users that the "parent" error trace is included intentionally in the first place

However, go ahead and close this if you think this isn't the best approach 🙂 We can always circle back later anyway

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also continue the discussion - maybe we can come up with a way to gain this enhancement without a perceived complexity increase in the language

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, let's keep brainstorming

The main complexity increase I see is for folks writing panic handlers / error trace printers who want to hide the function doing the dumping from the error trace. Is that the increase that jumps out to you?

If so, we might be able to sweep some of that complexity under the rug by adding std.debug.dumpErrorTrace or similar


std.debug.dumpStackTrace(trace.*);
}
};
Expand Down
9 changes: 9 additions & 0 deletions lib/std/start.zig
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,9 @@ inline fn initEventLoopAndCallMain() u8 {
loop.init() catch |err| {
std.log.err("{s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
if (builtin.zig_backend != .stage1)
trace.index -= 1; // exclude this error-handling block

std.debug.dumpStackTrace(trace.*);
}
return 1;
Expand Down Expand Up @@ -561,6 +564,9 @@ inline fn initEventLoopAndCallWinMain() std.os.windows.INT {
loop.init() catch |err| {
std.log.err("{s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
if (builtin.zig_backend != .stage1)
trace.index -= 1; // exclude this error-handling block

std.debug.dumpStackTrace(trace.*);
}
return 1;
Expand Down Expand Up @@ -617,6 +623,9 @@ pub fn callMain() u8 {
const result = root.main() catch |err| {
std.log.err("{s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
if (builtin.zig_backend != .stage1)
trace.index -= 1; // exclude this error-handling block

std.debug.dumpStackTrace(trace.*);
}
return 1;
Expand Down
3 changes: 3 additions & 0 deletions lib/test_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub fn main() void {
fail_count += 1;
progress.log("FAIL ({s})\n", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
if (builtin.zig_backend != .stage1)
trace.index -= 1; // exclude this error-handling block

std.debug.dumpStackTrace(trace.*);
}
test_node.end();
Expand Down
41 changes: 31 additions & 10 deletions src/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2965,7 +2965,7 @@ fn varDecl(
// The const init expression may have modified the error return trace, so signal
// to Sema that it should save the new index for restoring later.
if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node))
_ = try gz.addSaveErrRetIndex(.{ .if_of_error_type = init_inst });
_ = try gz.addSaveErrRetIndex(.{ .if_of_error_type = init_inst }, false);

const sub_scope = try block_arena.create(Scope.LocalVal);
sub_scope.* = .{
Expand Down Expand Up @@ -3038,7 +3038,7 @@ fn varDecl(
// The const init expression may have modified the error return trace, so signal
// to Sema that it should save the new index for restoring later.
if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node))
_ = try init_scope.addSaveErrRetIndex(.{ .if_of_error_type = init_inst });
_ = try init_scope.addSaveErrRetIndex(.{ .if_of_error_type = init_inst }, false);

const zir_tags = astgen.instructions.items(.tag);
const zir_datas = astgen.instructions.items(.data);
Expand Down Expand Up @@ -5395,10 +5395,21 @@ fn orelseCatchExpr(
var else_scope = block_scope.makeSubBlock(scope);
defer else_scope.unstack();

// We know that the operand (almost certainly) modified the error return trace,
// so signal to Sema that it should save the new index for restoring later.
if (do_err_trace and nodeMayAppendToErrorTrace(tree, lhs))
_ = try else_scope.addSaveErrRetIndex(.always);
if (do_err_trace) {
const catch_token = tree.nodes.items(.main_token)[node];
const catch_token_start = tree.tokens.items(.start)[catch_token];
astgen.advanceSourceCursor(catch_token_start);
const catch_line = astgen.source_line - else_scope.decl_line;
const catch_column = astgen.source_column;

// Add an entry to the error return trace, unless this is `catch unreachable`
const emit_ret_trace_entry = tree.nodes.items(.tag)[rhs] != .unreachable_literal;

// We know that the operand (almost certainly) modified the error return trace,
// so signal to Sema that it should save the new index for restoring later.
try emitDbgStmt(&else_scope, catch_line, catch_column);
_ = try else_scope.addSaveErrRetIndex(.always, emit_ret_trace_entry);
}

var err_val_scope: Scope.LocalVal = undefined;
const else_sub_scope = blk: {
Expand Down Expand Up @@ -5817,10 +5828,18 @@ fn ifExpr(
var else_scope = parent_gz.makeSubBlock(scope);
defer else_scope.unstack();

// We know that the operand (almost certainly) modified the error return trace,
// so signal to Sema that it should save the new index for restoring later.
if (do_err_trace and nodeMayAppendToErrorTrace(tree, if_full.ast.cond_expr))
_ = try else_scope.addSaveErrRetIndex(.always);
if (do_err_trace) {
const else_token = if_full.else_token;
const else_token_start = tree.tokens.items(.start)[else_token];
astgen.advanceSourceCursor(else_token_start);
const else_line = astgen.source_line - else_scope.decl_line;
const else_column = astgen.source_column;

// We know that the operand (almost certainly) modified the error return trace,
// so signal to Sema that it should save the new index for restoring later.
try emitDbgStmt(&else_scope, else_line, else_column);
_ = try else_scope.addSaveErrRetIndex(.always, true);
}

const else_node = if_full.ast.else_expr;
const else_info: struct {
Expand Down Expand Up @@ -11733,10 +11752,12 @@ const GenZir = struct {
always: void,
if_of_error_type: Zir.Inst.Ref,
},
emit_ret_trace_entry: bool,
) !Zir.Inst.Index {
return gz.addAsIndex(.{
.tag = .save_err_ret_index,
.data = .{ .save_err_ret_index = .{
.emit_ret_trace_entry = emit_ret_trace_entry,
.operand = if (cond == .if_of_error_type) cond.if_of_error_type else .none,
} },
});
Expand Down
29 changes: 27 additions & 2 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17162,8 +17162,33 @@ fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
break :b operand_ty.isError();
};

if (save_index)
block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
if (save_index) {
const src = sema.src;

const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", src);

if (inst_data.emit_ret_trace_entry and sema.owner_func.?.calls_or_awaits_errorable_fn) {
// This .save_err_ret_index is from a catch { ... } or else |err| { ... } block.
// Add an entry to the error return trace, so that it's obvious how we made it into the block.

const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
const return_err_fn = try sema.getBuiltin(block, src, "returnError");
const args: [1]Air.Inst.Ref = .{err_return_trace};

_ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
}

block.error_return_trace_index = try block.addInst(.{
.tag = .save_err_return_trace_index,
.data = .{ .ty_pl = .{
.ty = try sema.addType(stack_trace_ty),
.payload = @intCast(u32, field_index),
} },
});
}
}

fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
Expand Down
1 change: 1 addition & 0 deletions src/Zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2638,6 +2638,7 @@ pub const Inst = struct {
},
save_err_ret_index: struct {
operand: Ref, // If error type (or .none), save new trace index
emit_ret_trace_entry: bool, // Add this location to the error trace
},
restore_err_ret_index: struct {
block: Ref, // If restored, the index is from this block's entrypoint
Expand Down
2 changes: 1 addition & 1 deletion src/print_zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2306,7 +2306,7 @@ const Writer = struct {
const inst_data = self.code.instructions.items(.data)[inst].save_err_ret_index;

try self.writeInstRef(stream, inst_data.operand);
try stream.writeAll(")");
try stream.print(", emit_ret_trace_entry={})", .{inst_data.emit_ret_trace_entry});
}

fn writeRestoreErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
Expand Down
18 changes: 18 additions & 0 deletions test/stack_traces.zig
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:18: [address] in main (test)
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:6:5: [address] in main (test)
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
Expand All @@ -345,6 +348,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:6:18: [address] in [function]
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return foo() catch error.AndMyCarIsOutOfGas;
\\ ^
Expand Down Expand Up @@ -585,6 +591,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:11: [address] in main (test)
\\ foo() catch { // error trace should include foo()
\\ ^
\\source.zig:6:5: [address] in bar (test)
\\ return error.AndMyCarIsOutOfGas;
\\ ^
Expand All @@ -603,6 +612,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:11: [address] in [function]
\\ foo() catch { // error trace should include foo()
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return error.AndMyCarIsOutOfGas;
\\ ^
Expand Down Expand Up @@ -649,6 +661,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:23: [address] in main (test)
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
\\ ^
\\source.zig:6:5: [address] in bar (test)
\\ return error.AndMyCarIsOutOfGas;
\\ ^
Expand All @@ -667,6 +682,9 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\source.zig:2:5: [address] in [function]
\\ return error.TheSkyIsFalling;
\\ ^
\\source.zig:10:23: [address] in [function]
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
\\ ^
\\source.zig:6:5: [address] in [function]
\\ return error.AndMyCarIsOutOfGas;
\\ ^
Expand Down