Commit d00da05ecb

Veikka Tuominen <git@vexu.eu>
2022-07-11 16:54:53
Sema: validate extern types
1 parent 3ceb27c
lib/std/start.zig
@@ -108,7 +108,7 @@ fn callMain2() noreturn {
     exit2(0);
 }
 
-fn wasiMain2() noreturn {
+fn wasiMain2() callconv(.C) noreturn {
     switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
         .Void => {
             root.main();
src/Sema.zig
@@ -4740,12 +4740,19 @@ pub fn analyzeExport(
 
     try mod.ensureDeclAnalyzed(exported_decl_index);
     const exported_decl = mod.declPtr(exported_decl_index);
-    // TODO run the same checks as we do for C ABI struct fields
-    switch (exported_decl.ty.zigTypeTag()) {
-        .Fn, .Int, .Enum, .Struct, .Union, .Array, .Float, .Pointer, .Optional => {},
-        else => return sema.fail(block, src, "unable to export type '{}'", .{
-            exported_decl.ty.fmt(sema.mod),
-        }),
+
+    if (!(try sema.validateExternType(exported_decl.ty, .other))) {
+        const msg = msg: {
+            const msg = try sema.errMsg(block, src, "unable to export type '{}'", .{exported_decl.ty.fmt(sema.mod)});
+            errdefer msg.destroy(sema.gpa);
+
+            const src_decl = sema.mod.declPtr(block.src_decl);
+            try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), exported_decl.ty, .other);
+
+            try sema.addDeclaredHereNote(msg, exported_decl.ty);
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(block, msg);
     }
 
     const gpa = mod.gpa;
@@ -13799,7 +13806,20 @@ fn validatePtrTy(sema: *Sema, block: *Block, elem_src: LazySrcLoc, ty: Type) Com
     } else if (ptr_info.size == .Many and pointee_tag == .Opaque) {
         return sema.fail(block, elem_src, "unknown-length pointer to opaque not allowed", .{});
     } else if (ptr_info.size == .C) {
-        // TODO check extern type
+        const elem_ty = ptr_info.pointee_type;
+        if (!(try sema.validateExternType(elem_ty, .other))) {
+            const msg = msg: {
+                const msg = try sema.errMsg(block, elem_src, "C pointers cannot point to non-C-ABI-compatible type '{}'", .{elem_ty.fmt(sema.mod)});
+                errdefer msg.destroy(sema.gpa);
+
+                const src_decl = sema.mod.declPtr(block.src_decl);
+                try sema.explainWhyTypeIsNotExtern(block, elem_src, msg, elem_src.toSrcLoc(src_decl), elem_ty, .other);
+
+                try sema.addDeclaredHereNote(msg, elem_ty);
+                break :msg msg;
+            };
+            return sema.failWithOwnedErrorMsg(block, msg);
+        }
         if (pointee_tag == .Opaque) {
             return sema.fail(block, elem_src, "C pointers cannot point to opaque types", .{});
         }
@@ -18169,6 +18189,119 @@ fn explainWhyTypeIsComptime(
     }
 }
 
+const ExternPosition = enum {
+    ret_ty,
+    param_ty,
+    other,
+};
+
+fn validateExternType(sema: *Sema, ty: Type, position: ExternPosition) CompileError!bool {
+    switch (ty.zigTypeTag()) {
+        .Type,
+        .ComptimeFloat,
+        .ComptimeInt,
+        .EnumLiteral,
+        .Undefined,
+        .Null,
+        .ErrorUnion,
+        .ErrorSet,
+        .BoundFn,
+        .Void,
+        .Frame,
+        => return false,
+        .NoReturn => return position == .ret_ty,
+        .Opaque,
+        .Bool,
+        .Float,
+        .Pointer,
+        .AnyFrame,
+        => return true,
+        .Int => switch (ty.intInfo(sema.mod.getTarget()).bits) {
+            8, 16, 32, 64, 128 => return true,
+            else => return false,
+        },
+        .Fn => return !ty.fnCallingConventionAllowsZigTypes(),
+        .Enum => {
+            var buf: Type.Payload.Bits = undefined;
+            return sema.validateExternType(ty.intTagType(&buf), position);
+        },
+        .Struct, .Union => switch (ty.containerLayout()) {
+            .Extern, .Packed => return true,
+            else => return false,
+        },
+        .Array => {
+            if (position == .ret_ty or position == .param_ty) return false;
+            return sema.validateExternType(ty.elemType2(), .other);
+        },
+        .Vector => return sema.validateExternType(ty.elemType2(), .other),
+        .Optional => return ty.isPtrLikeOptional(),
+    }
+}
+
+fn explainWhyTypeIsNotExtern(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    msg: *Module.ErrorMsg,
+    src_loc: Module.SrcLoc,
+    ty: Type,
+    position: ExternPosition,
+) CompileError!void {
+    const mod = sema.mod;
+    switch (ty.zigTypeTag()) {
+        .Opaque,
+        .Bool,
+        .Float,
+        .Pointer,
+        .AnyFrame,
+        => return,
+
+        .Type,
+        .ComptimeFloat,
+        .ComptimeInt,
+        .EnumLiteral,
+        .Undefined,
+        .Null,
+        .ErrorUnion,
+        .ErrorSet,
+        .BoundFn,
+        .Frame,
+        => return,
+
+        .Void => try mod.errNoteNonLazy(src_loc, msg, "'void' is a zero bit type; for C 'void' use 'anyopaque'", .{}),
+        .NoReturn => try mod.errNoteNonLazy(src_loc, msg, "'noreturn' is only allowed as a return type", .{}),
+        .Int => if (ty.intInfo(sema.mod.getTarget()).bits > 128) {
+            try mod.errNoteNonLazy(src_loc, msg, "only integers with less than 128 bits are extern compatible", .{});
+        } else {
+            try mod.errNoteNonLazy(src_loc, msg, "only integers with power of two bits are extern compatible", .{});
+        },
+        .Fn => switch (ty.fnCallingConvention()) {
+            .Unspecified => try mod.errNoteNonLazy(src_loc, msg, "extern function must specify calling convention", .{}),
+            .Async => try mod.errNoteNonLazy(src_loc, msg, "async function cannot be extern", .{}),
+            .Inline => try mod.errNoteNonLazy(src_loc, msg, "inline function cannot be extern", .{}),
+            else => return,
+        },
+        .Enum => {
+            var buf: Type.Payload.Bits = undefined;
+            const tag_ty = ty.intTagType(&buf);
+            try mod.errNoteNonLazy(src_loc, msg, "enum tag type '{}' is not extern compatible", .{tag_ty.fmt(sema.mod)});
+            try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, tag_ty, position);
+        },
+        .Struct => try mod.errNoteNonLazy(src_loc, msg, "only structs with packed or extern layout are extern compatible", .{}),
+        .Union => try mod.errNoteNonLazy(src_loc, msg, "only unions with packed or extern layout are extern compatible", .{}),
+        .Array => {
+            if (position == .ret_ty) {
+                try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a return type", .{});
+            } else if (position == .param_ty) {
+                try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a parameter type", .{});
+            }
+            try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, ty.elemType2(), position);
+        },
+        .Vector => try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, ty.elemType2(), position),
+        .Optional => try mod.errNoteNonLazy(src_loc, msg, "only pointer like optionals are extern compatible", .{}),
+    }
+}
+
 pub const PanicId = enum {
     unreach,
     unwrap_null,
@@ -24012,6 +24145,20 @@ fn resolveStructFully(
         struct_obj.status = .fully_resolved_wip;
         for (struct_obj.fields.values()) |field| {
             try sema.resolveTypeFully(block, src, field.ty);
+
+            if (struct_obj.layout == .Extern and !(try sema.validateExternType(field.ty, .other))) {
+                const msg = msg: {
+                    const msg = try sema.errMsg(block, src, "extern structs cannot contain fields of type '{}'", .{field.ty.fmt(sema.mod)});
+                    errdefer msg.destroy(sema.gpa);
+
+                    const src_decl = sema.mod.declPtr(block.src_decl);
+                    try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), field.ty, .other);
+
+                    try sema.addDeclaredHereNote(msg, field.ty);
+                    break :msg msg;
+                };
+                return sema.failWithOwnedErrorMsg(block, msg);
+            }
         }
         struct_obj.status = .fully_resolved;
     }
