diff options
| author | Veikka Tuominen <git@vexu.eu> | 2022-07-11 17:54:53 +0300 |
|---|---|---|
| committer | Veikka Tuominen <git@vexu.eu> | 2022-07-11 18:47:29 +0300 |
| commit | d00da05ecbeca36e8ca25dcb1238bf98b38081a5 (patch) | |
| tree | 3c47892fb4a3485ba7a3400a523729b16484646b | |
| parent | 3ceb27c8401df17e5a7f522d68104da79501b5b6 (diff) | |
| download | zig-d00da05ecbeca36e8ca25dcb1238bf98b38081a5.tar.gz zig-d00da05ecbeca36e8ca25dcb1238bf98b38081a5.zip | |
Sema: validate extern types
12 files changed, 240 insertions, 52 deletions
diff --git a/lib/std/start.zig b/lib/std/start.zig index cf07986387..788c979d48 100644 --- 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 index 953336ee39..48cab0f04c 100644 --- 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 index f14ad3a85b..765f1da18c 100644 --- 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 new file mode 100644 index 0000000000..aeb5f42dec --- /dev/null +++ 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 new file mode 100644 index 0000000000..606dda4b6e --- /dev/null +++ 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/stage1/obj/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 index 8f45a0e5dc..1740574389 100644 --- a/test/cases/compile_errors/stage1/obj/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 @@ -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 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 new file mode 100644 index 0000000000..5fb36b707d --- /dev/null +++ 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 new file mode 100644 index 0000000000..9a6c77363c --- /dev/null +++ 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 deleted file mode 100644 index c432bedd6b..0000000000 --- a/test/cases/compile_errors/stage1/obj/exported_enum_without_explicit_integer_tag_type.zig +++ /dev/null @@ -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_non-extern-compatible_integer_tag_type.zig b/test/cases/compile_errors/stage1/obj/extern_struct_with_non-extern-compatible_integer_tag_type.zig deleted file mode 100644 index aa508056a1..0000000000 --- a/test/cases/compile_errors/stage1/obj/extern_struct_with_non-extern-compatible_integer_tag_type.zig +++ /dev/null @@ -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 deleted file mode 100644 index dc2e2690b8..0000000000 --- a/test/cases/compile_errors/stage1/obj/invalid_optional_type_in_extern_struct.zig +++ /dev/null @@ -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 index 552f672ad6..552f672ad6 100644 --- 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 |
