Skip to content

Stage2 validate extern types #12075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/compiler_rt/multi3.zig
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ const twords = extern union {
s: S,

const S = if (native_endian == .Little)
struct {
extern struct {
low: u64,
high: u64,
}
else
struct {
extern struct {
high: u64,
low: u64,
};
Expand Down
4 changes: 2 additions & 2 deletions lib/compiler_rt/shift.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ fn Dwords(comptime T: type, comptime signed_half: bool) type {

all: T,
s: if (native_endian == .Little)
struct { low: HalfT, high: HalfT }
extern struct { low: HalfT, high: HalfT }
else
struct { high: HalfT, low: HalfT },
extern struct { high: HalfT, low: HalfT },
};
}

Expand Down
4 changes: 2 additions & 2 deletions lib/std/c/haiku.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub extern "c" fn _kern_get_current_team() i32;
pub const sem_t = extern struct {
type: i32,
u: extern union {
named_sem_id: ?i32,
unnamed_sem: ?i32,
named_sem_id: i32,
unnamed_sem: i32,
},
padding: [2]i32,
};
Expand Down
2 changes: 1 addition & 1 deletion lib/std/start.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
186 changes: 177 additions & 9 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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", .{});
}
Expand Down Expand Up @@ -18098,10 +18118,12 @@ fn explainWhyTypeIsComptime(
.NoReturn,
.Undefined,
.Null,
.Opaque,
.Optional,
=> return,

.Opaque => {
try mod.errNoteNonLazy(src_loc, msg, "opaque type '{}' has undefined size", .{ty.fmt(sema.mod)});
},

.Array, .Vector => {
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType());
},
Expand All @@ -18124,6 +18146,10 @@ fn explainWhyTypeIsComptime(
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType());
},

.Optional => {
var buf: Type.Payload.ElemType = undefined;
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.optionalChild(&buf));
},
.ErrorUnion => {
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.errorUnionPayload());
},
Expand Down Expand Up @@ -18163,6 +18189,120 @@ fn explainWhyTypeIsComptime(
}
}

const ExternPosition = enum {
ret_ty,
param_ty,
union_field,
other,
};

fn validateExternType(sema: *Sema, ty: Type, position: ExternPosition) CompileError!bool {
switch (ty.zigTypeTag()) {
.Type,
.ComptimeFloat,
.ComptimeInt,
.EnumLiteral,
.Undefined,
.Null,
.ErrorUnion,
.ErrorSet,
.BoundFn,
.Frame,
=> return false,
.Void => return position == .union_field,
.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,
Expand Down Expand Up @@ -24006,6 +24146,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;
}
Expand Down Expand Up @@ -24039,6 +24193,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, .union_field))) {
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, .union_field);

try sema.addDeclaredHereNote(msg, field.ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
union_obj.status = .fully_resolved;
}
Expand Down
9 changes: 8 additions & 1 deletion src/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()) {
Expand Down
7 changes: 4 additions & 3 deletions test/behavior/union.zig
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,19 @@ test "comptime union field access" {

const FooExtern = extern union {
int: i32,
str: struct {
slice: []const u8,
str: extern struct {
slice: [*:0]const u8,
},
};

test "basic extern unions" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;

var foo = FooExtern{ .int = 1 };
try expect(foo.int == 1);
foo.str.slice = "Well";
try expect(std.mem.eql(u8, foo.str.slice, "Well"));
try expect(std.mem.eql(u8, std.mem.sliceTo(foo.str.slice, 0), "Well"));
}

const ExternPtrOrInt = extern union {
Expand Down
11 changes: 11 additions & 0 deletions test/cases/compile_errors/c_pointer_to_void.zig
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading