diff --git a/ctest/templates/test.c b/ctest/templates/test.c index 71d084d1eb810..bcc4fc613319a 100644 --- a/ctest/templates/test.c +++ b/ctest/templates/test.c @@ -29,65 +29,78 @@ typedef void (*ctest_void_func)(void); +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ {%- for const_cstr in ctx.const_cstr_tests +%} static char *ctest_const_{{ const_cstr.id }}_val_static = {{ const_cstr.c_val }}; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN char *ctest_const_cstr__{{ const_cstr.id }}(void) { return ctest_const_{{ const_cstr.id }}_val_static; } {%- endfor +%} + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ {%- for constant in ctx.const_tests +%} static {{ constant.c_ty }} ctest_const_{{ constant.id }}_val_static = {{ constant.c_val }}; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN {{ constant.c_ty }} *ctest_const__{{ constant.id }}(void) { return &ctest_const_{{ constant.id }}_val_static; } {%- endfor +%} + +/* Query the size and alignment of all types */ {%- for item in ctx.size_align_tests +%} -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__{{ item.id }}(void) { return sizeof({{ item.c_ty }}); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__{{ item.id }}(void) { return CTEST_ALIGNOF({{ item.c_ty }}); } {%- endfor +%} + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ {%- for alias in ctx.signededness_tests +%} -// Return `1` if the type is signed, otherwise return `0`. -// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` CTEST_EXTERN uint32_t ctest_signededness_of__{{ alias.id }}(void) { {{ alias.c_ty }} all_ones = ({{ alias.c_ty }}) -1; return all_ones < 0; } {%- endfor +%} + +/* Query the offsets of fields and their sizes. */ {%- for item in ctx.field_size_offset_tests +%} -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}(void) { return offsetof({{ item.c_ty }}, {{ item.c_field }}); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) { return sizeof((({{ item.c_ty }}){}).{{ item.c_field }}); } {%- endfor +%} + +/* Query a pointer to a field given a pointer to its struct */ {%- for item in ctx.field_ptr_tests +%} -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. +{# + // This field can have a normal data type, or it could be a function pointer or an array, which + // have different syntax. A typedef is used for convenience, but the syntax must be precomputed. +#} typedef {{ item.volatile_keyword }}{{ item.field_return_type }}; CTEST_EXTERN ctest_field_ty__{{ item.id }}__{{ item.field.ident() }} ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) { @@ -107,28 +120,38 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #endif + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ {%- 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. CTEST_EXTERN {{ 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. + {# + // 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. + {# + // 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. + {# + // 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; @@ -154,9 +177,9 @@ CTEST_EXTERN {{ item.c_ty }} ctest_roundtrip__{{ item.id }}( #pragma warning(disable:4191) #endif +/* Query a function's pointer */ {%- for item in ctx.foreign_fn_tests +%} -// Return a function pointer. CTEST_EXTERN ctest_void_func ctest_foreign_fn__{{ item.id }}(void) { return (ctest_void_func){{ item.c_val }}; } @@ -167,11 +190,14 @@ CTEST_EXTERN ctest_void_func ctest_foreign_fn__{{ item.id }}(void) { #pragma warning(default:4191) #endif + +/* Query pointers to statics */ {%- for static_ in ctx.foreign_static_tests +%} -// Return a pointer to the static variable content. CTEST_EXTERN void *ctest_static__{{ static_.id }}(void) { - // FIXME(ctest): Not correct due to casting the function to a data pointer. + {# + // FIXME(ctest): Not correct due to casting the function to a data pointer. + #} return (void *)&{{ static_.c_val }}; } {%- endfor +%} diff --git a/ctest/templates/test.rs b/ctest/templates/test.rs index 5b2cd9690acbb..e35ee15624045 100644 --- a/ctest/templates/test.rs +++ b/ctest/templates/test.rs @@ -72,23 +72,25 @@ mod generated_tests { eprintln!("{s}"); } + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ {%- for const_cstr in ctx.const_cstr_tests +%} - // Test that the string constant is the same in both Rust and C. - // While fat pointers can't be translated, we instead use * const c_char. pub fn {{ const_cstr.test_name }}() { {{ ctest_extern }} "C" { fn ctest_const_cstr__{{ const_cstr.id }}() -> *const c_char; } - // SAFETY: we assume that `c_char` pointer consts are for C strings. + {# /* SAFETY: we assume that `c_char` pointer consts are for C strings. */ #} let r_val = unsafe { let r_ptr: *const c_char = {{ const_cstr.rust_val }}; assert!(!r_ptr.is_null(), "const `{{ const_cstr.rust_val }}` is null"); CStr::from_ptr(r_ptr) }; - // SAFETY: FFI call returns a valid C string. + {# /* SAFETY: FFI call returns a valid C string. */ #} let c_val = unsafe { let c_ptr: *const c_char = ctest_const_cstr__{{ const_cstr.id }}(); CStr::from_ptr(c_ptr) @@ -98,18 +100,23 @@ mod generated_tests { } {%- endfor +%} + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ {%- for constant in ctx.const_tests +%} - // Test that the value of the constant is the same in both Rust and C. - // This performs a byte by byte comparison of the constant value. pub fn {{ constant.test_name }}() { type T = {{ constant.rust_ty }}; {{ ctest_extern }} "C" { fn ctest_const__{{ constant.id }}() -> *const T; } - /* HACK: The slices may contain uninitialized data! We do this because - * there isn't a good way to recursively iterate all fields. */ + {# + /* HACK: The slices may contain uninitialized data! We do this because + * there isn't a good way to recursively iterate all fields. */ + #} let r_val: T = {{ constant.rust_val }}; let r_bytes = unsafe { @@ -125,9 +132,10 @@ mod generated_tests { } {%- endfor +%} + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ {%- for item in ctx.size_align_tests +%} - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn {{ item.test_name }}() { {{ ctest_extern }} "C" { fn ctest_size_of__{{ item.id }}() -> u64; @@ -145,13 +153,15 @@ mod generated_tests { } {%- endfor +%} + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ {%- for alias in ctx.signededness_tests +%} - /// Make sure that the signededness of a type alias in Rust and C is the same. - /// - /// This is done by casting 0 to that type and flipping all of its bits. For unsigned types, - /// this would result in a value larger than zero. For signed types, this results in a value - /// smaller than 0. pub fn {{ alias.test_name }}() { {{ ctest_extern }} "C" { fn ctest_signededness_of__{{ alias.id }}() -> u32; @@ -164,9 +174,10 @@ mod generated_tests { } {%- endfor +%} + +/* Make sure that the offset and size of a field in a struct/union is the same. */ {%- for item in ctx.field_size_offset_tests +%} - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn {{ item.test_name }}() { {{ ctest_extern }} "C" { fn ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() -> u64; @@ -176,26 +187,27 @@ mod generated_tests { let uninit_ty = MaybeUninit::<{{ item.id }}>::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + {# /* SAFETY: we assume the field access doesn't wrap */ #} let ty_ptr = unsafe { &raw const (*uninit_ty).{{ item.field.ident() }} }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + {# /* SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the + * test should be skipped. */ #} let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + {# /* SAFETY: FFI call with no preconditions */ #} let ctest_field_offset = unsafe { ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() }; check_same(offset_of!({{ item.id }}, {{ item.field.ident() }}) as u64, ctest_field_offset, "field offset `{{ item.field.ident() }}` of `{{ item.id }}`"); - // SAFETY: FFI call with no preconditions + {# /* SAFETY: FFI call with no preconditions */ #} let ctest_field_size = unsafe { ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `{{ item.field.ident() }}` of `{{ item.id }}`"); } {%- endfor +%} + +/* Tests if the pointer to the field is the same in Rust and C. */ {%- for item in ctx.field_ptr_tests +%} - /// Tests if the pointer to the field is the same in Rust and C. pub fn {{ item.test_name }}() { {{ ctest_extern }} "C" { fn ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(a: *const {{ item.id }}) -> *mut u8; @@ -215,24 +227,25 @@ mod generated_tests { {%- endfor +%} +/* 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. + */ {%- 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? + {# /* 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. + {# /* If there are no fields, v and bar become unused. */ #} #[allow(unused_mut)] let mut v = Vec::<(usize, usize)>::new(); #[allow(unused_variables)] @@ -248,11 +261,11 @@ mod generated_tests { 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 + {# /* 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 { @@ -262,10 +275,10 @@ mod generated_tests { 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. + {# /* 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 }}; {{ ctest_extern }} "C" { @@ -283,9 +296,10 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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. + {# /* Fill the uninitialized 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 }; @@ -311,7 +325,7 @@ mod generated_tests { 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. + {# /* 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) }; @@ -322,7 +336,7 @@ mod generated_tests { } } - // Check that value returned from C contains the bytes we expect. + {# /* 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; @@ -337,9 +351,9 @@ mod generated_tests { } {%- endfor +%} +/* Check if the Rust and C side function pointers point to the same underlying function. */ {%- for item in ctx.foreign_fn_tests +%} - /// Check if the Rust and C side function pointers point to the same underlying function. pub fn {{ item.test_name }}() { {{ ctest_extern }} "C" { fn ctest_foreign_fn__{{ item.id }}() -> unsafe extern "C" fn(); @@ -350,9 +364,9 @@ mod generated_tests { } {%- endfor +%} +/* Tests if the pointer to the static variable matches in both Rust and C. */ {%- for static_ in ctx.foreign_static_tests +%} - // Tests if the pointer to the static variable matches in both Rust and C. pub fn {{ static_.test_name }}() { {{ ctest_extern }} "C" { fn ctest_static__{{ static_.id }}() -> *const {{ static_.rust_ty }}; diff --git a/ctest/tests/input/hierarchy.out.c b/ctest/tests/input/hierarchy.out.c index 9e3d295597859..d34e157ee8f72 100644 --- a/ctest/tests/input/hierarchy.out.c +++ b/ctest/tests/input/hierarchy.out.c @@ -16,27 +16,49 @@ typedef void (*ctest_void_func)(void); +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + static bool ctest_const_ON_val_static = ON; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN bool *ctest_const__ON(void) { return &ctest_const_ON_val_static; } -// Return the size of a type. -CTEST_EXTERN uint64_t ctest_size_of__in6_addr(void) { return sizeof(in6_addr); } -// Return the alignment of a type. +/* Query the size and alignment of all types */ + +CTEST_EXTERN uint64_t ctest_size_of__in6_addr(void) { return sizeof(in6_addr); } CTEST_EXTERN uint64_t ctest_align_of__in6_addr(void) { return CTEST_ALIGNOF(in6_addr); } -// Return `1` if the type is signed, otherwise return `0`. -// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ + CTEST_EXTERN uint32_t ctest_signededness_of__in6_addr(void) { in6_addr all_ones = (in6_addr) -1; return all_ones < 0; } + +/* Query the offsets of fields and their sizes. */ + + +/* Query a pointer to a field given a pointer to its struct */ + #ifdef _MSC_VER // Disable signed/unsigned conversion warnings on MSVC. // These trigger even if the conversion is explicit. @@ -49,26 +71,28 @@ CTEST_EXTERN uint32_t ctest_signededness_of__in6_addr(void) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #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. + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ + CTEST_EXTERN 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; @@ -92,7 +116,8 @@ CTEST_EXTERN in6_addr ctest_roundtrip__in6_addr( #pragma warning(disable:4191) #endif -// Return a function pointer. +/* Query a function's pointer */ + CTEST_EXTERN ctest_void_func ctest_foreign_fn__malloc(void) { return (ctest_void_func)malloc; } @@ -102,8 +127,10 @@ CTEST_EXTERN ctest_void_func ctest_foreign_fn__malloc(void) { #pragma warning(default:4191) #endif -// Return a pointer to the static variable content. + +/* Query pointers to statics */ + CTEST_EXTERN void *ctest_static__in6addr_any(void) { - // FIXME(ctest): Not correct due to casting the function to a data pointer. + return (void *)&in6addr_any; } diff --git a/ctest/tests/input/hierarchy.out.rs b/ctest/tests/input/hierarchy.out.rs index 2538b797663a7..9a9ab6151488a 100644 --- a/ctest/tests/input/hierarchy.out.rs +++ b/ctest/tests/input/hierarchy.out.rs @@ -68,16 +68,24 @@ mod generated_tests { eprintln!("{s}"); } - // Test that the value of the constant is the same in both Rust and C. - // This performs a byte by byte comparison of the constant value. + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ + + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ + pub fn ctest_const_ON() { type T = bool; extern "C" { fn ctest_const__ON() -> *const T; } - /* HACK: The slices may contain uninitialized data! We do this because - * there isn't a good way to recursively iterate all fields. */ + let r_val: T = ON; let r_bytes = unsafe { @@ -92,7 +100,9 @@ mod generated_tests { check_same_bytes(r_bytes, c_bytes, "`ON` value"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ + pub fn ctest_size_align_in6_addr() { extern "C" { fn ctest_size_of__in6_addr() -> u64; @@ -109,11 +119,14 @@ mod generated_tests { check_same(rust_align, c_align, "`in6_addr` align"); } - /// Make sure that the signededness of a type alias in Rust and C is the same. - /// - /// This is done by casting 0 to that type and flipping all of its bits. For unsigned types, - /// this would result in a value larger than zero. For signed types, this results in a value - /// smaller than 0. + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ + pub fn ctest_signededness_in6_addr() { extern "C" { fn ctest_signededness_of__in6_addr() -> u32; @@ -125,33 +138,37 @@ 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. + +/* Make sure that the offset and size of a field in a struct/union is the same. */ + + +/* Tests if the pointer to the field is the same in Rust and C. */ + +/* 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 { @@ -161,10 +178,7 @@ mod generated_tests { 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" { @@ -182,9 +196,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -210,7 +222,7 @@ mod generated_tests { 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) }; @@ -221,7 +233,7 @@ mod generated_tests { } } - // 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; @@ -235,7 +247,8 @@ mod generated_tests { } } - /// Check if the Rust and C side function pointers point to the same underlying function. +/* Check if the Rust and C side function pointers point to the same underlying function. */ + pub fn ctest_foreign_fn_malloc() { extern "C" { fn ctest_foreign_fn__malloc() -> unsafe extern "C" fn(); @@ -245,7 +258,8 @@ mod generated_tests { check_same(actual, expected, "`malloc` function pointer"); } - // Tests if the pointer to the static variable matches in both Rust and C. +/* Tests if the pointer to the static variable matches in both Rust and C. */ + pub fn ctest_static_in6addr_any() { extern "C" { fn ctest_static__in6addr_any() -> *const in6_addr; diff --git a/ctest/tests/input/macro.out.c b/ctest/tests/input/macro.out.c index 975ba9a8e371d..fd66dbe297e8e 100644 --- a/ctest/tests/input/macro.out.c +++ b/ctest/tests/input/macro.out.c @@ -19,88 +19,95 @@ typedef void (*ctest_void_func)(void); -// Return the size of a type. -CTEST_EXTERN uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ -// Return the alignment of a type. + +/* Query the size and alignment of all types */ + +CTEST_EXTERN uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } CTEST_EXTERN uint64_t ctest_align_of__VecU8(void) { return CTEST_ALIGNOF(struct VecU8); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__VecU16(void) { return sizeof(struct VecU16); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__VecU16(void) { return CTEST_ALIGNOF(struct VecU16); } -// Return the offset of a struct/union field. + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ + + +/* Query the offsets of fields and their sizes. */ + CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) { return offsetof(struct VecU8, x); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) { return sizeof(((struct VecU8){}).x); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { return offsetof(struct VecU8, y); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) { return sizeof(((struct VecU8){}).y); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { return offsetof(struct VecU16, x); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) { return sizeof(((struct VecU16){}).x); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { return offsetof(struct VecU16, y); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) { return sizeof(((struct VecU16){}).y); } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + +/* Query a pointer to a field given a pointer to its struct */ + + typedef uint8_t *ctest_field_ty__VecU8__x; CTEST_EXTERN ctest_field_ty__VecU8__x ctest_field_ptr__VecU8__x(struct VecU8 *b) { return &b->x; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint8_t *ctest_field_ty__VecU8__y; CTEST_EXTERN ctest_field_ty__VecU8__y ctest_field_ptr__VecU8__y(struct VecU8 *b) { return &b->y; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint16_t *ctest_field_ty__VecU16__x; CTEST_EXTERN ctest_field_ty__VecU16__x ctest_field_ptr__VecU16__x(struct VecU16 *b) { return &b->x; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint16_t *ctest_field_ty__VecU16__y; CTEST_EXTERN ctest_field_ty__VecU16__y ctest_field_ptr__VecU16__y(struct VecU16 *b) { @@ -119,26 +126,28 @@ ctest_field_ptr__VecU16__y(struct VecU16 *b) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #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. + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ + CTEST_EXTERN 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; @@ -146,26 +155,20 @@ CTEST_EXTERN struct VecU8 ctest_roundtrip__VecU8( 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. CTEST_EXTERN 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; @@ -189,7 +192,12 @@ CTEST_EXTERN struct VecU16 ctest_roundtrip__VecU16( #pragma warning(disable:4191) #endif +/* Query a function's pointer */ + #ifdef _MSC_VER // Pop allow for 4191 #pragma warning(default:4191) #endif + + +/* Query pointers to statics */ diff --git a/ctest/tests/input/macro.out.edition-2024.c b/ctest/tests/input/macro.out.edition-2024.c index 975ba9a8e371d..fd66dbe297e8e 100644 --- a/ctest/tests/input/macro.out.edition-2024.c +++ b/ctest/tests/input/macro.out.edition-2024.c @@ -19,88 +19,95 @@ typedef void (*ctest_void_func)(void); -// Return the size of a type. -CTEST_EXTERN uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ -// Return the alignment of a type. + +/* Query the size and alignment of all types */ + +CTEST_EXTERN uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } CTEST_EXTERN uint64_t ctest_align_of__VecU8(void) { return CTEST_ALIGNOF(struct VecU8); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__VecU16(void) { return sizeof(struct VecU16); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__VecU16(void) { return CTEST_ALIGNOF(struct VecU16); } -// Return the offset of a struct/union field. + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ + + +/* Query the offsets of fields and their sizes. */ + CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) { return offsetof(struct VecU8, x); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) { return sizeof(((struct VecU8){}).x); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { return offsetof(struct VecU8, y); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) { return sizeof(((struct VecU8){}).y); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { return offsetof(struct VecU16, x); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) { return sizeof(((struct VecU16){}).x); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { return offsetof(struct VecU16, y); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) { return sizeof(((struct VecU16){}).y); } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + +/* Query a pointer to a field given a pointer to its struct */ + + typedef uint8_t *ctest_field_ty__VecU8__x; CTEST_EXTERN ctest_field_ty__VecU8__x ctest_field_ptr__VecU8__x(struct VecU8 *b) { return &b->x; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint8_t *ctest_field_ty__VecU8__y; CTEST_EXTERN ctest_field_ty__VecU8__y ctest_field_ptr__VecU8__y(struct VecU8 *b) { return &b->y; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint16_t *ctest_field_ty__VecU16__x; CTEST_EXTERN ctest_field_ty__VecU16__x ctest_field_ptr__VecU16__x(struct VecU16 *b) { return &b->x; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint16_t *ctest_field_ty__VecU16__y; CTEST_EXTERN ctest_field_ty__VecU16__y ctest_field_ptr__VecU16__y(struct VecU16 *b) { @@ -119,26 +126,28 @@ ctest_field_ptr__VecU16__y(struct VecU16 *b) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #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. + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ + CTEST_EXTERN 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; @@ -146,26 +155,20 @@ CTEST_EXTERN struct VecU8 ctest_roundtrip__VecU8( 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. CTEST_EXTERN 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; @@ -189,7 +192,12 @@ CTEST_EXTERN struct VecU16 ctest_roundtrip__VecU16( #pragma warning(disable:4191) #endif +/* Query a function's pointer */ + #ifdef _MSC_VER // Pop allow for 4191 #pragma warning(default:4191) #endif + + +/* Query pointers to statics */ diff --git a/ctest/tests/input/macro.out.edition-2024.rs b/ctest/tests/input/macro.out.edition-2024.rs index 0e4e3ec8b0b29..6970695f02ff0 100644 --- a/ctest/tests/input/macro.out.edition-2024.rs +++ b/ctest/tests/input/macro.out.edition-2024.rs @@ -68,7 +68,20 @@ mod generated_tests { eprintln!("{s}"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ + + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ + + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ + pub fn ctest_size_align_VecU8() { unsafe extern "C" { fn ctest_size_of__VecU8() -> u64; @@ -85,7 +98,6 @@ mod generated_tests { check_same(rust_align, c_align, "`VecU8` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_VecU16() { unsafe extern "C" { fn ctest_size_of__VecU16() -> u64; @@ -102,7 +114,17 @@ mod generated_tests { check_same(rust_align, c_align, "`VecU16` align"); } - /// Make sure that the offset and size of a field in a struct/union is the same. + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ + + +/* Make sure that the offset and size of a field in a struct/union is the same. */ + pub fn ctest_field_size_offset_VecU8_x() { unsafe extern "C" { fn ctest_offset_of__VecU8__x() -> u64; @@ -112,23 +134,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).x }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU8__x() }; check_same(offset_of!(VecU8, x) as u64, ctest_field_offset, "field offset `x` of `VecU8`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU8__x() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `x` of `VecU8`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU8_y() { unsafe extern "C" { fn ctest_offset_of__VecU8__y() -> u64; @@ -138,23 +158,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).y }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU8__y() }; check_same(offset_of!(VecU8, y) as u64, ctest_field_offset, "field offset `y` of `VecU8`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU8__y() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `y` of `VecU8`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU16_x() { unsafe extern "C" { fn ctest_offset_of__VecU16__x() -> u64; @@ -164,23 +182,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).x }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU16__x() }; check_same(offset_of!(VecU16, x) as u64, ctest_field_offset, "field offset `x` of `VecU16`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU16__x() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `x` of `VecU16`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU16_y() { unsafe extern "C" { fn ctest_offset_of__VecU16__y() -> u64; @@ -190,23 +206,24 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).y }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU16__y() }; check_same(offset_of!(VecU16, y) as u64, ctest_field_offset, "field offset `y` of `VecU16`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU16__y() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `y` of `VecU16`"); } - /// Tests if the pointer to the field is the same in Rust and C. + +/* Tests if the pointer to the field is the same in Rust and C. */ + pub fn ctest_field_ptr_VecU8_x() { unsafe extern "C" { fn ctest_field_ptr__VecU8__x(a: *const VecU8) -> *mut u8; @@ -224,7 +241,6 @@ mod generated_tests { "field pointer access `x` of `VecU8`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU8_y() { unsafe extern "C" { fn ctest_field_ptr__VecU8__y(a: *const VecU8) -> *mut u8; @@ -242,7 +258,6 @@ mod generated_tests { "field pointer access `y` of `VecU8`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU16_x() { unsafe extern "C" { fn ctest_field_ptr__VecU16__x(a: *const VecU16) -> *mut u8; @@ -260,7 +275,6 @@ mod generated_tests { "field pointer access `x` of `VecU16`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU16_y() { unsafe extern "C" { fn ctest_field_ptr__VecU16__y(a: *const VecU16) -> *mut u8; @@ -278,22 +292,24 @@ mod generated_tests { "field pointer access `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. +/* 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)] @@ -314,11 +330,7 @@ mod generated_tests { 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 { @@ -328,10 +340,7 @@ mod generated_tests { 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; unsafe extern "C" { @@ -349,9 +358,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -377,7 +384,7 @@ mod generated_tests { 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) }; @@ -388,7 +395,7 @@ mod generated_tests { } } - // 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; @@ -402,22 +409,13 @@ mod generated_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__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)] @@ -438,11 +436,7 @@ mod generated_tests { 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 { @@ -452,10 +446,7 @@ mod generated_tests { 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; unsafe extern "C" { @@ -473,9 +464,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -501,7 +490,7 @@ mod generated_tests { 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) }; @@ -512,7 +501,7 @@ mod generated_tests { } } - // 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; @@ -525,6 +514,10 @@ mod generated_tests { } } } + +/* Check if the Rust and C side function pointers point to the same underlying function. */ + +/* Tests if the pointer to the static variable matches in both Rust and C. */ } use generated_tests::*; diff --git a/ctest/tests/input/macro.out.rs b/ctest/tests/input/macro.out.rs index 451d92df8c491..25555e0c25650 100644 --- a/ctest/tests/input/macro.out.rs +++ b/ctest/tests/input/macro.out.rs @@ -68,7 +68,20 @@ mod generated_tests { eprintln!("{s}"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ + + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ + + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ + pub fn ctest_size_align_VecU8() { extern "C" { fn ctest_size_of__VecU8() -> u64; @@ -85,7 +98,6 @@ mod generated_tests { check_same(rust_align, c_align, "`VecU8` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_VecU16() { extern "C" { fn ctest_size_of__VecU16() -> u64; @@ -102,7 +114,17 @@ mod generated_tests { check_same(rust_align, c_align, "`VecU16` align"); } - /// Make sure that the offset and size of a field in a struct/union is the same. + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ + + +/* Make sure that the offset and size of a field in a struct/union is the same. */ + pub fn ctest_field_size_offset_VecU8_x() { extern "C" { fn ctest_offset_of__VecU8__x() -> u64; @@ -112,23 +134,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).x }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU8__x() }; check_same(offset_of!(VecU8, x) as u64, ctest_field_offset, "field offset `x` of `VecU8`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU8__x() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `x` of `VecU8`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU8_y() { extern "C" { fn ctest_offset_of__VecU8__y() -> u64; @@ -138,23 +158,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).y }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU8__y() }; check_same(offset_of!(VecU8, y) as u64, ctest_field_offset, "field offset `y` of `VecU8`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU8__y() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `y` of `VecU8`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU16_x() { extern "C" { fn ctest_offset_of__VecU16__x() -> u64; @@ -164,23 +182,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).x }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU16__x() }; check_same(offset_of!(VecU16, x) as u64, ctest_field_offset, "field offset `x` of `VecU16`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU16__x() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `x` of `VecU16`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_VecU16_y() { extern "C" { fn ctest_offset_of__VecU16__y() -> u64; @@ -190,23 +206,24 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).y }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__VecU16__y() }; check_same(offset_of!(VecU16, y) as u64, ctest_field_offset, "field offset `y` of `VecU16`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__VecU16__y() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `y` of `VecU16`"); } - /// Tests if the pointer to the field is the same in Rust and C. + +/* Tests if the pointer to the field is the same in Rust and C. */ + pub fn ctest_field_ptr_VecU8_x() { extern "C" { fn ctest_field_ptr__VecU8__x(a: *const VecU8) -> *mut u8; @@ -224,7 +241,6 @@ mod generated_tests { "field pointer access `x` of `VecU8`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU8_y() { extern "C" { fn ctest_field_ptr__VecU8__y(a: *const VecU8) -> *mut u8; @@ -242,7 +258,6 @@ mod generated_tests { "field pointer access `y` of `VecU8`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU16_x() { extern "C" { fn ctest_field_ptr__VecU16__x(a: *const VecU16) -> *mut u8; @@ -260,7 +275,6 @@ mod generated_tests { "field pointer access `x` of `VecU16`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_VecU16_y() { extern "C" { fn ctest_field_ptr__VecU16__y(a: *const VecU16) -> *mut u8; @@ -278,22 +292,24 @@ mod generated_tests { "field pointer access `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. +/* 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)] @@ -314,11 +330,7 @@ mod generated_tests { 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 { @@ -328,10 +340,7 @@ mod generated_tests { 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" { @@ -349,9 +358,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -377,7 +384,7 @@ mod generated_tests { 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) }; @@ -388,7 +395,7 @@ mod generated_tests { } } - // 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; @@ -402,22 +409,13 @@ mod generated_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__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)] @@ -438,11 +436,7 @@ mod generated_tests { 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 { @@ -452,10 +446,7 @@ mod generated_tests { 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" { @@ -473,9 +464,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -501,7 +490,7 @@ mod generated_tests { 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) }; @@ -512,7 +501,7 @@ mod generated_tests { } } - // 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; @@ -525,6 +514,10 @@ mod generated_tests { } } } + +/* Check if the Rust and C side function pointers point to the same underlying function. */ + +/* Tests if the pointer to the static variable matches in both Rust and C. */ } use generated_tests::*; diff --git a/ctest/tests/input/simple.out.with-renames.c b/ctest/tests/input/simple.out.with-renames.c index 3f94ab81c3b9f..0d4dddccfde43 100644 --- a/ctest/tests/input/simple.out.with-renames.c +++ b/ctest/tests/input/simple.out.with-renames.c @@ -16,204 +16,177 @@ typedef void (*ctest_void_func)(void); +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + static char *ctest_const_A_val_static = A; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN char *ctest_const_cstr__A(void) { return ctest_const_A_val_static; } static char *ctest_const_B_val_static = C_B; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN char *ctest_const_cstr__B(void) { return ctest_const_B_val_static; } + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + static enum Color ctest_const_RED_val_static = RED; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN enum Color *ctest_const__RED(void) { return &ctest_const_RED_val_static; } static enum Color ctest_const_BLUE_val_static = BLUE; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN enum Color *ctest_const__BLUE(void) { return &ctest_const_BLUE_val_static; } static enum Color ctest_const_GREEN_val_static = GREEN; -// Define a function that returns a pointer to the value of the constant to test. -// This will later be called on the Rust side via FFI. CTEST_EXTERN enum Color *ctest_const__GREEN(void) { return &ctest_const_GREEN_val_static; } -// Return the size of a type. -CTEST_EXTERN uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } -// Return the alignment of a type. +/* Query the size and alignment of all types */ + +CTEST_EXTERN uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } CTEST_EXTERN uint64_t ctest_align_of__Byte(void) { return CTEST_ALIGNOF(Byte); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__volatile_char(void) { return sizeof(volatile_char); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__volatile_char(void) { return CTEST_ALIGNOF(volatile_char); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__gregset_t(void) { return sizeof(gregset_t); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__gregset_t(void) { return CTEST_ALIGNOF(gregset_t); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__Color(void) { return sizeof(enum Color); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__Color(void) { return CTEST_ALIGNOF(enum Color); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__Person(void) { return CTEST_ALIGNOF(struct Person); } -// Return the size of a type. CTEST_EXTERN uint64_t ctest_size_of__Word(void) { return sizeof(union Word); } - -// Return the alignment of a type. CTEST_EXTERN uint64_t ctest_align_of__Word(void) { return CTEST_ALIGNOF(union Word); } -// Return `1` if the type is signed, otherwise return `0`. -// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ + CTEST_EXTERN uint32_t ctest_signededness_of__Byte(void) { Byte all_ones = (Byte) -1; return all_ones < 0; } -// Return `1` if the type is signed, otherwise return `0`. -// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` CTEST_EXTERN uint32_t ctest_signededness_of__volatile_char(void) { volatile_char all_ones = (volatile_char) -1; return all_ones < 0; } -// Return the offset of a struct/union field. + +/* Query the offsets of fields and their sizes. */ + CTEST_EXTERN uint64_t ctest_offset_of__Person__name(void) { return offsetof(struct Person, name); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Person__name(void) { return sizeof(((struct Person){}).name); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__Person__age(void) { return offsetof(struct Person, age); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Person__age(void) { return sizeof(((struct Person){}).age); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__Person__job(void) { return offsetof(struct Person, job); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Person__job(void) { return sizeof(((struct Person){}).job); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__Person__favorite_color(void) { return offsetof(struct Person, favorite_color); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Person__favorite_color(void) { return sizeof(((struct Person){}).favorite_color); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__Word__word(void) { return offsetof(union Word, word); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Word__word(void) { return sizeof(((union Word){}).word); } -// Return the offset of a struct/union field. CTEST_EXTERN uint64_t ctest_offset_of__Word__byte(void) { return offsetof(union Word, byte); } -// Return the size of a struct/union field. CTEST_EXTERN uint64_t ctest_size_of__Word__byte(void) { return sizeof(((union Word){}).byte); } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + +/* Query a pointer to a field given a pointer to its struct */ + + typedef const char **ctest_field_ty__Person__name; CTEST_EXTERN ctest_field_ty__Person__name ctest_field_ptr__Person__name(struct Person *b) { return &b->name; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint8_t *ctest_field_ty__Person__age; CTEST_EXTERN ctest_field_ty__Person__age ctest_field_ptr__Person__age(struct Person *b) { return &b->age; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef void (**ctest_field_ty__Person__job)(uint8_t, const char *); CTEST_EXTERN ctest_field_ty__Person__job ctest_field_ptr__Person__job(struct Person *b) { return &b->job; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef enum Color *ctest_field_ty__Person__favorite_color; CTEST_EXTERN ctest_field_ty__Person__favorite_color ctest_field_ptr__Person__favorite_color(struct Person *b) { return &b->favorite_color; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef uint16_t *ctest_field_ty__Word__word; CTEST_EXTERN ctest_field_ty__Word__word ctest_field_ptr__Word__word(union Word *b) { return &b->word; } -// Return a pointer to a struct/union field. -// This field can have a normal data type, or it could be a function pointer or an array, which -// have different syntax. A typedef is used for convenience, but the syntax must be precomputed. + typedef Byte (*ctest_field_ty__Word__byte)[2]; CTEST_EXTERN ctest_field_ty__Word__byte ctest_field_ptr__Word__byte(union Word *b) { @@ -232,26 +205,28 @@ ctest_field_ptr__Word__byte(union Word *b) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #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. + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ + CTEST_EXTERN 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; @@ -259,26 +234,20 @@ CTEST_EXTERN Byte ctest_roundtrip__Byte( 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. CTEST_EXTERN volatile_char ctest_roundtrip__volatile_char( volatile_char value, const uint8_t is_padding_byte[sizeof(volatile_char)], uint8_t value_bytes[sizeof(volatile_char)] ) { int size = (int)sizeof(volatile_char); - // 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; @@ -286,26 +255,20 @@ CTEST_EXTERN volatile_char ctest_roundtrip__volatile_char( 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. CTEST_EXTERN enum Color ctest_roundtrip__Color( enum Color value, const uint8_t is_padding_byte[sizeof(enum Color)], uint8_t value_bytes[sizeof(enum Color)] ) { int size = (int)sizeof(enum Color); - // 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; @@ -313,26 +276,20 @@ CTEST_EXTERN enum Color ctest_roundtrip__Color( 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. CTEST_EXTERN 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; @@ -340,26 +297,20 @@ CTEST_EXTERN struct Person ctest_roundtrip__Person( 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. CTEST_EXTERN 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; @@ -383,7 +334,8 @@ CTEST_EXTERN union Word ctest_roundtrip__Word( #pragma warning(disable:4191) #endif -// Return a function pointer. +/* Query a function's pointer */ + CTEST_EXTERN ctest_void_func ctest_foreign_fn__calloc(void) { return (ctest_void_func)calloc; } @@ -393,8 +345,10 @@ CTEST_EXTERN ctest_void_func ctest_foreign_fn__calloc(void) { #pragma warning(default:4191) #endif -// Return a pointer to the static variable content. + +/* Query pointers to statics */ + CTEST_EXTERN void *ctest_static__byte(void) { - // FIXME(ctest): Not correct due to casting the function to a data pointer. + return (void *)&byte; } diff --git a/ctest/tests/input/simple.out.with-renames.rs b/ctest/tests/input/simple.out.with-renames.rs index bc0d9d014d1a0..b025039103584 100644 --- a/ctest/tests/input/simple.out.with-renames.rs +++ b/ctest/tests/input/simple.out.with-renames.rs @@ -68,21 +68,24 @@ mod generated_tests { eprintln!("{s}"); } - // Test that the string constant is the same in both Rust and C. - // While fat pointers can't be translated, we instead use * const c_char. + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ + pub fn ctest_const_cstr_A() { extern "C" { fn ctest_const_cstr__A() -> *const c_char; } - // SAFETY: we assume that `c_char` pointer consts are for C strings. + let r_val = unsafe { let r_ptr: *const c_char = A; assert!(!r_ptr.is_null(), "const `A` is null"); CStr::from_ptr(r_ptr) }; - // SAFETY: FFI call returns a valid C string. + let c_val = unsafe { let c_ptr: *const c_char = ctest_const_cstr__A(); CStr::from_ptr(c_ptr) @@ -91,21 +94,19 @@ mod generated_tests { check_same(r_val, c_val, "const `A` string"); } - // Test that the string constant is the same in both Rust and C. - // While fat pointers can't be translated, we instead use * const c_char. pub fn ctest_const_cstr_B() { extern "C" { fn ctest_const_cstr__B() -> *const c_char; } - // SAFETY: we assume that `c_char` pointer consts are for C strings. + let r_val = unsafe { let r_ptr: *const c_char = B; assert!(!r_ptr.is_null(), "const `B` is null"); CStr::from_ptr(r_ptr) }; - // SAFETY: FFI call returns a valid C string. + let c_val = unsafe { let c_ptr: *const c_char = ctest_const_cstr__B(); CStr::from_ptr(c_ptr) @@ -114,16 +115,19 @@ mod generated_tests { check_same(r_val, c_val, "const `B` string"); } - // Test that the value of the constant is the same in both Rust and C. - // This performs a byte by byte comparison of the constant value. + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ + pub fn ctest_const_RED() { type T = Color; extern "C" { fn ctest_const__RED() -> *const T; } - /* HACK: The slices may contain uninitialized data! We do this because - * there isn't a good way to recursively iterate all fields. */ + let r_val: T = RED; let r_bytes = unsafe { @@ -138,16 +142,13 @@ mod generated_tests { check_same_bytes(r_bytes, c_bytes, "`RED` value"); } - // Test that the value of the constant is the same in both Rust and C. - // This performs a byte by byte comparison of the constant value. pub fn ctest_const_BLUE() { type T = Color; extern "C" { fn ctest_const__BLUE() -> *const T; } - /* HACK: The slices may contain uninitialized data! We do this because - * there isn't a good way to recursively iterate all fields. */ + let r_val: T = BLUE; let r_bytes = unsafe { @@ -162,16 +163,13 @@ mod generated_tests { check_same_bytes(r_bytes, c_bytes, "`BLUE` value"); } - // Test that the value of the constant is the same in both Rust and C. - // This performs a byte by byte comparison of the constant value. pub fn ctest_const_GREEN() { type T = Color; extern "C" { fn ctest_const__GREEN() -> *const T; } - /* HACK: The slices may contain uninitialized data! We do this because - * there isn't a good way to recursively iterate all fields. */ + let r_val: T = GREEN; let r_bytes = unsafe { @@ -186,7 +184,9 @@ mod generated_tests { check_same_bytes(r_bytes, c_bytes, "`GREEN` value"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ + pub fn ctest_size_align_Byte() { extern "C" { fn ctest_size_of__Byte() -> u64; @@ -203,7 +203,6 @@ mod generated_tests { check_same(rust_align, c_align, "`Byte` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_volatile_char() { extern "C" { fn ctest_size_of__volatile_char() -> u64; @@ -220,7 +219,6 @@ mod generated_tests { check_same(rust_align, c_align, "`volatile_char` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_gregset_t() { extern "C" { fn ctest_size_of__gregset_t() -> u64; @@ -237,7 +235,6 @@ mod generated_tests { check_same(rust_align, c_align, "`gregset_t` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_Color() { extern "C" { fn ctest_size_of__Color() -> u64; @@ -254,7 +251,6 @@ mod generated_tests { check_same(rust_align, c_align, "`Color` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_Person() { extern "C" { fn ctest_size_of__Person() -> u64; @@ -271,7 +267,6 @@ mod generated_tests { check_same(rust_align, c_align, "`Person` align"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. pub fn ctest_size_align_Word() { extern "C" { fn ctest_size_of__Word() -> u64; @@ -288,11 +283,14 @@ mod generated_tests { check_same(rust_align, c_align, "`Word` align"); } - /// Make sure that the signededness of a type alias in Rust and C is the same. - /// - /// This is done by casting 0 to that type and flipping all of its bits. For unsigned types, - /// this would result in a value larger than zero. For signed types, this results in a value - /// smaller than 0. + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ + pub fn ctest_signededness_Byte() { extern "C" { fn ctest_signededness_of__Byte() -> u32; @@ -304,11 +302,6 @@ mod generated_tests { check_same((all_ones < all_zeros) as u32, c_is_signed, "`Byte` signed"); } - /// Make sure that the signededness of a type alias in Rust and C is the same. - /// - /// This is done by casting 0 to that type and flipping all of its bits. For unsigned types, - /// this would result in a value larger than zero. For signed types, this results in a value - /// smaller than 0. pub fn ctest_signededness_volatile_char() { extern "C" { fn ctest_signededness_of__volatile_char() -> u32; @@ -320,7 +313,9 @@ mod generated_tests { check_same((all_ones < all_zeros) as u32, c_is_signed, "`volatile_char` signed"); } - /// Make sure that the offset and size of a field in a struct/union is the same. + +/* Make sure that the offset and size of a field in a struct/union is the same. */ + pub fn ctest_field_size_offset_Person_name() { extern "C" { fn ctest_offset_of__Person__name() -> u64; @@ -330,23 +325,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).name }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Person__name() }; check_same(offset_of!(Person, name) as u64, ctest_field_offset, "field offset `name` of `Person`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Person__name() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `name` of `Person`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_Person_age() { extern "C" { fn ctest_offset_of__Person__age() -> u64; @@ -356,23 +349,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).age }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Person__age() }; check_same(offset_of!(Person, age) as u64, ctest_field_offset, "field offset `age` of `Person`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Person__age() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `age` of `Person`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_Person_job() { extern "C" { fn ctest_offset_of__Person__job() -> u64; @@ -382,23 +373,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).job }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Person__job() }; check_same(offset_of!(Person, job) as u64, ctest_field_offset, "field offset `job` of `Person`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Person__job() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `job` of `Person`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_Person_favorite_color() { extern "C" { fn ctest_offset_of__Person__favorite_color() -> u64; @@ -408,23 +397,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).favorite_color }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Person__favorite_color() }; check_same(offset_of!(Person, favorite_color) as u64, ctest_field_offset, "field offset `favorite_color` of `Person`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Person__favorite_color() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `favorite_color` of `Person`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_Word_word() { extern "C" { fn ctest_offset_of__Word__word() -> u64; @@ -434,23 +421,21 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).word }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Word__word() }; check_same(offset_of!(Word, word) as u64, ctest_field_offset, "field offset `word` of `Word`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Word__word() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `word` of `Word`"); } - /// Make sure that the offset and size of a field in a struct/union is the same. pub fn ctest_field_size_offset_Word_byte() { extern "C" { fn ctest_offset_of__Word__byte() -> u64; @@ -460,23 +445,24 @@ mod generated_tests { let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - // SAFETY: we assume the field access doesn't wrap + let ty_ptr = unsafe { &raw const (*uninit_ty).byte }; - // SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the - // test should be skipped. + let val = unsafe { ty_ptr.read_unaligned() }; - // SAFETY: FFI call with no preconditions + let ctest_field_offset = unsafe { ctest_offset_of__Word__byte() }; check_same(offset_of!(Word, byte) as u64, ctest_field_offset, "field offset `byte` of `Word`"); - // SAFETY: FFI call with no preconditions + let ctest_field_size = unsafe { ctest_size_of__Word__byte() }; check_same(size_of_val(&val) as u64, ctest_field_size, "field size `byte` of `Word`"); } - /// Tests if the pointer to the field is the same in Rust and C. + +/* Tests if the pointer to the field is the same in Rust and C. */ + pub fn ctest_field_ptr_Person_name() { extern "C" { fn ctest_field_ptr__Person__name(a: *const Person) -> *mut u8; @@ -494,7 +480,6 @@ mod generated_tests { "field pointer access `name` of `Person`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_Person_age() { extern "C" { fn ctest_field_ptr__Person__age(a: *const Person) -> *mut u8; @@ -512,7 +497,6 @@ mod generated_tests { "field pointer access `age` of `Person`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_Person_job() { extern "C" { fn ctest_field_ptr__Person__job(a: *const Person) -> *mut u8; @@ -530,7 +514,6 @@ mod generated_tests { "field pointer access `job` of `Person`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_Person_favorite_color() { extern "C" { fn ctest_field_ptr__Person__favorite_color(a: *const Person) -> *mut u8; @@ -548,7 +531,6 @@ mod generated_tests { "field pointer access `favorite_color` of `Person`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_Word_word() { extern "C" { fn ctest_field_ptr__Word__word(a: *const Word) -> *mut u8; @@ -566,7 +548,6 @@ mod generated_tests { "field pointer access `word` of `Word`"); } - /// Tests if the pointer to the field is the same in Rust and C. pub fn ctest_field_ptr_Word_byte() { extern "C" { fn ctest_field_ptr__Word__byte(a: *const Word) -> *mut u8; @@ -584,33 +565,31 @@ mod generated_tests { "field pointer access `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. +/* 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 { @@ -620,10 +599,7 @@ mod generated_tests { 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" { @@ -641,9 +617,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -669,7 +643,7 @@ mod generated_tests { 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) }; @@ -680,7 +654,7 @@ mod generated_tests { } } - // 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; @@ -694,33 +668,20 @@ mod generated_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__volatile_char() -> 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 { @@ -730,10 +691,7 @@ mod generated_tests { 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_volatile_char() { type U = volatile_char; extern "C" { @@ -751,9 +709,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -779,7 +735,7 @@ mod generated_tests { ctest_roundtrip__volatile_char(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) }; @@ -790,7 +746,7 @@ mod generated_tests { } } - // 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; @@ -804,33 +760,20 @@ mod generated_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__Color() -> 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 { @@ -840,10 +783,7 @@ mod generated_tests { 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_Color() { type U = Color; extern "C" { @@ -861,9 +801,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -889,7 +827,7 @@ mod generated_tests { ctest_roundtrip__Color(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) }; @@ -900,7 +838,7 @@ mod generated_tests { } } - // 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; @@ -914,22 +852,13 @@ mod generated_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__Person() -> Vec { if 4 == 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)] @@ -964,11 +893,7 @@ mod generated_tests { let size = size_of_val(&val); let off = offset_of!(Person, favorite_color); 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 { @@ -978,10 +903,7 @@ mod generated_tests { 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" { @@ -999,9 +921,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -1027,7 +947,7 @@ mod generated_tests { 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) }; @@ -1038,7 +958,7 @@ mod generated_tests { } } - // 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; @@ -1052,22 +972,13 @@ mod generated_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__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)] @@ -1088,11 +999,7 @@ mod generated_tests { 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 { @@ -1102,10 +1009,7 @@ mod generated_tests { 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" { @@ -1123,9 +1027,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -1151,7 +1053,7 @@ mod generated_tests { 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) }; @@ -1162,7 +1064,7 @@ mod generated_tests { } } - // 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; @@ -1176,7 +1078,8 @@ mod generated_tests { } } - /// Check if the Rust and C side function pointers point to the same underlying function. +/* Check if the Rust and C side function pointers point to the same underlying function. */ + pub fn ctest_foreign_fn_calloc() { extern "C" { fn ctest_foreign_fn__calloc() -> unsafe extern "C" fn(); @@ -1186,7 +1089,8 @@ mod generated_tests { check_same(actual, expected, "`calloc` function pointer"); } - // Tests if the pointer to the static variable matches in both Rust and C. +/* Tests if the pointer to the static variable matches in both Rust and C. */ + pub fn ctest_static_byte() { extern "C" { fn ctest_static__byte() -> *const Byte; diff --git a/ctest/tests/input/simple.out.with-skips.c b/ctest/tests/input/simple.out.with-skips.c index 43eed83cac567..f35a37b3957c1 100644 --- a/ctest/tests/input/simple.out.with-skips.c +++ b/ctest/tests/input/simple.out.with-skips.c @@ -16,19 +16,43 @@ typedef void (*ctest_void_func)(void); -// Return the size of a type. -CTEST_EXTERN uint64_t ctest_size_of__volatile_char(void) { return sizeof(volatile_char); } +/* Query a pointer to string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + + +/* Query a pointer to non-string constants. + * + * Define a function that returns a pointer to the value of the constant to test. + * This will later be called on the Rust side via FFI. + */ + + +/* Query the size and alignment of all types */ -// Return the alignment of a type. +CTEST_EXTERN uint64_t ctest_size_of__volatile_char(void) { return sizeof(volatile_char); } CTEST_EXTERN uint64_t ctest_align_of__volatile_char(void) { return CTEST_ALIGNOF(volatile_char); } -// Return `1` if the type is signed, otherwise return `0`. -// Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + +/* Query the signedness of a type. + * + * Return `1` if the type is signed, otherwise return `0`. + * Casting -1 to the aliased type if signed evaluates to `-1 < 0`, if unsigned to `MAX_VALUE < 0` + */ + CTEST_EXTERN uint32_t ctest_signededness_of__volatile_char(void) { volatile_char all_ones = (volatile_char) -1; return all_ones < 0; } + +/* Query the offsets of fields and their sizes. */ + + +/* Query a pointer to a field given a pointer to its struct */ + #ifdef _MSC_VER // Disable signed/unsigned conversion warnings on MSVC. // These trigger even if the conversion is explicit. @@ -41,26 +65,28 @@ CTEST_EXTERN uint32_t ctest_signededness_of__volatile_char(void) { #pragma GCC diagnostic ignored "-Wignored-qualifiers" #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. + +/* Write a nonrepeating bitpattern to a data type + * + * 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. + */ + CTEST_EXTERN volatile_char ctest_roundtrip__volatile_char( volatile_char value, const uint8_t is_padding_byte[sizeof(volatile_char)], uint8_t value_bytes[sizeof(volatile_char)] ) { int size = (int)sizeof(volatile_char); - // 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; @@ -84,7 +110,12 @@ CTEST_EXTERN volatile_char ctest_roundtrip__volatile_char( #pragma warning(disable:4191) #endif +/* Query a function's pointer */ + #ifdef _MSC_VER // Pop allow for 4191 #pragma warning(default:4191) #endif + + +/* Query pointers to statics */ diff --git a/ctest/tests/input/simple.out.with-skips.rs b/ctest/tests/input/simple.out.with-skips.rs index 5f84cfc5f7f69..3e5a6b051ce33 100644 --- a/ctest/tests/input/simple.out.with-skips.rs +++ b/ctest/tests/input/simple.out.with-skips.rs @@ -68,7 +68,20 @@ mod generated_tests { eprintln!("{s}"); } - /// Compare the size and alignment of the type in Rust and C, making sure they are the same. + +/* Test that the string constant is the same in both Rust and C. + * While fat pointers can't be translated, we instead use * const c_char. + */ + + +/* Test that the value of the constant is the same in both Rust and C. + * + * This performs a byte by byte comparison of the constant value. + */ + + +/* Compare the size and alignment of the type in Rust and C, making sure they are the same. */ + pub fn ctest_size_align_volatile_char() { extern "C" { fn ctest_size_of__volatile_char() -> u64; @@ -85,11 +98,14 @@ mod generated_tests { check_same(rust_align, c_align, "`volatile_char` align"); } - /// Make sure that the signededness of a type alias in Rust and C is the same. - /// - /// This is done by casting 0 to that type and flipping all of its bits. For unsigned types, - /// this would result in a value larger than zero. For signed types, this results in a value - /// smaller than 0. + +/* Make sure that the signededness of a type alias in Rust and C is the same. + * + * This is done by casting 0 to that type and flipping all of its bits. For unsigned types, + * this would result in a value larger than zero. For signed types, this results in a value + * smaller than 0. + */ + pub fn ctest_signededness_volatile_char() { extern "C" { fn ctest_signededness_of__volatile_char() -> u32; @@ -101,33 +117,37 @@ mod generated_tests { check_same((all_ones < all_zeros) as u32, c_is_signed, "`volatile_char` 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. + +/* Make sure that the offset and size of a field in a struct/union is the same. */ + + +/* Tests if the pointer to the field is the same in Rust and C. */ + +/* 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__volatile_char() -> 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 { @@ -137,10 +157,7 @@ mod generated_tests { 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_volatile_char() { type U = volatile_char; extern "C" { @@ -158,9 +175,7 @@ mod generated_tests { let input_ptr = input.as_mut_ptr().cast::(); - // Fill the uninitialized 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 }; @@ -186,7 +201,7 @@ mod generated_tests { ctest_roundtrip__volatile_char(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) }; @@ -197,7 +212,7 @@ mod generated_tests { } } - // 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; @@ -210,6 +225,10 @@ mod generated_tests { } } } + +/* Check if the Rust and C side function pointers point to the same underlying function. */ + +/* Tests if the pointer to the static variable matches in both Rust and C. */ } use generated_tests::*;