Commit 3b6e8fa59e

Andrew Kelley <andrew@ziglang.org>
2022-03-10 00:02:42
Sema: fix crash with `@sizeOf` on unions
1 parent fd85cfe
src/Module.zig
@@ -1281,7 +1281,7 @@ pub const Union = struct {
         var payload_align: u32 = 0;
         const fields = u.fields.values();
         for (fields) |field, i| {
-            if (!field.ty.hasRuntimeBits()) continue;
+            if (!field.ty.hasRuntimeBitsIgnoreComptime()) continue;
 
             const field_align = a: {
                 if (field.abi_align.tag() == .abi_align_default) {
src/Sema.zig
@@ -9955,6 +9955,7 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
         .BoundFn,
         .Opaque,
         => return sema.fail(block, src, "no size available for type '{}'", .{operand_ty}),
+
         .Type,
         .EnumLiteral,
         .ComptimeFloat,
src/type.zig
@@ -1967,7 +1967,9 @@ pub const Type = extern union {
     /// There are two reasons a type will return false:
     /// * the type is a comptime-only type. For example, the type `type` itself.
     /// * the type has only one possible value, making its ABI size 0.
-    pub fn hasRuntimeBits(ty: Type) bool {
+    /// When `ignore_comptime_only` is true, then types that are comptime only
+    /// may return false positives.
+    pub fn hasRuntimeBitsAdvanced(ty: Type, ignore_comptime_only: bool) bool {
         return switch (ty.tag()) {
             .u1,
             .u8,
@@ -2063,7 +2065,7 @@ pub const Type = extern union {
             .const_slice,
             .mut_slice,
             .pointer,
-            => !ty.comptimeOnly(),
+            => if (ignore_comptime_only) true else !comptimeOnly(ty),
 
             .@"struct" => {
                 const struct_obj = ty.castTag(.@"struct").?.data;
@@ -2075,7 +2077,7 @@ pub const Type = extern union {
                 }
                 assert(struct_obj.haveFieldTypes());
                 for (struct_obj.fields.values()) |value| {
-                    if (value.ty.hasRuntimeBits())
+                    if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only))
                         return true;
                 } else {
                     return false;
@@ -2093,14 +2095,14 @@ pub const Type = extern union {
             .enum_numbered, .enum_nonexhaustive => {
                 var buffer: Payload.Bits = undefined;
                 const int_tag_ty = ty.intTagType(&buffer);
-                return int_tag_ty.hasRuntimeBits();
+                return int_tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only);
             },
 
             .@"union" => {
                 const union_obj = ty.castTag(.@"union").?.data;
                 assert(union_obj.haveFieldTypes());
                 for (union_obj.fields.values()) |value| {
-                    if (value.ty.hasRuntimeBits())
+                    if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only))
                         return true;
                 } else {
                     return false;
@@ -2108,27 +2110,29 @@ pub const Type = extern union {
             },
             .union_tagged => {
                 const union_obj = ty.castTag(.union_tagged).?.data;
-                if (union_obj.tag_ty.hasRuntimeBits()) {
+                if (union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) {
                     return true;
                 }
                 assert(union_obj.haveFieldTypes());
                 for (union_obj.fields.values()) |value| {
-                    if (value.ty.hasRuntimeBits())
+                    if (value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only))
                         return true;
                 } else {
                     return false;
                 }
             },
 
-            .array, .vector => ty.arrayLen() != 0 and ty.elemType().hasRuntimeBits(),
+            .array, .vector => ty.arrayLen() != 0 and
+                ty.elemType().hasRuntimeBitsAdvanced(ignore_comptime_only),
             .array_u8 => ty.arrayLen() != 0,
-            .array_sentinel => ty.childType().hasRuntimeBits(),
+            .array_sentinel => ty.childType().hasRuntimeBitsAdvanced(ignore_comptime_only),
 
             .int_signed, .int_unsigned => ty.cast(Payload.Bits).?.data != 0,
 
             .error_union => {
                 const payload = ty.castTag(.error_union).?.data;
-                return payload.error_set.hasRuntimeBits() or payload.payload.hasRuntimeBits();
+                return payload.error_set.hasRuntimeBitsAdvanced(ignore_comptime_only) or
+                    payload.payload.hasRuntimeBitsAdvanced(ignore_comptime_only);
             },
 
             .tuple, .anon_struct => {
@@ -2136,7 +2140,7 @@ pub const Type = extern union {
                 for (tuple.types) |field_ty, i| {
                     const val = tuple.values[i];
                     if (val.tag() != .unreachable_value) continue; // comptime field
-                    if (field_ty.hasRuntimeBits()) return true;
+                    if (field_ty.hasRuntimeBitsAdvanced(ignore_comptime_only)) return true;
                 }
                 return false;
             },
@@ -2148,6 +2152,14 @@ pub const Type = extern union {
         };
     }
 
+    pub fn hasRuntimeBits(ty: Type) bool {
+        return hasRuntimeBitsAdvanced(ty, false);
+    }
+
+    pub fn hasRuntimeBitsIgnoreComptime(ty: Type) bool {
+        return hasRuntimeBitsAdvanced(ty, true);
+    }
+
     pub fn isFnOrHasRuntimeBits(ty: Type) bool {
         switch (ty.zigTypeTag()) {
             .Fn => {
test/behavior/for.zig
@@ -178,7 +178,8 @@ fn mangleString(s: []u8) void {
 }
 
 test "for copies its payload" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
@@ -186,7 +187,7 @@ test "for copies its payload" {
             for (x) |value, i| {
                 // Modify the original array
                 x[i] += 99;
-                try expectEqual(value, i + 1);
+                try expect(value == i + 1);
             }
         }
     };
test/behavior/sizeof_and_typeof.zig
@@ -233,7 +233,10 @@ test "@bitSizeOf" {
 }
 
 test "@sizeOf comparison against zero" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage1) {
+        // stage1 gets the wrong answer for size of pointers to zero bit types
+        return error.SkipZigTest;
+    }
 
     const S0 = struct {
         f: *@This(),
@@ -263,12 +266,13 @@ test "@sizeOf comparison against zero" {
     };
     const S = struct {
         fn doTheTest(comptime T: type, comptime result: bool) !void {
-            try expectEqual(result, @sizeOf(T) > 0);
+            try expect(result == (@sizeOf(T) > 0));
         }
     };
     // Zero-sized type
     try S.doTheTest(u0, false);
-    try S.doTheTest(*u0, false);
+    // Pointers to zero sized types still have addresses.
+    try S.doTheTest(*u0, true);
     // Non byte-sized type
     try S.doTheTest(u1, true);
     try S.doTheTest(*u1, true);