@@ -24045,6 +24192,20 @@ fn resolveUnionFully(
         union_obj.status = .fully_resolved_wip;
         for (union_obj.fields.values()) |field| {
             try sema.resolveTypeFully(block, src, field.ty);
+
+            if (union_obj.layout == .Extern and !(try sema.validateExternType(field.ty, .other))) {
+                const msg = msg: {
+                    const msg = try sema.errMsg(block, src, "extern unions cannot contain fields of type '{}'", .{field.ty.fmt(sema.mod)});
+                    errdefer msg.destroy(sema.gpa);
+
+                    const src_decl = sema.mod.declPtr(block.src_decl);
+                    try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), field.ty, .other);
+
+                    try sema.addDeclaredHereNote(msg, field.ty);
+                    break :msg msg;
+                };
+                return sema.failWithOwnedErrorMsg(block, msg);
+            }
         }
         union_obj.status = .fully_resolved;
     }
src/type.zig
@@ -3935,7 +3935,6 @@ pub const Type = extern union {
 
     /// Returns true if the type is optional and would be lowered to a single pointer
     /// address value, using 0 for null. Note that this returns true for C pointers.
-    /// See also `hasOptionalRepr`.
     pub fn isPtrLikeOptional(self: Type) bool {
         switch (self.tag()) {
             .optional_single_const_pointer,
@@ -4630,6 +4629,14 @@ pub const Type = extern union {
         };
     }
 
+    /// Asserts the type is a function.
+    pub fn fnCallingConventionAllowsZigTypes(self: Type) bool {
+        return switch (self.fnCallingConvention()) {
+            .Unspecified, .Async, .Inline, .PtxKernel => true,
+            else => false,
+        };
+    }
+
     /// Asserts the type is a function.
     pub fn fnIsVarArgs(self: Type) bool {
         return switch (self.tag()) {
test/cases/compile_errors/stage1/obj/exported_enum_without_explicit_integer_tag_type.zig
@@ -1,15 +0,0 @@
-const E = enum { one, two };
-comptime {
-    @export(E, .{ .name = "E" });
-}
-const e: E = .two;
-comptime {
-    @export(e, .{ .name = "e" });
-}
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:3:13: error: exported enum without explicit integer tag type
-// tmp.zig:7:13: error: exported enum value without explicit integer tag type
test/cases/compile_errors/stage1/obj/extern_struct_with_non-extern-compatible_integer_tag_type.zig
@@ -1,14 +0,0 @@
-pub const E = enum(u31) { A, B, C };
-pub const S = extern struct {
-    e: E,
-};
-export fn entry() void {
-    const s: S = undefined;
-    _ = s;
-}
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:3:5: error: extern structs cannot contain fields of type 'E'
test/cases/compile_errors/stage1/obj/invalid_optional_type_in_extern_struct.zig
@@ -1,10 +0,0 @@
-const stroo = extern struct {
-    moo: ?[*c]u8,
-};
-export fn testf(fluff: *stroo) void { _ = fluff; }
-
-// error
-// backend=stage1
-// target=native
-//
-// tmp.zig:2:5: error: extern structs cannot contain fields of type '?[*c]u8'
test/cases/compile_errors/stage1/obj/optional_pointer_to_void_in_extern_struct.zig → test/cases/compile_errors/stage1/optional_pointer_to_void_in_extern_struct.zig
File renamed without changes
test/cases/compile_errors/c_pointer_to_void.zig
@@ -0,0 +1,11 @@
+export fn entry() void {
+    var a: [*c]void = undefined;
+    _ = a;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :1:1: error: C pointers cannot point to non-C-ABI-compatible type 'void'
+// :1:1: note: 'void' is a zero bit type; for C 'void' use 'anyopaque'
test/cases/compile_errors/exported_enum_without_explicit_integer_tag_type.zig
@@ -0,0 +1,18 @@
+const E = enum { one, two };
+comptime {
+    @export(E, .{ .name = "E" });
+}
+const e: E = .two;
+comptime {
+    @export(e, .{ .name = "e" });
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :3:5: error: unable to export type 'type'
+// :7:5: error: unable to export type 'tmp.E'
+// :7:5: note: enum tag type 'u1' is not extern compatible
+// :7:5: note: only integers with power of two bits are extern compatible
+// :1:11: note: enum declared here
test/cases/compile_errors/stage1/obj/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig → test/cases/compile_errors/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig
@@ -25,19 +25,21 @@ pub const E = enum {
 @"227",@"228",@"229",@"230",@"231",@"232",@"233",@"234",@"235",
 @"236",@"237",@"238",@"239",@"240",@"241",@"242",@"243",@"244",
 @"245",@"246",@"247",@"248",@"249",@"250",@"251",@"252",@"253",
-@"254",@"255"
+@"254",@"255", @"256"
 };
 pub const S = extern struct {
     e: E,
 };
 export fn entry() void {
-    if (@typeInfo(E).Enum.tag_type != u8) @compileError("did not infer u8 tag type");
     const s: S = undefined;
     _ = s;
 }
 
 // error
-// backend=stage1
+// backend=stage2
 // target=native
 //
-// tmp.zig:31:5: error: extern structs cannot contain fields of type 'E'
+// :33:8: error: extern structs cannot contain fields of type 'tmp.E'
+// :33:8: note: enum tag type 'u9' is not extern compatible
+// :33:8: note: only integers with power of two bits are extern compatible
+// :1:15: note: enum declared here
test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig
@@ -0,0 +1,17 @@
+pub const E = enum(u31) { A, B, C };
+pub const S = extern struct {
+    e: E,
+};
+export fn entry() void {
+    const s: S = undefined;
+    _ = s;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :5:8: error: extern structs cannot contain fields of type 'tmp.E'
+// :5:8: note: enum tag type 'u31' is not extern compatible
+// :5:8: note: only integers with power of two bits are extern compatible
+// :1:15: note: enum declared here
test/cases/compile_errors/invalid_optional_type_in_extern_struct.zig
@@ -0,0 +1,11 @@
+const stroo = extern struct {
+    moo: ?[*c]u8,
+};
+export fn testf(fluff: *stroo) void { _ = fluff; }
+
+// error
+// backend=stage2
+// target=native
+//
+// :4:8: error: extern structs cannot contain fields of type '?[*c]u8'
+// :4:8: note: only pointer like optionals are extern compatible