diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index 9c8bd8837628..eee3b366d066 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -43,7 +43,7 @@ pub struct TestGenerator { pub(crate) volatile_items: Vec, array_arg: Option, skip_private: bool, - skip_roundtrip: Option, + pub(crate) skip_roundtrip: Option, pub(crate) skip_signededness: Option, } diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index 5705cdfdc634..c7281c86a2fd 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -51,6 +51,7 @@ impl CTestTemplate { pub(crate) struct TestTemplate { pub field_ptr_tests: Vec, pub field_size_offset_tests: Vec, + pub roundtrip_tests: Vec, pub signededness_tests: Vec, pub size_align_tests: Vec, pub const_cstr_tests: Vec, @@ -76,6 +77,7 @@ impl TestTemplate { template.populate_signededness_tests(&helper)?; template.populate_field_size_offset_tests(&helper)?; template.populate_field_ptr_tests(&helper)?; + template.populate_roundtrip_tests(&helper)?; Ok(template) } @@ -245,6 +247,55 @@ impl TestTemplate { Ok(()) } + /// Populates roundtrip tests for aliases/structs/unions. + /// + /// It also keeps track of the names of each test. + fn populate_roundtrip_tests( + &mut self, + helper: &TranslateHelper, + ) -> Result<(), TranslationError> { + for alias in helper.ffi_items.aliases() { + let c_ty = helper.c_type(alias)?; + self.add_roundtrip_test(helper, alias.ident(), &[], &c_ty, true); + } + for struct_ in helper.ffi_items.structs() { + let c_ty = helper.c_type(struct_)?; + self.add_roundtrip_test(helper, struct_.ident(), &struct_.fields, &c_ty, false); + } + for union_ in helper.ffi_items.unions() { + let c_ty = helper.c_type(union_)?; + self.add_roundtrip_test(helper, union_.ident(), &union_.fields, &c_ty, false); + } + + Ok(()) + } + + fn add_roundtrip_test( + &mut self, + helper: &TranslateHelper, + ident: &str, + fields: &[Field], + c_ty: &str, + is_alias: bool, + ) { + let should_skip_roundtrip_test = helper + .generator + .skip_roundtrip + .as_ref() + .is_some_and(|skip| skip(ident)); + if !should_skip_roundtrip_test { + let item = TestRoundtrip { + test_name: roundtrip_test_ident(ident), + id: ident.into(), + fields: fields.iter().filter(|f| f.public).cloned().collect(), + c_ty: c_ty.into(), + is_alias, + }; + self.roundtrip_tests.push(item.clone()); + self.test_idents.push(item.test_name); + } + } + /// Populates field tests for structs/unions. /// /// It also keeps track of the names of each test. @@ -393,6 +444,15 @@ pub(crate) struct TestFieldSizeOffset { pub c_ty: BoxStr, } +#[derive(Clone, Debug)] +pub(crate) struct TestRoundtrip { + pub test_name: BoxStr, + pub id: BoxStr, + pub fields: Vec, + pub c_ty: BoxStr, + pub is_alias: bool, +} + fn signededness_test_ident(ident: &str) -> BoxStr { format!("ctest_signededness_{ident}").into() } @@ -417,6 +477,10 @@ fn field_size_offset_test_ident(ident: &str, field_ident: &str) -> BoxStr { format!("ctest_field_size_offset_{ident}_{field_ident}").into() } +fn roundtrip_test_ident(ident: &str) -> BoxStr { + format!("ctest_roundtrip_{ident}").into() +} + /// Wrap methods that depend on both ffi items and the generator. pub(crate) struct TranslateHelper<'a> { ffi_items: &'a FfiItems, diff --git a/ctest-next/templates/test.c b/ctest-next/templates/test.c index 6fabec490b45..dfa9b95778b0 100644 --- a/ctest-next/templates/test.c +++ b/ctest-next/templates/test.c @@ -78,3 +78,43 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) { return &b->{{ item.c_field }}; } {%- endfor +%} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif +{%- for item in ctx.roundtrip_tests +%} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +{{ item.c_ty }} ctest_roundtrip__{{ item.id }}( + {{ item.c_ty }} value, + const uint8_t is_padding_byte[sizeof({{ item.c_ty }})], + uint8_t value_bytes[sizeof({{ item.c_ty }})] +) { + int size = (int)sizeof({{ item.c_ty }}); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +{%- endfor +%} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/templates/test.rs b/ctest-next/templates/test.rs index 02b7923761b8..fe82f095da7c 100644 --- a/ctest-next/templates/test.rs +++ b/ctest-next/templates/test.rs @@ -9,7 +9,8 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::{CStr, c_char}; + #[allow(unused_imports)] + use std::ffi::{CStr, c_int, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[allow(unused_imports)] @@ -189,6 +190,129 @@ mod generated_tests { check_same(field_ptr.cast(), ctest_field_ptr, "field type {{ item.field.ident() }} of {{ item.id }}"); } + +{%- endfor +%} + +{%- for item in ctx.roundtrip_tests +%} + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__{{ item.id }}() -> Vec { + if {{ item.fields.len() }} == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!{{ item.is_alias }}; size_of::<{{ item.id }}>()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::<{{ item.id }}>::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + {%- for field in item.fields +%} + + let ty_ptr = unsafe { &raw const ((*bar).{{ field.ident() }}) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!({{ item.id }}, {{ field.ident() }}); + v.push((off, size)); + {%- endfor +%} + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::<{{ item.id }}>()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn {{ item.test_name }}() { + type U = {{ item.id }}; + extern "C" { + fn ctest_size_of__{{ item.id }}() -> u64; + fn ctest_roundtrip__{{ item.id }}( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__{{ item.id }}(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__{{ item.id }}() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of {{ item.c_ty }} is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::<{{ item.id }}>()]; + let r: U = unsafe { + ctest_roundtrip__{{ item.id }}(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"{{ item.id }}\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ item.id }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } {%- endfor +%} } diff --git a/ctest-next/tests/input/hierarchy.out.c b/ctest-next/tests/input/hierarchy.out.c index f7dbaf08abf3..246e4aef0f75 100644 --- a/ctest-next/tests/input/hierarchy.out.c +++ b/ctest-next/tests/input/hierarchy.out.c @@ -27,3 +27,40 @@ uint32_t ctest_signededness_of__in6_addr(void) { in6_addr all_ones = (in6_addr) -1; return all_ones < 0; } + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +in6_addr ctest_roundtrip__in6_addr( + in6_addr value, + const uint8_t is_padding_byte[sizeof(in6_addr)], + uint8_t value_bytes[sizeof(in6_addr)] +) { + int size = (int)sizeof(in6_addr); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/hierarchy.out.rs b/ctest-next/tests/input/hierarchy.out.rs index 17781198c42e..7a528abe8616 100644 --- a/ctest-next/tests/input/hierarchy.out.rs +++ b/ctest-next/tests/input/hierarchy.out.rs @@ -6,7 +6,8 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::{CStr, c_char}; + #[allow(unused_imports)] + use std::ffi::{CStr, c_int, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[allow(unused_imports)] @@ -102,6 +103,116 @@ mod generated_tests { check_same((all_ones < all_zeros) as u32, c_is_signed, "in6_addr signed"); } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__in6_addr() -> Vec { + if 0 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!true; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_in6_addr() { + type U = in6_addr; + extern "C" { + fn ctest_size_of__in6_addr() -> u64; + fn ctest_roundtrip__in6_addr( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__in6_addr(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__in6_addr() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of in6_addr is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__in6_addr(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"in6_addr\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"in6_addr\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } } use generated_tests::*; @@ -124,4 +235,5 @@ fn run_all() { ctest_const_ON(); ctest_size_align_in6_addr(); ctest_signededness_in6_addr(); + ctest_roundtrip_in6_addr(); } diff --git a/ctest-next/tests/input/macro.out.c b/ctest-next/tests/input/macro.out.c index 8c381ec67b83..e9772cc888ff 100644 --- a/ctest-next/tests/input/macro.out.c +++ b/ctest-next/tests/input/macro.out.c @@ -94,3 +94,67 @@ ctest_field_ty__VecU16__y ctest_field_ptr__VecU16__y(struct VecU16 *b) { return &b->y; } + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct VecU8 ctest_roundtrip__VecU8( + struct VecU8 value, + const uint8_t is_padding_byte[sizeof(struct VecU8)], + uint8_t value_bytes[sizeof(struct VecU8)] +) { + int size = (int)sizeof(struct VecU8); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct VecU16 ctest_roundtrip__VecU16( + struct VecU16 value, + const uint8_t is_padding_byte[sizeof(struct VecU16)], + uint8_t value_bytes[sizeof(struct VecU16)] +) { + int size = (int)sizeof(struct VecU16); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/macro.out.rs b/ctest-next/tests/input/macro.out.rs index 1dcebf274d22..95b52a2567d5 100644 --- a/ctest-next/tests/input/macro.out.rs +++ b/ctest-next/tests/input/macro.out.rs @@ -6,7 +6,8 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::{CStr, c_char}; + #[allow(unused_imports)] + use std::ffi::{CStr, c_int, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[allow(unused_imports)] @@ -253,6 +254,254 @@ mod generated_tests { check_same(field_ptr.cast(), ctest_field_ptr, "field type y of VecU16"); } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__VecU8() -> Vec { + if 2 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).x) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(VecU8, x); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).y) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(VecU8, y); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_VecU8() { + type U = VecU8; + extern "C" { + fn ctest_size_of__VecU8() -> u64; + fn ctest_roundtrip__VecU8( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__VecU8(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__VecU8() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of struct VecU8 is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__VecU8(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"VecU8\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU8\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__VecU16() -> Vec { + if 2 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).x) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(VecU16, x); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).y) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(VecU16, y); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_VecU16() { + type U = VecU16; + extern "C" { + fn ctest_size_of__VecU16() -> u64; + fn ctest_roundtrip__VecU16( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__VecU16(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__VecU16() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of struct VecU16 is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__VecU16(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"VecU16\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU16\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } } use generated_tests::*; @@ -282,4 +531,6 @@ fn run_all() { ctest_field_ptr_VecU8_y(); ctest_field_ptr_VecU16_x(); ctest_field_ptr_VecU16_y(); + ctest_roundtrip_VecU8(); + ctest_roundtrip_VecU16(); } diff --git a/ctest-next/tests/input/simple.out.with-renames.c b/ctest-next/tests/input/simple.out.with-renames.c index 3672a3ac5ed3..21577c84b432 100644 --- a/ctest-next/tests/input/simple.out.with-renames.c +++ b/ctest-next/tests/input/simple.out.with-renames.c @@ -142,3 +142,94 @@ ctest_field_ty__Word__byte ctest_field_ptr__Word__byte(union Word *b) { return &b->byte; } + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +Byte ctest_roundtrip__Byte( + Byte value, + const uint8_t is_padding_byte[sizeof(Byte)], + uint8_t value_bytes[sizeof(Byte)] +) { + int size = (int)sizeof(Byte); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct Person ctest_roundtrip__Person( + struct Person value, + const uint8_t is_padding_byte[sizeof(struct Person)], + uint8_t value_bytes[sizeof(struct Person)] +) { + int size = (int)sizeof(struct Person); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +union Word ctest_roundtrip__Word( + union Word value, + const uint8_t is_padding_byte[sizeof(union Word)], + uint8_t value_bytes[sizeof(union Word)] +) { + int size = (int)sizeof(union Word); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-renames.rs b/ctest-next/tests/input/simple.out.with-renames.rs index 929d9187c0d5..8e857409e54e 100644 --- a/ctest-next/tests/input/simple.out.with-renames.rs +++ b/ctest-next/tests/input/simple.out.with-renames.rs @@ -6,7 +6,8 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::{CStr, c_char}; + #[allow(unused_imports)] + use std::ffi::{CStr, c_int, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[allow(unused_imports)] @@ -376,6 +377,371 @@ mod generated_tests { check_same(field_ptr.cast(), ctest_field_ptr, "field type byte of Word"); } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Byte() -> Vec { + if 0 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!true; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Byte() { + type U = Byte; + extern "C" { + fn ctest_size_of__Byte() -> u64; + fn ctest_roundtrip__Byte( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Byte(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Byte() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of Byte is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Byte(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Byte\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Person() -> Vec { + if 3 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).name) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).age) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, age); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).job) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, job); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Person() { + type U = Person; + extern "C" { + fn ctest_size_of__Person() -> u64; + fn ctest_roundtrip__Person( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Person(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Person() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of struct Person is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Person(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Person\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Word() -> Vec { + if 2 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).word) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).byte) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Word, byte); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Word() { + type U = Word; + extern "C" { + fn ctest_size_of__Word() -> u64; + fn ctest_roundtrip__Word( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Word(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Word() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of union Word is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Word(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Word\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } } use generated_tests::*; @@ -411,4 +777,7 @@ fn run_all() { ctest_field_ptr_Person_job(); ctest_field_ptr_Word_word(); ctest_field_ptr_Word_byte(); + ctest_roundtrip_Byte(); + ctest_roundtrip_Person(); + ctest_roundtrip_Word(); } diff --git a/ctest-next/tests/input/simple.out.with-skips.c b/ctest-next/tests/input/simple.out.with-skips.c index 6657807b60b2..15ba758f40ad 100644 --- a/ctest-next/tests/input/simple.out.with-skips.c +++ b/ctest-next/tests/input/simple.out.with-skips.c @@ -134,3 +134,94 @@ ctest_field_ty__Word__byte ctest_field_ptr__Word__byte(union Word *b) { return &b->byte; } + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +Byte ctest_roundtrip__Byte( + Byte value, + const uint8_t is_padding_byte[sizeof(Byte)], + uint8_t value_bytes[sizeof(Byte)] +) { + int size = (int)sizeof(Byte); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +struct Person ctest_roundtrip__Person( + struct Person value, + const uint8_t is_padding_byte[sizeof(struct Person)], + uint8_t value_bytes[sizeof(struct Person)] +) { + int size = (int)sizeof(struct Person); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust +// remains unchanged. +// It checks if the size is the same as well as if the padding bytes are all in the correct place. +union Word ctest_roundtrip__Word( + union Word value, + const uint8_t is_padding_byte[sizeof(union Word)], + uint8_t value_bytes[sizeof(union Word)] +) { + int size = (int)sizeof(union Word); + // Mark `p` as volatile so that the C compiler does not optimize away the pattern we create. + // Otherwise the Rust side would not be able to see it. + volatile uint8_t* p = (volatile uint8_t*)&value; + int i = 0; + for (i = 0; i < size; ++i) { + // We skip padding bytes in both Rust and C because writing to it is undefined. + // Instead we just make sure the the placement of the padding bytes remains the same. + if (is_padding_byte[i]) { continue; } + value_bytes[i] = p[i]; + // After we check that the pattern remained unchanged from Rust to C, we invert the pattern + // and send it back to Rust to make sure that it remains unchanged from C to Rust. + uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256); + d = d == 0 ? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-skips.rs b/ctest-next/tests/input/simple.out.with-skips.rs index 31b1dc06698a..16eca7882733 100644 --- a/ctest-next/tests/input/simple.out.with-skips.rs +++ b/ctest-next/tests/input/simple.out.with-skips.rs @@ -6,7 +6,8 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::{CStr, c_char}; + #[allow(unused_imports)] + use std::ffi::{CStr, c_int, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[allow(unused_imports)] @@ -353,6 +354,371 @@ mod generated_tests { check_same(field_ptr.cast(), ctest_field_ptr, "field type byte of Word"); } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Byte() -> Vec { + if 0 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!true; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Byte() { + type U = Byte; + extern "C" { + fn ctest_size_of__Byte() -> u64; + fn ctest_roundtrip__Byte( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Byte(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Byte() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of Byte is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Byte(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Byte\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Person() -> Vec { + if 3 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).name) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).age) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, age); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).job) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Person, job); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Person() { + type U = Person; + extern "C" { + fn ctest_size_of__Person() -> u64; + fn ctest_roundtrip__Person( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Person(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Person() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of struct Person is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Person(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Person\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + + /// Generates a padding map for a specific type. + /// + /// Essentially, it returns a list of bytes, whose length is equal to the size of the type in + /// bytes. Each element corresponds to a byte and has two values. `true` if the byte is padding, + /// and `false` if the byte is not padding. + /// + /// For aliases we assume that there are no padding bytes, for structs and unions, + /// if there are no fields, then everything is padding, if there are fields, then we have to + /// go through each field and figure out the padding. + fn roundtrip_padding__Word() -> Vec { + if 2 == 0 { + // FIXME(ctest): What if it's an alias to a struct/union? + return vec![!false; size_of::()] + } + + // If there are no fields, v and bar become unused. + #[allow(unused_mut)] + let mut v = Vec::<(usize, usize)>::new(); + #[allow(unused_variables)] + let bar = MaybeUninit::::zeroed(); + #[allow(unused_variables)] + let bar = bar.as_ptr(); + + let ty_ptr = unsafe { &raw const ((*bar).word) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + + let ty_ptr = unsafe { &raw const ((*bar).byte) }; + let val = unsafe { ty_ptr.read_unaligned() }; + + let size = size_of_val(&val); + let off = offset_of!(Word, byte); + v.push((off, size)); + // This vector contains `true` if the byte is padding and `false` if the byte is not + // padding. Initialize all bytes as: + // - padding if we have fields, this means that only the fields will be checked + // - no-padding if we have a type alias: if this causes problems the type alias should + // be skipped + let mut is_padding_byte = vec![true; size_of::()]; + for (off, size) in &v { + for i in 0..*size { + is_padding_byte[off + i] = false; + } + } + is_padding_byte + } + + /// Tests whether a type alias when passed to C and back to Rust remains unchanged. + /// + /// It checks if the size is the same as well as if the padding bytes are all in the + /// correct place. For this test to be sound, `T` must be valid for any bitpattern. + pub fn ctest_roundtrip_Word() { + type U = Word; + extern "C" { + fn ctest_size_of__Word() -> u64; + fn ctest_roundtrip__Word( + input: MaybeUninit, is_padding_byte: *const bool, value_bytes: *mut u8 + ) -> U; + } + + const SIZE: usize = size_of::(); + + let is_padding_byte = roundtrip_padding__Word(); + let mut expected = vec![0u8; SIZE]; + let mut input = MaybeUninit::::zeroed(); + + let input_ptr = input.as_mut_ptr().cast::(); + + // Fill the unitialized memory with a deterministic pattern. + // From Rust to C: every byte will be labelled from 1 to 255, with 0 turning into 42. + // From C to Rust: every byte will be inverted from before (254 -> 1), but 0 is still 42. + for i in 0..SIZE { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + unsafe { + input_ptr.add(i).write_volatile(c); + expected[i] = d; + } + } + + let c_size = unsafe { ctest_size_of__Word() } as usize; + if SIZE != c_size { + FAILED.store(true, Ordering::Relaxed); + eprintln!( + "size of union Word is {c_size} in C and {SIZE} in Rust\n", + ); + return; + } + + let mut c_value_bytes = vec![0; size_of::()]; + let r: U = unsafe { + ctest_roundtrip__Word(input, is_padding_byte.as_ptr(), c_value_bytes.as_mut_ptr()) + }; + + // Check that the value bytes as read from C match the byte we sent from Rust. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = unsafe { *input_ptr.add(i) }; + let c = c_value_bytes[i]; + if rust != c { + eprintln!("rust[{}] = {} != {} (C): Rust \"Word\" -> C", i, rust, c); + FAILED.store(true, Ordering::Relaxed); + } + } + + // Check that value returned from C contains the bytes we expect. + for (i, is_padding_byte) in is_padding_byte.iter().enumerate() { + if *is_padding_byte { continue; } + let rust = expected[i] as usize; + let c = unsafe { (&raw const r).cast::().add(i).read_volatile() as usize }; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } } use generated_tests::*; @@ -387,4 +753,7 @@ fn run_all() { ctest_field_ptr_Person_job(); ctest_field_ptr_Word_word(); ctest_field_ptr_Word_byte(); + ctest_roundtrip_Byte(); + ctest_roundtrip_Person(); + ctest_roundtrip_Word(); }