diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-09-21 02:56:21 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-21 02:56:21 -0400 |
| commit | 902f6db67b2d25f238fb13e458a12e06df62dadb (patch) | |
| tree | 5736f130758bcdc22babb60ac2ded441d43eb28e /src | |
| parent | 62ecc154d9ad065aee57d81afd3a478dd8360fb7 (diff) | |
| parent | d7d21672b83b64fd522a5998780bfde6ef724557 (diff) | |
| download | zig-902f6db67b2d25f238fb13e458a12e06df62dadb.tar.gz zig-902f6db67b2d25f238fb13e458a12e06df62dadb.zip | |
Merge pull request #12889 from ziglang/unwrap-error-switch
safety: show error return trace when unwrapping error in switch
Diffstat (limited to 'src')
| -rw-r--r-- | src/AstGen.zig | 29 | ||||
| -rw-r--r-- | src/Sema.zig | 212 | ||||
| -rw-r--r-- | src/Zir.zig | 16 | ||||
| -rw-r--r-- | src/crash_report.zig | 4 | ||||
| -rw-r--r-- | src/print_zir.zig | 2 | ||||
| -rw-r--r-- | src/stage1/codegen.cpp | 14 |
6 files changed, 190 insertions, 87 deletions
diff --git a/src/AstGen.zig b/src/AstGen.zig index 132525e4d4..0f04cd4b08 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -884,33 +884,6 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr catch_token + 2 else null; - - var rhs = node_datas[node].rhs; - while (true) switch (node_tags[rhs]) { - .grouped_expression => rhs = node_datas[rhs].lhs, - .unreachable_literal => { - if (payload_token != null and mem.eql(u8, tree.tokenSlice(payload_token.?), "_")) { - return astgen.failTok(payload_token.?, "discard of error capture; omit it instead", .{}); - } else if (payload_token != null) { - return astgen.failTok(payload_token.?, "unused capture", .{}); - } - const lhs = node_datas[node].lhs; - - const operand = try reachableExpr(gz, scope, switch (rl) { - .ref => .ref, - else => .none, - }, lhs, lhs); - const result = try gz.addUnNode(switch (rl) { - .ref => .err_union_payload_safe_ptr, - else => .err_union_payload_safe, - }, operand, node); - switch (rl) { - .none, .coerced_ty, .discard, .ref => return result, - else => return rvalue(gz, rl, result, lhs), - } - }, - else => break, - }; switch (rl) { .ref => return orelseCatchExpr( gz, @@ -2375,9 +2348,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .optional_payload_unsafe, .optional_payload_safe_ptr, .optional_payload_unsafe_ptr, - .err_union_payload_safe, .err_union_payload_unsafe, - .err_union_payload_safe_ptr, .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, diff --git a/src/Sema.zig b/src/Sema.zig index 171b349758..82593f3935 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -747,10 +747,8 @@ fn analyzeBodyInner( .int_to_enum => try sema.zirIntToEnum(block, inst), .err_union_code => try sema.zirErrUnionCode(block, inst), .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst), - .err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true), - .err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, true), - .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst, false), - .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false), + .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst), + .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst), .error_union_type => try sema.zirErrorUnionType(block, inst), .error_value => try sema.zirErrorValue(block, inst), .field_ptr => try sema.zirFieldPtr(block, inst, false), @@ -1355,6 +1353,8 @@ fn analyzeBodyInner( const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition, "condition in comptime branch must be comptime known"); const inline_body = if (cond.val.toBool()) then_body else else_body; + + try sema.maybeErrorUnwrapCondbr(block, inline_body, extra.data.condition, cond_src); const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse break always_noreturn; if (inst == break_data.block_inst) { @@ -1955,7 +1955,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { err_msg.src_loc.file_scope.sub_file_path, err_msg.src_loc.lazy, }); - crash_report.compilerPanic("unexpected compile error occurred", null); + crash_report.compilerPanic("unexpected compile error occurred", null, null); } const mod = sema.mod; @@ -7426,7 +7426,6 @@ fn zirErrUnionPayload( sema: *Sema, block: *Block, inst: Zir.Inst.Index, - safety_check: bool, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7441,7 +7440,7 @@ fn zirErrUnionPayload( err_union_ty.fmt(sema.mod), }); } - return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, safety_check); + return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, false); } fn analyzeErrUnionPayload( @@ -7479,7 +7478,6 @@ fn zirErrUnionPayloadPtr( sema: *Sema, block: *Block, inst: Zir.Inst.Index, - safety_check: bool, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7488,7 +7486,7 @@ fn zirErrUnionPayloadPtr( const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); - return sema.analyzeErrUnionPayloadPtr(block, src, operand, safety_check, false); + return sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); } fn analyzeErrUnionPayloadPtr( @@ -9247,6 +9245,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var empty_enum = false; const operand_ty = sema.typeOf(operand); + const err_set = operand_ty.zigTypeTag() == .ErrorSet; var else_error_ty: ?Type = null; @@ -9829,6 +9828,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item, undefined) catch unreachable; if (operand_val.eql(item_val, operand_ty, sema.mod)) { + if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } } @@ -9851,6 +9851,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item, undefined) catch unreachable; if (operand_val.eql(item_val, operand_ty, sema.mod)) { + if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } } @@ -9868,6 +9869,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if ((try sema.compare(block, src, operand_val, .gte, first_tv.val, operand_ty)) and (try sema.compare(block, src, operand_val, .lte, last_tv.val, operand_ty))) { + if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } } @@ -9875,6 +9877,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += body_len; } } + if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, special.body, operand); return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); } @@ -9885,6 +9888,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (special_prong == .none) { return sema.fail(block, src, "switch must handle all possibilities", .{}); } + if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand)) { + return Air.Inst.Ref.unreachable_value; + } return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); } @@ -9927,7 +9933,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError break :blk field_ty.zigTypeTag() != .NoReturn; } else true; - if (analyze_body) { + if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { + // nothing to do here + } else if (analyze_body) { _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { error.ComptimeBreak => { const zir_datas = sema.code.instructions.items(.data); @@ -9995,7 +10003,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - if (analyze_body) { + if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { + // nothing to do here + } else if (analyze_body) { _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { error.ComptimeBreak => { const zir_datas = sema.code.instructions.items(.data); @@ -10085,18 +10095,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { + // nothing to do here + } else { + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; + } try wip_captures.finalize(); @@ -10141,8 +10155,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } else false else true; - - if (special.body.len != 0 and analyze_body) { + if (special.body.len != 0 and err_set and + try sema.maybeErrorUnwrap(&case_block, special.body, operand)) + { + // nothing to do here + } else if (special.body.len != 0 and analyze_body) { _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) { error.ComptimeBreak => { const zir_datas = sema.code.instructions.items(.data); @@ -10400,6 +10417,109 @@ fn validateSwitchNoRange( return sema.failWithOwnedErrorMsg(msg); } +fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !bool { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.use_llvm; + + if (!this_feature_is_implemented_in_the_backend) return false; + + const tags = sema.code.instructions.items(.tag); + for (body) |inst| { + switch (tags[inst]) { + .dbg_block_begin, + .dbg_block_end, + .dbg_stmt, + .@"unreachable", + .str, + .as_node, + .panic, + .field_val, + => {}, + else => return false, + } + } + + for (body) |inst| { + const air_inst = switch (tags[inst]) { + .dbg_block_begin, + .dbg_block_end, + => continue, + .dbg_stmt => { + try sema.zirDbgStmt(block, inst); + continue; + }, + .str => try sema.zirStr(block, inst), + .as_node => try sema.zirAsNode(block, inst), + .field_val => try sema.zirFieldVal(block, inst), + .@"unreachable" => { + const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable"; + const src = inst_data.src(); + + const panic_fn = try sema.getBuiltin(block, src, "panicUnwrapError"); + const err_return_trace = try sema.getErrorReturnTrace(block, src); + const args: [2]Air.Inst.Ref = .{ err_return_trace, operand }; + _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null); + return true; + }, + .panic => { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const msg_inst = try sema.resolveInst(inst_data.operand); + + const panic_fn = try sema.getBuiltin(block, src, "panic"); + const err_return_trace = try sema.getErrorReturnTrace(block, src); + const args: [3]Air.Inst.Ref = .{ msg_inst, err_return_trace, .null_value }; + _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null); + return true; + }, + else => unreachable, + }; + if (sema.typeOf(air_inst).isNoReturn()) + return true; + try sema.inst_map.put(sema.gpa, inst, air_inst); + } + unreachable; +} + +fn maybeErrorUnwrapCondbr(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, cond: Zir.Inst.Ref, cond_src: LazySrcLoc) !void { + const index = Zir.refToIndex(cond) orelse return; + if (sema.code.instructions.items(.tag)[index] != .is_non_err) return; + + const err_inst_data = sema.code.instructions.items(.data)[index].un_node; + const err_operand = try sema.resolveInst(err_inst_data.operand); + const operand_ty = sema.typeOf(err_operand); + if (operand_ty.zigTypeTag() == .ErrorSet) { + try sema.maybeErrorUnwrapComptime(block, body, err_operand); + return; + } + if (try sema.resolveDefinedValue(block, cond_src, err_operand)) |val| { + if (val.getError() == null) return; + try sema.maybeErrorUnwrapComptime(block, body, err_operand); + } +} + +fn maybeErrorUnwrapComptime(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !void { + const tags = sema.code.instructions.items(.tag); + const inst = for (body) |inst| { + switch (tags[inst]) { + .dbg_block_begin, + .dbg_block_end, + .dbg_stmt, + => {}, + .@"unreachable" => break inst, + else => return, + } + } else return; + const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable"; + const src = inst_data.src(); + + if (try sema.resolveDefinedValue(block, src, operand)) |val| { + if (val.getError()) |name| { + return sema.fail(block, src, "caught unexpected error '{s}'", .{name}); + } + } +} + fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; @@ -15152,6 +15272,8 @@ fn zirCondbr( if (try sema.resolveDefinedValue(parent_block, cond_src, cond)) |cond_val| { const body = if (cond_val.toBool()) then_body else else_body; + + try sema.maybeErrorUnwrapCondbr(parent_block, body, extra.data.condition, cond_src); // We use `analyzeBodyInner` since we want to propagate any possible // `error.ComptimeBreak` to the caller. return sema.analyzeBodyInner(parent_block, body); @@ -15182,18 +15304,34 @@ fn zirCondbr( const true_instructions = sub_block.instructions.toOwnedSlice(gpa); defer gpa.free(true_instructions); - _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&sub_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, + const err_cond = blk: { + const index = Zir.refToIndex(extra.data.condition) orelse break :blk null; + if (sema.code.instructions.items(.tag)[index] != .is_non_err) break :blk null; + + const err_inst_data = sema.code.instructions.items(.data)[index].un_node; + const err_operand = try sema.resolveInst(err_inst_data.operand); + const operand_ty = sema.typeOf(err_operand); + assert(operand_ty.zigTypeTag() == .ErrorUnion); + const result_ty = operand_ty.errorUnionSet(); + break :blk try sub_block.addTyOp(.unwrap_errunion_err, result_ty, err_operand); }; + + if (err_cond != null and try sema.maybeErrorUnwrap(&sub_block, else_body, err_cond.?)) { + // nothing to do + } else { + _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&sub_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; + } try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len + true_instructions.len + sub_block.instructions.items.len); _ = try parent_block.addInst(.{ @@ -21001,7 +21139,7 @@ fn panicWithMsg( try Type.optional(arena, ptr_stack_trace_ty), Value.@"null", ); - const args: [2]Air.Inst.Ref = .{ msg_inst, null_stack_trace }; + const args: [3]Air.Inst.Ref = .{ msg_inst, null_stack_trace, .null_value }; _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null); return always_noreturn; } diff --git a/src/Zir.zig b/src/Zir.zig index 4953e575f9..9fa0f42e41 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -629,20 +629,10 @@ pub const Inst = struct { /// No safety checks. /// Uses the `un_node` field. optional_payload_unsafe_ptr, - /// E!T => T with safety. - /// Given an error union value, returns the payload value, with a safety check - /// that the value is not an error. Used for catch, if, and while. - /// Uses the `un_node` field. - err_union_payload_safe, /// E!T => T without safety. /// Given an error union value, returns the payload value. No safety checks. /// Uses the `un_node` field. err_union_payload_unsafe, - /// *E!T => *T with safety. - /// Given a pointer to an error union value, returns a pointer to the payload value, - /// with a safety check that the value is not an error. Used for catch, if, and while. - /// Uses the `un_node` field. - err_union_payload_safe_ptr, /// *E!T => *T without safety. /// Given a pointer to a error union value, returns a pointer to the payload value. /// No safety checks. @@ -1120,9 +1110,7 @@ pub const Inst = struct { .optional_payload_unsafe, .optional_payload_safe_ptr, .optional_payload_unsafe_ptr, - .err_union_payload_safe, .err_union_payload_unsafe, - .err_union_payload_safe_ptr, .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, @@ -1421,9 +1409,7 @@ pub const Inst = struct { .optional_payload_unsafe, .optional_payload_safe_ptr, .optional_payload_unsafe_ptr, - .err_union_payload_safe, .err_union_payload_unsafe, - .err_union_payload_safe_ptr, .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, @@ -1692,9 +1678,7 @@ pub const Inst = struct { .optional_payload_unsafe = .un_node, .optional_payload_safe_ptr = .un_node, .optional_payload_unsafe_ptr = .un_node, - .err_union_payload_safe = .un_node, .err_union_payload_unsafe = .un_node, - .err_union_payload_safe_ptr = .un_node, .err_union_payload_unsafe_ptr = .un_node, .err_union_code = .un_node, .err_union_code_ptr = .un_node, diff --git a/src/crash_report.zig b/src/crash_report.zig index d38f436aa0..04fbabca09 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -155,10 +155,10 @@ fn writeFullyQualifiedDeclWithFile(mod: *Module, decl: *Decl, stream: anytype) ! try decl.renderFullyQualifiedDebugName(mod, stream); } -pub fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { +pub fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, maybe_ret_addr: ?usize) noreturn { PanicSwitch.preDispatch(); @setCold(true); - const ret_addr = @returnAddress(); + const ret_addr = maybe_ret_addr orelse @returnAddress(); const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; PanicSwitch.dispatch(error_return_trace, stack_ctx, msg); } diff --git a/src/print_zir.zig b/src/print_zir.zig index 9e8a3f1481..e2081172e6 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -170,9 +170,7 @@ const Writer = struct { .optional_payload_unsafe, .optional_payload_safe_ptr, .optional_payload_unsafe_ptr, - .err_union_payload_safe, .err_union_payload_unsafe, - .err_union_payload_safe_ptr, .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 55aa73a3b7..2341ad4483 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -1086,11 +1086,23 @@ static void gen_panic(CodeGen *g, LLVMValueRef msg_arg, LLVMValueRef stack_trace if (stack_trace_arg == nullptr) { stack_trace_arg = LLVMConstNull(get_llvm_type(g, ptr_to_stack_trace_type(g))); } + LLVMValueRef null_ret_alloc; + { + ZigValue null_val = {}; + null_val.special = ConstValSpecialStatic; + null_val.data.x_optional = nullptr; + null_val.type = get_optional_type2(g, g->builtin_types.entry_usize); + LLVMValueRef null_ret_val = gen_const_val(g, &null_val, ""); + null_ret_alloc = build_alloca(g, null_val.type, "ret_addr", 0); + LLVMBuildStore(g->builder, null_ret_val, null_ret_alloc); + } + LLVMValueRef args[] = { msg_arg, stack_trace_arg, + null_ret_alloc, }; - ZigLLVMBuildCall(g->builder, LLVMGlobalGetValueType(fn_val), fn_val, args, 2, llvm_cc, ZigLLVM_CallAttrAuto, ""); + ZigLLVMBuildCall(g->builder, LLVMGlobalGetValueType(fn_val), fn_val, args, 3, llvm_cc, ZigLLVM_CallAttrAuto, ""); if (!stack_trace_is_llvm_alloca) { // The stack trace argument is not in the stack of the caller, so // we'd like to set tail call here, but because slices (the type of msg_arg) are |
