From ab4ec35b8bb3a19361afa315f77cce5f6054b109 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Apr 2022 11:13:31 +0300 Subject: [PATCH] stage2: add runtime safety for unwrapping error --- lib/compiler_rt.zig | 24 +++++----- lib/std/builtin.zig | 4 ++ src/AstGen.zig | 27 +++++++++++ src/Sema.zig | 106 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 139 insertions(+), 22 deletions(-) diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index fdf5940702..563d3d0820 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -198,19 +198,17 @@ comptime { const __trunctfxf2 = @import("compiler_rt/trunc_f80.zig").__trunctfxf2; @export(__trunctfxf2, .{ .name = "__trunctfxf2", .linkage = linkage }); - if (builtin.zig_backend == .stage1) { // TODO - switch (arch) { - .i386, - .x86_64, - => { - const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; - @export(zig_probe_stack, .{ - .name = "__zig_probe_stack", - .linkage = linkage, - }); - }, - else => {}, - } + switch (arch) { + .i386, + .x86_64, + => { + const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; + @export(zig_probe_stack, .{ + .name = "__zig_probe_stack", + .linkage = linkage, + }); + }, + else => {}, } const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2; diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index a8069fa490..02aa7239f5 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -846,6 +846,10 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn } } +pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn { + std.debug.panicExtra(st, "attempt to unwrap error: {s}", .{@errorName(err)}); +} + pub noinline fn returnError(maybe_st: ?*StackTrace) void { @setCold(true); const st = maybe_st orelse return; diff --git a/src/AstGen.zig b/src/AstGen.zig index d4895aa2e4..3e502625db 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -856,6 +856,33 @@ 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, diff --git a/src/Sema.zig b/src/Sema.zig index a7dd905dd6..76888834d3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6248,8 +6248,7 @@ fn zirErrUnionPayload( } try sema.requireRuntimeBlock(block, src); if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err); } const result_ty = operand_ty.errorUnionPayload(); return block.addTyOp(.unwrap_errunion_payload, result_ty, operand); @@ -6330,8 +6329,7 @@ fn analyzeErrUnionPayloadPtr( try sema.requireRuntimeBlock(block, src); if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr); } const air_tag: Air.Inst.Tag = if (initializing) .errunion_payload_ptr_set @@ -13400,6 +13398,10 @@ fn zirErrorReturnTrace( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + return sema.getErrorReturnTrace(block, src); +} + +fn getErrorReturnTrace(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!Air.Inst.Ref { 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 opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); @@ -16852,7 +16854,6 @@ fn explainWhyTypeIsComptime( pub const PanicId = enum { unreach, unwrap_null, - unwrap_errunion, cast_to_null, incorrect_alignment, invalid_error_code, @@ -16962,12 +16963,100 @@ fn panicWithMsg( try Type.optional(arena, ptr_stack_trace_ty), Value.@"null", ); - const args = try arena.create([2]Air.Inst.Ref); - args.* = .{ msg_inst, null_stack_trace }; - _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, args); + const args: [2]Air.Inst.Ref = .{ msg_inst, null_stack_trace }; + _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args); return always_noreturn; } +fn panicUnwrapError( + sema: *Sema, + parent_block: *Block, + src: LazySrcLoc, + operand: Air.Inst.Ref, + unwrap_err_tag: Air.Inst.Tag, + is_non_err_tag: Air.Inst.Tag, +) !void { + const ok = try parent_block.addUnOp(is_non_err_tag, operand); + const gpa = sema.gpa; + + var fail_block: Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .namespace = parent_block.namespace, + .wip_capture_scope = parent_block.wip_capture_scope, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + }; + + defer fail_block.instructions.deinit(gpa); + + { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.object_format == .c or + sema.mod.comp.bin_file.options.use_llvm; + + if (!this_feature_is_implemented_in_the_backend) { + // TODO implement this feature in all the backends and then delete this branch + _ = try fail_block.addNoOp(.breakpoint); + _ = try fail_block.addNoOp(.unreach); + } else { + const panic_fn = try sema.getBuiltin(&fail_block, src, "panicUnwrapError"); + const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand); + const err_return_trace = try sema.getErrorReturnTrace(&fail_block, src); + const args: [2]Air.Inst.Ref = .{ err_return_trace, err }; + _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); + } + } + + try parent_block.instructions.ensureUnusedCapacity(gpa, 1); + + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + + 1 + // The main block only needs space for the cond_br. + @typeInfo(Air.CondBr).Struct.fields.len + + 1 + // The ok branch of the cond_br only needs space for the br. + fail_block.instructions.items.len); + + try sema.air_instructions.ensureUnusedCapacity(gpa, 3); + const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); + const cond_br_inst = block_inst + 1; + const br_inst = cond_br_inst + 1; + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .block, + .data = .{ .ty_pl = .{ + .ty = .void_type, + .payload = sema.addExtraAssumeCapacity(Air.Block{ + .body_len = 1, + }), + } }, + }); + sema.air_extra.appendAssumeCapacity(cond_br_inst); + + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .cond_br, + .data = .{ .pl_op = .{ + .operand = ok, + .payload = sema.addExtraAssumeCapacity(Air.CondBr{ + .then_body_len = 1, + .else_body_len = @intCast(u32, fail_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendAssumeCapacity(br_inst); + sema.air_extra.appendSliceAssumeCapacity(fail_block.instructions.items); + + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .br, + .data = .{ .br = .{ + .block_inst = block_inst, + .operand = .void_value, + } }, + }); + + parent_block.instructions.appendAssumeCapacity(block_inst); +} + fn safetyPanic( sema: *Sema, block: *Block, @@ -16977,7 +17066,6 @@ fn safetyPanic( const msg = switch (panic_id) { .unreach => "reached unreachable code", .unwrap_null => "attempt to use null value", - .unwrap_errunion => "unreachable error occurred", .cast_to_null => "cast causes pointer to be null", .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code",