rust/tests/ui/structs-enums/enum-non-c-like-repr-c.rs
2023-01-11 09:32:08 +00:00

175 lines
5.0 KiB
Rust

// run-pass
// This test deserializes an enum in-place by transmuting to a union that
// should have the same layout, and manipulating the tag and payloads
// independently. This verifies that `repr(some_int)` has a stable representation,
// and that we don't miscompile these kinds of manipulations.
use std::time::Duration;
use std::mem;
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum MyEnum {
A(u32), // Single primitive value
B { x: u8, y: i16, z: u8 }, // Composite, and the offset of `y` and `z`
// depend on tag being internal
C, // Empty
D(Option<u32>), // Contains an enum
E(Duration), // Contains a struct
}
#[repr(C)]
struct MyEnumRepr {
tag: MyEnumTag,
payload: MyEnumPayload,
}
#[repr(C)]
#[allow(non_snake_case)]
union MyEnumPayload {
A: MyEnumVariantA,
B: MyEnumVariantB,
D: MyEnumVariantD,
E: MyEnumVariantE,
}
#[repr(C)] #[derive(Copy, Clone)] enum MyEnumTag { A, B, C, D, E }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantA(u32);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantB {x: u8, y: i16, z: u8 }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantD(Option<u32>);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantE(Duration);
fn main() {
let result: Vec<Result<MyEnum, ()>> = vec![
Ok(MyEnum::A(17)),
Ok(MyEnum::B { x: 206, y: 1145, z: 78 }),
Ok(MyEnum::C),
Err(()),
Ok(MyEnum::D(Some(407))),
Ok(MyEnum::D(None)),
Ok(MyEnum::E(Duration::from_secs(100))),
Err(()),
];
// Binary serialized version of the above (little-endian)
let input: Vec<u8> = vec![
0, 17, 0, 0, 0,
1, 206, 121, 4, 78,
2,
8, /* invalid tag value */
3, 0, 151, 1, 0, 0,
3, 1,
4, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, /* incomplete value */
];
let mut output = vec![];
let mut buf = &input[..];
unsafe {
// This should be safe, because we don't match on it unless it's fully formed,
// and it doesn't have a destructor.
//
// Furthermore, there are no types within MyEnum which cannot be initialized with zero,
// specifically, though padding and such are present, there are no references or similar
// types.
let mut dest: MyEnum = mem::zeroed();
while buf.len() > 0 {
match parse_my_enum(&mut dest, &mut buf) {
Ok(()) => output.push(Ok(dest)),
Err(()) => output.push(Err(())),
}
}
}
assert_eq!(output, result);
}
fn parse_my_enum<'a>(dest: &'a mut MyEnum, buf: &mut &[u8]) -> Result<(), ()> {
unsafe {
// Should be correct to do this transmute.
let dest: &'a mut MyEnumRepr = mem::transmute(dest);
let tag = read_u8(buf)?;
dest.tag = match tag {
0 => MyEnumTag::A,
1 => MyEnumTag::B,
2 => MyEnumTag::C,
3 => MyEnumTag::D,
4 => MyEnumTag::E,
_ => return Err(()),
};
match dest.tag {
MyEnumTag::A => {
dest.payload.A.0 = read_u32_le(buf)?;
}
MyEnumTag::B => {
dest.payload.B.x = read_u8(buf)?;
dest.payload.B.y = read_u16_le(buf)? as i16;
dest.payload.B.z = read_u8(buf)?;
}
MyEnumTag::C => {
/* do nothing */
}
MyEnumTag::D => {
let is_some = read_u8(buf)? == 0;
if is_some {
dest.payload.D.0 = Some(read_u32_le(buf)?);
} else {
dest.payload.D.0 = None;
}
}
MyEnumTag::E => {
let secs = read_u64_le(buf)?;
let nanos = read_u32_le(buf)?;
dest.payload.E.0 = Duration::new(secs, nanos);
}
}
Ok(())
}
}
// reader helpers
fn read_u64_le(buf: &mut &[u8]) -> Result<u64, ()> {
if buf.len() < 8 { return Err(()) }
let val = (buf[0] as u64) << 0
| (buf[1] as u64) << 8
| (buf[2] as u64) << 16
| (buf[3] as u64) << 24
| (buf[4] as u64) << 32
| (buf[5] as u64) << 40
| (buf[6] as u64) << 48
| (buf[7] as u64) << 56;
*buf = &buf[8..];
Ok(val)
}
fn read_u32_le(buf: &mut &[u8]) -> Result<u32, ()> {
if buf.len() < 4 { return Err(()) }
let val = (buf[0] as u32) << 0
| (buf[1] as u32) << 8
| (buf[2] as u32) << 16
| (buf[3] as u32) << 24;
*buf = &buf[4..];
Ok(val)
}
fn read_u16_le(buf: &mut &[u8]) -> Result<u16, ()> {
if buf.len() < 2 { return Err(()) }
let val = (buf[0] as u16) << 0
| (buf[1] as u16) << 8;
*buf = &buf[2..];
Ok(val)
}
fn read_u8(buf: &mut &[u8]) -> Result<u8, ()> {
if buf.len() < 1 { return Err(()) }
let val = buf[0];
*buf = &buf[1..];
Ok(val)
}