commit d00da05ecbeca36e8ca25dcb1238bf98b38081a5 (tree) parent 3ceb27c8401df17e5a7f522d68104da79501b5b6 Author: Veikka Tuominen <git@vexu.eu> Date: Mon, 11 Jul 2022 17:54:53 +0300 Sema: validate extern types Diffstat:
13 files changed, 279 insertions(+), 91 deletions(-)
diff --git a/lib/std/start.zig b/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(); diff --git a/src/Sema.zig b/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; } diff --git a/src/type.zig b/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, @@ -4631,6 +4630,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()) { .fn_noreturn_no_args => false, diff --git a/test/cases/compile_errors/c_pointer_to_void.zig b/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' diff --git a/test/cases/compile_errors/exported_enum_without_explicit_integer_tag_type.zig b/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 diff --git a/test/cases/compile_errors/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig b/test/cases/compile_errors/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig @@ -0,0 +1,45 @@ +pub const E = enum { +@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"11",@"12", +@"13",@"14",@"15",@"16",@"17",@"18",@"19",@"20",@"21",@"22",@"23", +@"24",@"25",@"26",@"27",@"28",@"29",@"30",@"31",@"32",@"33",@"34", +@"35",@"36",@"37",@"38",@"39",@"40",@"41",@"42",@"43",@"44",@"45", +@"46",@"47",@"48",@"49",@"50",@"51",@"52",@"53",@"54",@"55",@"56", +@"57",@"58",@"59",@"60",@"61",@"62",@"63",@"64",@"65",@"66",@"67", +@"68",@"69",@"70",@"71",@"72",@"73",@"74",@"75",@"76",@"77",@"78", +@"79",@"80",@"81",@"82",@"83",@"84",@"85",@"86",@"87",@"88",@"89", +@"90",@"91",@"92",@"93",@"94",@"95",@"96",@"97",@"98",@"99",@"100", +@"101",@"102",@"103",@"104",@"105",@"106",@"107",@"108",@"109", +@"110",@"111",@"112",@"113",@"114",@"115",@"116",@"117",@"118", +@"119",@"120",@"121",@"122",@"123",@"124",@"125",@"126",@"127", +@"128",@"129",@"130",@"131",@"132",@"133",@"134",@"135",@"136", +@"137",@"138",@"139",@"140",@"141",@"142",@"143",@"144",@"145", +@"146",@"147",@"148",@"149",@"150",@"151",@"152",@"153",@"154", +@"155",@"156",@"157",@"158",@"159",@"160",@"161",@"162",@"163", +@"164",@"165",@"166",@"167",@"168",@"169",@"170",@"171",@"172", +@"173",@"174",@"175",@"176",@"177",@"178",@"179",@"180",@"181", +@"182",@"183",@"184",@"185",@"186",@"187",@"188",@"189",@"190", +@"191",@"192",@"193",@"194",@"195",@"196",@"197",@"198",@"199", +@"200",@"201",@"202",@"203",@"204",@"205",@"206",@"207",@"208", +@"209",@"210",@"211",@"212",@"213",@"214",@"215",@"216",@"217", +@"218",@"219",@"220",@"221",@"222",@"223",@"224",@"225",@"226", +@"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", @"256" +}; +pub const S = extern struct { + e: E, +}; +export fn entry() void { + const s: S = undefined; + _ = s; +} + +// error +// backend=stage2 +// target=native +// +// :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 diff --git a/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig b/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 diff --git a/test/cases/compile_errors/invalid_optional_type_in_extern_struct.zig b/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 diff --git a/test/cases/compile_errors/stage1/obj/exported_enum_without_explicit_integer_tag_type.zig b/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 diff --git a/test/cases/compile_errors/stage1/obj/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig b/test/cases/compile_errors/stage1/obj/extern_struct_with_extern-compatible_but_inferred_integer_tag_type.zig @@ -1,43 +0,0 @@ -pub const E = enum { -@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"11",@"12", -@"13",@"14",@"15",@"16",@"17",@"18",@"19",@"20",@"21",@"22",@"23", -@"24",@"25",@"26",@"27",@"28",@"29",@"30",@"31",@"32",@"33",@"34", -@"35",@"36",@"37",@"38",@"39",@"40",@"41",@"42",@"43",@"44",@"45", -@"46",@"47",@"48",@"49",@"50",@"51",@"52",@"53",@"54",@"55",@"56", -@"57",@"58",@"59",@"60",@"61",@"62",@"63",@"64",@"65",@"66",@"67", -@"68",@"69",@"70",@"71",@"72",@"73",@"74",@"75",@"76",@"77",@"78", -@"79",@"80",@"81",@"82",@"83",@"84",@"85",@"86",@"87",@"88",@"89", -@"90",@"91",@"92",@"93",@"94",@"95",@"96",@"97",@"98",@"99",@"100", -@"101",@"102",@"103",@"104",@"105",@"106",@"107",@"108",@"109", -@"110",@"111",@"112",@"113",@"114",@"115",@"116",@"117",@"118", -@"119",@"120",@"121",@"122",@"123",@"124",@"125",@"126",@"127", -@"128",@"129",@"130",@"131",@"132",@"133",@"134",@"135",@"136", -@"137",@"138",@"139",@"140",@"141",@"142",@"143",@"144",@"145", -@"146",@"147",@"148",@"149",@"150",@"151",@"152",@"153",@"154", -@"155",@"156",@"157",@"158",@"159",@"160",@"161",@"162",@"163", -@"164",@"165",@"166",@"167",@"168",@"169",@"170",@"171",@"172", -@"173",@"174",@"175",@"176",@"177",@"178",@"179",@"180",@"181", -@"182",@"183",@"184",@"185",@"186",@"187",@"188",@"189",@"190", -@"191",@"192",@"193",@"194",@"195",@"196",@"197",@"198",@"199", -@"200",@"201",@"202",@"203",@"204",@"205",@"206",@"207",@"208", -@"209",@"210",@"211",@"212",@"213",@"214",@"215",@"216",@"217", -@"218",@"219",@"220",@"221",@"222",@"223",@"224",@"225",@"226", -@"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" -}; -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 -// target=native -// -// tmp.zig:31:5: error: extern structs cannot contain fields of type 'E' diff --git a/test/cases/compile_errors/stage1/obj/extern_struct_with_non-extern-compatible_integer_tag_type.zig b/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' diff --git a/test/cases/compile_errors/stage1/obj/invalid_optional_type_in_extern_struct.zig b/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' diff --git a/test/cases/compile_errors/stage1/obj/optional_pointer_to_void_in_extern_struct.zig b/test/cases/compile_errors/stage1/optional_pointer_to_void_in_extern_struct.zig