authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-06 14:59:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-08-06 14:59:10-07:00
logb3d463c9e6ab41d55849e6cfbbc83d6a1f171ef9
tree7d7076d0933c119ee2a0d58c868d8b06bd400089
parent3e2defd36c0bf90a0604a7618f57beaa4077139c
parent75275a1514b6954bed09c4c14a325e883a129c7b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #12337 from Vexu/stage2-safety

Stage2: implement remaining runtime safety checks

33 files changed, 378 insertions(+), 102 deletions(-)

src/Air.zig+5
......@@ -660,6 +660,10 @@ pub const Inst = struct {
660660 /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`.
661661 atomic_rmw,
662662
663 /// Returns true if enum tag value has a name.
664 /// Uses the `un_op` field.
665 is_named_enum_value,
666
663667 /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive.
664668 /// Result type is always `[:0]const u8`.
665669 /// Uses the `un_op` field.
......@@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
10571061 .is_non_err,
10581062 .is_err_ptr,
10591063 .is_non_err_ptr,
1064 .is_named_enum_value,
10601065 => return Type.bool,
10611066
10621067 .const_ty => return Type.type,
src/Liveness.zig+2
......@@ -291,6 +291,7 @@ pub fn categorizeOperand(
291291 .is_non_err_ptr,
292292 .ptrtoint,
293293 .bool_to_int,
294 .is_named_enum_value,
294295 .tag_name,
295296 .error_name,
296297 .sqrt,
......@@ -858,6 +859,7 @@ fn analyzeInst(
858859 .bool_to_int,
859860 .ret,
860861 .ret_load,
862 .is_named_enum_value,
861863 .tag_name,
862864 .error_name,
863865 .sqrt,
src/Sema.zig+159-53
......@@ -1578,8 +1578,7 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
15781578
15791579 // st.index = 0;
15801580 const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true);
1581 const zero = try sema.addConstant(Type.usize, Value.zero);
1582 try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store);
1581 try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, .zero_usize, src, .store);
15831582
15841583 // @errorReturnTrace() = &st;
15851584 _ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr);
......@@ -6949,8 +6948,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
69496948 }
69506949
69516950 try sema.requireRuntimeBlock(block, src, operand_src);
6952 // TODO insert safety check to make sure the value matches an enum value
6953 return block.addTyOp(.intcast, dest_ty, operand);
6951 const result = try block.addTyOp(.intcast, dest_ty, operand);
6952 if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum() and sema.mod.comp.bin_file.options.use_llvm) {
6953 const ok = try block.addUnOp(.is_named_enum_value, result);
6954 try sema.addSafetyCheck(block, ok, .invalid_enum_value);
6955 }
6956 return result;
69546957}
69556958
69566959/// Pointer in, pointer out.
......@@ -9707,7 +9710,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
97079710 }
97089711
97099712 var final_else_body: []const Air.Inst.Index = &.{};
9710 if (special.body.len != 0 or !is_first) {
9713 if (special.body.len != 0 or !is_first or case_block.wantSafety()) {
97119714 var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope);
97129715 defer wip_captures.deinit();
97139716
......@@ -9730,9 +9733,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
97309733 } else {
97319734 // We still need a terminator in this block, but we have proven
97329735 // that it is unreachable.
9733 // TODO this should be a special safety panic other than unreachable, something
9734 // like "panic: switch operand had corrupt value not allowed by the type"
9735 try case_block.addUnreachable(src, true);
9736 if (case_block.wantSafety()) {
9737 _ = try sema.safetyPanic(&case_block, src, .corrupt_switch);
9738 } else {
9739 _ = try case_block.addNoOp(.unreach);
9740 }
97369741 }
97379742
97389743 try wip_captures.finalize();
......@@ -10241,34 +10246,57 @@ fn zirShl(
1024110246 } else rhs;
1024210247
1024310248 try sema.requireRuntimeBlock(block, src, runtime_src);
10244 if (block.wantSafety() and air_tag == .shl_exact) {
10245 const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty);
10246 const op_ov = try block.addInst(.{
10247 .tag = .shl_with_overflow,
10248 .data = .{ .ty_pl = .{
10249 .ty = try sema.addType(op_ov_tuple_ty),
10250 .payload = try sema.addExtra(Air.Bin{
10251 .lhs = lhs,
10252 .rhs = rhs,
10253 }),
10254 } },
10255 });
10256 const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty);
10257 const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector)
10258 try block.addInst(.{
10259 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
10260 .data = .{ .reduce = .{
10261 .operand = ov_bit,
10262 .operation = .Or,
10249 if (block.wantSafety()) {
10250 const bit_count = scalar_ty.intInfo(target).bits;
10251 if (!std.math.isPowerOfTwo(bit_count)) {
10252 const bit_count_val = try Value.Tag.int_u64.create(sema.arena, bit_count);
10253
10254 const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
10255 const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val));
10256 const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty));
10257 break :ok try block.addInst(.{
10258 .tag = .reduce,
10259 .data = .{ .reduce = .{
10260 .operand = lt,
10261 .operation = .And,
10262 } },
10263 });
10264 } else ok: {
10265 const bit_count_inst = try sema.addConstant(rhs_ty, bit_count_val);
10266 break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst);
10267 };
10268 try sema.addSafetyCheck(block, ok, .shift_rhs_too_big);
10269 }
10270
10271 if (air_tag == .shl_exact) {
10272 const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty);
10273 const op_ov = try block.addInst(.{
10274 .tag = .shl_with_overflow,
10275 .data = .{ .ty_pl = .{
10276 .ty = try sema.addType(op_ov_tuple_ty),
10277 .payload = try sema.addExtra(Air.Bin{
10278 .lhs = lhs,
10279 .rhs = rhs,
10280 }),
1026310281 } },
10264 })
10265 else
10266 ov_bit;
10267 const zero_ov = try sema.addConstant(Type.@"u1", Value.zero);
10268 const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov);
10282 });
10283 const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty);
10284 const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector)
10285 try block.addInst(.{
10286 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
10287 .data = .{ .reduce = .{
10288 .operand = ov_bit,
10289 .operation = .Or,
10290 } },
10291 })
10292 else
10293 ov_bit;
10294 const zero_ov = try sema.addConstant(Type.@"u1", Value.zero);
10295 const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov);
1026910296
10270 try sema.addSafetyCheck(block, no_ov, .shl_overflow);
10271 return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty);
10297 try sema.addSafetyCheck(block, no_ov, .shl_overflow);
10298 return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty);
10299 }
1027210300 }
1027310301 return block.addBinOp(air_tag, lhs, new_rhs);
1027410302}
......@@ -10347,20 +10375,43 @@ fn zirShr(
1034710375
1034810376 try sema.requireRuntimeBlock(block, src, runtime_src);
1034910377 const result = try block.addBinOp(air_tag, lhs, rhs);
10350 if (block.wantSafety() and air_tag == .shr_exact) {
10351 const back = try block.addBinOp(.shl, result, rhs);
10352
10353 const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
10354 const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty));
10355 break :ok try block.addInst(.{
10356 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
10357 .data = .{ .reduce = .{
10358 .operand = eql,
10359 .operation = .And,
10360 } },
10361 });
10362 } else try block.addBinOp(.cmp_eq, lhs, back);
10363 try sema.addSafetyCheck(block, ok, .shr_overflow);
10378 if (block.wantSafety()) {
10379 const bit_count = scalar_ty.intInfo(target).bits;
10380 if (!std.math.isPowerOfTwo(bit_count)) {
10381 const bit_count_val = try Value.Tag.int_u64.create(sema.arena, bit_count);
10382
10383 const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
10384 const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val));
10385 const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty));
10386 break :ok try block.addInst(.{
10387 .tag = .reduce,
10388 .data = .{ .reduce = .{
10389 .operand = lt,
10390 .operation = .And,
10391 } },
10392 });
10393 } else ok: {
10394 const bit_count_inst = try sema.addConstant(rhs_ty, bit_count_val);
10395 break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst);
10396 };
10397 try sema.addSafetyCheck(block, ok, .shift_rhs_too_big);
10398 }
10399
10400 if (air_tag == .shr_exact) {
10401 const back = try block.addBinOp(.shl, result, rhs);
10402
10403 const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
10404 const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty));
10405 break :ok try block.addInst(.{
10406 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
10407 .data = .{ .reduce = .{
10408 .operand = eql,
10409 .operation = .And,
10410 } },
10411 });
10412 } else try block.addBinOp(.cmp_eq, lhs, back);
10413 try sema.addSafetyCheck(block, ok, .shr_overflow);
10414 }
1036410415 }
1036510416 return result;
1036610417}
......@@ -15961,6 +16012,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1596116012 const field_name = enum_ty.enumFieldName(field_index);
1596216013 return sema.addStrLit(block, field_name);
1596316014 }
16015 try sema.requireRuntimeBlock(block, src, operand_src);
16016 if (block.wantSafety() and sema.mod.comp.bin_file.options.use_llvm) {
16017 const ok = try block.addUnOp(.is_named_enum_value, casted_operand);
16018 try sema.addSafetyCheck(block, ok, .invalid_enum_value);
16019 }
1596416020 // In case the value is runtime-known, we have an AIR instruction for this instead
1596516021 // of trying to lower it in Sema because an optimization pass may result in the operand
1596616022 // being comptime-known, which would let us elide the `tag_name` AIR instruction.
......@@ -16942,7 +16998,7 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
1694216998 }
1694316999
1694417000 try sema.requireRuntimeBlock(block, src, operand_src);
16945 if (block.wantSafety()) {
17001 if (block.wantSafety() and try sema.typeHasRuntimeBits(block, sema.src, type_res.elemType2())) {
1694617002 if (!type_res.isAllowzeroPtr()) {
1694717003 const is_non_zero = try block.addBinOp(.cmp_neq, operand_coerced, .zero_usize);
1694817004 try sema.addSafetyCheck(block, is_non_zero, .cast_to_null);
......@@ -17234,7 +17290,9 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1723417290 }
1723517291
1723617292 try sema.requireRuntimeBlock(block, inst_data.src(), ptr_src);
17237 if (block.wantSafety() and dest_align > 1) {
17293 if (block.wantSafety() and dest_align > 1 and
17294 try sema.typeHasRuntimeBits(block, sema.src, dest_ty.elemType2()))
17295 {
1723817296 const val_payload = try sema.arena.create(Value.Payload.U64);
1723917297 val_payload.* = .{
1724017298 .base = .{ .tag = .int_u64 },
......@@ -17253,7 +17311,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1725317311 const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
1725417312 const ok = if (ptr_ty.isSlice()) ok: {
1725517313 const len = try sema.analyzeSliceLen(block, ptr_src, ptr);
17256 const len_zero = try block.addBinOp(.cmp_eq, len, try sema.addConstant(Type.usize, Value.zero));
17314 const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize);
1725717315 break :ok try block.addBinOp(.bit_or, len_zero, is_aligned);
1725817316 } else is_aligned;
1725917317 try sema.addSafetyCheck(block, ok, .incorrect_alignment);
......@@ -20114,6 +20172,9 @@ pub const PanicId = enum {
2011420172 /// TODO make this call `std.builtin.panicInactiveUnionField`.
2011520173 inactive_union_field,
2011620174 integer_part_out_of_bounds,
20175 corrupt_switch,
20176 shift_rhs_too_big,
20177 invalid_enum_value,
2011720178};
2011820179
2011920180fn addSafetyCheck(
......@@ -20408,6 +20469,9 @@ fn safetyPanic(
2040820469 .exact_division_remainder => "exact division produced remainder",
2040920470 .inactive_union_field => "access of inactive union field",
2041020471 .integer_part_out_of_bounds => "integer part of floating point value out of bounds",
20472 .corrupt_switch => "switch on corrupt value",
20473 .shift_rhs_too_big => "shift amount is greater than the type size",
20474 .invalid_enum_value => "invalid enum value",
2041120475 };
2041220476
2041320477 const msg_inst = msg_inst: {
......@@ -22096,7 +22160,6 @@ fn coerceExtra(
2209622160 .ok => {},
2209722161 else => break :src_c_ptr,
2209822162 }
22099 // TODO add safety check for null pointer
2210022163 return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
2210122164 }
2210222165
......@@ -24569,6 +24632,24 @@ fn coerceCompatiblePtrs(
2456924632 return sema.addConstant(dest_ty, val);
2457024633 }
2457124634 try sema.requireRuntimeBlock(block, inst_src, null);
24635 const inst_ty = sema.typeOf(inst);
24636 const inst_allows_zero = (inst_ty.zigTypeTag() == .Pointer and inst_ty.ptrAllowsZero()) or true;
24637 if (block.wantSafety() and inst_allows_zero and !dest_ty.ptrAllowsZero() and
24638 try sema.typeHasRuntimeBits(block, sema.src, dest_ty.elemType2()))
24639 {
24640 const actual_ptr = if (inst_ty.isSlice())
24641 try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty)
24642 else
24643 inst;
24644 const ptr_int = try block.addUnOp(.ptrtoint, actual_ptr);
24645 const is_non_zero = try block.addBinOp(.cmp_neq, ptr_int, .zero_usize);
24646 const ok = if (inst_ty.isSlice()) ok: {
24647 const len = try sema.analyzeSliceLen(block, inst_src, inst);
24648 const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize);
24649 break :ok try block.addBinOp(.bit_or, len_zero, is_non_zero);
24650 } else is_non_zero;
24651 try sema.addSafetyCheck(block, ok, .cast_to_null);
24652 }
2457224653 return sema.bitCast(block, dest_ty, inst, inst_src);
2457324654}
2457424655
......@@ -25708,6 +25789,27 @@ fn analyzeSlice(
2570825789 const new_ptr_val = opt_new_ptr_val orelse {
2570925790 const result = try block.addBitCast(return_ty, new_ptr);
2571025791 if (block.wantSafety()) {
25792 // requirement: slicing C ptr is non-null
25793 if (ptr_ptr_child_ty.isCPtr()) {
25794 const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true);
25795 try sema.addSafetyCheck(block, is_non_null, .unwrap_null);
25796 }
25797
25798 if (slice_ty.isSlice()) {
25799 const slice_len_inst = try block.addTyOp(.slice_len, Type.usize, ptr_or_slice);
25800 const actual_len = if (slice_ty.sentinel() == null)
25801 slice_len_inst
25802 else
25803 try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src);
25804
25805 const actual_end = if (slice_sentinel != null)
25806 try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src)
25807 else
25808 end;
25809
25810 try sema.panicIndexOutOfBounds(block, src, actual_end, actual_len, .cmp_lte);
25811 }
25812
2571125813 // requirement: result[new_len] == slice_sentinel
2571225814 try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
2571325815 }
......@@ -25769,7 +25871,11 @@ fn analyzeSlice(
2576925871 break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src);
2577025872 } else null;
2577125873 if (opt_len_inst) |len_inst| {
25772 try sema.panicIndexOutOfBounds(block, src, end, len_inst, .cmp_lte);
25874 const actual_end = if (slice_sentinel != null)
25875 try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src)
25876 else
25877 end;
25878 try sema.panicIndexOutOfBounds(block, src, actual_end, len_inst, .cmp_lte);
2577325879 }
2577425880
2577525881 // requirement: start <= end
src/arch/aarch64/CodeGen.zig+2
......@@ -777,6 +777,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
777777 .float_to_int_optimized,
778778 => return self.fail("TODO implement optimized float mode", .{}),
779779
780 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
781
780782 .wasm_memory_size => unreachable,
781783 .wasm_memory_grow => unreachable,
782784 // zig fmt: on
src/arch/arm/CodeGen.zig+2
......@@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
768768 .float_to_int_optimized,
769769 => return self.fail("TODO implement optimized float mode", .{}),
770770
771 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
772
771773 .wasm_memory_size => unreachable,
772774 .wasm_memory_grow => unreachable,
773775 // zig fmt: on
src/arch/riscv64/CodeGen.zig+2
......@@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
693693 .float_to_int_optimized,
694694 => return self.fail("TODO implement optimized float mode", .{}),
695695
696 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
697
696698 .wasm_memory_size => unreachable,
697699 .wasm_memory_grow => unreachable,
698700 // zig fmt: on
src/arch/sparc64/CodeGen.zig+2
......@@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
705705 .float_to_int_optimized,
706706 => @panic("TODO implement optimized float mode"),
707707
708 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
709
708710 .wasm_memory_size => unreachable,
709711 .wasm_memory_grow => unreachable,
710712 // zig fmt: on
src/arch/wasm/CodeGen.zig+1
......@@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
16211621 .tag_name,
16221622 .err_return_trace,
16231623 .set_err_return_trace,
1624 .is_named_enum_value,
16241625 => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
16251626
16261627 .add_optimized,
src/arch/x86_64/CodeGen.zig+2
......@@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
775775 .float_to_int_optimized,
776776 => return self.fail("TODO implement optimized float mode", .{}),
777777
778 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
779
778780 .wasm_memory_size => unreachable,
779781 .wasm_memory_grow => unreachable,
780782 // zig fmt: on
src/codegen/c.zig+3-1
......@@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
19521952 .reduce_optimized,
19531953 .float_to_int_optimized,
19541954 => return f.fail("TODO implement optimized float mode", .{}),
1955
1956 .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
19551957 // zig fmt: on
19561958 };
19571959 switch (result_value) {
......@@ -3250,7 +3252,7 @@ fn airIsNull(
32503252
32513253 const ty = f.air.typeOf(un_op);
32523254 var opt_buf: Type.Payload.ElemType = undefined;
3253 const payload_ty = if (ty.zigTypeTag() == .Pointer)
3255 const payload_ty = if (deref_suffix[0] != 0)
32543256 ty.childType().optionalChild(&opt_buf)
32553257 else
32563258 ty.optionalChild(&opt_buf);
src/codegen/llvm.zig+87
......@@ -201,6 +201,8 @@ pub const Object = struct {
201201 /// * it works for functions not all globals.
202202 /// Therefore, this table keeps track of the mapping.
203203 decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value),
204 /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction.
205 named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value),
204206 /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
205207 /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
206208 /// TODO we need to remove entries from this map in response to incremental compilation
......@@ -377,6 +379,7 @@ pub const Object = struct {
377379 .target_data = target_data,
378380 .target = options.target,
379381 .decl_map = .{},
382 .named_enum_map = .{},
380383 .type_map = .{},
381384 .type_map_arena = std.heap.ArenaAllocator.init(gpa),
382385 .di_type_map = .{},
......@@ -396,6 +399,7 @@ pub const Object = struct {
396399 self.llvm_module.dispose();
397400 self.context.dispose();
398401 self.decl_map.deinit(gpa);
402 self.named_enum_map.deinit(gpa);
399403 self.type_map.deinit(gpa);
400404 self.type_map_arena.deinit();
401405 self.extern_collisions.deinit(gpa);
......@@ -4180,6 +4184,8 @@ pub const FuncGen = struct {
41804184 .union_init => try self.airUnionInit(inst),
41814185 .prefetch => try self.airPrefetch(inst),
41824186
4187 .is_named_enum_value => try self.airIsNamedEnumValue(inst),
4188
41834189 .reduce => try self.airReduce(inst, false),
41844190 .reduce_optimized => try self.airReduce(inst, true),
41854191
......@@ -7882,6 +7888,87 @@ pub const FuncGen = struct {
78827888 }
78837889 }
78847890
7891 fn airIsNamedEnumValue(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
7892 if (self.liveness.isUnused(inst)) return null;
7893
7894 const un_op = self.air.instructions.items(.data)[inst].un_op;
7895 const operand = try self.resolveInst(un_op);
7896 const enum_ty = self.air.typeOf(un_op);
7897
7898 const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty);
7899 const params = [_]*const llvm.Value{operand};
7900 return self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, "");
7901 }
7902
7903 fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*const llvm.Value {
7904 const enum_decl = enum_ty.getOwnerDecl();
7905
7906 // TODO: detect when the type changes and re-emit this function.
7907 const gop = try self.dg.object.named_enum_map.getOrPut(self.dg.gpa, enum_decl);
7908 if (gop.found_existing) return gop.value_ptr.*;
7909 errdefer assert(self.dg.object.named_enum_map.remove(enum_decl));
7910
7911 var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
7912 defer arena_allocator.deinit();
7913 const arena = arena_allocator.allocator();
7914
7915 const mod = self.dg.module;
7916 const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{
7917 try mod.declPtr(enum_decl).getFullyQualifiedName(mod),
7918 });
7919
7920 var int_tag_type_buffer: Type.Payload.Bits = undefined;
7921 const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
7922 const param_types = [_]*const llvm.Type{try self.dg.lowerType(int_tag_ty)};
7923
7924 const llvm_ret_ty = try self.dg.lowerType(Type.bool);
7925 const fn_type = llvm.functionType(llvm_ret_ty, &param_types, param_types.len, .False);
7926 const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
7927 fn_val.setLinkage(.Internal);
7928 fn_val.setFunctionCallConv(.Fast);
7929 self.dg.addCommonFnAttributes(fn_val);
7930 gop.value_ptr.* = fn_val;
7931
7932 const prev_block = self.builder.getInsertBlock();
7933 const prev_debug_location = self.builder.getCurrentDebugLocation2();
7934 defer {
7935 self.builder.positionBuilderAtEnd(prev_block);
7936 if (self.di_scope != null) {
7937 self.builder.setCurrentDebugLocation2(prev_debug_location);
7938 }
7939 }
7940
7941 const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry");
7942 self.builder.positionBuilderAtEnd(entry_block);
7943 self.builder.clearCurrentDebugLocation();
7944
7945 const fields = enum_ty.enumFields();
7946 const named_block = self.dg.context.appendBasicBlock(fn_val, "Named");
7947 const unnamed_block = self.dg.context.appendBasicBlock(fn_val, "Unnamed");
7948 const tag_int_value = fn_val.getParam(0);
7949 const switch_instr = self.builder.buildSwitch(tag_int_value, unnamed_block, @intCast(c_uint, fields.count()));
7950
7951 for (fields.keys()) |_, field_index| {
7952 const this_tag_int_value = int: {
7953 var tag_val_payload: Value.Payload.U32 = .{
7954 .base = .{ .tag = .enum_field_index },
7955 .data = @intCast(u32, field_index),
7956 };
7957 break :int try self.dg.lowerValue(.{
7958 .ty = enum_ty,
7959 .val = Value.initPayload(&tag_val_payload.base),
7960 });
7961 };
7962 switch_instr.addCase(this_tag_int_value, named_block);
7963 }
7964 self.builder.positionBuilderAtEnd(named_block);
7965 _ = self.builder.buildRet(self.dg.context.intType(1).constInt(1, .False));
7966
7967 self.builder.positionBuilderAtEnd(unnamed_block);
7968 _ = self.builder.buildRet(self.dg.context.intType(1).constInt(0, .False));
7969 return fn_val;
7970 }
7971
78857972 fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
78867973 if (self.liveness.isUnused(inst)) return null;
78877974
src/print_air.zig+1
......@@ -170,6 +170,7 @@ const Writer = struct {
170170 .bool_to_int,
171171 .ret,
172172 .ret_load,
173 .is_named_enum_value,
173174 .tag_name,
174175 .error_name,
175176 .sqrt,
test/behavior/switch.zig+1
......@@ -531,6 +531,7 @@ test "switch with null and T peer types and inferred result location type" {
531531test "switch prongs with cases with identical payload types" {
532532 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
533533 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
534 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
534535
535536 const Union = union(enum) {
536537 A: usize,
test/cases/safety/@intToEnum - no matching tag value.zig +6-3
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "invalid enum value")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810const Foo = enum {
911 A,
......@@ -18,6 +20,7 @@ fn bar(a: u2) Foo {
1820 return @intToEnum(Foo, a);
1921}
2022fn baz(_: Foo) void {}
23
2124// run
22// backend=stage1
25// backend=llvm
2326// target=native
test/cases/safety/@tagName on corrupted enum value.zig +2-1
......@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
1111const E = enum(u32) {
1212 X = 1,
13 Y = 2,
1314};
1415
1516pub fn main() !void {
......@@ -21,5 +22,5 @@ pub fn main() !void {
2122}
2223
2324// run
24// backend=stage1
25// backend=llvm
2526// target=native
test/cases/safety/@tagName on corrupted union value.zig +2-1
......@@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
1111const U = union(enum(u32)) {
1212 X: u8,
13 Y: i8,
1314};
1415
1516pub fn main() !void {
......@@ -22,5 +23,5 @@ pub fn main() !void {
2223}
2324
2425// run
25// backend=stage1
26// backend=llvm
2627// target=native
test/cases/safety/cast []u8 to bigger slice of wrong size.zig +6-4
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "exact division produced remainder")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810
911pub fn main() !void {
......@@ -15,5 +17,5 @@ fn widenSlice(slice: []align(1) const u8) []align(1) const i32 {
1517 return std.mem.bytesAsSlice(i32, slice);
1618}
1719// run
18// backend=stage1
19// target=native
\ No newline at end of file
20// backend=llvm
21// target=native
test/cases/safety/empty slice with sentinel out of bounds.zig +2-2
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "index out of bounds")) {
5 if (std.mem.eql(u8, message, "attempt to index out of bound: index 1, len 0")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
......@@ -17,5 +17,5 @@ pub fn main() !void {
1717}
1818
1919// run
20// backend=stage1
20// backend=llvm
2121// target=native
test/cases/safety/pointer casting null to non-optional pointer.zig +7-3
......@@ -1,16 +1,20 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "cast causes pointer to be null")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
10
811pub fn main() !void {
912 var c_ptr: [*c]u8 = 0;
1013 var zig_ptr: *u8 = c_ptr;
1114 _ = zig_ptr;
1215 return error.TestFailed;
1316}
17
1418// run
15// backend=stage1
19// backend=llvm
1620// target=native
test/cases/safety/pointer slice sentinel mismatch.zig +3-3
......@@ -2,14 +2,14 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "sentinel mismatch")) {
5 if (std.mem.eql(u8, message, "sentinel mismatch: expected 0, found 4")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
99}
1010
1111pub fn main() !void {
12 var buf: [4]u8 = undefined;
12 var buf: [4]u8 = .{ 1, 2, 3, 4 };
1313 const ptr: [*]u8 = &buf;
1414 const slice = ptr[0..3 :0];
1515 _ = slice;
......@@ -17,5 +17,5 @@ pub fn main() !void {
1717}
1818
1919// run
20// backend=stage1
20// backend=llvm
2121// target=native
test/cases/safety/shift left by huge amount.zig +1-1
......@@ -17,5 +17,5 @@ pub fn main() !void {
1717}
1818
1919// run
20// backend=stage1
20// backend=llvm
2121// target=native
test/cases/safety/shift right by huge amount.zig +1-1
......@@ -17,5 +17,5 @@ pub fn main() !void {
1717}
1818
1919// run
20// backend=stage1
20// backend=llvm
2121// target=native
test/cases/safety/signed integer division overflow - vectors.zig +6-4
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "integer overflow")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810
911pub fn main() !void {
......@@ -17,5 +19,5 @@ fn div(a: @Vector(4, i16), b: @Vector(4, i16)) @Vector(4, i16) {
1719 return @divTrunc(a, b);
1820}
1921// run
20// backend=stage1
21// target=native
\ No newline at end of file
22// backend=llvm
23// target=native
test/cases/safety/signed integer division overflow.zig +6-4
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "integer overflow")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810
911pub fn main() !void {
......@@ -15,5 +17,5 @@ fn div(a: i16, b: i16) i16 {
1517 return @divTrunc(a, b);
1618}
1719// run
18// backend=stage1
19// target=native
\ No newline at end of file
20// backend=llvm
21// target=native
test/cases/safety/slice sentinel mismatch - floats.zig +3-3
......@@ -2,19 +2,19 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "sentinel mismatch")) {
5 if (std.mem.eql(u8, message, "sentinel mismatch: expected 1.20000004e+00, found 4.0e+00")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
99}
1010
1111pub fn main() !void {
12 var buf: [4]f32 = undefined;
12 var buf: [4]f32 = .{ 1, 2, 3, 4 };
1313 const slice = buf[0..3 :1.2];
1414 _ = slice;
1515 return error.TestFailed;
1616}
1717
1818// run
19// backend=stage1
19// backend=llvm
2020// target=native
test/cases/safety/slice sentinel mismatch - optional pointers.zig +3-3
......@@ -2,19 +2,19 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "sentinel mismatch")) {
5 if (std.mem.eql(u8, message, "sentinel mismatch: expected null, found i32@10")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
99}
1010
1111pub fn main() !void {
12 var buf: [4]?*i32 = undefined;
12 var buf: [4]?*i32 = .{ @intToPtr(*i32, 4), @intToPtr(*i32, 8), @intToPtr(*i32, 12), @intToPtr(*i32, 16) };
1313 const slice = buf[0..3 :null];
1414 _ = slice;
1515 return error.TestFailed;
1616}
1717
1818// run
19// backend=stage1
19// backend=llvm
2020// target=native
test/cases/safety/slice slice sentinel mismatch.zig +3-3
......@@ -2,18 +2,18 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "sentinel mismatch")) {
5 if (std.mem.eql(u8, message, "sentinel mismatch: expected 0, found 4")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
99}
1010pub fn main() !void {
11 var buf: [4]u8 = undefined;
11 var buf: [4]u8 = .{ 1, 2, 3, 4 };
1212 const slice = buf[0..];
1313 const slice2 = slice[0..3 :0];
1414 _ = slice2;
1515 return error.TestFailed;
1616}
1717// run
18// backend=stage1
18// backend=llvm
1919// target=native
test/cases/safety/slice with sentinel out of bounds - runtime len.zig created+22
......@@ -0,0 +1,22 @@
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 index out of bound: index 5, len 4")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11pub fn main() !void {
12 var buf = [4]u8{ 'a', 'b', 'c', 0 };
13 const input: []u8 = &buf;
14 var len: usize = 4;
15 const slice = input[0..len :0];
16 _ = slice;
17 return error.TestFailed;
18}
19
20// run
21// backend=llvm
22// target=native
test/cases/safety/slice with sentinel out of bounds.zig +2-2
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "index out of bounds")) {
5 if (std.mem.eql(u8, message, "attempt to index out of bound: index 5, len 4")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
......@@ -17,5 +17,5 @@ pub fn main() !void {
1717}
1818
1919// run
20// backend=stage1
20// backend=llvm
2121// target=native
test/cases/safety/slicing null C pointer - runtime len.zig created+20
......@@ -0,0 +1,20 @@
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 use null value")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
9}
10
11pub fn main() !void {
12 var ptr: [*c]const u32 = null;
13 var len: usize = 3;
14 var slice = ptr[0..len];
15 _ = slice;
16 return error.TestFailed;
17}
18// run
19// backend=llvm
20// target=native
test/cases/safety/slicing null C pointer.zig +6-4
......@@ -1,9 +1,11 @@
11const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
4 _ = message;
54 _ = stack_trace;
6 std.process.exit(0);
5 if (std.mem.eql(u8, message, "attempt to use null value")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810
911pub fn main() !void {
......@@ -13,5 +15,5 @@ pub fn main() !void {
1315 return error.TestFailed;
1416}
1517// run
16// backend=stage1
17// target=native
\ No newline at end of file
18// backend=llvm
19// target=native
test/cases/safety/switch on corrupted enum value.zig +4-3
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "reached unreachable code")) {
5 if (std.mem.eql(u8, message, "switch on corrupt value")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
......@@ -10,17 +10,18 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
1111const E = enum(u32) {
1212 X = 1,
13 Y = 2,
1314};
1415
1516pub fn main() !void {
1617 var e: E = undefined;
1718 @memset(@ptrCast([*]u8, &e), 0x55, @sizeOf(E));
1819 switch (e) {
19 .X => @breakpoint(),
20 .X, .Y => @breakpoint(),
2021 }
2122 return error.TestFailed;
2223}
2324
2425// run
25// backend=stage1
26// backend=llvm
2627// target=native
test/cases/safety/switch on corrupted union value.zig +4-3
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
44 _ = stack_trace;
5 if (std.mem.eql(u8, message, "reached unreachable code")) {
5 if (std.mem.eql(u8, message, "switch on corrupt value")) {
66 std.process.exit(0);
77 }
88 std.process.exit(1);
......@@ -10,17 +10,18 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur
1010
1111const U = union(enum(u32)) {
1212 X: u8,
13 Y: i8,
1314};
1415
1516pub fn main() !void {
1617 var u: U = undefined;
1718 @memset(@ptrCast([*]u8, &u), 0x55, @sizeOf(U));
1819 switch (u) {
19 .X => @breakpoint(),
20 .X, .Y => @breakpoint(),
2021 }
2122 return error.TestFailed;
2223}
2324
2425// run
25// backend=stage1
26// backend=llvm
2627// target=native