authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-16 16:32:49+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-23 15:40:11+03:00
logff7ec4efb5a6da565b92bc7b129d03680a4a72bd
tree14006dc9e492015837a97328ccb85ac77f3e27e3
parent55fe34100f8b516480cf530eb58d00ea8b665765

Sema: bad union field access safety


15 files changed, 194 insertions(+), 90 deletions(-)

src/AstGen.zig+2-1
......@@ -1729,7 +1729,7 @@ fn structInitExprRlPtrInner(
17291729 for (struct_init.ast.fields) |field_init| {
17301730 const name_token = tree.firstToken(field_init) - 2;
17311731 const str_index = try astgen.identAsString(name_token);
1732 const field_ptr = try gz.addPlNode(.field_ptr, field_init, Zir.Inst.Field{
1732 const field_ptr = try gz.addPlNode(.field_ptr_init, field_init, Zir.Inst.Field{
17331733 .lhs = result_ptr,
17341734 .field_name_start = str_index,
17351735 });
......@@ -2287,6 +2287,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
22872287 .elem_ptr_imm,
22882288 .elem_val_node,
22892289 .field_ptr,
2290 .field_ptr_init,
22902291 .field_val,
22912292 .field_call_bind,
22922293 .field_ptr_named,
src/Module.zig+1-1
......@@ -787,7 +787,7 @@ pub const Decl = struct {
787787 const opaque_obj = ty.cast(Type.Payload.Opaque).?.data;
788788 return &opaque_obj.namespace;
789789 },
790 .@"union", .union_tagged => {
790 .@"union", .union_safety_tagged, .union_tagged => {
791791 const union_obj = ty.cast(Type.Payload.Union).?.data;
792792 return &union_obj.namespace;
793793 },
src/Sema.zig+90-41
......@@ -739,7 +739,8 @@ fn analyzeBodyInner(
739739 .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false),
740740 .error_union_type => try sema.zirErrorUnionType(block, inst),
741741 .error_value => try sema.zirErrorValue(block, inst),
742 .field_ptr => try sema.zirFieldPtr(block, inst),
742 .field_ptr => try sema.zirFieldPtr(block, inst, false),
743 .field_ptr_init => try sema.zirFieldPtr(block, inst, true),
743744 .field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
744745 .field_val => try sema.zirFieldVal(block, inst),
745746 .field_val_named => try sema.zirFieldValNamed(block, inst),
......@@ -1547,11 +1548,11 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
15471548 const st_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty));
15481549
15491550 // st.instruction_addresses = &addrs;
1550 const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src);
1551 const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src, true);
15511552 try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store);
15521553
15531554 // st.index = 0;
1554 const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src);
1555 const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true);
15551556 const zero = try sema.addConstant(Type.usize, Value.zero);
15561557 try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store);
15571558
......@@ -2614,7 +2615,14 @@ fn zirUnionDecl(
26142615 const new_decl_arena_allocator = new_decl_arena.allocator();
26152616
26162617 const union_obj = try new_decl_arena_allocator.create(Module.Union);
2617 const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union";
2618 const type_tag = if (small.has_tag_type or small.auto_enum_tag)
2619 Type.Tag.union_tagged
2620 else if (small.layout != .Auto)
2621 Type.Tag.@"union"
2622 else switch (block.sema.mod.optimizeMode()) {
2623 .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged,
2624 .ReleaseFast, .ReleaseSmall => Type.Tag.@"union",
2625 };
26182626 const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union);
26192627 union_payload.* = .{
26202628 .base = .{ .tag = type_tag },
......@@ -7923,7 +7931,7 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
79237931 return sema.fieldVal(block, src, object, field_name, field_name_src);
79247932}
79257933
7926fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
7934fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: bool) CompileError!Air.Inst.Ref {
79277935 const tracy = trace(@src());
79287936 defer tracy.end();
79297937
......@@ -7933,7 +7941,7 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
79337941 const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
79347942 const field_name = sema.code.nullTerminatedString(extra.field_name_start);
79357943 const object_ptr = try sema.resolveInst(extra.lhs);
7936 return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
7944 return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing);
79377945}
79387946
79397947fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -7972,7 +7980,7 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
79727980 const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
79737981 const object_ptr = try sema.resolveInst(extra.lhs);
79747982 const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime known");
7975 return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
7983 return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
79767984}
79777985
79787986fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
......@@ -14536,7 +14544,7 @@ fn zirStructInit(
1453614544 .@"addrspace" = target_util.defaultAddressSpace(target, .local),
1453714545 });
1453814546 const alloc = try block.addTy(.alloc, alloc_ty);
14539 const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty);
14547 const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty, true);
1454014548 try sema.storePtr(block, src, field_ptr, init_inst);
1454114549 const new_tag = try sema.addConstant(resolved_ty.unionTagTypeHypothetical(), tag_val);
1454214550 _ = try block.addBinOp(.set_union_tag, alloc, new_tag);
......@@ -15604,13 +15612,21 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
1560415612 if (decls_val.sliceLen(mod) > 0) {
1560515613 return sema.fail(block, src, "reified unions must have no decls", .{});
1560615614 }
15615 const layout = layout_val.toEnum(std.builtin.Type.ContainerLayout);
1560715616
1560815617 var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
1560915618 errdefer new_decl_arena.deinit();
1561015619 const new_decl_arena_allocator = new_decl_arena.allocator();
1561115620
1561215621 const union_obj = try new_decl_arena_allocator.create(Module.Union);
15613 const type_tag: Type.Tag = if (!tag_type_val.isNull()) .union_tagged else .@"union";
15622 const type_tag = if (!tag_type_val.isNull())
15623 Type.Tag.union_tagged
15624 else if (layout != .Auto)
15625 Type.Tag.@"union"
15626 else switch (block.sema.mod.optimizeMode()) {
15627 .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged,
15628 .ReleaseFast, .ReleaseSmall => Type.Tag.@"union",
15629 };
1561415630 const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union);
1561515631 union_payload.* = .{
1561615632 .base = .{ .tag = type_tag },
......@@ -15631,7 +15647,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
1563115647 .fields = .{},
1563215648 .node_offset = src.node_offset.x,
1563315649 .zir_index = inst,
15634 .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout),
15650 .layout = layout,
1563515651 .status = .have_field_types,
1563615652 .namespace = .{
1563715653 .parent = block.namespace,
......@@ -15641,11 +15657,15 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
1564115657 };
1564215658
1564315659 // Tag type
15660 var enum_field_names: ?*Module.EnumNumbered.NameMap = null;
1564415661 const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod));
15645 union_obj.tag_ty = if (tag_type_val.optionalValue()) |payload_val| blk: {
15662 if (tag_type_val.optionalValue()) |payload_val| {
1564615663 var buffer: Value.ToTypeBuffer = undefined;
15647 break :blk try payload_val.toType(&buffer).copy(new_decl_arena_allocator);
15648 } else try sema.generateUnionTagTypeSimple(block, fields_len, null);
15664 union_obj.tag_ty = try payload_val.toType(&buffer).copy(new_decl_arena_allocator);
15665 } else {
15666 union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len, null);
15667 enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields;
15668 }
1564915669
1565015670 // Fields
1565115671 if (fields_len > 0) {
......@@ -15669,6 +15689,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
1566915689 sema.mod,
1567015690 );
1567115691
15692 if (enum_field_names) |set| {
15693 set.putAssumeCapacity(field_name, {});
15694 }
15695
1567215696 const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
1567315697 if (gop.found_existing) {
1567415698 // TODO: better source location
......@@ -18898,6 +18922,8 @@ pub const PanicId = enum {
1889818922 divide_by_zero,
1889918923 remainder_division_zero_negative,
1890018924 exact_division_remainder,
18925 /// TODO make this call `std.builtin.panicInactiveUnionField`.
18926 inactive_union_field,
1890118927};
1890218928
1890318929fn addSafetyCheck(
......@@ -19120,6 +19146,7 @@ fn safetyPanic(
1912019146 .divide_by_zero => "division by zero",
1912119147 .remainder_division_zero_negative => "remainder division by zero or negative value",
1912219148 .exact_division_remainder => "exact division produced remainder",
19149 .inactive_union_field => "access of inactive union field",
1912319150 };
1912419151
1912519152 const msg_inst = msg_inst: {
......@@ -19339,7 +19366,7 @@ fn fieldVal(
1933919366 },
1934019367 .Union => if (is_pointer_to) {
1934119368 // Avoid loading the entire union by fetching a pointer and loading that
19342 const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty);
19369 const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false);
1934319370 return sema.analyzeLoad(block, src, field_ptr, object_src);
1934419371 } else {
1934519372 return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty);
......@@ -19356,6 +19383,7 @@ fn fieldPtr(
1935619383 object_ptr: Air.Inst.Ref,
1935719384 field_name: []const u8,
1935819385 field_name_src: LazySrcLoc,
19386 initializing: bool,
1935919387) CompileError!Air.Inst.Ref {
1936019388 // When editing this function, note that there is corresponding logic to be edited
1936119389 // in `fieldVal`. This function takes a pointer and returns a pointer.
......@@ -19547,7 +19575,7 @@ fn fieldPtr(
1954719575 try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
1954819576 else
1954919577 object_ptr;
19550 return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty);
19578 return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);
1955119579 },
1955219580 else => {},
1955319581 }
......@@ -19995,6 +20023,7 @@ fn unionFieldPtr(
1999520023 field_name: []const u8,
1999620024 field_name_src: LazySrcLoc,
1999720025 unresolved_union_ty: Type,
20026 initializing: bool,
1999820027) CompileError!Air.Inst.Ref {
1999920028 const arena = sema.arena;
2000020029 assert(unresolved_union_ty.zigTypeTag() == .Union);
......@@ -20010,30 +20039,32 @@ fn unionFieldPtr(
2001020039 .@"addrspace" = union_ptr_ty.ptrAddressSpace(),
2001120040 });
2001220041
20013 if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| {
20042 if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: {
2001420043 switch (union_obj.layout) {
20015 .Auto => {
20016 // TODO emit the access of inactive union field error commented out below.
20017 // In order to do that, we need to first solve the problem that AstGen
20018 // emits field_ptr instructions in order to initialize union values.
20019 // In such case we need to know that the field_ptr instruction (which is
20020 // calling this unionFieldPtr function) is *initializing* the union,
20021 // in which case we would skip this check, and in fact we would actually
20022 // set the union tag here and the payload to undefined.
20023
20024 //const tag_and_val = union_val.castTag(.@"union").?.data;
20025 //var field_tag_buf: Value.Payload.U32 = .{
20026 // .base = .{ .tag = .enum_field_index },
20027 // .data = field_index,
20028 //};
20029 //const field_tag = Value.initPayload(&field_tag_buf.base);
20030 //const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, mod);
20031 //if (!tag_matches) {
20032 // // TODO enhance this saying which one was active
20033 // // and which one was accessed, and showing where the union was declared.
20034 // return sema.fail(block, src, "access of inactive union field", .{});
20035 //}
20036 // TODO add runtime safety check for the active tag
20044 .Auto => if (!initializing) {
20045 const union_val = (try sema.pointerDeref(block, src, union_ptr_val, union_ptr_ty)) orelse
20046 break :ct;
20047 if (union_val.isUndef()) {
20048 return sema.failWithUseOfUndef(block, src);
20049 }
20050 const tag_and_val = union_val.castTag(.@"union").?.data;
20051 var field_tag_buf: Value.Payload.U32 = .{
20052 .base = .{ .tag = .enum_field_index },
20053 .data = field_index,
20054 };
20055 const field_tag = Value.initPayload(&field_tag_buf.base);
20056 const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod);
20057 if (!tag_matches) {
20058 const msg = msg: {
20059 const active_index = tag_and_val.tag.castTag(.enum_field_index).?.data;
20060 const active_field_name = union_obj.fields.keys()[active_index];
20061 const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name });
20062 errdefer msg.destroy(sema.gpa);
20063 try sema.addDeclaredHereNote(msg, union_ty);
20064 break :msg msg;
20065 };
20066 return sema.failWithOwnedErrorMsg(block, msg);
20067 }
2003720068 },
2003820069 .Packed, .Extern => {},
2003920070 }
......@@ -20048,6 +20079,16 @@ fn unionFieldPtr(
2004820079 }
2004920080
2005020081 try sema.requireRuntimeBlock(block, src, null);
20082 if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) {
20083 const enum_ty = union_ty.unionTagTypeHypothetical();
20084 const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index);
20085 const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val);
20086 // TODO would it be better if get_union_tag supported pointers to unions?
20087 const union_val = try block.addTyOp(.load, union_ty, union_ptr);
20088 const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_val);
20089 const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
20090 try sema.addSafetyCheck(block, ok, .inactive_union_field);
20091 }
2005120092 return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty);
2005220093}
2005320094
......@@ -20106,6 +20147,14 @@ fn unionFieldVal(
2010620147 }
2010720148
2010820149 try sema.requireRuntimeBlock(block, src, null);
20150 if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) {
20151 const enum_ty = union_ty.unionTagTypeHypothetical();
20152 const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index);
20153 const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val);
20154 const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_byval);
20155 const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
20156 try sema.addSafetyCheck(block, ok, .inactive_union_field);
20157 }
2010920158 return block.addStructFieldVal(union_byval, field_index, field.ty);
2011020159}
2011120160
......@@ -25424,7 +25473,7 @@ pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type)
2542425473 try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj);
2542525474 return ty;
2542625475 },
25427 .@"union", .union_tagged => {
25476 .@"union", .union_safety_tagged, .union_tagged => {
2542825477 const union_obj = ty.cast(Type.Payload.Union).?.data;
2542925478 try sema.resolveTypeFieldsUnion(block, src, ty, union_obj);
2543025479 return ty;
......@@ -26449,7 +26498,7 @@ pub fn typeHasOnePossibleValue(
2644926498 return null;
2645026499 }
2645126500 },
26452 .@"union", .union_tagged => {
26501 .@"union", .union_safety_tagged, .union_tagged => {
2645326502 const resolved_ty = try sema.resolveTypeFields(block, src, ty);
2645426503 const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
2645526504 const tag_val = (try sema.typeHasOnePossibleValue(block, src, union_obj.tag_ty)) orelse
......@@ -27081,7 +27130,7 @@ pub fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ
2708127130 }
2708227131 },
2708327132
27084 .@"union", .union_tagged => {
27133 .@"union", .union_safety_tagged, .union_tagged => {
2708527134 const union_obj = ty.cast(Type.Payload.Union).?.data;
2708627135 switch (union_obj.requires_comptime) {
2708727136 .no, .wip => return false,
src/Zir.zig+5
......@@ -410,6 +410,8 @@ pub const Inst = struct {
410410 /// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
411411 /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
412412 field_ptr,
413 /// Same as `field_ptr` but used for struct init.
414 field_ptr_init,
413415 /// Given a struct or object that contains virtual fields, returns the named field.
414416 /// The field name is stored in string_bytes. Used by a.b syntax.
415417 /// This instruction also accepts a pointer.
......@@ -1070,6 +1072,7 @@ pub const Inst = struct {
10701072 .@"export",
10711073 .export_value,
10721074 .field_ptr,
1075 .field_ptr_init,
10731076 .field_val,
10741077 .field_call_bind,
10751078 .field_ptr_named,
......@@ -1370,6 +1373,7 @@ pub const Inst = struct {
13701373 .elem_ptr_imm,
13711374 .elem_val_node,
13721375 .field_ptr,
1376 .field_ptr_init,
13731377 .field_val,
13741378 .field_call_bind,
13751379 .field_ptr_named,
......@@ -1629,6 +1633,7 @@ pub const Inst = struct {
16291633 .@"export" = .pl_node,
16301634 .export_value = .pl_node,
16311635 .field_ptr = .pl_node,
1636 .field_ptr_init = .pl_node,
16321637 .field_val = .pl_node,
16331638 .field_ptr_named = .pl_node,
16341639 .field_val_named = .pl_node,
src/arch/wasm/abi.zig+2-2
......@@ -77,7 +77,7 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
7777 .Union => {
7878 const layout = ty.unionGetLayout(target);
7979 if (layout.payload_size == 0 and layout.tag_size != 0) {
80 return classifyType(ty.unionTagType().?, target);
80 return classifyType(ty.unionTagTypeSafety().?, target);
8181 }
8282 if (ty.unionFields().count() > 1) return memory;
8383 return classifyType(ty.unionFields().values()[0].ty, target);
......@@ -111,7 +111,7 @@ pub fn scalarType(ty: Type, target: std.Target) Type {
111111 .Union => {
112112 const layout = ty.unionGetLayout(target);
113113 if (layout.payload_size == 0 and layout.tag_size != 0) {
114 return scalarType(ty.unionTagType().?, target);
114 return scalarType(ty.unionTagTypeSafety().?, target);
115115 }
116116 std.debug.assert(ty.unionFields().count() == 1);
117117 return scalarType(ty.unionFields().values()[0].ty, target);
src/codegen/c.zig+9-9
......@@ -504,7 +504,7 @@ pub const DeclGen = struct {
504504 if (field_ty.hasRuntimeBitsIgnoreComptime()) {
505505 try writer.writeAll("&(");
506506 try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty);
507 if (field_ptr.container_ty.tag() == .union_tagged) {
507 if (field_ptr.container_ty.tag() == .union_tagged or field_ptr.container_ty.tag() == .union_safety_tagged) {
508508 try writer.print(")->payload.{ }", .{fmtIdent(field_name)});
509509 } else {
510510 try writer.print(")->{ }", .{fmtIdent(field_name)});
......@@ -842,7 +842,7 @@ pub const DeclGen = struct {
842842 try dg.renderTypecast(writer, ty);
843843 try writer.writeAll("){");
844844
845 if (ty.unionTagType()) |tag_ty| {
845 if (ty.unionTagTypeSafety()) |tag_ty| {
846846 if (layout.tag_size != 0) {
847847 try writer.writeAll(".tag = ");
848848 try dg.renderValue(writer, tag_ty, union_obj.tag, location);
......@@ -858,7 +858,7 @@ pub const DeclGen = struct {
858858 try writer.print(".{ } = ", .{fmtIdent(field_name)});
859859 try dg.renderValue(writer, field_ty, union_obj.val, location);
860860 }
861 if (ty.unionTagType()) |_| {
861 if (ty.unionTagTypeSafety()) |_| {
862862 try writer.writeAll("}");
863863 }
864864 try writer.writeAll("}");
......@@ -1110,7 +1110,7 @@ pub const DeclGen = struct {
11101110 defer buffer.deinit();
11111111
11121112 try buffer.appendSlice("typedef ");
1113 if (t.unionTagType()) |tag_ty| {
1113 if (t.unionTagTypeSafety()) |tag_ty| {
11141114 const name: CValue = .{ .bytes = "tag" };
11151115 try buffer.appendSlice("struct {\n ");
11161116 if (layout.tag_size != 0) {
......@@ -1134,7 +1134,7 @@ pub const DeclGen = struct {
11341134 }
11351135 try buffer.appendSlice("} ");
11361136
1137 if (t.unionTagType()) |_| {
1137 if (t.unionTagTypeSafety()) |_| {
11381138 try buffer.appendSlice("payload;\n} ");
11391139 }
11401140
......@@ -3368,7 +3368,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
33683368 field_name = fields.keys()[index];
33693369 field_val_ty = fields.values()[index].ty;
33703370 },
3371 .@"union", .union_tagged => {
3371 .@"union", .union_safety_tagged, .union_tagged => {
33723372 const fields = struct_ty.unionFields();
33733373 field_name = fields.keys()[index];
33743374 field_val_ty = fields.values()[index].ty;
......@@ -3383,7 +3383,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
33833383 },
33843384 else => unreachable,
33853385 }
3386 const payload = if (struct_ty.tag() == .union_tagged) "payload." else "";
3386 const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else "";
33873387
33883388 const inst_ty = f.air.typeOfIndex(inst);
33893389 const local = try f.allocLocal(inst_ty, .Const);
......@@ -3415,7 +3415,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
34153415 defer buf.deinit();
34163416 const field_name = switch (struct_ty.tag()) {
34173417 .@"struct" => struct_ty.structFields().keys()[extra.field_index],
3418 .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
3418 .@"union", .union_safety_tagged, .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
34193419 .tuple, .anon_struct => blk: {
34203420 const tuple = struct_ty.tupleFields();
34213421 if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none;
......@@ -3425,7 +3425,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
34253425 },
34263426 else => unreachable,
34273427 };
3428 const payload = if (struct_ty.tag() == .union_tagged) "payload." else "";
3428 const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else "";
34293429
34303430 const inst_ty = f.air.typeOfIndex(inst);
34313431 const local = try f.allocLocal(inst_ty, .Const);
src/codegen/llvm.zig+2-2
......@@ -3404,7 +3404,7 @@ pub const DeclGen = struct {
34043404
34053405 if (layout.payload_size == 0) {
34063406 return lowerValue(dg, .{
3407 .ty = tv.ty.unionTagType().?,
3407 .ty = tv.ty.unionTagTypeSafety().?,
34083408 .val = tag_and_val.tag,
34093409 });
34103410 }
......@@ -3446,7 +3446,7 @@ pub const DeclGen = struct {
34463446 }
34473447 }
34483448 const llvm_tag_value = try lowerValue(dg, .{
3449 .ty = tv.ty.unionTagType().?,
3449 .ty = tv.ty.unionTagTypeSafety().?,
34503450 .val = tag_and_val.tag,
34513451 });
34523452 var fields: [3]*const llvm.Value = undefined;
src/print_zir.zig+1
......@@ -390,6 +390,7 @@ const Writer = struct {
390390 .switch_block => try self.writeSwitchBlock(stream, inst),
391391
392392 .field_ptr,
393 .field_ptr_init,
393394 .field_val,
394395 .field_call_bind,
395396 => try self.writePlNodeField(stream, inst),
src/type.zig+56-27
......@@ -149,6 +149,7 @@ pub const Type = extern union {
149149 => return .Enum,
150150
151151 .@"union",
152 .union_safety_tagged,
152153 .union_tagged,
153154 .type_info,
154155 => return .Union,
......@@ -902,7 +903,7 @@ pub const Type = extern union {
902903 .reduce_op,
903904 => unreachable, // needed to resolve the type before now
904905
905 .@"union", .union_tagged => {
906 .@"union", .union_safety_tagged, .union_tagged => {
906907 const a_union_obj = a.cast(Payload.Union).?.data;
907908 const b_union_obj = (b.cast(Payload.Union) orelse return false).data;
908909 return a_union_obj == b_union_obj;
......@@ -1210,7 +1211,7 @@ pub const Type = extern union {
12101211 .reduce_op,
12111212 => unreachable, // needed to resolve the type before now
12121213
1213 .@"union", .union_tagged => {
1214 .@"union", .union_safety_tagged, .union_tagged => {
12141215 const union_obj: *const Module.Union = ty.cast(Payload.Union).?.data;
12151216 std.hash.autoHash(hasher, std.builtin.TypeId.Union);
12161217 std.hash.autoHash(hasher, union_obj);
......@@ -1479,7 +1480,7 @@ pub const Type = extern union {
14791480 .error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
14801481 .empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope),
14811482 .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
1482 .@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union),
1483 .@"union", .union_safety_tagged, .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union),
14831484 .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple),
14841485 .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered),
14851486 .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull),
......@@ -1603,7 +1604,7 @@ pub const Type = extern union {
16031604 @tagName(t), struct_obj.owner_decl,
16041605 });
16051606 },
1606 .@"union", .union_tagged => {
1607 .@"union", .union_safety_tagged, .union_tagged => {
16071608 const union_obj = ty.cast(Payload.Union).?.data;
16081609 return writer.print("({s} decl={d})", .{
16091610 @tagName(t), union_obj.owner_decl,
......@@ -1989,7 +1990,7 @@ pub const Type = extern union {
19891990 const decl = mod.declPtr(struct_obj.owner_decl);
19901991 try decl.renderFullyQualifiedName(mod, writer);
19911992 },
1992 .@"union", .union_tagged => {
1993 .@"union", .union_safety_tagged, .union_tagged => {
19931994 const union_obj = ty.cast(Payload.Union).?.data;
19941995 const decl = mod.declPtr(union_obj.owner_decl);
19951996 try decl.renderFullyQualifiedName(mod, writer);
......@@ -2485,8 +2486,8 @@ pub const Type = extern union {
24852486 return false;
24862487 }
24872488 },
2488 .union_tagged => {
2489 const union_obj = ty.castTag(.union_tagged).?.data;
2489 .union_safety_tagged, .union_tagged => {
2490 const union_obj = ty.cast(Payload.Union).?.data;
24902491 if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
24912492 return true;
24922493 }
......@@ -2644,7 +2645,7 @@ pub const Type = extern union {
26442645
26452646 .optional => ty.isPtrLikeOptional(),
26462647 .@"struct" => ty.castTag(.@"struct").?.data.layout != .Auto,
2647 .@"union" => ty.castTag(.@"union").?.data.layout != .Auto,
2648 .@"union", .union_safety_tagged => ty.cast(Payload.Union).?.data.layout != .Auto,
26482649 .union_tagged => false,
26492650 };
26502651 }
......@@ -3050,11 +3051,10 @@ pub const Type = extern union {
30503051 },
30513052 .@"union" => {
30523053 const union_obj = ty.castTag(.@"union").?.data;
3053 // TODO pass `true` for have_tag when unions have a safety tag
30543054 return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, false);
30553055 },
3056 .union_tagged => {
3057 const union_obj = ty.castTag(.union_tagged).?.data;
3056 .union_safety_tagged, .union_tagged => {
3057 const union_obj = ty.cast(Payload.Union).?.data;
30583058 return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, true);
30593059 },
30603060
......@@ -3232,11 +3232,10 @@ pub const Type = extern union {
32323232 },
32333233 .@"union" => {
32343234 const union_obj = ty.castTag(.@"union").?.data;
3235 // TODO pass `true` for have_tag when unions have a safety tag
32363235 return abiSizeAdvancedUnion(ty, target, strat, union_obj, false);
32373236 },
3238 .union_tagged => {
3239 const union_obj = ty.castTag(.union_tagged).?.data;
3237 .union_safety_tagged, .union_tagged => {
3238 const union_obj = ty.cast(Payload.Union).?.data;
32403239 return abiSizeAdvancedUnion(ty, target, strat, union_obj, true);
32413240 },
32423241
......@@ -3526,7 +3525,7 @@ pub const Type = extern union {
35263525 return try bitSizeAdvanced(int_tag_ty, target, sema_kit);
35273526 },
35283527
3529 .@"union", .union_tagged => {
3528 .@"union", .union_safety_tagged, .union_tagged => {
35303529 if (sema_kit) |sk| _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty);
35313530 const union_obj = ty.cast(Payload.Union).?.data;
35323531 assert(union_obj.haveFieldTypes());
......@@ -4194,6 +4193,33 @@ pub const Type = extern union {
41944193 };
41954194 }
41964195
4196 /// Same as `unionTagType` but includes safety tag.
4197 /// Codegen should use this version.
4198 pub fn unionTagTypeSafety(ty: Type) ?Type {
4199 return switch (ty.tag()) {
4200 .union_safety_tagged, .union_tagged => {
4201 const union_obj = ty.cast(Payload.Union).?.data;
4202 assert(union_obj.haveFieldTypes());
4203 return union_obj.tag_ty;
4204 },
4205
4206 .atomic_order,
4207 .atomic_rmw_op,
4208 .calling_convention,
4209 .address_space,
4210 .float_mode,
4211 .reduce_op,
4212 .call_options,
4213 .prefetch_options,
4214 .export_options,
4215 .extern_options,
4216 .type_info,
4217 => unreachable, // needed to call resolveTypeFields first
4218
4219 else => null,
4220 };
4221 }
4222
41974223 /// Asserts the type is a union; returns the tag type, even if the tag will
41984224 /// not be stored at runtime.
41994225 pub fn unionTagTypeHypothetical(ty: Type) Type {
......@@ -4225,8 +4251,8 @@ pub const Type = extern union {
42254251 const union_obj = ty.castTag(.@"union").?.data;
42264252 return union_obj.getLayout(target, false);
42274253 },
4228 .union_tagged => {
4229 const union_obj = ty.castTag(.union_tagged).?.data;
4254 .union_safety_tagged, .union_tagged => {
4255 const union_obj = ty.cast(Payload.Union).?.data;
42304256 return union_obj.getLayout(target, true);
42314257 },
42324258 else => unreachable,
......@@ -4238,6 +4264,7 @@ pub const Type = extern union {
42384264 .tuple, .empty_struct_literal, .anon_struct => .Auto,
42394265 .@"struct" => ty.castTag(.@"struct").?.data.layout,
42404266 .@"union" => ty.castTag(.@"union").?.data.layout,
4267 .union_safety_tagged => ty.castTag(.union_safety_tagged).?.data.layout,
42414268 .union_tagged => ty.castTag(.union_tagged).?.data.layout,
42424269 else => unreachable,
42434270 };
......@@ -4936,7 +4963,7 @@ pub const Type = extern union {
49364963 return null;
49374964 }
49384965 },
4939 .@"union", .union_tagged => {
4966 .@"union", .union_safety_tagged, .union_tagged => {
49404967 const union_obj = ty.cast(Payload.Union).?.data;
49414968 const tag_val = union_obj.tag_ty.onePossibleValue() orelse return null;
49424969 const only_field = union_obj.fields.values()[0];
......@@ -5114,7 +5141,7 @@ pub const Type = extern union {
51145141 }
51155142 },
51165143
5117 .@"union", .union_tagged => {
5144 .@"union", .union_safety_tagged, .union_tagged => {
51185145 const union_obj = ty.cast(Type.Payload.Union).?.data;
51195146 switch (union_obj.requires_comptime) {
51205147 .wip, .unknown => unreachable, // This function asserts types already resolved.
......@@ -5167,6 +5194,7 @@ pub const Type = extern union {
51675194 .empty_struct => self.castTag(.empty_struct).?.data,
51685195 .@"opaque" => &self.castTag(.@"opaque").?.data.namespace,
51695196 .@"union" => &self.castTag(.@"union").?.data.namespace,
5197 .union_safety_tagged => &self.castTag(.union_safety_tagged).?.data.namespace,
51705198 .union_tagged => &self.castTag(.union_tagged).?.data.namespace,
51715199
51725200 else => null,
......@@ -5439,7 +5467,7 @@ pub const Type = extern union {
54395467 const struct_obj = ty.castTag(.@"struct").?.data;
54405468 return struct_obj.fields.values()[index].ty;
54415469 },
5442 .@"union", .union_tagged => {
5470 .@"union", .union_safety_tagged, .union_tagged => {
54435471 const union_obj = ty.cast(Payload.Union).?.data;
54445472 return union_obj.fields.values()[index].ty;
54455473 },
......@@ -5456,7 +5484,7 @@ pub const Type = extern union {
54565484 assert(struct_obj.layout != .Packed);
54575485 return struct_obj.fields.values()[index].normalAlignment(target);
54585486 },
5459 .@"union", .union_tagged => {
5487 .@"union", .union_safety_tagged, .union_tagged => {
54605488 const union_obj = ty.cast(Payload.Union).?.data;
54615489 return union_obj.fields.values()[index].normalAlignment(target);
54625490 },
......@@ -5619,8 +5647,8 @@ pub const Type = extern union {
56195647 },
56205648
56215649 .@"union" => return 0,
5622 .union_tagged => {
5623 const union_obj = ty.castTag(.union_tagged).?.data;
5650 .union_safety_tagged, .union_tagged => {
5651 const union_obj = ty.cast(Payload.Union).?.data;
56245652 const layout = union_obj.getLayout(target, true);
56255653 if (layout.tag_align >= layout.payload_align) {
56265654 // {Tag, Payload}
......@@ -5660,7 +5688,7 @@ pub const Type = extern union {
56605688 const error_set = ty.castTag(.error_set).?.data;
56615689 return error_set.srcLoc(mod);
56625690 },
5663 .@"union", .union_tagged => {
5691 .@"union", .union_safety_tagged, .union_tagged => {
56645692 const union_obj = ty.cast(Payload.Union).?.data;
56655693 return union_obj.srcLoc(mod);
56665694 },
......@@ -5704,7 +5732,7 @@ pub const Type = extern union {
57045732 const error_set = ty.castTag(.error_set).?.data;
57055733 return error_set.owner_decl;
57065734 },
5707 .@"union", .union_tagged => {
5735 .@"union", .union_safety_tagged, .union_tagged => {
57085736 const union_obj = ty.cast(Payload.Union).?.data;
57095737 return union_obj.owner_decl;
57105738 },
......@@ -5748,7 +5776,7 @@ pub const Type = extern union {
57485776 const error_set = ty.castTag(.error_set).?.data;
57495777 return error_set.node_offset;
57505778 },
5751 .@"union", .union_tagged => {
5779 .@"union", .union_safety_tagged, .union_tagged => {
57525780 const union_obj = ty.cast(Payload.Union).?.data;
57535781 return union_obj.node_offset;
57545782 },
......@@ -5893,6 +5921,7 @@ pub const Type = extern union {
58935921 @"opaque",
58945922 @"struct",
58955923 @"union",
5924 union_safety_tagged,
58965925 union_tagged,
58975926 enum_simple,
58985927 enum_numbered,
......@@ -6009,7 +6038,7 @@ pub const Type = extern union {
60096038 .error_set_single => Payload.Name,
60106039 .@"opaque" => Payload.Opaque,
60116040 .@"struct" => Payload.Struct,
6012 .@"union", .union_tagged => Payload.Union,
6041 .@"union", .union_safety_tagged, .union_tagged => Payload.Union,
60136042 .enum_full, .enum_nonexhaustive => Payload.EnumFull,
60146043 .enum_simple => Payload.EnumSimple,
60156044 .enum_numbered => Payload.EnumNumbered,
test/behavior/bugs/1381.zig+2
......@@ -12,8 +12,10 @@ const A = union(enum) {
1212};
1313
1414test "union that needs padding bytes inside an array" {
15 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1516 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1617 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
18 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
1719
1820 var as = [_]A{
1921 A{ .B = B{ .D = 1 } },
test/behavior/struct.zig+3
......@@ -998,6 +998,9 @@ test "tuple element initialized with fn call" {
998998}
999999
10001000test "struct with union field" {
1001 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1002 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1003
10011004 const Value = struct {
10021005 ref: u32 = 2,
10031006 kind: union(enum) {
test/behavior/type.zig+1-1
......@@ -412,7 +412,7 @@ test "Type.Union" {
412412
413413 const Untagged = @Type(.{
414414 .Union = .{
415 .layout = .Auto,
415 .layout = .Extern,
416416 .tag_type = null,
417417 .fields = &.{
418418 .{ .name = "int", .field_type = i32, .alignment = @alignOf(f32) },
test/behavior/union.zig+13
......@@ -37,6 +37,7 @@ test "init union with runtime value - floats" {
3737
3838test "basic unions" {
3939 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
40 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
4041
4142 var foo = Foo{ .int = 1 };
4243 try expect(foo.int == 1);
......@@ -430,9 +431,11 @@ const Foo1 = union(enum) {
430431var glbl: Foo1 = undefined;
431432
432433test "global union with single field is correctly initialized" {
434 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
433435 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
434436 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
435437 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
438 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
436439
437440 glbl = Foo1{
438441 .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
......@@ -473,8 +476,11 @@ test "update the tag value for zero-sized unions" {
473476}
474477
475478test "union initializer generates padding only if needed" {
479 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
476480 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
481 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
477482 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
483 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
478484
479485 const U = union(enum) {
480486 A: u24,
......@@ -747,9 +753,11 @@ fn Setter(attr: Attribute) type {
747753}
748754
749755test "return union init with void payload" {
756 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
750757 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
751758 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
752759 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
760 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
753761
754762 const S = struct {
755763 fn entry() !void {
......@@ -775,6 +783,7 @@ test "@unionInit stored to a const" {
775783 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
776784 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
777785 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
786 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
778787
779788 const S = struct {
780789 const U = union(enum) {
......@@ -937,6 +946,7 @@ test "cast from anonymous struct to union" {
937946 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
938947 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
939948 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
949 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
940950
941951 const S = struct {
942952 const U = union(enum) {
......@@ -969,6 +979,7 @@ test "cast from pointer to anonymous struct to pointer to union" {
969979 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
970980 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
971981 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
982 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
972983
973984 const S = struct {
974985 const U = union(enum) {
......@@ -1104,6 +1115,8 @@ test "union enum type gets a separate scope" {
11041115
11051116test "global variable struct contains union initialized to non-most-aligned field" {
11061117 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
1118 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1119 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
11071120
11081121 const T = struct {
11091122 const U = union(enum) {
test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig+1-2
......@@ -13,5 +13,4 @@ export fn entry() void {
1313// backend=stage2
1414// target=native
1515//
16// :9:14: error: expected type 'type', found 'tmp.U'
17// :1:11: note: union declared here
16// :9:8: error: use of undefined value here causes undefined behavior
test/cases/safety/bad union field access.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, "access of inactive union field")) {
6 std.process.exit(0);
7 }
8 std.process.exit(1);
79}
810
911const Foo = union {
......@@ -21,5 +23,5 @@ fn bar(f: *Foo) void {
2123 f.float = 12.34;
2224}
2325// run
24// backend=stage1
25// target=native
\ No newline at end of file
26// backend=llvm
27// target=native