authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-11-29 14:53:54+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-11-29 21:44:08+02:00
log17ff002bc0ac55850e647fc3a70a43d1d874f6ab
tree700cbc62d0ec95cc2b660cb8d33ab0970e39c9f8
parent6337c04244d9c27cc6535340347d4c127f4742eb

Sema: improve safety panic for access of inactive union field


4 files changed, 45 insertions(+), 83 deletions(-)

doc/langref.html.in+1-1
...@@ -3803,7 +3803,7 @@ test "switch on non-exhaustive enum" {...@@ -3803,7 +3803,7 @@ test "switch on non-exhaustive enum" {
3803 {#link|Accessing the non-active field|Wrong Union Field Access#} is3803 {#link|Accessing the non-active field|Wrong Union Field Access#} is
3804 safety-checked {#link|Undefined Behavior#}:3804 safety-checked {#link|Undefined Behavior#}:
3805 </p>3805 </p>
3806 {#code_begin|test_err|inactive union field#}3806 {#code_begin|test_err|access of union field 'float' while field 'int' is active#}
3807const Payload = union {3807const Payload = union {
3808 int: i64,3808 int: i64,
3809 float: f64,3809 float: f64,
lib/std/builtin.zig+5
...@@ -868,6 +868,11 @@ pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn {...@@ -868,6 +868,11 @@ pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn {
868 std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end });868 std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end });
869}869}
870870
871pub fn panicInactiveUnionField(active: anytype, wanted: @TypeOf(active)) noreturn {
872 @setCold(true);
873 std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ @tagName(wanted), @tagName(active) });
874}
875
871pub const panic_messages = struct {876pub const panic_messages = struct {
872 pub const unreach = "reached unreachable code";877 pub const unreach = "reached unreachable code";
873 pub const unwrap_null = "attempt to use null value";878 pub const unwrap_null = "attempt to use null value";
src/Sema.zig+38-81
...@@ -22120,7 +22120,6 @@ pub const PanicId = enum {...@@ -22120,7 +22120,6 @@ pub const PanicId = enum {
22120 shr_overflow,22120 shr_overflow,
22121 divide_by_zero,22121 divide_by_zero,
22122 exact_division_remainder,22122 exact_division_remainder,
22123 /// TODO make this call `std.builtin.panicInactiveUnionField`.
22124 inactive_union_field,22123 inactive_union_field,
22125 integer_part_out_of_bounds,22124 integer_part_out_of_bounds,
22126 corrupt_switch,22125 corrupt_switch,
...@@ -22296,90 +22295,40 @@ fn panicUnwrapError(...@@ -22296,90 +22295,40 @@ fn panicUnwrapError(
22296fn panicIndexOutOfBounds(22295fn panicIndexOutOfBounds(
22297 sema: *Sema,22296 sema: *Sema,
22298 parent_block: *Block,22297 parent_block: *Block,
22299 src: LazySrcLoc,
22300 index: Air.Inst.Ref,22298 index: Air.Inst.Ref,
22301 len: Air.Inst.Ref,22299 len: Air.Inst.Ref,
22302 cmp_op: Air.Inst.Tag,22300 cmp_op: Air.Inst.Tag,
22303) !void {22301) !void {
22304 assert(!parent_block.is_comptime);22302 assert(!parent_block.is_comptime);
22305 const ok = try parent_block.addBinOp(cmp_op, index, len);22303 const ok = try parent_block.addBinOp(cmp_op, index, len);
22306 const gpa = sema.gpa;22304 try sema.safetyPanicFormatted(parent_block, ok, "panicOutOfBounds", &.{ index, len });
22307
22308 var fail_block: Block = .{
22309 .parent = parent_block,
22310 .sema = sema,
22311 .src_decl = parent_block.src_decl,
22312 .namespace = parent_block.namespace,
22313 .wip_capture_scope = parent_block.wip_capture_scope,
22314 .instructions = .{},
22315 .inlining = parent_block.inlining,
22316 .is_comptime = false,
22317 };
22318
22319 defer fail_block.instructions.deinit(gpa);
22320
22321 {
22322 const this_feature_is_implemented_in_the_backend =
22323 sema.mod.comp.bin_file.options.use_llvm;
22324
22325 if (!this_feature_is_implemented_in_the_backend) {
22326 // TODO implement this feature in all the backends and then delete this branch
22327 _ = try fail_block.addNoOp(.breakpoint);
22328 _ = try fail_block.addNoOp(.unreach);
22329 } else {
22330 const panic_fn = try sema.getBuiltin("panicOutOfBounds");
22331 const args: [2]Air.Inst.Ref = .{ index, len };
22332 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
22333 }
22334 }
22335 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
22336}22305}
2233722306
22338fn panicStartLargerThanEnd(22307fn panicStartLargerThanEnd(
22339 sema: *Sema,22308 sema: *Sema,
22340 parent_block: *Block,22309 parent_block: *Block,
22341 src: LazySrcLoc,
22342 start: Air.Inst.Ref,22310 start: Air.Inst.Ref,
22343 end: Air.Inst.Ref,22311 end: Air.Inst.Ref,
22344) !void {22312) !void {
22345 assert(!parent_block.is_comptime);22313 assert(!parent_block.is_comptime);
22346 const ok = try parent_block.addBinOp(.cmp_lte, start, end);22314 const ok = try parent_block.addBinOp(.cmp_lte, start, end);
22347 const gpa = sema.gpa;22315 try sema.safetyPanicFormatted(parent_block, ok, "panicStartGreaterThanEnd", &.{ start, end });
2234822316}
22349 var fail_block: Block = .{
22350 .parent = parent_block,
22351 .sema = sema,
22352 .src_decl = parent_block.src_decl,
22353 .namespace = parent_block.namespace,
22354 .wip_capture_scope = parent_block.wip_capture_scope,
22355 .instructions = .{},
22356 .inlining = parent_block.inlining,
22357 .is_comptime = false,
22358 };
22359
22360 defer fail_block.instructions.deinit(gpa);
22361
22362 {
22363 const this_feature_is_implemented_in_the_backend =
22364 sema.mod.comp.bin_file.options.use_llvm;
2236522317
22366 if (!this_feature_is_implemented_in_the_backend) {22318fn panicInactiveUnionField(
22367 // TODO implement this feature in all the backends and then delete this branch22319 sema: *Sema,
22368 _ = try fail_block.addNoOp(.breakpoint);22320 parent_block: *Block,
22369 _ = try fail_block.addNoOp(.unreach);22321 active_tag: Air.Inst.Ref,
22370 } else {22322 wanted_tag: Air.Inst.Ref,
22371 const panic_fn = try sema.getBuiltin("panicStartGreaterThanEnd");22323) !void {
22372 const args: [2]Air.Inst.Ref = .{ start, end };22324 assert(!parent_block.is_comptime);
22373 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);22325 const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
22374 }22326 try sema.safetyPanicFormatted(parent_block, ok, "panicInactiveUnionField", &.{ active_tag, wanted_tag });
22375 }
22376 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
22377}22327}
2237822328
22379fn panicSentinelMismatch(22329fn panicSentinelMismatch(
22380 sema: *Sema,22330 sema: *Sema,
22381 parent_block: *Block,22331 parent_block: *Block,
22382 src: LazySrcLoc,
22383 maybe_sentinel: ?Value,22332 maybe_sentinel: ?Value,
22384 sentinel_ty: Type,22333 sentinel_ty: Type,
22385 ptr: Air.Inst.Ref,22334 ptr: Air.Inst.Ref,
...@@ -22413,9 +22362,20 @@ fn panicSentinelMismatch(...@@ -22413,9 +22362,20 @@ fn panicSentinelMismatch(
22413 else {22362 else {
22414 const panic_fn = try sema.getBuiltin("checkNonScalarSentinel");22363 const panic_fn = try sema.getBuiltin("checkNonScalarSentinel");
22415 const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };22364 const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };
22416 _ = try sema.analyzeCall(parent_block, panic_fn, src, src, .auto, false, &args, null);22365 _ = try sema.analyzeCall(parent_block, panic_fn, sema.src, sema.src, .auto, false, &args, null);
22417 return;22366 return;
22418 };22367 };
22368
22369 try sema.safetyPanicFormatted(parent_block, ok, "panicSentinelMismatch", &.{ expected_sentinel, actual_sentinel });
22370}
22371
22372fn safetyPanicFormatted(
22373 sema: *Sema,
22374 parent_block: *Block,
22375 ok: Air.Inst.Ref,
22376 func: []const u8,
22377 args: []const Air.Inst.Ref,
22378) CompileError!void {
22419 const gpa = sema.gpa;22379 const gpa = sema.gpa;
2242022380
22421 var fail_block: Block = .{22381 var fail_block: Block = .{
...@@ -22440,9 +22400,8 @@ fn panicSentinelMismatch(...@@ -22440,9 +22400,8 @@ fn panicSentinelMismatch(
22440 _ = try fail_block.addNoOp(.breakpoint);22400 _ = try fail_block.addNoOp(.breakpoint);
22441 _ = try fail_block.addNoOp(.unreach);22401 _ = try fail_block.addNoOp(.unreach);
22442 } else {22402 } else {
22443 const panic_fn = try sema.getBuiltin("panicSentinelMismatch");22403 const panic_fn = try sema.getBuiltin(func);
22444 const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };22404 _ = try sema.analyzeCall(&fail_block, panic_fn, sema.src, sema.src, .auto, false, args, null);
22445 _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
22446 }22405 }
22447 }22406 }
22448 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);22407 try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
...@@ -23465,8 +23424,7 @@ fn unionFieldPtr(...@@ -23465,8 +23424,7 @@ fn unionFieldPtr(
23465 // TODO would it be better if get_union_tag supported pointers to unions?23424 // TODO would it be better if get_union_tag supported pointers to unions?
23466 const union_val = try block.addTyOp(.load, union_ty, union_ptr);23425 const union_val = try block.addTyOp(.load, union_ty, union_ptr);
23467 const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_val);23426 const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_val);
23468 const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);23427 try sema.panicInactiveUnionField(block, active_tag, wanted_tag);
23469 try sema.addSafetyCheck(block, ok, .inactive_union_field);
23470 }23428 }
23471 if (field.ty.zigTypeTag() == .NoReturn) {23429 if (field.ty.zigTypeTag() == .NoReturn) {
23472 _ = try block.addNoOp(.unreach);23430 _ = try block.addNoOp(.unreach);
...@@ -23537,8 +23495,7 @@ fn unionFieldVal(...@@ -23537,8 +23495,7 @@ fn unionFieldVal(
23537 const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index);23495 const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index);
23538 const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val);23496 const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val);
23539 const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval);23497 const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval);
23540 const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);23498 try sema.panicInactiveUnionField(block, active_tag, wanted_tag);
23541 try sema.addSafetyCheck(block, ok, .inactive_union_field);
23542 }23499 }
23543 if (field.ty.zigTypeTag() == .NoReturn) {23500 if (field.ty.zigTypeTag() == .NoReturn) {
23544 _ = try block.addNoOp(.unreach);23501 _ = try block.addNoOp(.unreach);
...@@ -23849,7 +23806,7 @@ fn elemValArray(...@@ -23849,7 +23806,7 @@ fn elemValArray(
23849 if (maybe_index_val == null) {23806 if (maybe_index_val == null) {
23850 const len_inst = try sema.addIntUnsigned(Type.usize, array_len);23807 const len_inst = try sema.addIntUnsigned(Type.usize, array_len);
23851 const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt;23808 const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt;
23852 try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);23809 try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
23853 }23810 }
23854 }23811 }
23855 return block.addBinOp(.array_elem_val, array, elem_index);23812 return block.addBinOp(.array_elem_val, array, elem_index);
...@@ -23910,7 +23867,7 @@ fn elemPtrArray(...@@ -23910,7 +23867,7 @@ fn elemPtrArray(
23910 if (block.wantSafety() and offset == null) {23867 if (block.wantSafety() and offset == null) {
23911 const len_inst = try sema.addIntUnsigned(Type.usize, array_len);23868 const len_inst = try sema.addIntUnsigned(Type.usize, array_len);
23912 const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt;23869 const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt;
23913 try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);23870 try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
23914 }23871 }
2391523872
23916 return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty);23873 return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty);
...@@ -23966,7 +23923,7 @@ fn elemValSlice(...@@ -23966,7 +23923,7 @@ fn elemValSlice(
23966 else23923 else
23967 try block.addTyOp(.slice_len, Type.usize, slice);23924 try block.addTyOp(.slice_len, Type.usize, slice);
23968 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;23925 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
23969 try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);23926 try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
23970 }23927 }
23971 try sema.queueFullTypeResolution(sema.typeOf(slice));23928 try sema.queueFullTypeResolution(sema.typeOf(slice));
23972 return block.addBinOp(.slice_elem_val, slice, elem_index);23929 return block.addBinOp(.slice_elem_val, slice, elem_index);
...@@ -24025,7 +23982,7 @@ fn elemPtrSlice(...@@ -24025,7 +23982,7 @@ fn elemPtrSlice(
24025 break :len try block.addTyOp(.slice_len, Type.usize, slice);23982 break :len try block.addTyOp(.slice_len, Type.usize, slice);
24026 };23983 };
24027 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;23984 const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
24028 try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);23985 try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
24029 }23986 }
24030 return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);23987 return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);
24031}23988}
...@@ -28072,7 +28029,7 @@ fn analyzeSlice(...@@ -28072,7 +28029,7 @@ fn analyzeSlice(
2807228029
28073 if (block.wantSafety() and !block.is_comptime) {28030 if (block.wantSafety() and !block.is_comptime) {
28074 // requirement: start <= end28031 // requirement: start <= end
28075 try sema.panicStartLargerThanEnd(block, src, start, end);28032 try sema.panicStartLargerThanEnd(block, start, end);
28076 }28033 }
28077 const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src, false);28034 const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src, false);
28078 const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);28035 const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);
...@@ -28116,11 +28073,11 @@ fn analyzeSlice(...@@ -28116,11 +28073,11 @@ fn analyzeSlice(
28116 else28073 else
28117 end;28074 end;
2811828075
28119 try sema.panicIndexOutOfBounds(block, src, actual_end, actual_len, .cmp_lte);28076 try sema.panicIndexOutOfBounds(block, actual_end, actual_len, .cmp_lte);
28120 }28077 }
2812128078
28122 // requirement: result[new_len] == slice_sentinel28079 // requirement: result[new_len] == slice_sentinel
28123 try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);28080 try sema.panicSentinelMismatch(block, slice_sentinel, elem_ty, result, new_len);
28124 }28081 }
28125 return result;28082 return result;
28126 };28083 };
...@@ -28184,11 +28141,11 @@ fn analyzeSlice(...@@ -28184,11 +28141,11 @@ fn analyzeSlice(
28184 try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)28141 try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
28185 else28142 else
28186 end;28143 end;
28187 try sema.panicIndexOutOfBounds(block, src, actual_end, len_inst, .cmp_lte);28144 try sema.panicIndexOutOfBounds(block, actual_end, len_inst, .cmp_lte);
28188 }28145 }
2818928146
28190 // requirement: start <= end28147 // requirement: start <= end
28191 try sema.panicIndexOutOfBounds(block, src, start, end, .cmp_lte);28148 try sema.panicIndexOutOfBounds(block, start, end, .cmp_lte);
28192 }28149 }
28193 const result = try block.addInst(.{28150 const result = try block.addInst(.{
28194 .tag = .slice,28151 .tag = .slice,
...@@ -28202,7 +28159,7 @@ fn analyzeSlice(...@@ -28202,7 +28159,7 @@ fn analyzeSlice(
28202 });28159 });
28203 if (block.wantSafety()) {28160 if (block.wantSafety()) {
28204 // requirement: result[new_len] == slice_sentinel28161 // requirement: result[new_len] == slice_sentinel
28205 try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);28162 try sema.panicSentinelMismatch(block, slice_sentinel, elem_ty, result, new_len);
28206 }28163 }
28207 return result;28164 return result;
28208}28165}
test/cases/safety/bad union field access.zig +1-1
...@@ -2,7 +2,7 @@ const std = @import("std");...@@ -2,7 +2,7 @@ const std = @import("std");
22
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
4 _ = stack_trace;4 _ = stack_trace;
5 if (std.mem.eql(u8, message, "access of inactive union field")) {5 if (std.mem.eql(u8, message, "access of union field 'float' while field 'int' is active")) {
6 std.process.exit(0);6 std.process.exit(0);
7 }7 }
8 std.process.exit(1);8 std.process.exit(1);