-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Uncaught segfault in debug mode: Safety of enum fields in packed structs is circumvented by @bitCast
#21372
Comments
@bitcast
@bitcast
@bitcast
@bitCast
|
In general, Your motivating example is probably not worth adding safety checks over IMHO: you turned it into an illegal enum, and back into a perfectly cromulent integer, without doing anything which would get you in trouble. But there are some consequences here which should be addressed nonetheless. This segfaults: const TwoEnum = enum(u8) {
one = 1,
two,
};
const PackedTwo = packed struct {
one_or_two: TwoEnum,
};
test "switch on bitcast" {
const three: u8 = 3;
const oops: PackedTwo = @bitCast(three);
switch (oops.one_or_two) {
.one => std.debug.print("fine\n", .{}),
.two => std.debug.print("also fine\n", .{}),
}
} It's hard to draw a clear line for how much responsibility the compiler should take when user code jukes the type system with a Debatable whether debug-mode I'd suggest editing the title of this issue to have the word "segfault" in it. The example you picked is basically harmless, but it can have effects which are not. |
After thinking about this, I support at least panic on invalid values. The proposal @rohlem linked I think is good. Here is a real-world use case code snippet from me https://github.com/kj4tmp/zecm: I am de-serializing a byte stream containing many bit fields and sparse enums. This can be made more convenient by using packed structs containing enums. I am using the little endian layout of packed structs to define a binary protocol schema. I realize this might not be an intended use case for packed structs, but it is certainly convenient (and does not extend very well to big-endian protocols :). I already expected a panic on invalid values and I have been very careful to make sure all my deserialized enums are non-exhaustive to force myself to handle invalid values from the byte stream. /// convert a packed struct to bytes that can be sent via ethercat
///
/// the packed struct must have bitwidth that is a multiple of 8
pub fn eCatFromPack(pack: anytype) [@divExact(@bitSizeOf(@TypeOf(pack)), 8)]u8 {
comptime assert(isECatPackable(@TypeOf(pack)));
var bytes: [@divExact(@bitSizeOf(@TypeOf(pack)), 8)]u8 = undefined;
switch (native_endian) {
.little => {
bytes = @bitCast(pack);
},
.big => {
bytes = @bitCast(pack);
std.mem.reverse(u8, &bytes);
},
}
return bytes;
}
pub fn isECatPackable(comptime T: type) bool {
if (@bitSizeOf(T) % 8 != 0) return false;
return switch (@typeInfo(T)) {
.@"struct" => |_struct| blk: {
// must be a packed struct
break :blk (_struct.layout == .@"packed");
},
.int, .float => true,
.@"union" => |_union| blk: {
// must be a packed union
break :blk (_union.layout == .@"packed");
},
else => false,
};
}
/// Read a packed struct, int, or float from a reader containing
/// EtherCAT (little endian) data into host endian representation.
pub fn packFromECatReader(comptime T: type, reader: anytype) !T {
comptime assert(isECatPackable(T));
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
try reader.readNoEof(&bytes);
return packFromECat(T, bytes);
}
pub fn packFromECat(comptime T: type, ecat_bytes: [@divExact(@bitSizeOf(T), 8)]u8) T {
comptime assert(isECatPackable(T));
switch (native_endian) {
.little => {
return @bitCast(ecat_bytes);
},
.big => {
var bytes_copy = ecat_bytes;
std.mem.reverse(u8, &bytes_copy);
return @bitCast(bytes_copy);
},
}
unreachable;
}
|
This isn't really the right place for me to give feedback on the approach you're taking with the library, but there are ways to accomplish what you're trying to do which don't circumvent the type system. Which why I suggest changing the title to reflect the fact that you (well, me, but there's no point filing a separate issue since it's the same problem) found a segfault in Debug mode.
const long: u64 = @bitCast(@as(f64, 3.14159)); That circumvents type safety. It isn't clear from your title or example why this use of For example, if we change the test in my example to this: test "switch on invalid enum" {
const three: u8 = 3;
const oops: TwoEnum = @enumFromInt(three);
switch (oops.one_or_two) {
.one => std.debug.print("fine\n", .{}),
.two => std.debug.print("also fine\n", .{}),
}
} It returns the error By extension, if there are illegal values for an enum in your packed structs, you can assign that enum to the field with |
@bitCast
@bitCast
Thanks for the example! I edited the issue description and title. |
Zig Version
0.14.0-dev.1511+54b668f8a
Steps to Reproduce and Observed Behavior
The following test fails:
Edit: (Thanks to @mnemnion ) Also this segfaults:
Expected Behavior
I expected a panic on the bitcast, or perhaps a compile error.
However, a compile error would make the embedded use case of packed structs directly representing memory registers harder to implement. Maybe require bitcasted enums to be non-exhaustive?
related:
#18462
#3647
The text was updated successfully, but these errors were encountered: