Skip to content

Commit dc815e5

Browse files
authored
Merge pull request #12075 from Vexu/stage2-validate-extern
Stage2 validate extern types
2 parents 8b3f15f + 122c76a commit dc815e5

17 files changed

+260
-63
lines changed

lib/compiler_rt/multi3.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ const twords = extern union {
5959
s: S,
6060

6161
const S = if (native_endian == .Little)
62-
struct {
62+
extern struct {
6363
low: u64,
6464
high: u64,
6565
}
6666
else
67-
struct {
67+
extern struct {
6868
high: u64,
6969
low: u64,
7070
};

lib/compiler_rt/shift.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ fn Dwords(comptime T: type, comptime signed_half: bool) type {
3131

3232
all: T,
3333
s: if (native_endian == .Little)
34-
struct { low: HalfT, high: HalfT }
34+
extern struct { low: HalfT, high: HalfT }
3535
else
36-
struct { high: HalfT, low: HalfT },
36+
extern struct { high: HalfT, low: HalfT },
3737
};
3838
}
3939

lib/std/c/haiku.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub extern "c" fn _kern_get_current_team() i32;
3030
pub const sem_t = extern struct {
3131
type: i32,
3232
u: extern union {
33-
named_sem_id: ?i32,
34-
unnamed_sem: ?i32,
33+
named_sem_id: i32,
34+
unnamed_sem: i32,
3535
},
3636
padding: [2]i32,
3737
};

lib/std/start.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn callMain2() noreturn {
108108
exit2(0);
109109
}
110110

111-
fn wasiMain2() noreturn {
111+
fn wasiMain2() callconv(.C) noreturn {
112112
switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
113113
.Void => {
114114
root.main();

src/Sema.zig

Lines changed: 177 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4740,12 +4740,19 @@ pub fn analyzeExport(
47404740

47414741
try mod.ensureDeclAnalyzed(exported_decl_index);
47424742
const exported_decl = mod.declPtr(exported_decl_index);
4743-
// TODO run the same checks as we do for C ABI struct fields
4744-
switch (exported_decl.ty.zigTypeTag()) {
4745-
.Fn, .Int, .Enum, .Struct, .Union, .Array, .Float, .Pointer, .Optional => {},
4746-
else => return sema.fail(block, src, "unable to export type '{}'", .{
4747-
exported_decl.ty.fmt(sema.mod),
4748-
}),
4743+
4744+
if (!(try sema.validateExternType(exported_decl.ty, .other))) {
4745+
const msg = msg: {
4746+
const msg = try sema.errMsg(block, src, "unable to export type '{}'", .{exported_decl.ty.fmt(sema.mod)});
4747+
errdefer msg.destroy(sema.gpa);
4748+
4749+
const src_decl = sema.mod.declPtr(block.src_decl);
4750+
try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), exported_decl.ty, .other);
4751+
4752+
try sema.addDeclaredHereNote(msg, exported_decl.ty);
4753+
break :msg msg;
4754+
};
4755+
return sema.failWithOwnedErrorMsg(block, msg);
47494756
}
47504757

47514758
const gpa = mod.gpa;
@@ -13799,7 +13806,20 @@ fn validatePtrTy(sema: *Sema, block: *Block, elem_src: LazySrcLoc, ty: Type) Com
1379913806
} else if (ptr_info.size == .Many and pointee_tag == .Opaque) {
1380013807
return sema.fail(block, elem_src, "unknown-length pointer to opaque not allowed", .{});
1380113808
} else if (ptr_info.size == .C) {
13802-
// TODO check extern type
13809+
const elem_ty = ptr_info.pointee_type;
13810+
if (!(try sema.validateExternType(elem_ty, .other))) {
13811+
const msg = msg: {
13812+
const msg = try sema.errMsg(block, elem_src, "C pointers cannot point to non-C-ABI-compatible type '{}'", .{elem_ty.fmt(sema.mod)});
13813+
errdefer msg.destroy(sema.gpa);
13814+
13815+
const src_decl = sema.mod.declPtr(block.src_decl);
13816+
try sema.explainWhyTypeIsNotExtern(block, elem_src, msg, elem_src.toSrcLoc(src_decl), elem_ty, .other);
13817+
13818+
try sema.addDeclaredHereNote(msg, elem_ty);
13819+
break :msg msg;
13820+
};
13821+
return sema.failWithOwnedErrorMsg(block, msg);
13822+
}
1380313823
if (pointee_tag == .Opaque) {
1380413824
return sema.fail(block, elem_src, "C pointers cannot point to opaque types", .{});
1380513825
}
@@ -18098,10 +18118,12 @@ fn explainWhyTypeIsComptime(
1809818118
.NoReturn,
1809918119
.Undefined,
1810018120
.Null,
18101-
.Opaque,
18102-
.Optional,
1810318121
=> return,
1810418122

18123+
.Opaque => {
18124+
try mod.errNoteNonLazy(src_loc, msg, "opaque type '{}' has undefined size", .{ty.fmt(sema.mod)});
18125+
},
18126+
1810518127
.Array, .Vector => {
1810618128
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType());
1810718129
},
@@ -18124,6 +18146,10 @@ fn explainWhyTypeIsComptime(
1812418146
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType());
1812518147
},
1812618148

18149+
.Optional => {
18150+
var buf: Type.Payload.ElemType = undefined;
18151+
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.optionalChild(&buf));
18152+
},
1812718153
.ErrorUnion => {
1812818154
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.errorUnionPayload());
1812918155
},
@@ -18163,6 +18189,120 @@ fn explainWhyTypeIsComptime(
1816318189
}
1816418190
}
1816518191

18192+
const ExternPosition = enum {
18193+
ret_ty,
18194+
param_ty,
18195+
union_field,
18196+
other,
18197+
};
18198+
18199+
fn validateExternType(sema: *Sema, ty: Type, position: ExternPosition) CompileError!bool {
18200+
switch (ty.zigTypeTag()) {
18201+
.Type,
18202+
.ComptimeFloat,
18203+
.ComptimeInt,
18204+
.EnumLiteral,
18205+
.Undefined,
18206+
.Null,
18207+
.ErrorUnion,
18208+
.ErrorSet,
18209+
.BoundFn,
18210+
.Frame,
18211+
=> return false,
18212+
.Void => return position == .union_field,
18213+
.NoReturn => return position == .ret_ty,
18214+
.Opaque,
18215+
.Bool,
18216+
.Float,
18217+
.Pointer,
18218+
.AnyFrame,
18219+
=> return true,
18220+
.Int => switch (ty.intInfo(sema.mod.getTarget()).bits) {
18221+
8, 16, 32, 64, 128 => return true,
18222+
else => return false,
18223+
},
18224+
.Fn => return !ty.fnCallingConventionAllowsZigTypes(),
18225+
.Enum => {
18226+
var buf: Type.Payload.Bits = undefined;
18227+
return sema.validateExternType(ty.intTagType(&buf), position);
18228+
},
18229+
.Struct, .Union => switch (ty.containerLayout()) {
18230+
.Extern, .Packed => return true,
18231+
else => return false,
18232+
},
18233+
.Array => {
18234+
if (position == .ret_ty or position == .param_ty) return false;
18235+
return sema.validateExternType(ty.elemType2(), .other);
18236+
},
18237+
.Vector => return sema.validateExternType(ty.elemType2(), .other),
18238+
.Optional => return ty.isPtrLikeOptional(),
18239+
}
18240+
}
18241+
18242+
fn explainWhyTypeIsNotExtern(
18243+
sema: *Sema,
18244+
block: *Block,
18245+
src: LazySrcLoc,
18246+
msg: *Module.ErrorMsg,
18247+
src_loc: Module.SrcLoc,
18248+
ty: Type,
18249+
position: ExternPosition,
18250+
) CompileError!void {
18251+
const mod = sema.mod;
18252+
switch (ty.zigTypeTag()) {
18253+
.Opaque,
18254+
.Bool,
18255+
.Float,
18256+
.Pointer,
18257+
.AnyFrame,
18258+
=> return,
18259+
18260+
.Type,
18261+
.ComptimeFloat,
18262+
.ComptimeInt,
18263+
.EnumLiteral,
18264+
.Undefined,
18265+
.Null,
18266+
.ErrorUnion,
18267+
.ErrorSet,
18268+
.BoundFn,
18269+
.Frame,
18270+
=> return,
18271+
18272+
.Void => try mod.errNoteNonLazy(src_loc, msg, "'void' is a zero bit type; for C 'void' use 'anyopaque'", .{}),
18273+
.NoReturn => try mod.errNoteNonLazy(src_loc, msg, "'noreturn' is only allowed as a return type", .{}),
18274+
.Int => if (ty.intInfo(sema.mod.getTarget()).bits > 128) {
18275+
try mod.errNoteNonLazy(src_loc, msg, "only integers with less than 128 bits are extern compatible", .{});
18276+
} else {
18277+
try mod.errNoteNonLazy(src_loc, msg, "only integers with power of two bits are extern compatible", .{});
18278+
},
18279+
.Fn => switch (ty.fnCallingConvention()) {
18280+
.Unspecified => try mod.errNoteNonLazy(src_loc, msg, "extern function must specify calling convention", .{}),
18281+
.Async => try mod.errNoteNonLazy(src_loc, msg, "async function cannot be extern", .{}),
18282+
.Inline => try mod.errNoteNonLazy(src_loc, msg, "inline function cannot be extern", .{}),
18283+
else => return,
18284+
},
18285+
.Enum => {
18286+
var buf: Type.Payload.Bits = undefined;
18287+
const tag_ty = ty.intTagType(&buf);
18288+
try mod.errNoteNonLazy(src_loc, msg, "enum tag type '{}' is not extern compatible", .{tag_ty.fmt(sema.mod)});
18289+
try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, tag_ty, position);
18290+
},
18291+
.Struct => try mod.errNoteNonLazy(src_loc, msg, "only structs with packed or extern layout are extern compatible", .{}),
18292+
.Union => try mod.errNoteNonLazy(src_loc, msg, "only unions with packed or extern layout are extern compatible", .{}),
18293+
.Array => {
18294+
if (position == .ret_ty) {
18295+
try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a return type", .{});
18296+
} else if (position == .param_ty) {
18297+
try mod.errNoteNonLazy(src_loc, msg, "arrays are not allowed as a parameter type", .{});
18298+
}
18299+
try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, ty.elemType2(), position);
18300+
},
18301+
.Vector => try sema.explainWhyTypeIsNotExtern(block, src, msg, src_loc, ty.elemType2(), position),
18302+
.Optional => try mod.errNoteNonLazy(src_loc, msg, "only pointer like optionals are extern compatible", .{}),
18303+
}
18304+
}
18305+
1816618306
pub const PanicId = enum {
1816718307
unreach,
1816818308
unwrap_null,
@@ -24006,6 +24146,20 @@ fn resolveStructFully(
2400624146
struct_obj.status = .fully_resolved_wip;
2400724147
for (struct_obj.fields.values()) |field| {
2400824148
try sema.resolveTypeFully(block, src, field.ty);
24149+
24150+
if (struct_obj.layout == .Extern and !(try sema.validateExternType(field.ty, .other))) {
24151+
const msg = msg: {
24152+
const msg = try sema.errMsg(block, src, "extern structs cannot contain fields of type '{}'", .{field.ty.fmt(sema.mod)});
24153+
errdefer msg.destroy(sema.gpa);
24154+
24155+
const src_decl = sema.mod.declPtr(block.src_decl);
24156+
try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), field.ty, .other);
24157+
24158+
try sema.addDeclaredHereNote(msg, field.ty);
24159+
break :msg msg;
24160+
};
24161+
return sema.failWithOwnedErrorMsg(block, msg);
24162+
}
2400924163
}
2401024164
struct_obj.status = .fully_resolved;
2401124165
}
@@ -24039,6 +24193,20 @@ fn resolveUnionFully(
2403924193
union_obj.status = .fully_resolved_wip;
2404024194
for (union_obj.fields.values()) |field| {
2404124195
try sema.resolveTypeFully(block, src, field.ty);
24196+
24197+
if (union_obj.layout == .Extern and !(try sema.validateExternType(field.ty, .union_field))) {
24198+
const msg = msg: {
24199+
const msg = try sema.errMsg(block, src, "extern unions cannot contain fields of type '{}'", .{field.ty.fmt(sema.mod)});
24200+
errdefer msg.destroy(sema.gpa);
24201+
24202+
const src_decl = sema.mod.declPtr(block.src_decl);
24203+
try sema.explainWhyTypeIsNotExtern(block, src, msg, src.toSrcLoc(src_decl), field.ty, .union_field);
24204+
24205+
try sema.addDeclaredHereNote(msg, field.ty);
24206+
break :msg msg;
24207+
};
24208+
return sema.failWithOwnedErrorMsg(block, msg);
24209+
}
2404224210
}
2404324211
union_obj.status = .fully_resolved;
2404424212
}

