authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-09-10 23:09:09+03:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-20 19:05:00-07:00
log8f2e82dbf63aedc64af5c701c4798e9fbd51de72
treeeb189f7eccfa65ca0fd5cebf7c06e4f62b40cae9
parent62ecc154d9ad065aee57d81afd3a478dd8360fb7

safety: show error return trace when unwrapping error in switch


5 files changed, 194 insertions(+), 82 deletions(-)

src/AstGen.zig-29
......@@ -884,33 +884,6 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
884884 catch_token + 2
885885 else
886886 null;
887
888 var rhs = node_datas[node].rhs;
889 while (true) switch (node_tags[rhs]) {
890 .grouped_expression => rhs = node_datas[rhs].lhs,
891 .unreachable_literal => {
892 if (payload_token != null and mem.eql(u8, tree.tokenSlice(payload_token.?), "_")) {
893 return astgen.failTok(payload_token.?, "discard of error capture; omit it instead", .{});
894 } else if (payload_token != null) {
895 return astgen.failTok(payload_token.?, "unused capture", .{});
896 }
897 const lhs = node_datas[node].lhs;
898
899 const operand = try reachableExpr(gz, scope, switch (rl) {
900 .ref => .ref,
901 else => .none,
902 }, lhs, lhs);
903 const result = try gz.addUnNode(switch (rl) {
904 .ref => .err_union_payload_safe_ptr,
905 else => .err_union_payload_safe,
906 }, operand, node);
907 switch (rl) {
908 .none, .coerced_ty, .discard, .ref => return result,
909 else => return rvalue(gz, rl, result, lhs),
910 }
911 },
912 else => break,
913 };
914887 switch (rl) {
915888 .ref => return orelseCatchExpr(
916889 gz,
......@@ -2375,9 +2348,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
23752348 .optional_payload_unsafe,
23762349 .optional_payload_safe_ptr,
23772350 .optional_payload_unsafe_ptr,
2378 .err_union_payload_safe,
23792351 .err_union_payload_unsafe,
2380 .err_union_payload_safe_ptr,
23812352 .err_union_payload_unsafe_ptr,
23822353 .err_union_code,
23832354 .err_union_code_ptr,
src/Sema.zig+173-35
......@@ -747,10 +747,8 @@ fn analyzeBodyInner(
747747 .int_to_enum => try sema.zirIntToEnum(block, inst),
748748 .err_union_code => try sema.zirErrUnionCode(block, inst),
749749 .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
750 .err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true),
751 .err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, true),
752 .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst, false),
753 .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false),
750 .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst),
751 .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst),
754752 .error_union_type => try sema.zirErrorUnionType(block, inst),
755753 .error_value => try sema.zirErrorValue(block, inst),
756754 .field_ptr => try sema.zirFieldPtr(block, inst, false),
......@@ -1355,6 +1353,8 @@ fn analyzeBodyInner(
13551353 const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
13561354 const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition, "condition in comptime branch must be comptime known");
13571355 const inline_body = if (cond.val.toBool()) then_body else else_body;
1356
1357 try sema.maybeErrorUnwrapCondbr(block, inline_body, extra.data.condition, cond_src);
13581358 const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse
13591359 break always_noreturn;
13601360 if (inst == break_data.block_inst) {
......@@ -7426,7 +7426,6 @@ fn zirErrUnionPayload(
74267426 sema: *Sema,
74277427 block: *Block,
74287428 inst: Zir.Inst.Index,
7429 safety_check: bool,
74307429) CompileError!Air.Inst.Ref {
74317430 const tracy = trace(@src());
74327431 defer tracy.end();
......@@ -7441,7 +7440,7 @@ fn zirErrUnionPayload(
74417440 err_union_ty.fmt(sema.mod),
74427441 });
74437442 }
7444 return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, safety_check);
7443 return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, false);
74457444}
74467445
74477446fn analyzeErrUnionPayload(
......@@ -7479,7 +7478,6 @@ fn zirErrUnionPayloadPtr(
74797478 sema: *Sema,
74807479 block: *Block,
74817480 inst: Zir.Inst.Index,
7482 safety_check: bool,
74837481) CompileError!Air.Inst.Ref {
74847482 const tracy = trace(@src());
74857483 defer tracy.end();
......@@ -7488,7 +7486,7 @@ fn zirErrUnionPayloadPtr(
74887486 const operand = try sema.resolveInst(inst_data.operand);
74897487 const src = inst_data.src();
74907488
7491 return sema.analyzeErrUnionPayloadPtr(block, src, operand, safety_check, false);
7489 return sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false);
74927490}
74937491
74947492fn analyzeErrUnionPayloadPtr(
......@@ -9247,6 +9245,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
92479245 var empty_enum = false;
92489246
92499247 const operand_ty = sema.typeOf(operand);
9248 const err_set = operand_ty.zigTypeTag() == .ErrorSet;
92509249
92519250 var else_error_ty: ?Type = null;
92529251
......@@ -9829,6 +9828,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98299828 // Validation above ensured these will succeed.
98309829 const item_val = sema.resolveConstValue(&child_block, .unneeded, item, undefined) catch unreachable;
98319830 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
9831 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
98329832 return sema.resolveBlockBody(block, src, &child_block, body, inst, merges);
98339833 }
98349834 }
......@@ -9851,6 +9851,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98519851 // Validation above ensured these will succeed.
98529852 const item_val = sema.resolveConstValue(&child_block, .unneeded, item, undefined) catch unreachable;
98539853 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
9854 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
98549855 return sema.resolveBlockBody(block, src, &child_block, body, inst, merges);
98559856 }
98569857 }
......@@ -9868,6 +9869,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98689869 if ((try sema.compare(block, src, operand_val, .gte, first_tv.val, operand_ty)) and
98699870 (try sema.compare(block, src, operand_val, .lte, last_tv.val, operand_ty)))
98709871 {
9872 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
98719873 return sema.resolveBlockBody(block, src, &child_block, body, inst, merges);
98729874 }
98739875 }
......@@ -9875,6 +9877,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98759877 extra_index += body_len;
98769878 }
98779879 }
9880 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, special.body, operand);
98789881 return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges);
98799882 }
98809883
......@@ -9885,6 +9888,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98859888 if (special_prong == .none) {
98869889 return sema.fail(block, src, "switch must handle all possibilities", .{});
98879890 }
9891 if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand)) {
9892 return Air.Inst.Ref.unreachable_value;
9893 }
98889894 return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges);
98899895 }
98909896
......@@ -9927,7 +9933,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
99279933 break :blk field_ty.zigTypeTag() != .NoReturn;
99289934 } else true;
99299935
9930 if (analyze_body) {
9936 if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
9937 // nothing to do here
9938 } else if (analyze_body) {
99319939 _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
99329940 error.ComptimeBreak => {
99339941 const zir_datas = sema.code.instructions.items(.data);
......@@ -9995,7 +10003,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
999510003
999610004 const body = sema.code.extra[extra_index..][0..body_len];
999710005 extra_index += body_len;
9998 if (analyze_body) {
10006 if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
10007 // nothing to do here
10008 } else if (analyze_body) {
999910009 _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
1000010010 error.ComptimeBreak => {
1000110011 const zir_datas = sema.code.instructions.items(.data);
......@@ -10085,18 +10095,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
1008510095
1008610096 const body = sema.code.extra[extra_index..][0..body_len];
1008710097 extra_index += body_len;
10088 _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
10089 error.ComptimeBreak => {
10090 const zir_datas = sema.code.instructions.items(.data);
10091 const break_data = zir_datas[sema.comptime_break_inst].@"break";
10092 try sema.addRuntimeBreak(&case_block, .{
10093 .block_inst = break_data.block_inst,
10094 .operand = break_data.operand,
10095 .inst = sema.comptime_break_inst,
10096 });
10097 },
10098 else => |e| return e,
10099 };
10098 if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) {
10099 // nothing to do here
10100 } else {
10101 _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
10102 error.ComptimeBreak => {
10103 const zir_datas = sema.code.instructions.items(.data);
10104 const break_data = zir_datas[sema.comptime_break_inst].@"break";
10105 try sema.addRuntimeBreak(&case_block, .{
10106 .block_inst = break_data.block_inst,
10107 .operand = break_data.operand,
10108 .inst = sema.comptime_break_inst,
10109 });
10110 },
10111 else => |e| return e,
10112 };
10113 }
1010010114
1010110115 try wip_captures.finalize();
1010210116
......@@ -10141,8 +10155,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
1014110155 } else false
1014210156 else
1014310157 true;
10144
10145 if (special.body.len != 0 and analyze_body) {
10158 if (special.body.len != 0 and err_set and
10159 try sema.maybeErrorUnwrap(&case_block, special.body, operand))
10160 {
10161 // nothing to do here
10162 } else if (special.body.len != 0 and analyze_body) {
1014610163 _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) {
1014710164 error.ComptimeBreak => {
1014810165 const zir_datas = sema.code.instructions.items(.data);
......@@ -10400,6 +10417,109 @@ fn validateSwitchNoRange(
1040010417 return sema.failWithOwnedErrorMsg(msg);
1040110418}
1040210419
10420fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !bool {
10421 const this_feature_is_implemented_in_the_backend =
10422 sema.mod.comp.bin_file.options.use_llvm;
10423
10424 if (!this_feature_is_implemented_in_the_backend) return false;
10425
10426 const tags = sema.code.instructions.items(.tag);
10427 for (body) |inst| {
10428 switch (tags[inst]) {
10429 .dbg_block_begin,
10430 .dbg_block_end,
10431 .dbg_stmt,
10432 .@"unreachable",
10433 .str,
10434 .as_node,
10435 .panic,
10436 .field_val,
10437 => {},
10438 else => return false,
10439 }
10440 }
10441
10442 for (body) |inst| {
10443 const air_inst = switch (tags[inst]) {
10444 .dbg_block_begin,
10445 .dbg_block_end,
10446 => continue,
10447 .dbg_stmt => {
10448 try sema.zirDbgStmt(block, inst);
10449 continue;
10450 },
10451 .str => try sema.zirStr(block, inst),
10452 .as_node => try sema.zirAsNode(block, inst),
10453 .field_val => try sema.zirFieldVal(block, inst),
10454 .@"unreachable" => {
10455 const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable";
10456 const src = inst_data.src();
10457
10458 const panic_fn = try sema.getBuiltin(block, src, "panicUnwrapError");
10459 const err_return_trace = try sema.getErrorReturnTrace(block, src);
10460 const args: [2]Air.Inst.Ref = .{ err_return_trace, operand };
10461 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
10462 return true;
10463 },
10464 .panic => {
10465 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
10466 const src = inst_data.src();
10467 const msg_inst = try sema.resolveInst(inst_data.operand);
10468
10469 const panic_fn = try sema.getBuiltin(block, src, "panic");
10470 const err_return_trace = try sema.getErrorReturnTrace(block, src);
10471 const args: [2]Air.Inst.Ref = .{ msg_inst, err_return_trace };
10472 _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
10473 return true;
10474 },
10475 else => unreachable,
10476 };
10477 if (sema.typeOf(air_inst).isNoReturn())
10478 return true;
10479 try sema.inst_map.put(sema.gpa, inst, air_inst);
10480 }
10481 unreachable;
10482}
10483
10484fn maybeErrorUnwrapCondbr(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, cond: Zir.Inst.Ref, cond_src: LazySrcLoc) !void {
10485 const index = Zir.refToIndex(cond) orelse return;
10486 if (sema.code.instructions.items(.tag)[index] != .is_non_err) return;
10487
10488 const err_inst_data = sema.code.instructions.items(.data)[index].un_node;
10489 const err_operand = try sema.resolveInst(err_inst_data.operand);
10490 const operand_ty = sema.typeOf(err_operand);
10491 if (operand_ty.zigTypeTag() == .ErrorSet) {
10492 try sema.maybeErrorUnwrapComptime(block, body, err_operand);
10493 return;
10494 }
10495 if (try sema.resolveDefinedValue(block, cond_src, err_operand)) |val| {
10496 if (val.getError() == null) return;
10497 try sema.maybeErrorUnwrapComptime(block, body, err_operand);
10498 }
10499}
10500
10501fn maybeErrorUnwrapComptime(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !void {
10502 const tags = sema.code.instructions.items(.tag);
10503 const inst = for (body) |inst| {
10504 switch (tags[inst]) {
10505 .dbg_block_begin,
10506 .dbg_block_end,
10507 .dbg_stmt,
10508 => {},
10509 .@"unreachable" => break inst,
10510 else => return,
10511 }
10512 } else return;
10513 const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable";
10514 const src = inst_data.src();
10515
10516 if (try sema.resolveDefinedValue(block, src, operand)) |val| {
10517 if (val.getError()) |name| {
10518 return sema.fail(block, src, "caught unexpected error '{s}'", .{name});
10519 }
10520 }
10521}
10522
1040310523fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
1040410524 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
1040510525 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
......@@ -15152,6 +15272,8 @@ fn zirCondbr(
1515215272
1515315273 if (try sema.resolveDefinedValue(parent_block, cond_src, cond)) |cond_val| {
1515415274 const body = if (cond_val.toBool()) then_body else else_body;
15275
15276 try sema.maybeErrorUnwrapCondbr(parent_block, body, extra.data.condition, cond_src);
1515515277 // We use `analyzeBodyInner` since we want to propagate any possible
1515615278 // `error.ComptimeBreak` to the caller.
1515715279 return sema.analyzeBodyInner(parent_block, body);
......@@ -15182,18 +15304,34 @@ fn zirCondbr(
1518215304 const true_instructions = sub_block.instructions.toOwnedSlice(gpa);
1518315305 defer gpa.free(true_instructions);
1518415306
15185 _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) {
15186 error.ComptimeBreak => {
15187 const zir_datas = sema.code.instructions.items(.data);
15188 const break_data = zir_datas[sema.comptime_break_inst].@"break";
15189 try sema.addRuntimeBreak(&sub_block, .{
15190 .block_inst = break_data.block_inst,
15191 .operand = break_data.operand,
15192 .inst = sema.comptime_break_inst,
15193 });
15194 },
15195 else => |e| return e,
15307 const err_cond = blk: {
15308 const index = Zir.refToIndex(extra.data.condition) orelse break :blk null;
15309 if (sema.code.instructions.items(.tag)[index] != .is_non_err) break :blk null;
15310
15311 const err_inst_data = sema.code.instructions.items(.data)[index].un_node;
15312 const err_operand = try sema.resolveInst(err_inst_data.operand);
15313 const operand_ty = sema.typeOf(err_operand);
15314 assert(operand_ty.zigTypeTag() == .ErrorUnion);
15315 const result_ty = operand_ty.errorUnionSet();
15316 break :blk try sub_block.addTyOp(.unwrap_errunion_err, result_ty, err_operand);
1519615317 };
15318
15319 if (err_cond != null and try sema.maybeErrorUnwrap(&sub_block, else_body, err_cond.?)) {
15320 // nothing to do
15321 } else {
15322 _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) {
15323 error.ComptimeBreak => {
15324 const zir_datas = sema.code.instructions.items(.data);
15325 const break_data = zir_datas[sema.comptime_break_inst].@"break";
15326 try sema.addRuntimeBreak(&sub_block, .{
15327 .block_inst = break_data.block_inst,
15328 .operand = break_data.operand,
15329 .inst = sema.comptime_break_inst,
15330 });
15331 },
15332 else => |e| return e,
15333 };
15334 }
1519715335 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
1519815336 true_instructions.len + sub_block.instructions.items.len);
1519915337 _ = try parent_block.addInst(.{
src/Zir.zig-16
......@@ -629,20 +629,10 @@ pub const Inst = struct {
629629 /// No safety checks.
630630 /// Uses the `un_node` field.
631631 optional_payload_unsafe_ptr,
632 /// E!T => T with safety.
633 /// Given an error union value, returns the payload value, with a safety check
634 /// that the value is not an error. Used for catch, if, and while.
635 /// Uses the `un_node` field.
636 err_union_payload_safe,
637632 /// E!T => T without safety.
638633 /// Given an error union value, returns the payload value. No safety checks.
639634 /// Uses the `un_node` field.
640635 err_union_payload_unsafe,
641 /// *E!T => *T with safety.
642 /// Given a pointer to an error union value, returns a pointer to the payload value,
643 /// with a safety check that the value is not an error. Used for catch, if, and while.
644 /// Uses the `un_node` field.
645 err_union_payload_safe_ptr,
646636 /// *E!T => *T without safety.
647637 /// Given a pointer to a error union value, returns a pointer to the payload value.
648638 /// No safety checks.
......@@ -1120,9 +1110,7 @@ pub const Inst = struct {
11201110 .optional_payload_unsafe,
11211111 .optional_payload_safe_ptr,
11221112 .optional_payload_unsafe_ptr,
1123 .err_union_payload_safe,
11241113 .err_union_payload_unsafe,
1125 .err_union_payload_safe_ptr,
11261114 .err_union_payload_unsafe_ptr,
11271115 .err_union_code,
11281116 .err_union_code_ptr,
......@@ -1421,9 +1409,7 @@ pub const Inst = struct {
14211409 .optional_payload_unsafe,
14221410 .optional_payload_safe_ptr,
14231411 .optional_payload_unsafe_ptr,
1424 .err_union_payload_safe,
14251412 .err_union_payload_unsafe,
1426 .err_union_payload_safe_ptr,
14271413 .err_union_payload_unsafe_ptr,
14281414 .err_union_code,
14291415 .err_union_code_ptr,
......@@ -1692,9 +1678,7 @@ pub const Inst = struct {
16921678 .optional_payload_unsafe = .un_node,
16931679 .optional_payload_safe_ptr = .un_node,
16941680 .optional_payload_unsafe_ptr = .un_node,
1695 .err_union_payload_safe = .un_node,
16961681 .err_union_payload_unsafe = .un_node,
1697 .err_union_payload_safe_ptr = .un_node,
16981682 .err_union_payload_unsafe_ptr = .un_node,
16991683 .err_union_code = .un_node,
17001684 .err_union_code_ptr = .un_node,
src/print_zir.zig-2
......@@ -170,9 +170,7 @@ const Writer = struct {
170170 .optional_payload_unsafe,
171171 .optional_payload_safe_ptr,
172172 .optional_payload_unsafe_ptr,
173 .err_union_payload_safe,
174173 .err_union_payload_unsafe,
175 .err_union_payload_safe_ptr,
176174 .err_union_payload_unsafe_ptr,
177175 .err_union_code,
178176 .err_union_code_ptr,
test/cases/safety/unwrap error switch.zig created+21
......@@ -0,0 +1,21 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "attempt to unwrap error: Whatever")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10pub fn main() !void {
11 bar() catch |err| switch (err) {
12 error.Whatever => unreachable,
13 };
14 return error.TestFailed;
15}
16fn bar() !void {
17 return error.Whatever;
18}
19// run
20// backend=llvm
21// target=native