src/type.zig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3935,7 +3935,6 @@ pub const Type = extern union {
39353935

39363936
/// Returns true if the type is optional and would be lowered to a single pointer
39373937
/// address value, using 0 for null. Note that this returns true for C pointers.
3938-
/// See also `hasOptionalRepr`.
39393938
pub fn isPtrLikeOptional(self: Type) bool {
39403939
switch (self.tag()) {
39413940
.optional_single_const_pointer,
@@ -4630,6 +4629,14 @@ pub const Type = extern union {
46304629
};
46314630
}
46324631

4632+
/// Asserts the type is a function.
4633+
pub fn fnCallingConventionAllowsZigTypes(self: Type) bool {
4634+
return switch (self.fnCallingConvention()) {
4635+
.Unspecified, .Async, .Inline, .PtxKernel => true,
4636+
else => false,
4637+
};
4638+
}
4639+
46334640
/// Asserts the type is a function.
46344641
pub fn fnIsVarArgs(self: Type) bool {
46354642
return switch (self.tag()) {

test/behavior/union.zig

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,19 @@ test "comptime union field access" {
8484

8585
const FooExtern = extern union {
8686
int: i32,
87-
str: struct {
88-
slice: []const u8,
87+
str: extern struct {
88+
slice: [*:0]const u8,
8989
},
9090
};
9191

9292
test "basic extern unions" {
93+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
9394
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
9495

9596
var foo = FooExtern{ .int = 1 };
9697
try expect(foo.int == 1);
9798
foo.str.slice = "Well";
98-
try expect(std.mem.eql(u8, foo.str.slice, "Well"));
99+
try expect(std.mem.eql(u8, std.mem.sliceTo(foo.str.slice, 0), "Well"));
99100
}
100101

101102
const ExternPtrOrInt = extern union {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export fn entry() void {
2+
var a: [*c]void = undefined;
3+
_ = a;
4+
}
5+
6+
// error
7+
// backend=stage2
8+
// target=native
9+
//
10+
// :1:1: error: C pointers cannot point to non-C-ABI-compatible type 'void'
11+
// :1:1: note: 'void' is a zero bit type; for C 'void' use 'anyopaque'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const E = enum { one, two };
2+
comptime {
3+
@export(E, .{ .name = "E" });
4+
}
5+
const e: E = .two;
6+
comptime {
7+
@export(e, .{ .name = "e" });
8+
}
9+
10+
// error
11+
// backend=stage2
12+
// target=native
13+
//
14+
// :3:5: error: unable to export type 'type'
15+
// :7:5: error: unable to export type 'tmp.E'
16+
// :7:5: note: enum tag type 'u1' is not extern compatible
17+
// :7:5: note: only integers with power of two bits are extern compatible
18+
// :1:11: note: enum declared here
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,21 @@ pub const E = enum {
2525
@"227",@"228",@"229",@"230",@"231",@"232",@"233",@"234",@"235",
2626
@"236",@"237",@"238",@"239",@"240",@"241",@"242",@"243",@"244",
2727
@"245",@"246",@"247",@"248",@"249",@"250",@"251",@"252",@"253",
28-
@"254",@"255"
28+
@"254",@"255", @"256"
2929
};
3030
pub const S = extern struct {
3131
e: E,
3232
};
3333
export fn entry() void {
34-
if (@typeInfo(E).Enum.tag_type != u8) @compileError("did not infer u8 tag type");
3534
const s: S = undefined;
3635
_ = s;
3736
}
3837

3938
// error
40-
// backend=stage1
39+
// backend=stage2
4140
// target=native
4241
//
43-
// tmp.zig:31:5: error: extern structs cannot contain fields of type 'E'
42+
// :33:8: error: extern structs cannot contain fields of type 'tmp.E'
43+
// :33:8: note: enum tag type 'u9' is not extern compatible
44+
// :33:8: note: only integers with power of two bits are extern compatible
45+
// :1:15: note: enum declared here

0 commit comments

Comments
 (0)