From 66cffca3b9311ca72c5e7ee1e82999f0bc5e6aff Mon Sep 17 00:00:00 2001 From: mbyx Date: Tue, 8 Jul 2025 14:56:57 +0500 Subject: [PATCH 1/2] ctest: add tests for aliases, structs, unions, as well as a test crate. --- Cargo.toml | 1 + ctest-next-test/Cargo.toml | 33 ++ ctest-next-test/build.rs | 62 +++ ctest-next-test/src/bin/t1-next.rs | 6 + ctest-next-test/src/bin/t2-next.rs | 6 + ctest-next-test/src/lib.rs | 6 + ctest-next-test/src/t1.c | 70 +++ ctest-next-test/src/t1.h | 187 +++++++ ctest-next-test/src/t1.rs | 205 ++++++++ ctest-next-test/src/t2.c | 2 + ctest-next-test/src/t2.h | 26 + ctest-next-test/src/t2.rs | 37 ++ ctest-next-test/tests/all.rs | 86 ++++ ctest-next/Cargo.toml | 1 + ctest-next/src/ast/constant.rs | 1 - ctest-next/src/ast/field.rs | 1 - ctest-next/src/ast/function.rs | 7 +- ctest-next/src/ast/static_variable.rs | 1 - ctest-next/src/ast/structure.rs | 7 +- ctest-next/src/ast/type_alias.rs | 1 - ctest-next/src/ast/union.rs | 6 +- ctest-next/src/ffi_items.rs | 21 +- ctest-next/src/generator.rs | 229 +++++++-- ctest-next/src/lib.rs | 16 +- ctest-next/src/macro_expansion.rs | 5 +- ctest-next/src/runner.rs | 22 +- ctest-next/src/template.rs | 139 +++++- ctest-next/src/tests.rs | 12 +- ctest-next/src/translator.rs | 53 +- ctest-next/templates/test.c | 274 ++++++++++- ctest-next/templates/test.rs | 459 +++++++++++++++++- ctest-next/tests/basic.rs | 9 +- ctest-next/tests/input/hierarchy.out.c | 67 +++ ctest-next/tests/input/hierarchy.out.rs | 100 +++- ctest-next/tests/input/macro.h | 2 + ctest-next/tests/input/macro.out.c | 238 +++++++++ ctest-next/tests/input/macro.out.rs | 284 ++++++++++- ctest-next/tests/input/macro.rs | 4 + ctest-next/tests/input/simple.out.c | 15 - ctest-next/tests/input/simple.out.rs | 79 --- .../tests/input/simple.out.with-renames.c | 243 ++++++++++ .../tests/input/simple.out.with-renames.rs | 388 ++++++++++++++- .../tests/input/simple.out.with-skips.c | 243 ++++++++++ .../tests/input/simple.out.with-skips.rs | 385 ++++++++++++++- ctest-next/tests/input/simple.rs | 4 +- ctest-next/tests/usage.rs | 60 +++ 46 files changed, 3921 insertions(+), 182 deletions(-) create mode 100644 ctest-next-test/Cargo.toml create mode 100644 ctest-next-test/build.rs create mode 100644 ctest-next-test/src/bin/t1-next.rs create mode 100644 ctest-next-test/src/bin/t2-next.rs create mode 100644 ctest-next-test/src/lib.rs create mode 100644 ctest-next-test/src/t1.c create mode 100644 ctest-next-test/src/t1.h create mode 100644 ctest-next-test/src/t1.rs create mode 100644 ctest-next-test/src/t2.c create mode 100644 ctest-next-test/src/t2.h create mode 100644 ctest-next-test/src/t2.rs create mode 100644 ctest-next-test/tests/all.rs delete mode 100644 ctest-next/tests/input/simple.out.c delete mode 100644 ctest-next/tests/input/simple.out.rs create mode 100644 ctest-next/tests/usage.rs diff --git a/Cargo.toml b/Cargo.toml index 8582ebcf9a134..f8e8ffb72a559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,7 @@ extra_traits = [] members = [ "ctest", "ctest-next", + "ctest-next-test", "ctest-test", "libc-test", ] diff --git a/ctest-next-test/Cargo.toml b/ctest-next-test/Cargo.toml new file mode 100644 index 0000000000000..ed41ff224bc4c --- /dev/null +++ b/ctest-next-test/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "ctest-next-test" +version = "0.1.0" +authors = ["Alex Crichton "] +publish = false +edition = "2021" + +[build-dependencies] +ctest-next = { path = "../ctest-next" } +cc = "1.0" + +[dev-dependencies] +ctest-next = { path = "../ctest-next" } + +[dependencies] +libc = { path = ".." } + +[[bin]] +name = "t1-next" +test = false + +[[bin]] +name = "t2-next" +test = false + +# FIXME(msrv): These should be moved to the root Cargo.toml as `[workspace.lints.*]` +# once MSRV is above 1.64 and replaced with `[lints] workspace=true` + +[lints.rust] +# FIXME(cleanup): make ident usage consistent in each file +unused_qualifications = "allow" + +[lints.clippy] diff --git a/ctest-next-test/build.rs b/ctest-next-test/build.rs new file mode 100644 index 0000000000000..301f708454035 --- /dev/null +++ b/ctest-next-test/build.rs @@ -0,0 +1,62 @@ +use std::env; + +use ctest_next::{generate_test, TestGenerator}; + +fn main() { + let opt_level = env::var("OPT_LEVEL") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + + let profile = env::var("PROFILE").unwrap_or_default(); + if profile == "release" || opt_level >= 2 { + println!("cargo:rustc-cfg=optimized"); + } + + // FIXME(ctest): The .c files are ignored right now, I'm not sure if they + // were used or how they were used before. + cc::Build::new() + .include("src") + .warnings(false) + .file("src/t1.c") + .compile("libt1.a"); + println!("cargo:rerun-if-changed=src/t1.c"); + println!("cargo:rerun-if-changed=src/t1.h"); + + cc::Build::new() + .warnings(false) + .file("src/t2.c") + .compile("libt2.a"); + println!("cargo:rerun-if-changed=src/t2.c"); + println!("cargo:rerun-if-changed=src/t2.h"); + + let mut t1gen = TestGenerator::new(); + t1gen + .header("t1.h") + .include("src") + .skip_private(true) + .rename_fn(|f| f.link_name().unwrap_or(f.ident()).to_string().into()) + .rename_union_ty(|ty| (ty == "T1Union").then_some(ty.to_string())) + .rename_struct_ty(|ty| (ty == "Transparent").then_some(ty.to_string())) + .volatile_field(|s, f| s.ident() == "V" && f.ident() == "v") + .volatile_static(|s| s.ident() == "vol_ptr") + .volatile_static(|s| s.ident() == "T1_fn_ptr_vol") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol0" && p.ident() == "arg0") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol2" && p.ident() == "arg1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol2") + // The parameter `a` of the functions `T1r`, `T1s`, `T1t`, `T1v` is an array. + .array_arg(|f, p| matches!(f.ident(), "T1r" | "T1s" | "T1t" | "T1v") && p.ident() == "a") + .skip_roundtrip(|n| n == "Arr"); + generate_test(&mut t1gen, "src/t1.rs", "t1gen.rs").unwrap(); + + let mut t2gen = TestGenerator::new(); + t2gen + .header("t2.h") + .include("src") + // public C typedefs have to manually be specified because they are identical to normal + // structs on the Rust side. + .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) + .skip_roundtrip(|_| true); + generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap(); +} diff --git a/ctest-next-test/src/bin/t1-next.rs b/ctest-next-test/src/bin/t1-next.rs new file mode 100644 index 0000000000000..1cbeabd1d00a1 --- /dev/null +++ b/ctest-next-test/src/bin/t1-next.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use ctest_next_test::t1::*; + +include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest-next-test/src/bin/t2-next.rs b/ctest-next-test/src/bin/t2-next.rs new file mode 100644 index 0000000000000..04bc0563d9d5e --- /dev/null +++ b/ctest-next-test/src/bin/t2-next.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use ctest_next_test::t2::*; + +include!(concat!(env!("OUT_DIR"), "/t2gen.rs")); diff --git a/ctest-next-test/src/lib.rs b/ctest-next-test/src/lib.rs new file mode 100644 index 0000000000000..8bb86699f30c7 --- /dev/null +++ b/ctest-next-test/src/lib.rs @@ -0,0 +1,6 @@ +//! `ctest-next-test` is a test crate for testing `ctest-next`. It consists +//! of two test binaries, t1 and t2 that test various aspects of `ctest-next`, +//! such as validation and generation of tests. + +pub mod t1; +pub mod t2; diff --git a/ctest-next-test/src/t1.c b/ctest-next-test/src/t1.c new file mode 100644 index 0000000000000..24f9fe52bf215 --- /dev/null +++ b/ctest-next-test/src/t1.c @@ -0,0 +1,70 @@ +#include "t1.h" +#include +#include + +void T1a(void) {} +void *T1b(void) { return NULL; } +void *T1c(void *a) { return NULL; } +int32_t T1d(unsigned a) { return 0; } +void T1e(unsigned a, const struct T1Bar *b) {} +void T1f(void) {} +void T1g(int32_t *a) {} +void T1h(const int32_t *b) {} +void T1i(int32_t a[4]) {} +void T1j(const int32_t b[4]) {} +void T1o(int32_t (*a)[4]) {} +void T1p(int32_t (*const a)[4]) {} + +void T1r(Arr a) {} +void T1s(const Arr a) {} +void T1t(Arr *a) {} +void T1v(const Arr *a) {} + +unsigned T1static = 3; + +const uint8_t T1_static_u8 = 42; +uint8_t T1_static_mut_u8 = 37; + +uint8_t foo(uint8_t a, uint8_t b) { return a + b; } +void bar(uint8_t a) { return; } +void baz(void) { return; } + +uint32_t (*nested(uint8_t arg))(uint16_t) +{ + return NULL; +} + +uint32_t (*nested2(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) +{ + return NULL; +} + +uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t) = foo; +uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t) = foo; +void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t) = bar; +void (*const T1_static_const_fn_ptr_unsafe3)(void) = baz; + +const uint8_t T1_static_right = 7; +uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; + +uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; +uint32_t (*(*T1_fn_ptr_s2)(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) = nested2; + +const int32_t T1_arr0[2] = {0, 0}; +const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; +const int32_t T1_arr2[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; + +int32_t T1_arr3[2] = {0, 0}; +int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; +int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; + +int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; +const int16_t *T1_sref = (void *)(1337); + +volatile uint8_t *vol_ptr = NULL; +void *T1_vol0(volatile void *x, void *a) { return a ? a : (void *)x; } +volatile void *T1_vol1(void *x, void *b) { return b ? (volatile void *)x : (volatile void *)x; } +volatile void *T1_vol2(void *c, volatile void *x) { return c ? x : x; } + +// FIXME(#4365): duplicate symbol errors when enabled +// uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; diff --git a/ctest-next-test/src/t1.h b/ctest-next-test/src/t1.h new file mode 100644 index 0000000000000..08800525651d7 --- /dev/null +++ b/ctest-next-test/src/t1.h @@ -0,0 +1,187 @@ +#include + +typedef int32_t T1Foo; + +#define T1N 5 +#define T1S "foo" + +struct T1Bar +{ + int32_t a; + uint32_t b; + T1Foo c; + uint8_t d; + int64_t e[T1N]; + int64_t f[T1N][2]; +}; + +struct T1Baz +{ + uint64_t a; + struct T1Bar b; +}; + +typedef union +{ + uint64_t a; + uint32_t b; +} T1Union; + +union T1NoTypedefUnion +{ + uint64_t a; + uint32_t b; +}; + +struct T1StructWithUnion +{ + union T1NoTypedefUnion u; +}; + +typedef double T1TypedefDouble; +typedef int *T1TypedefPtr; +typedef struct T1Bar T1TypedefStruct; + +void T1a(void); +void *T1b(void); +void *T1c(void *); +int32_t T1d(unsigned); +void T1e(unsigned, const struct T1Bar *); +void T1f(void); +void T1g(int32_t *a); +void T1h(const int32_t *b); +void T1i(int32_t a[4]); +void T1j(const int32_t b[4]); +void T1o(int32_t (*a)[4]); +void T1p(int32_t (*const a)[4]); + +typedef int32_t(Arr)[4]; +typedef int32_t Transparent; + +void T1r(Arr a); +void T1s(const Arr a); +void T1t(Arr *a); +void T1v(const Arr *a); + +#define T1C 4 + +extern uint32_t T1static; +extern const uint8_t T1_static_u8; +/* FIXME(#4365): duplicate symbol errors when enabled +// uint8_t T1_static_mut_u8; +// uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t); +extern uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t); +*/ +extern void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t); +extern void (*const T1_static_const_fn_ptr_unsafe3)(void); + +extern const uint8_t T1_static_right; +/* FIXME(#4365): duplicate symbol errors when enabled +// uint8_t (*T1_static_right2)(uint8_t, uint8_t); + +// T1_fn_ptr_nested: function pointer to a function, taking a uint8_t, and +// returning a function pointer to a function taking a uint16_t and returning a +// uint32_t +uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t); + +// T1_fn_ptr_nested: function pointer to a function, taking a function pointer +// uint8_t -> uint8_t, and returning a function pointer to a function taking a +// uint16_t and returning a uint32_t +uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); +*/ + +extern const int32_t T1_arr0[2]; +extern const int32_t T1_arr1[2][3]; +extern const int32_t T1_arr2[1][2][3]; + +extern int32_t T1_arr3[2]; +extern int32_t T1_arr4[2][3]; +extern int32_t T1_arr5[1][2][3]; + +extern int32_t T1_arr42[1][2][3]; + +extern const int16_t *T1_sref; + +struct Q +{ + uint8_t *q0; + uint8_t **q1; + uint8_t q2; +}; + +struct T1_conflict_foo +{ + int a; +}; + +struct T1_conflict +{ + int foo; +}; + +// test packed structs +// +// on msvc there is only pragma pack +// on clang and gcc there is a packed attribute + +#pragma pack(push, 1) + +struct Pack +{ + uint8_t a; + uint16_t b; +}; + +#pragma pack(pop) + +#pragma pack(push, 4) + +struct Pack4 +{ + uint8_t a; + uint32_t b; +}; + +#pragma pack(pop) + +// volatile pointers in struct fields: +struct V +{ + volatile uint8_t *v; +}; + +// volatile pointers in externs: +extern volatile uint8_t *vol_ptr; + +// volatile pointers in function arguments: +void *T1_vol0(volatile void *, void *); +volatile void *T1_vol1(void *, void *); +volatile void *T1_vol2(void *, volatile void *); + +/* FIXME(#4365): duplicate symbol errors when enabled +// volatile function pointers: +uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); +*/ + +#define LOG_MAX_LINE_LENGTH (1400) + +typedef struct +{ + long tv_sec; + int tv_usec; +} timeval; + +typedef struct +{ + long level; + char const *file; + long line; + char const *module; + timeval tv; + char message[LOG_MAX_LINE_LENGTH]; +} log_record_t; + +typedef struct +{ + long double inner; +} LongDoubleWrap; diff --git a/ctest-next-test/src/t1.rs b/ctest-next-test/src/t1.rs new file mode 100644 index 0000000000000..9d8f5bbb30172 --- /dev/null +++ b/ctest-next-test/src/t1.rs @@ -0,0 +1,205 @@ +#![allow(non_camel_case_types)] + +use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void}; + +pub type T1Foo = i32; +pub const T1S: *const c_char = c"foo".as_ptr(); + +pub const T1N: i32 = 5; + +macro_rules! i { + ($i:item) => { + $i + }; +} + +#[repr(C)] +pub struct T1Bar { + pub a: i32, + pub b: u32, + pub c: T1Foo, + pub d: u8, + pub e: [i64; T1N as usize], + pub f: [[i64; 2]; T1N as usize], +} + +#[repr(C)] +pub struct T1Baz { + pub a: u64, + pub b: T1Bar, +} + +#[repr(C)] +pub union T1Union { + pub a: u64, + pub b: u32, +} + +#[repr(C)] +pub union T1NoTypedefUnion { + pub a: u64, + pub b: u32, +} + +#[repr(C)] +pub struct T1StructWithUnion { + pub u: T1NoTypedefUnion, +} + +#[repr(transparent)] +pub struct Transparent(i32); + +pub type T1TypedefDouble = c_double; +pub type T1TypedefPtr = *mut c_int; +pub type T1TypedefStruct = T1Bar; + +i! { + pub const T1C: u32 = 4; +} + +#[expect(unused)] +const NOT_PRESENT: u32 = 5; + +pub type Arr = [i32; 4]; + +extern "C" { + pub fn T1a(); + pub fn T1b() -> *mut c_void; + pub fn T1c(a: *mut c_void) -> *mut c_void; + pub fn T1d(a: c_uint) -> i32; + pub fn T1e(a: c_uint, b: *const T1Bar); + + #[link_name = "T1f"] + #[allow(clippy::unused_unit)] + pub fn f() -> (); + + pub fn T1g(a: *mut [i32; 4]); + pub fn T1h(a: *const [i32; 4]) -> !; + pub fn T1i(a: *mut [i32; 4]); + pub fn T1j(a: *const [i32; 4]) -> !; + pub fn T1o(a: *mut *mut [i32; 4]); + pub fn T1p(a: *const *const [i32; 4]) -> !; + + pub fn T1r(a: *mut Arr); + pub fn T1s(a: *const Arr) -> !; + pub fn T1t(a: *mut *mut Arr); + pub fn T1v(a: *const *const Arr) -> !; + + pub static T1static: c_uint; +} + +pub fn foo() { + let x = 1; + assert_eq!(x, 1); +} + +extern "C" { + pub static T1_static_u8: u8; + /* FIXME(#4365): duplicate symbol errors when enabled + // pub static mut T1_static_mut_u8: u8; + // pub static mut T1_static_mut_fn_ptr: extern "C" fn(u8, u8) -> u8; + pub static T1_static_const_fn_ptr_unsafe: unsafe extern "C" fn(u8, u8) -> u8; + */ + pub static T1_static_const_fn_ptr_unsafe2: unsafe extern "C" fn(u8) -> (); + pub static T1_static_const_fn_ptr_unsafe3: unsafe extern "C" fn() -> (); + + #[link_name = "T1_static_right"] + pub static T1_static_wrong: u8; + /* FIXME(#4365): duplicate symbol errors when enabled + // #[link_name = "T1_static_right2"] + // pub static mut T1_static_wrong2: extern "C" fn(u8, u8) -> u8; + + pub static T1_fn_ptr_s: unsafe extern "C" fn(u8) -> extern "C" fn(u16) -> u32; + pub static T1_fn_ptr_s2: unsafe extern "C" fn( + extern "C" fn(u8) -> u8, + extern "C" fn(u16) -> u16, + ) -> extern "C" fn(u16) -> u32; + */ + + pub static T1_arr0: [i32; 2]; + pub static T1_arr1: [[i32; 3]; 2]; + pub static T1_arr2: [[[i32; 3]; 2]; 1]; + + pub static mut T1_arr3: [i32; 2]; + pub static mut T1_arr4: [[i32; 3]; 2]; + pub static mut T1_arr5: [[[i32; 3]; 2]; 1]; + + #[link_name = "T1_arr42"] + pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; + + pub static mut T1_sref: &'static i16; +} + +#[repr(C)] +pub struct Q { + pub q0: *mut u8, + pub q1: *mut *mut u8, + pub q2: u8, +} + +#[repr(C)] +pub struct T1_conflict_foo { + a: i32, +} + +#[repr(C)] +pub struct T1_conflict { + pub foo: i32, +} + +#[repr(C, packed)] +pub struct Pack { + pub a: u8, + pub b: u16, +} + +#[repr(C, packed(4))] +pub struct Pack4 { + pub a: u8, + pub b: u32, +} + +#[repr(C)] +pub struct V { + pub v: *mut u8, +} + +extern "C" { + pub static mut vol_ptr: *mut u8; + pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; + pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; + pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; +} + +pub const LOG_MAX_LINE_LENGTH: usize = 1400; + +#[repr(C)] +struct timeval { + tv_sec: c_long, + tv_usec: c_int, +} + +#[expect(unused)] +#[repr(C)] +struct log_record_t { + level: c_long, + file: *const c_char, + line: c_long, + module: *const c_char, + tv: timeval, + message: [c_char; LOG_MAX_LINE_LENGTH], +} + +#[expect(unused)] +#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] +#[repr(C, align(16))] +struct LongDoubleWrap { + inner: u128, +} + +#[expect(unused)] +#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] +#[repr(C)] +struct LongDoubleWrap { + inner: c_double, +} diff --git a/ctest-next-test/src/t2.c b/ctest-next-test/src/t2.c new file mode 100644 index 0000000000000..ceeddfcf509ea --- /dev/null +++ b/ctest-next-test/src/t2.c @@ -0,0 +1,2 @@ + +void T2a() {} diff --git a/ctest-next-test/src/t2.h b/ctest-next-test/src/t2.h new file mode 100644 index 0000000000000..9fd4fc3b5aa6d --- /dev/null +++ b/ctest-next-test/src/t2.h @@ -0,0 +1,26 @@ +#include + +typedef int32_t T2Foo; +typedef int8_t T2Bar; + +typedef T2Foo T2TypedefFoo; +typedef unsigned T2TypedefInt; + +struct T2Baz +{ + int8_t _a; + int64_t a; + uint32_t b; +}; + +typedef struct +{ + uint32_t a; + int64_t b; +} T2Union; + +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// static void T2a(void) {} + +#define T2C 4 +#define T2S "a" diff --git a/ctest-next-test/src/t2.rs b/ctest-next-test/src/t2.rs new file mode 100644 index 0000000000000..cfd3bfb65b418 --- /dev/null +++ b/ctest-next-test/src/t2.rs @@ -0,0 +1,37 @@ +use std::ffi::{c_char, c_int}; + +pub type T2Foo = u32; +pub type T2Bar = u32; + +pub type T2TypedefFoo = T2Foo; +pub type T2TypedefInt = c_int; + +macro_rules! i { + ($i:item) => { + $i + }; +} + +#[repr(C)] +#[derive(Debug)] +pub struct T2Baz { + pub a: i64, + pub b: u32, +} + +#[repr(C)] +pub union T2Union { + pub a: u32, + pub b: i64, +} + +pub const T2C: i32 = 5; + +i! { + pub const T2S: *const c_char = c"b".as_ptr(); +} + +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// extern "C" { +// pub fn T2a(); +// } diff --git a/ctest-next-test/tests/all.rs b/ctest-next-test/tests/all.rs new file mode 100644 index 0000000000000..327693a53d485 --- /dev/null +++ b/ctest-next-test/tests/all.rs @@ -0,0 +1,86 @@ +// FIXME(ctest): this test doesn't work when cross compiling. +#![cfg(target_arch = "x86_64")] + +use std::collections::HashSet; +use std::env; +use std::process::{Command, ExitStatus}; + +/// Create a command that starts in the `target/debug` or `target/release` directory. +fn cmd(name: &str) -> Command { + let mut path = env::current_exe().unwrap(); + path.pop(); + if path.file_name().unwrap().to_str() == Some("deps") { + path.pop(); + } + path.push(name); + Command::new(path) +} + +/// Executes a command, returning stdout and stderr combined and it's status. +fn output(cmd: &mut Command) -> (String, ExitStatus) { + eprintln!("command: {cmd:?}"); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + + (stdout + &stderr, output.status) +} + +#[test] +fn t1_next() { + // t1 must run to completion without any errors. + let (output, status) = output(&mut cmd("t1-next")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); + eprintln!("output: {output}"); +} + +#[test] +fn t2_next() { + // t2 must fail to run to completion, and only have the errors we expect it to have. + let (output, status) = output(&mut cmd("t2-next")); + assert!(!status.success(), "output: {output}"); + + // FIXME(ctest): Errors currently commented out are not tested. + let errors = [ + "bad T2Foo signed", + "bad T2TypedefFoo signed", + "bad T2TypedefInt signed", + "bad T2Bar size", + "bad T2Bar align", + "bad T2Bar signed", + "bad T2Baz size", + "bad field offset a of T2Baz", + "bad field type a of T2Baz", + "bad field offset b of T2Baz", + "bad field type b of T2Baz", + // "bad T2a function pointer", + "bad T2C value at byte 0", + "bad T2S string", + "bad T2Union size", + "bad field type b of T2Union", + "bad field offset b of T2Union", + ]; + let mut errors = errors.iter().cloned().collect::>(); + + // Extract any errors that are not contained within the known error set. + let mut bad = false; + for line in output.lines().filter(|l| l.starts_with("bad ")) { + let msg = &line[..line.find(":").unwrap()]; + if !errors.remove(&msg) { + println!("unknown error: {msg:#?}"); + bad = true; + } + } + + // If any errors are left over, t2 did not run properly. + for error in errors { + println!("didn't find error: {error}"); + bad = true; + } + + if bad { + println!("output was:\n\n{output:#?}"); + panic!(); + } +} diff --git a/ctest-next/Cargo.toml b/ctest-next/Cargo.toml index cbfecf96f4bb9..d83bb2956f609 100644 --- a/ctest-next/Cargo.toml +++ b/ctest-next/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] askama = "0.14.0" cc = "1.2.25" +either = "1.15.0" proc-macro2 = { version = "1.0.95", features = ["span-locations"] } quote = "1.0.40" syn = { version = "2.0.101", features = ["full", "visit", "extra-traits"] } diff --git a/ctest-next/src/ast/constant.rs b/ctest-next/src/ast/constant.rs index 654d691df66d5..17c07e989bdc9 100644 --- a/ctest-next/src/ast/constant.rs +++ b/ctest-next/src/ast/constant.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a constant variable defined in Rust. #[derive(Debug, Clone)] pub struct Const { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/field.rs b/ctest-next/src/ast/field.rs index 4645a91f8b50e..9f14812e11ace 100644 --- a/ctest-next/src/ast/field.rs +++ b/ctest-next/src/ast/field.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a field in a struct or union defined in Rust. #[derive(Debug, Clone)] pub struct Field { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/function.rs b/ctest-next/src/ast/function.rs index ac41c702e5489..8722c2b82b740 100644 --- a/ctest-next/src/ast/function.rs +++ b/ctest-next/src/ast/function.rs @@ -5,11 +5,11 @@ use crate::{Abi, BoxStr, Parameter}; /// This structure is only used for parsing functions in extern blocks. #[derive(Debug, Clone)] pub struct Fn { - #[expect(unused)] pub(crate) public: bool, #[expect(unused)] pub(crate) abi: Abi, pub(crate) ident: BoxStr, + pub(crate) link_name: Option, #[expect(unused)] pub(crate) parameters: Vec, #[expect(unused)] @@ -21,4 +21,9 @@ impl Fn { pub fn ident(&self) -> &str { &self.ident } + + /// Return the name of the function to be linked C side with. + pub fn link_name(&self) -> Option<&str> { + self.link_name.as_deref() + } } diff --git a/ctest-next/src/ast/static_variable.rs b/ctest-next/src/ast/static_variable.rs index aa0ec664dc9e1..87219cdadd130 100644 --- a/ctest-next/src/ast/static_variable.rs +++ b/ctest-next/src/ast/static_variable.rs @@ -6,7 +6,6 @@ use crate::{Abi, BoxStr}; /// as a result it does not have a field for storing the expression. #[derive(Debug, Clone)] pub struct Static { - #[expect(unused)] pub(crate) public: bool, #[expect(unused)] pub(crate) abi: Abi, diff --git a/ctest-next/src/ast/structure.rs b/ctest-next/src/ast/structure.rs index 647f9e7053201..b9fae7fd574e5 100644 --- a/ctest-next/src/ast/structure.rs +++ b/ctest-next/src/ast/structure.rs @@ -3,10 +3,8 @@ use crate::{BoxStr, Field}; /// Represents a struct defined in Rust. #[derive(Debug, Clone)] pub struct Struct { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, - #[expect(unused)] pub(crate) fields: Vec, } @@ -15,4 +13,9 @@ impl Struct { pub fn ident(&self) -> &str { &self.ident } + + /// Return the public fields of the struct. + pub fn public_fields(&self) -> Vec<&Field> { + self.fields.iter().filter(|f| f.public).collect() + } } diff --git a/ctest-next/src/ast/type_alias.rs b/ctest-next/src/ast/type_alias.rs index f83992c9a7165..4947e934dde51 100644 --- a/ctest-next/src/ast/type_alias.rs +++ b/ctest-next/src/ast/type_alias.rs @@ -3,7 +3,6 @@ use crate::BoxStr; /// Represents a type alias defined in Rust. #[derive(Debug, Clone)] pub struct Type { - #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, pub(crate) ty: syn::Type, diff --git a/ctest-next/src/ast/union.rs b/ctest-next/src/ast/union.rs index caf0e30eb95a7..09abaf9e746f1 100644 --- a/ctest-next/src/ast/union.rs +++ b/ctest-next/src/ast/union.rs @@ -6,7 +6,6 @@ pub struct Union { #[expect(unused)] pub(crate) public: bool, pub(crate) ident: BoxStr, - #[expect(unused)] pub(crate) fields: Vec, } @@ -15,4 +14,9 @@ impl Union { pub fn ident(&self) -> &str { &self.ident } + + /// Return the public fields of the union. + pub(crate) fn public_fields(&self) -> Vec<&Field> { + self.fields.iter().filter(|f| f.public).collect() + } } diff --git a/ctest-next/src/ffi_items.rs b/ctest-next/src/ffi_items.rs index 85889d9d62f5b..9529c0ccc9b33 100644 --- a/ctest-next/src/ffi_items.rs +++ b/ctest-next/src/ffi_items.rs @@ -1,6 +1,7 @@ use std::ops::Deref; -use syn::{punctuated::Punctuated, visit::Visit}; +use syn::punctuated::Punctuated; +use syn::visit::Visit; use crate::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union}; @@ -36,7 +37,6 @@ impl FfiItems { } /// Return a list of all type aliases found. - #[cfg_attr(not(test), expect(unused))] pub(crate) fn aliases(&self) -> &Vec { &self.aliases } @@ -122,10 +122,27 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi syn::ReturnType::Type(_, ty) => Some(ty.deref().clone()), }; + let link_name = i + .attrs + .iter() + .find(|attr| attr.path().is_ident("link_name")) + .and_then(|attr| match &attr.meta { + syn::Meta::NameValue(nv) => { + if let syn::Expr::Lit(expr_lit) = &nv.value { + if let syn::Lit::Str(lit_str) = &expr_lit.lit { + return Some(lit_str.value().into_boxed_str()); + } + } + None + } + _ => None, + }); + table.foreign_functions.push(Fn { public, abi, ident, + link_name, parameters, return_type, }); diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index 0964d2527e79a..974d773f019a6 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -1,19 +1,18 @@ -use std::{ - env, - fs::File, - io::Write, - path::{Path, PathBuf}, -}; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; use askama::Template; +use either::Either; use syn::visit::Visit; use thiserror::Error; +use crate::ffi_items::FfiItems; +use crate::template::{CTestTemplate, RustTestTemplate}; use crate::{ - expand, - ffi_items::FfiItems, - template::{CTestTemplate, RustTestTemplate}, - Const, Field, MapInput, Parameter, Result, Static, Struct, Type, VolatileItemKind, + expand, Const, Field, MapInput, Parameter, Result, Static, Struct, Type, Union, + VolatileItemKind, }; /// A function that takes a mappable input and returns its mapping as Some, otherwise @@ -25,6 +24,8 @@ type Skip = Box bool>; type VolatileItem = Box bool>; /// A function that determines whether a function arument is an array. type ArrayArg = Box bool>; +/// A function that determines whether to skip round trip testing, taking in the identifier name. +type SkipRoundTrip = Box bool>; /// A builder used to generate a test suite. #[derive(Default)] @@ -37,10 +38,12 @@ pub struct TestGenerator { flags: Vec, defines: Vec<(String, Option)>, mapped_names: Vec, - skips: Vec, + pub(crate) volatile_items: Vec, + pub(crate) skips: Vec, verbose_skip: bool, - volatile_items: Vec, - array_arg: Option, + pub(crate) skip_private: bool, + pub(crate) array_arg: Option, + pub(crate) skip_roundtrip: Option, } #[derive(Debug, Error)] @@ -53,8 +56,8 @@ pub enum GenerationError { TemplateRender(String, String), #[error("unable to create or write template file: {0}")] OsError(std::io::Error), - #[error("unable to map Rust identifier or type")] - ItemMap, + #[error("one of {0} environment variable(s) not set")] + EnvVarNotFound(String), } impl TestGenerator { @@ -138,6 +141,21 @@ impl TestGenerator { self } + /// Non public items are not tested if `skip` is `true`. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_private(true); + /// ``` + pub fn skip_private(&mut self, skip: bool) -> &mut Self { + self.skip_private = skip; + self + } + /// Skipped item names are printed to `stderr` if `skip` is `true`. /// /// # Examples @@ -296,7 +314,33 @@ impl TestGenerator { self } - /// Configures whether all tests for a field are skipped or not. + /// Configures whether all tests for a struct field are skipped or not. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_struct_field(|s, f| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && f.ident() == "bar") + /// }); + /// ``` + pub fn skip_struct_field( + &mut self, + f: impl Fn(&Struct, &Field) -> bool + 'static, + ) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::Field(Either::Left(struct_), field) = item { + f(struct_, field) + } else { + false + } + })); + self + } + + /// Configures whether all tests for a union field are skipped or not. /// /// # Examples /// @@ -304,13 +348,45 @@ impl TestGenerator { /// use ctest_next::TestGenerator; /// /// let mut cfg = TestGenerator::new(); - /// cfg.skip_field(|s, f| { + /// cfg.skip_union_field(|s, f| { /// s.ident() == "foo_t" || (s.ident() == "bar_t" && f.ident() == "bar") /// }); /// ``` - pub fn skip_field(&mut self, f: impl Fn(&Struct, &Field) -> bool + 'static) -> &mut Self { + pub fn skip_union_field(&mut self, f: impl Fn(&Union, &Field) -> bool + 'static) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::Field(struct_, field) = item { + if let MapInput::Field(Either::Right(union_), field) = item { + f(union_, field) + } else { + false + } + })); + self + } + + /// Configures whether tests for the type of a field is skipped or not. + /// + /// The closure is given a Rust struct as well as a field within that + /// struct. A flag indicating whether the field's type should be tested is + /// returned. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_struct_field_type(|s, field| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && field.ident() == "bar") + /// }); + /// ``` + pub fn skip_struct_field_type( + &mut self, + f: impl Fn(&Struct, &Field) -> bool + 'static, + ) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::FieldType(Either::Left(struct_), field) = item { f(struct_, field) } else { false @@ -319,6 +395,38 @@ impl TestGenerator { self } + /// Configures whether tests for the type of a field is skipped or not. + /// + /// The closure is given a Rust union as well as a field within that + /// union. A flag indicating whether the field's type should be tested is + /// returned. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_union_field_type(|s, field| { + /// s.ident() == "foo_t" || (s.ident() == "bar_t" && field.ident() == "bar") + /// }); + /// ``` + pub fn skip_union_field_type( + &mut self, + f: impl Fn(&Union, &Field) -> bool + 'static, + ) -> &mut Self { + self.skips.push(Box::new(move |item| { + if let MapInput::FieldType(Either::Right(union_), field) = item { + f(union_, field) + } else { + false + } + })); + self + } + /// Configures whether all tests for a typedef are skipped or not. /// /// # Examples @@ -450,6 +558,17 @@ impl TestGenerator { } /// Configures how Rust `const`s names are translated to C. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.rename_constant(|c| { + /// (c.ident() == "FOO").then_some("BAR".to_string()) + /// }); + /// ``` pub fn rename_constant(&mut self, f: impl Fn(&Const) -> Option + 'static) -> &mut Self { self.mapped_names.push(Box::new(move |item| { if let MapInput::Const(c) = item { @@ -469,16 +588,16 @@ impl TestGenerator { /// use ctest_next::TestGenerator; /// /// let mut cfg = TestGenerator::new(); - /// cfg.rename_field(|_s, field| { + /// cfg.rename_struct_field(|_s, field| { /// Some(field.ident().replace("foo", "bar")) /// }); /// ``` - pub fn rename_field( + pub fn rename_struct_field( &mut self, f: impl Fn(&Struct, &Field) -> Option + 'static, ) -> &mut Self { self.mapped_names.push(Box::new(move |item| { - if let MapInput::Field(s, c) = item { + if let MapInput::Field(Either::Left(s), c) = item { f(s, c) } else { None @@ -487,6 +606,32 @@ impl TestGenerator { self } + /// Configures how a Rust union field is translated to a C union field. + /// + /// # Examples + /// + /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.rename_union_field(|_u, field| { + /// Some(field.ident().replace("foo", "bar")) + /// }); + /// ``` + pub fn rename_union_field( + &mut self, + f: impl Fn(&Union, &Field) -> Option + 'static, + ) -> &mut Self { + self.mapped_names.push(Box::new(move |item| { + if let MapInput::Field(Either::Right(u), c) = item { + f(u, c) + } else { + None + } + })); + self + } + /// Configures the name of a function in the generated C code. /// /// # Examples @@ -577,6 +722,26 @@ impl TestGenerator { self } + /// Configures whether the ABI roundtrip tests for a type are emitted. + /// + /// The closure is passed the name of a Rust type and returns whether the + /// tests are generated. + /// + /// By default all types undergo ABI roundtrip tests. Arrays cannot undergo + /// an ABI roundtrip because they cannot be returned by C functions, and + /// have to be manually skipped here. + /// + /// # Examples + /// ```no_run + /// cfg.skip_roundtrip(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_roundtrip(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self { + self.skip_roundtrip = Some(Box::new(f)); + self + } + /// Generate the Rust and C testing files. /// /// Returns the path to the generated file. @@ -594,13 +759,14 @@ impl TestGenerator { let mut ffi_items = FfiItems::new(); ffi_items.visit_file(&ast); - // FIXME(ctest): Does not filter out tests for fields. + // Does not filter out tests for fields, that is done in the template. self.filter_ffi_items(&mut ffi_items); let output_directory = self .out_dir .clone() - .unwrap_or_else(|| env::var("OUT_DIR").unwrap().into()); + .or_else(|| env::var("OUT_DIR").ok().map(Into::into)) + .ok_or(GenerationError::EnvVarNotFound("OUT_DIR".to_string()))?; let output_file_path = output_directory.join(output_file_path); // Generate the Rust side of the tests. @@ -632,7 +798,9 @@ impl TestGenerator { } /// Skips entire items such as structs, constants, and aliases from being tested. - /// Does not skip specific tests or specific fields. + /// + /// Does not skip specific tests or specific fields. If `skip_private` is true, + /// it will skip tests for all private items. fn filter_ffi_items(&self, ffi_items: &mut FfiItems) { let verbose = self.verbose_skip; @@ -642,6 +810,7 @@ impl TestGenerator { .$field .extract_if(.., |item| { self.skips.iter().any(|f| f(&MapInput::$variant(item))) + || (self.skip_private && !item.public) }) .collect(); if verbose { @@ -660,21 +829,23 @@ impl TestGenerator { } /// Maps Rust identifiers or types to C counterparts, or defaults to the original name. - pub(crate) fn map<'a>(&self, item: impl Into>) -> Result { + pub(crate) fn map<'a>(&self, item: impl Into>) -> String { let item = item.into(); if let Some(mapped) = self.mapped_names.iter().find_map(|f| f(&item)) { - return Ok(mapped); + return mapped; } - Ok(match item { + match item { MapInput::Const(c) => c.ident().to_string(), MapInput::Fn(f) => f.ident().to_string(), MapInput::Static(s) => s.ident().to_string(), MapInput::Struct(s) => s.ident().to_string(), + MapInput::Union(u) => u.ident().to_string(), MapInput::Alias(t) => t.ident().to_string(), MapInput::Field(_, f) => f.ident().to_string(), MapInput::StructType(ty) => format!("struct {ty}"), MapInput::UnionType(ty) => format!("union {ty}"), + MapInput::FieldType(_, f) => f.ident().to_string(), MapInput::Type(ty) => ty.to_string(), - }) + } } } diff --git a/ctest-next/src/lib.rs b/ctest-next/src/lib.rs index 244ef7c792b6d..7c9cc89d87094 100644 --- a/ctest-next/src/lib.rs +++ b/ctest-next/src/lib.rs @@ -20,6 +20,7 @@ mod template; mod translator; pub use ast::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union}; +use either::Either; pub use generator::TestGenerator; pub use macro_expansion::expand; pub use runner::{__compile_test, __run_test, generate_test}; @@ -36,11 +37,13 @@ type BoxStr = Box; /// /// This is necessary because `ctest` does not parse the header file, so it /// does not know which items are volatile. -#[derive(Debug)] +#[derive(Debug, Clone)] #[non_exhaustive] pub enum VolatileItemKind { /// A struct field. StructField(Struct, Field), + /// A union field. + UnionField(Union, Field), /// An extern static. Static(Static), /// A function argument. @@ -54,9 +57,9 @@ pub enum VolatileItemKind { pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct identifier. Struct(&'a Struct), + Union(&'a Union), Fn(&'a crate::Fn), - #[expect(unused)] - Field(&'a Struct, &'a Field), + Field(Either<&'a Struct, &'a Union>, &'a Field), Alias(&'a Type), Const(&'a Const), Static(&'a Static), @@ -64,6 +67,7 @@ pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct type. StructType(&'a str), UnionType(&'a str), + FieldType(Either<&'a Struct, &'a Union>, &'a Field), } /* The From impls make it easier to write code in the test templates. */ @@ -97,3 +101,9 @@ impl<'a> From<&'a Struct> for MapInput<'a> { MapInput::Struct(s) } } + +impl<'a> From<&'a Union> for MapInput<'a> { + fn from(s: &'a Union) -> Self { + MapInput::Union(s) + } +} diff --git a/ctest-next/src/macro_expansion.rs b/ctest-next/src/macro_expansion.rs index bab9c59866732..25220dff81bd9 100644 --- a/ctest-next/src/macro_expansion.rs +++ b/ctest-next/src/macro_expansion.rs @@ -1,4 +1,7 @@ -use std::{env, fs::canonicalize, path::Path, process::Command}; +use std::env; +use std::fs::canonicalize; +use std::path::Path; +use std::process::Command; use crate::Result; diff --git a/ctest-next/src/runner.rs b/ctest-next/src/runner.rs index 62b75f85fb7e8..3acfbc232daa6 100644 --- a/ctest-next/src/runner.rs +++ b/ctest-next/src/runner.rs @@ -1,26 +1,36 @@ -use crate::{Result, TestGenerator}; use std::env; use std::fs::{canonicalize, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; +use crate::generator::GenerationError; +use crate::{Result, TestGenerator}; + /// Generate all tests for the given crate and output the Rust side to a file. #[doc(hidden)] pub fn generate_test( generator: &mut TestGenerator, crate_path: impl AsRef, output_file_path: impl AsRef, -) -> Result { +) -> Result { let output_file_path = generator.generate_files(crate_path, output_file_path)?; // Search for the target and host to build for if specified manually // (generator.target, generator.host), // via build script (TARGET, HOST), or for internal testing (TARGET_PLATFORM, HOST_PLATFORM). - let target = generator.target.clone().unwrap_or_else(|| { - env::var("TARGET").unwrap_or_else(|_| env::var("TARGET_PLATFORM").unwrap()) - }); - let host = env::var("HOST").unwrap_or_else(|_| env::var("HOST_PLATFORM").unwrap()); + let target = generator + .target + .clone() + .or_else(|| env::var("TARGET").ok()) + .or_else(|| env::var("TARGET_PLATFORM").ok()) + .ok_or(GenerationError::EnvVarNotFound( + "TARGET, TARGET_PLATFORM".to_string(), + ))?; + + let host = env::var("HOST") + .or_else(|_| env::var("HOST_PLATFORM")) + .map_err(|_| GenerationError::EnvVarNotFound("HOST, HOST_PLATFORM".to_string()))?; let mut cfg = cc::Build::new(); cfg.file(output_file_path.with_extension("c")); diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index ff72896b8814d..86df820e3049f 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -1,9 +1,13 @@ +use std::ops::Deref; + use askama::Template; +use either::Either; use quote::ToTokens; +use crate::ffi_items::FfiItems; +use crate::translator::{translate_expr, Translator}; use crate::{ - ffi_items::FfiItems, generator::GenerationError, translator::Translator, MapInput, Result, - TestGenerator, + Field, MapInput, Result, Struct, TestGenerator, TranslationError, Union, VolatileItemKind, }; /// Represents the Rust side of the generated testing suite. @@ -11,7 +15,7 @@ use crate::{ #[template(path = "test.rs")] pub(crate) struct RustTestTemplate<'a> { ffi_items: &'a FfiItems, - #[expect(unused)] + translator: Translator, generator: &'a TestGenerator, } @@ -29,6 +33,7 @@ impl<'a> RustTestTemplate<'a> { pub(crate) fn new(ffi_items: &'a FfiItems, generator: &'a TestGenerator) -> Self { Self { ffi_items, + translator: Translator::new(), generator, } } @@ -45,28 +50,30 @@ impl<'a> CTestTemplate<'a> { } /// Returns the equivalent C/Cpp identifier of the Rust item. - pub(crate) fn c_ident(&self, item: impl Into>) -> Result { + pub(crate) fn c_ident(&self, item: impl Into>) -> String { self.generator.map(item) } /// Returns the equivalent C/Cpp type of the Rust item. - pub(crate) fn c_type(&self, item: impl Into>) -> Result { + pub(crate) fn c_type(&self, item: impl Into>) -> Result { let item: MapInput<'a> = item.into(); let (ident, ty) = match item { - MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)), - MapInput::Alias(a) => (a.ident(), self.translator.translate_type(&a.ty)), - MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)), - MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)), + MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)?), + MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), + MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)?), MapInput::Fn(_) => unimplemented!(), - MapInput::Struct(_) => unimplemented!(), + // For structs/unions/aliases, their type is the same as their identifier. + MapInput::Alias(a) => (a.ident(), a.ident().to_string()), + MapInput::Struct(s) => (s.ident(), s.ident().to_string()), + MapInput::Union(u) => (u.ident(), u.ident().to_string()), + MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), + MapInput::FieldType(_, _) => panic!("MapInput::FieldType is not allowed!"), MapInput::Type(_) => panic!("MapInput::Type is not allowed!"), }; - let ty = ty.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))?; - let item = if self.ffi_items.contains_struct(ident) { MapInput::StructType(&ty) } else if self.ffi_items.contains_union(ident) { @@ -74,6 +81,112 @@ impl<'a> CTestTemplate<'a> { } else { MapInput::Type(&ty) }; - self.generator.map(item) + + Ok(self.generator.map(item)) + } + + /// Modify a C function `signature` that returns a ptr `ty` to be correctly translated. + /// + /// Arrays and Function types in C have different rules for placement, such as array lengths + /// being placed after the parameter list. + pub(crate) fn c_signature( + &self, + ty: &syn::Type, + signature: &str, + ) -> Result { + let new_signature = match ty { + syn::Type::BareFn(f) => { + let (ret, mut args, variadic) = self.translator.translate_signature_partial(f)?; + let abi = f + .abi + .clone() + .unwrap() + .name + .map(|s| s.value()) + .unwrap_or("C".to_string()); + + if variadic { + args.push("...".to_string()); + } else if args.is_empty() { + args.push("void".to_string()); + } + + format!("{}({}**{})({})", ret, abi, signature, args.join(", ")) + } + // Handles up to 2D arrays. + syn::Type::Array(outer) => match outer.elem.deref() { + syn::Type::Array(inner) => format!( + "{}(*{})[{}][{}]", + self.translator.translate_type(inner.elem.deref())?, + signature, + translate_expr(&outer.len), + translate_expr(&inner.len) + ), + _ => format!( + "{}(*{})[{}]", + self.translator.translate_type(outer.elem.deref())?, + signature, + translate_expr(&outer.len) + ), + }, + _ => { + let unmapped_c_type = self.translator.translate_type(ty)?; + let map_input = if self + .ffi_items + .contains_struct(&ty.to_token_stream().to_string()) + { + MapInput::StructType(&unmapped_c_type) + } else if self + .ffi_items + .contains_union(&ty.to_token_stream().to_string()) + { + MapInput::UnionType(&unmapped_c_type) + } else { + MapInput::Type(&unmapped_c_type) + }; + format!("{}* {}", self.generator.map(map_input), signature) + } + }; + + Ok(new_signature) + } + + /// Returns the volatile keyword if the given item is volatile. + pub(crate) fn emit_volatile(&self, v: VolatileItemKind) -> &str { + if !self.generator.volatile_items.is_empty() + && self.generator.volatile_items.iter().any(|f| f(v.clone())) + { + "volatile " + } else { + "" + } } } + +/* Helper functions to make the template code readable. */ + +/// Determine whether a Rust alias/struct/union should have a round trip test. +/// +/// By default all alias/struct/unions are roundtripped. Aliases or fields with arrays should +/// not be part of the roundtrip. +pub(crate) fn should_roundtrip(gen: &TestGenerator, ident: &str) -> bool { + gen.skip_roundtrip.as_ref().is_none_or(|skip| !skip(ident)) +} + +/// Determine whether a struct field should be skipped for tests. +pub(crate) fn should_skip_field( + gen: &TestGenerator, + e: Either<&Struct, &Union>, + field: &Field, +) -> bool { + gen.skips.iter().any(|f| f(&MapInput::Field(e, field))) +} + +/// Determine whether a struct field type should be skipped for tests. +pub(crate) fn should_skip_field_type( + gen: &TestGenerator, + e: Either<&Struct, &Union>, + field: &Field, +) -> bool { + gen.skips.iter().any(|f| f(&MapInput::FieldType(e, field))) +} diff --git a/ctest-next/src/tests.rs b/ctest-next/src/tests.rs index 4a0178867ea77..31977486fd432 100644 --- a/ctest-next/src/tests.rs +++ b/ctest-next/src/tests.rs @@ -1,7 +1,9 @@ -use crate::{ffi_items::FfiItems, translator::Translator, Result, TranslationError}; - use syn::visit::Visit; +use crate::ffi_items::FfiItems; +use crate::translator::Translator; +use crate::{Result, TranslationError}; + const ALL_ITEMS: &str = r#" use std::os::raw::c_void; @@ -24,6 +26,7 @@ pub struct Array { extern "C" { static baz: u16; + #[link_name = "calloc"] fn malloc(size: usize) -> *mut c_void; } "#; @@ -54,6 +57,11 @@ fn test_extraction_ffi_items() { assert_eq!(collect_idents!(ffi_items.foreign_statics()), ["baz"]); assert_eq!(collect_idents!(ffi_items.structs()), ["Array"]); assert_eq!(collect_idents!(ffi_items.unions()), ["Word"]); + + assert_eq!( + ffi_items.foreign_functions()[0].link_name.as_deref(), + Some("calloc") + ); } #[test] diff --git a/ctest-next/src/translator.rs b/ctest-next/src/translator.rs index ae8c2a4556e24..b00556575b265 100644 --- a/ctest-next/src/translator.rs +++ b/ctest-next/src/translator.rs @@ -2,13 +2,16 @@ //! //! Simple to semi complex types are supported only. -use std::{fmt, ops::Deref}; +use std::fmt; +use std::ops::Deref; use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; use thiserror::Error; +use crate::ffi_items::FfiItems; + /// An error that occurs during translation, detailing cause and location. #[derive(Debug, Error)] pub struct TranslationError { @@ -218,7 +221,7 @@ impl Translator { } /// Translate a Rust primitive type into its C equivalent. - fn translate_primitive_type(&self, ty: &syn::Ident) -> String { + pub(crate) fn translate_primitive_type(&self, ty: &syn::Ident) -> String { match ty.to_string().as_str() { "usize" => "size_t".to_string(), "isize" => "ssize_t".to_string(), @@ -305,12 +308,56 @@ impl Translator { } } } + + /// Partially translate a Rust bare function type into its equivalent C type. + /// + /// It returns the translated return type, translated argument types, and whether + /// it is variadic as a tuple. + pub(crate) fn translate_signature_partial( + &self, + signature: &syn::TypeBareFn, + ) -> Result<(String, Vec, bool), TranslationError> { + let args = signature + .inputs + .iter() + .map(|arg| self.translate_type(&arg.ty)) + .collect::, TranslationError>>()?; + let return_type = match &signature.output { + syn::ReturnType::Default => "void".to_string(), + syn::ReturnType::Type(_, ty) => match ty.deref() { + syn::Type::Never(_) => "void".to_string(), + syn::Type::Tuple(tuple) if tuple.elems.is_empty() => "void".to_string(), + _ => self.translate_type(ty.deref())?, + }, + }; + Ok((return_type, args, signature.variadic.is_some())) + } + + /// Determine whether a C type is a signed type. + pub(crate) fn has_sign(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { + match ty { + syn::Type::Path(path) => { + let ident = path.path.segments.last().unwrap().ident.clone(); + // The only thing other than a primitive that can be signed is an alias. + if let Some(aliased) = ffi_items.aliases().iter().find(|a| ident == a.ident()) { + return self.has_sign(ffi_items, &aliased.ty); + } + match self.translate_primitive_type(&ident).as_str() { + "char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" + | "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" + | "size_t" | "ssize_t" => true, + s => s.starts_with("signed ") || s.starts_with("unsigned "), + } + } + _ => false, + } + } } /// Translate a simple Rust expression to C. /// /// This function will just pass the expression as is in most cases. -fn translate_expr(expr: &syn::Expr) -> String { +pub(crate) fn translate_expr(expr: &syn::Expr) -> String { match expr { syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(), syn::Expr::Cast(c) => translate_expr(c.expr.deref()), diff --git a/ctest-next/templates/test.c b/ctest-next/templates/test.c index ef7f09a3e019b..f6b9b9eba6118 100644 --- a/ctest-next/templates/test.c +++ b/ctest-next/templates/test.c @@ -12,8 +12,8 @@ {%- for constant in ffi_items.constants() +%} {%- let ident = constant.ident() +%} -{%- let c_type = self.c_type(*constant)? +%} -{%- let c_ident = self.c_ident(*constant)? +%} +{%- let c_type = self.c_type(*constant).unwrap() +%} +{%- let c_ident = self.c_ident(*constant) +%} static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }}; @@ -24,3 +24,273 @@ static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }}; } {%- endfor +%} +{%- for alias in ffi_items.aliases() +%} +{%- let ident = alias.ident() +%} +{%- let c_type = self.c_type(*alias).unwrap() +%} + +// Return the size of a type. +uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } + +// Return the alignment of a type. +uint64_t __test_align_{{ ident }}(void) { + typedef struct { + unsigned char c; + {{ c_type }} v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +{%- if translator.has_sign(ffi_items, alias.ty) +%} + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t __test_signed_{{ ident }}(void) { + return ((({{ c_type }}) -1) < 0); +} +{%- endif +%} + +{%- if self::should_roundtrip(generator, ident) +%} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the type alias `x` 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. +{{ c_type }} __test_roundtrip_{{ ident }}( + int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({{ c_type }}); + if (size != rust_size) { + fprintf( + stderr, + "size of {{ c_type }} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif +{%- endif +%} +{%- endfor +%} + +{%- for structure in ffi_items.structs() +%} +{%- let ident = structure.ident() +%} +{%- let c_type = self.c_type(*structure).unwrap() +%} + +// Return the size of a type. +uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } + +// Return the alignment of a type. +uint64_t __test_align_{{ ident }}(void) { + typedef struct { + unsigned char c; + {{ c_type }} v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +{%- for field in structure.fields +%} +{%- if !self::should_skip_field(generator, Either::Left(structure), field) +%} +{%- let rust_field_name = field.ident() +%} +{%- let c_field_name = self.c_ident(MapInput::Field(Either::Left(structure), field)) +%} + +uint64_t __test_offset_{{ ident }}_{{ rust_field_name }}(void) { + return offsetof({{ c_type }}, {{ c_field_name }}); +} + +uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { + {{ c_type }}* foo = NULL; + return sizeof(foo->{{ c_field_name }}); +} + +{%- if !self::should_skip_field_type(generator, Either::Left(structure), field) +%} +{%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} +{%- let volatile = self.emit_volatile(VolatileItemKind::StructField(structure.clone(), field.clone())) +%} + +{{ volatile }}{{ signature }} { + return &b->{{ c_field_name }}; +} +{%- endif +%} +{%- endif +%} +{%- endfor +%} + +{%- if self::should_roundtrip(generator, ident) +%} +{%- let c_type = self.c_type(*structure).unwrap() +%} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +{{ c_type }} __test_roundtrip_{{ ident }}( + int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({{ c_type }}); + if (size != rust_size) { + fprintf( + stderr, + "size of {{ c_type }} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif +{%- endif +%} +{%- endfor +%} + +{%- for union_ in ffi_items.unions() +%} +{%- let ident = union_.ident() +%} +{%- let c_type = self.c_type(*union_).unwrap() +%} + +// Return the size of a type. +uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } + +// Return the alignment of a type. +uint64_t __test_align_{{ ident }}(void) { + typedef struct { + unsigned char c; + {{ c_type }} v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +{%- for field in union_.fields +%} +{%- if !self::should_skip_field(generator, Either::Right(union_), field) +%} +{%- let rust_field_name = field.ident() +%} +{%- let c_field_name = self.c_ident(MapInput::Field(Either::Right(union_), field)) +%} + +uint64_t __test_offset_{{ ident }}_{{ rust_field_name }}(void) { + return offsetof({{ c_type }}, {{ c_field_name }}); +} + +uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { + {{ c_type }}* foo = NULL; + return sizeof(foo->{{ c_field_name }}); +} + +{%- if !self::should_skip_field_type(generator, Either::Right(union_), field) +%} +{%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} +{%- let volatile = self.emit_volatile(VolatileItemKind::UnionField(union_.clone(), field.clone())) +%} + +{{ volatile }}{{ signature }} { + return &b->{{ c_field_name }}; +} +{%- endif +%} +{%- endif +%} +{%- endfor +%} + +{%- if self::should_roundtrip(generator, ident) +%} +{%- let c_type = self.c_type(*union_).unwrap() +%} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +{{ c_type }} __test_roundtrip_{{ ident }}( + int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({{ c_type }}); + if (size != rust_size) { + fprintf( + stderr, + "size of {{ c_type }} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif +{%- endif +%} +{%- endfor +%} + diff --git a/ctest-next/templates/test.rs b/ctest-next/templates/test.rs index cd8cf4caf2de3..9d560378f328c 100644 --- a/ctest-next/templates/test.rs +++ b/ctest-next/templates/test.rs @@ -7,10 +7,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, ptr, slice}; + use std::mem::offset_of; use super::*; @@ -51,13 +51,14 @@ mod generated_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_{{ ident }}() { + use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_{{ ident }}() -> *const *const u8; } let val = {{ ident }}; unsafe { let ptr = *__test_const_{{ ident }}(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const {{ ident }} not utf8"); let c = ::std::ffi::CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const {{ ident }} not utf8"); @@ -82,7 +83,430 @@ mod generated_tests { for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. - check_same_hex(b1, b2, &format!("{{ ident }} value at byte {}", i)); + check_same_hex(b1, b2, &format!("{{ ident }} value at byte {i}")); + } + } + } + {%- endif +%} + {%- endfor +%} + + {%- for alias in ffi_items.aliases() +%} + {%- let ident = alias.ident() +%} + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_{{ ident }}() { + extern "C" { + fn __test_size_{{ ident }}() -> u64; + fn __test_align_{{ ident }}() -> u64; + } + unsafe { + check_same(mem::size_of::<{{ ident }}>() as u64, + __test_size_{{ ident }}(), "{{ ident }} size"); + check_same(mem::align_of::<{{ ident }}>() as u64, + __test_align_{{ ident }}(), "{{ ident }} align"); + } + } + + {%- if translator.has_sign(ffi_items, alias.ty) +%} + + /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_{{ ident }}() { + extern "C" { + fn __test_signed_{{ ident }}() -> u32; + } + unsafe { + check_same(((!(0 as {{ ident }})) < (0 as {{ ident }})) as u32, + __test_signed_{{ ident }}(), "{{ ident }} signed"); + } + } + {%- endif +%} + + {%- if self::should_roundtrip(generator, ident) +%} + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> Vec { + vec![0; mem::size_of::<{{ ident }}>()] + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_{{ ident }}() { + use std::ffi::c_int; + + type U = {{ ident }}; + extern "C" { + fn __test_roundtrip_{{ ident }}( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_{{ ident }}(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + {%- endif +%} + {%- endfor +%} + + {%- for structure in ffi_items.structs() +%} + {%- let ident = structure.ident() +%} + {%- let fields = structure.public_fields() +%} + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_{{ ident }}() { + extern "C" { + fn __test_size_{{ ident }}() -> u64; + fn __test_align_{{ ident }}() -> u64; + } + unsafe { + check_same(mem::size_of::<{{ ident }}>() as u64, + __test_size_{{ ident }}(), "{{ ident }} size"); + check_same(mem::align_of::<{{ ident }}>() as u64, + __test_align_{{ ident }}(), "{{ ident }} align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_{{ ident }}() { + {%- for field in structure.public_fields() +%} + {%- if !self::should_skip_field(generator, Either::Left(structure), field) +%} + + extern "C" { + fn __test_offset_{{ ident }}_{{ field.ident() }}() -> u64; + fn __test_fsize_{{ ident }}_{{ field.ident() }}() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!({{ ident }}, {{ field.ident() }}), + __test_offset_{{ ident }}_{{ field.ident() }}() as usize, + "field offset {{ field.ident() }} of {{ ident }}"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_{{ ident }}_{{ field.ident() }}(), + "field size {{ field.ident() }} of {{ ident }}"); + } + {%- if !self::should_skip_field_type(generator, Either::Left(structure), field) +%} + + extern "C" { + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *mut {{ ident }}) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + check_same(field_ptr as *mut _, + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr_mut), + "field type {{ field.ident() }} of {{ ident }}"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + {%- endif +%} + {%- endif +%} + {%- endfor +%} + } + + {%- if self::should_roundtrip(generator, ident) +%} + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> Vec { + // stores (offset, size) for each field + {#- If there is no public field these become unused. +#} + {%- if fields.len() > 0 +%} + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let bar = bar.as_ptr(); + {%- else +%} + let v = Vec::<(usize, usize)>::new(); + {%- endif +%} + {%- for field in fields +%} + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!({{ ident }}, {{ field.ident() }}); + v.push((off, size)); + } + {%- endfor +%} + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::<{{ ident }}>(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_{{ ident }}() { + use std::ffi::c_int; + + type U = {{ ident }}; + extern "C" { + fn __test_roundtrip_{{ ident }}( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_{{ ident }}(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + {%- endif +%} + {%- endfor +%} + + {%- for union_ in ffi_items.unions() +%} + {%- let ident = union_.ident() +%} + {%- let fields = union_.public_fields() +%} + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_{{ ident }}() { + extern "C" { + fn __test_size_{{ ident }}() -> u64; + fn __test_align_{{ ident }}() -> u64; + } + unsafe { + check_same(mem::size_of::<{{ ident }}>() as u64, + __test_size_{{ ident }}(), "{{ ident }} size"); + check_same(mem::align_of::<{{ ident }}>() as u64, + __test_align_{{ ident }}(), "{{ ident }} align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_{{ ident }}() { + {%- for field in union_.public_fields() +%} + {%- if !self::should_skip_field(generator, Either::Right(union_), field) +%} + + extern "C" { + fn __test_offset_{{ ident }}_{{ field.ident() }}() -> u64; + fn __test_fsize_{{ ident }}_{{ field.ident() }}() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!({{ ident }}, {{ field.ident() }}), + __test_offset_{{ ident }}_{{ field.ident() }}() as usize, + "field offset {{ field.ident() }} of {{ ident }}"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_{{ ident }}_{{ field.ident() }}(), + "field size {{ field.ident() }} of {{ ident }}"); + } + {%- if !self::should_skip_field_type(generator, Either::Right(union_), field) +%} + + extern "C" { + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *mut {{ ident }}) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + check_same(field_ptr as *mut _, + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr_mut), + "field type {{ field.ident() }} of {{ ident }}"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + {%- endif +%} + {%- endif +%} + {%- endfor +%} + } + + {%- if self::should_roundtrip(generator, ident) +%} + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_{{ ident }}() -> Vec { + // stores (offset, size) for each field + {#- If there is no public field these become unused. +#} + {%- if fields.len() > 0 +%} + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let bar = bar.as_ptr(); + {%- else +%} + let v = Vec::<(usize, usize)>::new(); + {%- endif +%} + {%- for field in fields +%} + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).{{ field.ident() }}); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!({{ ident }}, {{ field.ident() }}); + v.push((off, size)); + } + {%- endfor +%} + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::<{{ ident }}>(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_{{ ident }}() { + use std::ffi::c_int; + + type U = {{ ident }}; + extern "C" { + fn __test_roundtrip_{{ ident }}( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_{{ ident }}(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } } } } @@ -110,5 +534,34 @@ fn run_all() { {%- for constant in ffi_items.constants() +%} const_{{ constant.ident() }}(); {%- endfor +%} + + {%- for alias in ffi_items.aliases() +%} + {%- let ident = alias.ident() +%} + size_align_{{ ident }}(); + {%- if translator.has_sign(ffi_items, alias.ty) +%} + sign_{{ ident }}(); + {%- endif +%} + {%- if self::should_roundtrip(generator, ident) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} + + {%- for structure in ffi_items.structs() +%} + {%- let ident = structure.ident() +%} + size_align_{{ ident }}(); + field_offset_size_{{ ident }}(); + {%- if self::should_roundtrip(generator, structure.ident()) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} + + {%- for union_ in ffi_items.unions() +%} + {%- let ident = union_.ident() +%} + size_align_{{ ident }}(); + field_offset_size_{{ ident }}(); + {%- if self::should_roundtrip(generator, union_.ident()) +%} + roundtrip_{{ ident }}(); + {%- endif +%} + {%- endfor +%} } diff --git a/ctest-next/tests/basic.rs b/ctest-next/tests/basic.rs index 15c5c1513e142..514248bc624b6 100644 --- a/ctest-next/tests/basic.rs +++ b/ctest-next/tests/basic.rs @@ -1,11 +1,8 @@ -use std::{ - env, fs, - path::{Path, PathBuf}, -}; - -use pretty_assertions::assert_eq; +use std::path::{Path, PathBuf}; +use std::{env, fs}; use ctest_next::{Result, TestGenerator, __compile_test, __run_test, generate_test}; +use pretty_assertions::assert_eq; // Headers are found relevative to the include directory, all files are generated // relative to the output directory. diff --git a/ctest-next/tests/input/hierarchy.out.c b/ctest-next/tests/input/hierarchy.out.c index 0574cbc03c6f1..993d8829e1f4c 100644 --- a/ctest-next/tests/input/hierarchy.out.c +++ b/ctest-next/tests/input/hierarchy.out.c @@ -13,3 +13,70 @@ static bool __test_const_ON_val = ON; bool* __test_const_ON(void) { return &__test_const_ON_val; } + +// Return the size of a type. +uint64_t __test_size_in6_addr(void) { return sizeof(in6_addr); } + +// Return the alignment of a type. +uint64_t __test_align_in6_addr(void) { + typedef struct { + unsigned char c; + in6_addr v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t __test_signed_in6_addr(void) { + return (((in6_addr) -1) < 0); +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the type alias `x` 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. +in6_addr __test_roundtrip_in6_addr( + int32_t rust_size, in6_addr value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(in6_addr); + if (size != rust_size) { + fprintf( + stderr, + "size of in6_addr is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"in6_addr\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/hierarchy.out.rs b/ctest-next/tests/input/hierarchy.out.rs index f301c77caf378..e3c98e94b966a 100644 --- a/ctest-next/tests/input/hierarchy.out.rs +++ b/ctest-next/tests/input/hierarchy.out.rs @@ -6,10 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, ptr, slice}; + use std::mem::offset_of; use super::*; @@ -56,7 +56,100 @@ mod generated_tests { for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. - check_same_hex(b1, b2, &format!("ON value at byte {}", i)); + check_same_hex(b1, b2, &format!("ON value at byte {i}")); + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_in6_addr() { + extern "C" { + fn __test_size_in6_addr() -> u64; + fn __test_align_in6_addr() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_in6_addr(), "in6_addr size"); + check_same(mem::align_of::() as u64, + __test_align_in6_addr(), "in6_addr align"); + } + } + + /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_in6_addr() { + extern "C" { + fn __test_signed_in6_addr() -> u32; + } + unsafe { + check_same(((!(0 as in6_addr)) < (0 as in6_addr)) as u32, + __test_signed_in6_addr(), "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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_in6_addr() -> Vec { + vec![0; mem::size_of::()] + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_in6_addr() { + use std::ffi::c_int; + + type U = in6_addr; + extern "C" { + fn __test_roundtrip_in6_addr( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_in6_addr(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_in6_addr(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"in6_addr\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } } } } @@ -80,4 +173,7 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { const_ON(); + size_align_in6_addr(); + sign_in6_addr(); + roundtrip_in6_addr(); } diff --git a/ctest-next/tests/input/macro.h b/ctest-next/tests/input/macro.h index 2b0ef6b80e351..940ce124b9cf9 100644 --- a/ctest-next/tests/input/macro.h +++ b/ctest-next/tests/input/macro.h @@ -11,3 +11,5 @@ struct VecU16 uint16_t x; uint16_t y; }; + +typedef const char *string; diff --git a/ctest-next/tests/input/macro.out.c b/ctest-next/tests/input/macro.out.c index 736c06b8291bd..8f79857b05095 100644 --- a/ctest-next/tests/input/macro.out.c +++ b/ctest-next/tests/input/macro.out.c @@ -5,3 +5,241 @@ #include #include #include + +// Return the size of a type. +uint64_t __test_size_string(void) { return sizeof(string); } + +// Return the alignment of a type. +uint64_t __test_align_string(void) { + typedef struct { + unsigned char c; + string v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the type alias `x` 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. +string __test_roundtrip_string( + int32_t rust_size, string value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(string); + if (size != rust_size) { + fprintf( + stderr, + "size of string is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"string\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_VecU8(void) { return sizeof(struct VecU8); } + +// Return the alignment of a type. +uint64_t __test_align_VecU8(void) { + typedef struct { + unsigned char c; + struct VecU8 v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_VecU8_x(void) { + return offsetof(struct VecU8, x); +} + +uint64_t __test_fsize_VecU8_x(void) { + struct VecU8* foo = NULL; + return sizeof(foo->x); +} + +uint8_t* __test_field_type_VecU8_x(struct VecU8* b) { + return &b->x; +} + +uint64_t __test_offset_VecU8_y(void) { + return offsetof(struct VecU8, y); +} + +uint64_t __test_fsize_VecU8_y(void) { + struct VecU8* foo = NULL; + return sizeof(foo->y); +} + +uint8_t* __test_field_type_VecU8_y(struct VecU8* b) { + return &b->y; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +struct VecU8 __test_roundtrip_VecU8( + int32_t rust_size, struct VecU8 value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct VecU8); + if (size != rust_size) { + fprintf( + stderr, + "size of struct VecU8 is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"VecU8\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_VecU16(void) { return sizeof(struct VecU16); } + +// Return the alignment of a type. +uint64_t __test_align_VecU16(void) { + typedef struct { + unsigned char c; + struct VecU16 v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_VecU16_x(void) { + return offsetof(struct VecU16, x); +} + +uint64_t __test_fsize_VecU16_x(void) { + struct VecU16* foo = NULL; + return sizeof(foo->x); +} + +uint16_t* __test_field_type_VecU16_x(struct VecU16* b) { + return &b->x; +} + +uint64_t __test_offset_VecU16_y(void) { + return offsetof(struct VecU16, y); +} + +uint64_t __test_fsize_VecU16_y(void) { + struct VecU16* foo = NULL; + return sizeof(foo->y); +} + +uint16_t* __test_field_type_VecU16_y(struct VecU16* b) { + return &b->y; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +struct VecU16 __test_roundtrip_VecU16( + int32_t rust_size, struct VecU16 value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct VecU16); + if (size != rust_size) { + fprintf( + stderr, + "size of struct VecU16 is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"VecU16\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/macro.out.rs b/ctest-next/tests/input/macro.out.rs index 61c7b4a3a4f91..61117d8be65d6 100644 --- a/ctest-next/tests/input/macro.out.rs +++ b/ctest-next/tests/input/macro.out.rs @@ -6,10 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, ptr, slice}; + use std::mem::offset_of; use super::*; @@ -40,6 +40,280 @@ mod generated_tests { NTESTS.fetch_add(1, Ordering::Relaxed); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_string() { + extern "C" { + fn __test_size_string() -> u64; + fn __test_align_string() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_string(), "string size"); + check_same(mem::align_of::() as u64, + __test_align_string(), "string align"); + } + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_string() -> Vec { + vec![0; mem::size_of::()] + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_string() { + use std::ffi::c_int; + + type U = string; + extern "C" { + fn __test_roundtrip_string( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_string(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_string(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"string\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_VecU8() { + extern "C" { + fn __test_size_VecU8() -> u64; + fn __test_align_VecU8() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_VecU8(), "VecU8 size"); + check_same(mem::align_of::() as u64, + __test_align_VecU8(), "VecU8 align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_VecU8() { + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_VecU8() -> Vec { + // stores (offset, size) for each field + let v = Vec::<(usize, usize)>::new(); + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_VecU8() { + use std::ffi::c_int; + + type U = VecU8; + extern "C" { + fn __test_roundtrip_VecU8( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_VecU8(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_VecU8(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU8\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_VecU16() { + extern "C" { + fn __test_size_VecU16() -> u64; + fn __test_align_VecU16() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_VecU16(), "VecU16 size"); + check_same(mem::align_of::() as u64, + __test_align_VecU16(), "VecU16 align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_VecU16() -> Vec { + // stores (offset, size) for each field + let v = Vec::<(usize, usize)>::new(); + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_VecU16() { + use std::ffi::c_int; + + type U = VecU16; + extern "C" { + fn __test_roundtrip_VecU16( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_VecU16(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_VecU16(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"VecU16\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -59,4 +333,12 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { + size_align_string(); + roundtrip_string(); + size_align_VecU8(); + field_offset_size_VecU8(); + roundtrip_VecU8(); + size_align_VecU16(); + field_offset_size_VecU16(); + roundtrip_VecU16(); } diff --git a/ctest-next/tests/input/macro.rs b/ctest-next/tests/input/macro.rs index d0ce80180663f..67530a5ed194c 100644 --- a/ctest-next/tests/input/macro.rs +++ b/ctest-next/tests/input/macro.rs @@ -1,3 +1,5 @@ +use std::os::raw::c_char; + macro_rules! vector { ($name:ident, $ty:ty) => { #[repr(C)] @@ -10,3 +12,5 @@ macro_rules! vector { vector!(VecU8, u8); vector!(VecU16, u16); + +type string = *const c_char; diff --git a/ctest-next/tests/input/simple.out.c b/ctest-next/tests/input/simple.out.c deleted file mode 100644 index 94df4ec988166..0000000000000 --- a/ctest-next/tests/input/simple.out.c +++ /dev/null @@ -1,15 +0,0 @@ -/* This file was autogenerated by ctest; do not modify directly */ - -#include -#include -#include -#include -#include - -static char const* __test_const_A_val = 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. -char const** __test_const_A(void) { - return &__test_const_A_val; -} diff --git a/ctest-next/tests/input/simple.out.rs b/ctest-next/tests/input/simple.out.rs deleted file mode 100644 index ed1daf847f356..0000000000000 --- a/ctest-next/tests/input/simple.out.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* This file was autogenerated by ctest; do not modify directly */ - -/// As this file is sometimes built using rustc, crate level attributes -/// are not allowed at the top-level, so we hack around this by keeping it -/// inside of a module. -mod generated_tests { - #![allow(non_snake_case)] - #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; - use std::fmt::{Debug, LowerHex}; - use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - - use super::*; - - pub static FAILED: AtomicBool = AtomicBool::new(false); - pub static NTESTS: AtomicUsize = AtomicUsize::new(0); - - /// Check that the value returned from the Rust and C side in a certain test is equivalent. - /// - /// Internally it will remember which checks failed and how many tests have been run. - fn check_same(rust: T, c: T, attr: &str) { - if rust != c { - eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); - FAILED.store(true, Ordering::Relaxed); - } else { - NTESTS.fetch_add(1, Ordering::Relaxed); - } - } - - /// Check that the value returned from the Rust and C side in a certain test is equivalent. - /// - /// Internally it will remember which checks failed and how many tests have been run. This - /// method is the same as `check_same` but prints errors in bytes in hex. - fn check_same_hex(rust: T, c: T, attr: &str) { - if rust != c { - eprintln!("bad {attr}: rust: {rust:?} ({rust:#x}) != c {c:?} ({c:#x})"); - FAILED.store(true, Ordering::Relaxed); - } else { - NTESTS.fetch_add(1, Ordering::Relaxed); - } - } - // Test that the string constant is the same in both Rust and C. - // While fat pointers can't be translated, we instead of * const c_char. - pub fn const_A() { - extern "C" { - fn __test_const_A() -> *const *const u8; - } - let val = A; - unsafe { - let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); - let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); - let c = c.to_str().expect("const A not utf8"); - check_same(val, c, "A string"); - } - } -} - -use generated_tests::*; - -fn main() { - println!("RUNNING ALL TESTS"); - run_all(); - if FAILED.load(std::sync::atomic::Ordering::Relaxed) { - panic!("some tests failed"); - } else { - println!( - "PASSED {} tests", - NTESTS.load(std::sync::atomic::Ordering::Relaxed) - ); - } -} - -// Run all tests by calling the functions that define them. -fn run_all() { - const_A(); -} diff --git a/ctest-next/tests/input/simple.out.with-renames.c b/ctest-next/tests/input/simple.out.with-renames.c index 8d6794dafe228..03a19b3b34cae 100644 --- a/ctest-next/tests/input/simple.out.with-renames.c +++ b/ctest-next/tests/input/simple.out.with-renames.c @@ -21,3 +21,246 @@ static char const* __test_const_B_val = C_B; char const** __test_const_B(void) { return &__test_const_B_val; } + +// Return the size of a type. +uint64_t __test_size_Byte(void) { return sizeof(Byte); } + +// Return the alignment of a type. +uint64_t __test_align_Byte(void) { + typedef struct { + unsigned char c; + Byte v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t __test_signed_Byte(void) { + return (((Byte) -1) < 0); +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the type alias `x` 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. +Byte __test_roundtrip_Byte( + int32_t rust_size, Byte value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(Byte); + if (size != rust_size) { + fprintf( + stderr, + "size of Byte is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Byte\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_Person(void) { return sizeof(struct Person); } + +// Return the alignment of a type. +uint64_t __test_align_Person(void) { + typedef struct { + unsigned char c; + struct Person v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_Person_name(void) { + return offsetof(struct Person, name); +} + +uint64_t __test_fsize_Person_name(void) { + struct Person* foo = NULL; + return sizeof(foo->name); +} + +char const** __test_field_type_Person_name(struct Person* b) { + return &b->name; +} + +uint64_t __test_offset_Person_age(void) { + return offsetof(struct Person, age); +} + +uint64_t __test_fsize_Person_age(void) { + struct Person* foo = NULL; + return sizeof(foo->age); +} + +uint8_t* __test_field_type_Person_age(struct Person* b) { + return &b->age; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +struct Person __test_roundtrip_Person( + int32_t rust_size, struct Person value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct Person); + if (size != rust_size) { + fprintf( + stderr, + "size of struct Person is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Person\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_Word(void) { return sizeof(union Word); } + +// Return the alignment of a type. +uint64_t __test_align_Word(void) { + typedef struct { + unsigned char c; + union Word v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_Word_word(void) { + return offsetof(union Word, word); +} + +uint64_t __test_fsize_Word_word(void) { + union Word* foo = NULL; + return sizeof(foo->word); +} + +uint16_t* __test_field_type_Word_word(union Word* b) { + return &b->word; +} + +uint64_t __test_offset_Word_byte(void) { + return offsetof(union Word, byte); +} + +uint64_t __test_fsize_Word_byte(void) { + union Word* foo = NULL; + return sizeof(foo->byte); +} + +Byte(*__test_field_type_Word_byte(union Word* b))[2] { + return &b->byte; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +union Word __test_roundtrip_Word( + int32_t rust_size, union Word value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(union Word); + if (size != rust_size) { + fprintf( + stderr, + "size of union Word is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Word\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-renames.rs b/ctest-next/tests/input/simple.out.with-renames.rs index 06929a6077860..a2e038012ca4c 100644 --- a/ctest-next/tests/input/simple.out.with-renames.rs +++ b/ctest-next/tests/input/simple.out.with-renames.rs @@ -6,10 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, ptr, slice}; + use std::mem::offset_of; use super::*; @@ -44,13 +44,14 @@ mod generated_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_A() { + use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_A() -> *const *const u8; } let val = A; unsafe { let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); let c = ::std::ffi::CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); @@ -61,19 +62,391 @@ mod generated_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_B() { + use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_B() -> *const *const u8; } let val = B; unsafe { let ptr = *__test_const_B(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const B not utf8"); let c = ::std::ffi::CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const B not utf8"); check_same(val, c, "B string"); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Byte() { + extern "C" { + fn __test_size_Byte() -> u64; + fn __test_align_Byte() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Byte(), "Byte size"); + check_same(mem::align_of::() as u64, + __test_align_Byte(), "Byte align"); + } + } + + /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_Byte() { + extern "C" { + fn __test_signed_Byte() -> u32; + } + unsafe { + check_same(((!(0 as Byte)) < (0 as Byte)) as u32, + __test_signed_Byte(), "Byte 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Byte() -> Vec { + vec![0; mem::size_of::()] + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Byte() { + use std::ffi::c_int; + + type U = Byte; + extern "C" { + fn __test_roundtrip_Byte( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Byte(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Byte(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Person() { + extern "C" { + fn __test_size_Person() -> u64; + fn __test_align_Person() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Person(), "Person size"); + check_same(mem::align_of::() as u64, + __test_align_Person(), "Person align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_Person() { + + extern "C" { + fn __test_offset_Person_name() -> u64; + fn __test_fsize_Person_name() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).name); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Person, name), + __test_offset_Person_name() as usize, + "field offset name of Person"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_Person_name(), + "field size name of Person"); + } + + extern "C" { + fn __test_field_type_Person_name(a: *mut Person) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).name); + check_same(field_ptr as *mut _, + __test_field_type_Person_name(ty_ptr_mut), + "field type name of Person"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Person() -> Vec { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::::uninit(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).name); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Person() { + use std::ffi::c_int; + + type U = Person; + extern "C" { + fn __test_roundtrip_Person( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Person(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Person(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Word() { + extern "C" { + fn __test_size_Word() -> u64; + fn __test_align_Word() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Word(), "Word size"); + check_same(mem::align_of::() as u64, + __test_align_Word(), "Word align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_Word() { + + extern "C" { + fn __test_offset_Word_word() -> u64; + fn __test_fsize_Word_word() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).word); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Word, word), + __test_offset_Word_word() as usize, + "field offset word of Word"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_Word_word(), + "field size word of Word"); + } + + extern "C" { + fn __test_field_type_Word_word(a: *mut Word) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).word); + check_same(field_ptr as *mut _, + __test_field_type_Word_word(ty_ptr_mut), + "field type word of Word"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Word() -> Vec { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::::uninit(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).word); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Word() { + use std::ffi::c_int; + + type U = Word; + extern "C" { + fn __test_roundtrip_Word( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Word(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Word(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -95,4 +468,13 @@ fn main() { fn run_all() { const_A(); const_B(); + size_align_Byte(); + sign_Byte(); + roundtrip_Byte(); + size_align_Person(); + field_offset_size_Person(); + roundtrip_Person(); + size_align_Word(); + field_offset_size_Word(); + roundtrip_Word(); } diff --git a/ctest-next/tests/input/simple.out.with-skips.c b/ctest-next/tests/input/simple.out.with-skips.c index 94df4ec988166..2205307767fa9 100644 --- a/ctest-next/tests/input/simple.out.with-skips.c +++ b/ctest-next/tests/input/simple.out.with-skips.c @@ -13,3 +13,246 @@ static char const* __test_const_A_val = A; char const** __test_const_A(void) { return &__test_const_A_val; } + +// Return the size of a type. +uint64_t __test_size_Byte(void) { return sizeof(Byte); } + +// Return the alignment of a type. +uint64_t __test_align_Byte(void) { + typedef struct { + unsigned char c; + Byte v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +// Return `1` if the type is signed, otherwise return `0`. +uint32_t __test_signed_Byte(void) { + return (((Byte) -1) < 0); +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the type alias `x` 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. +Byte __test_roundtrip_Byte( + int32_t rust_size, Byte value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(Byte); + if (size != rust_size) { + fprintf( + stderr, + "size of Byte is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Byte\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_Person(void) { return sizeof(struct Person); } + +// Return the alignment of a type. +uint64_t __test_align_Person(void) { + typedef struct { + unsigned char c; + struct Person v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_Person_name(void) { + return offsetof(struct Person, name); +} + +uint64_t __test_fsize_Person_name(void) { + struct Person* foo = NULL; + return sizeof(foo->name); +} + +char const** __test_field_type_Person_name(struct Person* b) { + return &b->name; +} + +uint64_t __test_offset_Person_age(void) { + return offsetof(struct Person, age); +} + +uint64_t __test_fsize_Person_age(void) { + struct Person* foo = NULL; + return sizeof(foo->age); +} + +uint8_t* __test_field_type_Person_age(struct Person* b) { + return &b->age; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +struct Person __test_roundtrip_Person( + int32_t rust_size, struct Person value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(struct Person); + if (size != rust_size) { + fprintf( + stderr, + "size of struct Person is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Person\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif + +// Return the size of a type. +uint64_t __test_size_Word(void) { return sizeof(union Word); } + +// Return the alignment of a type. +uint64_t __test_align_Word(void) { + typedef struct { + unsigned char c; + union Word v; + } type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; +} + +uint64_t __test_offset_Word_word(void) { + return offsetof(union Word, word); +} + +uint64_t __test_fsize_Word_word(void) { + union Word* foo = NULL; + return sizeof(foo->word); +} + +uint16_t* __test_field_type_Word_word(union Word* b) { + return &b->word; +} + +uint64_t __test_offset_Word_byte(void) { + return offsetof(union Word, byte); +} + +uint64_t __test_fsize_Word_byte(void) { + union Word* foo = NULL; + return sizeof(foo->byte); +} + +Byte(*__test_field_type_Word_byte(union Word* b))[2] { + return &b->byte; +} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union `x` 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. +union Word __test_roundtrip_Word( + int32_t rust_size, union Word value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof(union Word); + if (size != rust_size) { + fprintf( + stderr, + "size of union Word is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"Word\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif diff --git a/ctest-next/tests/input/simple.out.with-skips.rs b/ctest-next/tests/input/simple.out.with-skips.rs index dac02a72aab6c..cb7d7d46af7c6 100644 --- a/ctest-next/tests/input/simple.out.with-skips.rs +++ b/ctest-next/tests/input/simple.out.with-skips.rs @@ -6,10 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, ptr, slice}; + use std::mem::offset_of; use super::*; @@ -44,19 +44,391 @@ mod generated_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_A() { + use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_A() -> *const *const u8; } let val = A; unsafe { let ptr = *__test_const_A(); - let val = CStr::from_ptr(ptr.cast::()); + let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); let c = ::std::ffi::CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); check_same(val, c, "A string"); } } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Byte() { + extern "C" { + fn __test_size_Byte() -> u64; + fn __test_align_Byte() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Byte(), "Byte size"); + check_same(mem::align_of::() as u64, + __test_align_Byte(), "Byte align"); + } + } + + /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// + /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value + /// depending on whether a signed or unsigned type is used. This is simply checked on both + /// Rust and C sides to see if they are equal. + pub fn sign_Byte() { + extern "C" { + fn __test_signed_Byte() -> u32; + } + unsafe { + check_same(((!(0 as Byte)) < (0 as Byte)) as u32, + __test_signed_Byte(), "Byte 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Byte() -> Vec { + vec![0; mem::size_of::()] + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Byte() { + use std::ffi::c_int; + + type U = Byte; + extern "C" { + fn __test_roundtrip_Byte( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Byte(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Byte(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Person() { + extern "C" { + fn __test_size_Person() -> u64; + fn __test_align_Person() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Person(), "Person size"); + check_same(mem::align_of::() as u64, + __test_align_Person(), "Person align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_Person() { + + extern "C" { + fn __test_offset_Person_name() -> u64; + fn __test_fsize_Person_name() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).name); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Person, name), + __test_offset_Person_name() as usize, + "field offset name of Person"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_Person_name(), + "field size name of Person"); + } + + extern "C" { + fn __test_field_type_Person_name(a: *mut Person) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).name); + check_same(field_ptr as *mut _, + __test_field_type_Person_name(ty_ptr_mut), + "field type name of Person"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Person() -> Vec { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::::uninit(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).name); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!(Person, name); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Person() { + use std::ffi::c_int; + + type U = Person; + extern "C" { + fn __test_roundtrip_Person( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Person(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Person(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_Word() { + extern "C" { + fn __test_size_Word() -> u64; + fn __test_align_Word() -> u64; + } + unsafe { + check_same(mem::size_of::() as u64, + __test_size_Word(), "Word size"); + check_same(mem::align_of::() as u64, + __test_align_Word(), "Word align"); + } + } + + /// No idea what this does. + pub fn field_offset_size_Word() { + + extern "C" { + fn __test_offset_Word_word() -> u64; + fn __test_fsize_Word_word() -> u64; + } + unsafe { + let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).word); + let val = ty_ptr.read_unaligned(); + check_same(offset_of!(Word, word), + __test_offset_Word_word() as usize, + "field offset word of Word"); + check_same(mem::size_of_val(&val) as u64, + __test_fsize_Word_word(), + "field size word of Word"); + } + + extern "C" { + fn __test_field_type_Word_word(a: *mut Word) -> *mut u8; + } + unsafe { + let mut uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).word); + check_same(field_ptr as *mut _, + __test_field_type_Word_word(ty_ptr_mut), + "field type word of Word"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + } + } + + /// 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. `1` if the byte is padding, + /// and `0` if the byte is not padding. + /// + /// For type aliases, the padding map is all zeroes. + fn roundtrip_padding_Word() -> Vec { + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let bar = std::mem::MaybeUninit::::uninit(); + let bar = bar.as_ptr(); + unsafe { + let ty_ptr = std::ptr::addr_of!((*bar).word); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!(Word, word); + v.push((off, size)); + } + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // 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 + pad.resize(mem::size_of::(), 1); + for (off, size) in &v { + for i in 0..*size { + pad[off + i] = 0; + } + } + pad + } + + /// Tests whether the type alias `x` 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. + pub fn roundtrip_Word() { + use std::ffi::c_int; + + type U = Word; + extern "C" { + fn __test_roundtrip_Word( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + } + let pad = roundtrip_padding_Word(); + unsafe { + use std::mem::{MaybeUninit, size_of}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + let r: U = __test_roundtrip_Word(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate().take(size_of::()) { + if *elem == 1 { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } } use generated_tests::*; @@ -77,4 +449,13 @@ fn main() { // Run all tests by calling the functions that define them. fn run_all() { const_A(); + size_align_Byte(); + sign_Byte(); + roundtrip_Byte(); + size_align_Person(); + field_offset_size_Person(); + roundtrip_Person(); + size_align_Word(); + field_offset_size_Word(); + roundtrip_Word(); } diff --git a/ctest-next/tests/input/simple.rs b/ctest-next/tests/input/simple.rs index a6f22be4deb39..3374e69287732 100644 --- a/ctest-next/tests/input/simple.rs +++ b/ctest-next/tests/input/simple.rs @@ -4,13 +4,13 @@ pub type Byte = u8; #[repr(C)] pub struct Person { - name: *const c_char, + pub name: *const c_char, age: u8, } #[repr(C)] pub union Word { - word: u16, + pub word: u16, byte: [Byte; 2], } diff --git a/ctest-next/tests/usage.rs b/ctest-next/tests/usage.rs new file mode 100644 index 0000000000000..fb0b4cb48f626 --- /dev/null +++ b/ctest-next/tests/usage.rs @@ -0,0 +1,60 @@ +use std::env; + +use ctest_next::{generate_test, Result, TestGenerator}; + +/// Create a test generator configured to useful settings for this test. +fn default_generator(opt_level: u8, header: &str) -> Result { + env::set_var("OPT_LEVEL", opt_level.to_string()); + let mut generator = TestGenerator::new(); + generator.header(header); + + Ok(generator) +} + +#[test] +fn test_missing_out_dir() { + env::remove_var("OUT_DIR"); + + let mut gen = default_generator(1, "macro.h").unwrap(); + gen.include("tests/input"); + + let result = generate_test(&mut gen, "src/t1.rs", "out_dir_gen.rs"); + assert!(result.is_err(), "Expected error when OUT_DIR is missing"); +} + +#[test] +fn test_invalid_out_dir() { + let mut gen = default_generator(1, "macro.h").unwrap(); + gen.out_dir("/nonexistent_dir").include("tests/input"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "out_path_gen.rs").is_err(), + "Expected error with invalid output path" + ); +} + +#[test] +fn test_non_existent_header() { + let mut gen = default_generator(1, "macro.h").unwrap(); + let out_dir = tempfile::tempdir().unwrap(); + gen.out_dir(out_dir) + .include("tests/input") + .header("nonexistent_header.h"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "missing_header_gen.rs").is_err(), + "Expected error with non-existent header" + ); +} + +#[test] +fn test_invalid_include_path() { + let mut gen = default_generator(1, "macro.h").unwrap(); + let out_dir = tempfile::tempdir().unwrap(); + gen.out_dir(out_dir).include("nonexistent_directory"); + + assert!( + generate_test(&mut gen, "tests/input/macro.rs", "invalid_include_gen.rs").is_err(), + "Expected error with invalid include path" + ); +} From b474ed869413a37b5014e9d43f80d0bf06744f7b Mon Sep 17 00:00:00 2001 From: mbyx Date: Mon, 14 Jul 2025 15:49:37 +0500 Subject: [PATCH 2/2] ctest: move ctest-next-test to ctest-test --- Cargo.lock | 3 +- Cargo.toml | 1 - ctest-next-test/Cargo.toml | 33 -- ctest-next-test/build.rs | 62 --- ctest-next-test/src/bin/t1-next.rs | 6 - ctest-next-test/src/bin/t2-next.rs | 6 - ctest-next-test/src/lib.rs | 6 - ctest-next-test/src/t1.c | 70 --- ctest-next-test/src/t1.h | 187 ------- ctest-next-test/src/t1.rs | 205 -------- ctest-next-test/src/t2.c | 2 - ctest-next-test/src/t2.h | 26 - ctest-next-test/src/t2.rs | 37 -- ctest-next-test/tests/all.rs | 86 ---- ctest-next/Cargo.lock | 485 ++++++++++++++++++ ctest-next/Cargo.toml | 1 - ctest-next/src/ast/structure.rs | 4 +- ctest-next/src/ast/union.rs | 4 +- ctest-next/src/ffi_items.rs | 27 +- ctest-next/src/generator.rs | 22 +- ctest-next/src/lib.rs | 7 +- ctest-next/src/template.rs | 37 +- ctest-next/src/translator.rs | 4 +- ctest-next/templates/common/test_roundtrip.c | 48 ++ ctest-next/templates/common/test_roundtrip.rs | 54 ++ ctest-next/templates/common/test_size_align.c | 7 + .../templates/common/test_size_align.rs | 18 + ctest-next/templates/test.c | 214 +------- ctest-next/templates/test.rs | 332 +++--------- ctest-next/tests/input/hierarchy.out.c | 21 +- ctest-next/tests/input/hierarchy.out.rs | 66 +-- ctest-next/tests/input/macro.out.c | 67 +-- ctest-next/tests/input/macro.out.rs | 152 +++--- .../tests/input/simple.out.with-renames.c | 71 +-- .../tests/input/simple.out.with-renames.rs | 236 ++++----- .../tests/input/simple.out.with-skips.c | 71 +-- .../tests/input/simple.out.with-skips.rs | 233 +++++---- ctest-test/Cargo.toml | 15 +- ctest-test/build.rs | 113 ++-- ctest-test/src/bin/t1.rs | 1 - ctest-test/src/bin/t1_cxx.rs | 12 - ctest-test/src/bin/t2_cxx.rs | 11 - ctest-test/src/lib.rs | 5 +- ctest-test/src/t1.c | 51 +- ctest-test/src/t1.cpp | 1 - ctest-test/src/t1.h | 96 ++-- ctest-test/src/t1.rs | 32 +- ctest-test/src/t2.cpp | 1 - ctest-test/src/t2.h | 9 +- ctest-test/src/t2.rs | 11 +- ctest-test/tests/all.rs | 165 +----- 51 files changed, 1347 insertions(+), 2087 deletions(-) delete mode 100644 ctest-next-test/Cargo.toml delete mode 100644 ctest-next-test/build.rs delete mode 100644 ctest-next-test/src/bin/t1-next.rs delete mode 100644 ctest-next-test/src/bin/t2-next.rs delete mode 100644 ctest-next-test/src/lib.rs delete mode 100644 ctest-next-test/src/t1.c delete mode 100644 ctest-next-test/src/t1.h delete mode 100644 ctest-next-test/src/t1.rs delete mode 100644 ctest-next-test/src/t2.c delete mode 100644 ctest-next-test/src/t2.h delete mode 100644 ctest-next-test/src/t2.rs delete mode 100644 ctest-next-test/tests/all.rs create mode 100644 ctest-next/Cargo.lock create mode 100644 ctest-next/templates/common/test_roundtrip.c create mode 100644 ctest-next/templates/common/test_roundtrip.rs create mode 100644 ctest-next/templates/common/test_size_align.c create mode 100644 ctest-next/templates/common/test_size_align.rs delete mode 100644 ctest-test/src/bin/t1_cxx.rs delete mode 100644 ctest-test/src/bin/t2_cxx.rs delete mode 120000 ctest-test/src/t1.cpp delete mode 120000 ctest-test/src/t2.cpp diff --git a/Cargo.lock b/Cargo.lock index a6eb90ac30366..7ea29e12cf4c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,8 +147,7 @@ name = "ctest-test" version = "0.1.0" dependencies = [ "cc", - "cfg-if 1.0.1", - "ctest", + "ctest-next", "libc 1.0.0-alpha.1", ] diff --git a/Cargo.toml b/Cargo.toml index 7f1f52b387c65..42c2228cfbf37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,6 @@ extra_traits = [] members = [ "ctest", "ctest-next", - "ctest-next-test", "ctest-test", "libc-test", ] diff --git a/ctest-next-test/Cargo.toml b/ctest-next-test/Cargo.toml deleted file mode 100644 index ed41ff224bc4c..0000000000000 --- a/ctest-next-test/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "ctest-next-test" -version = "0.1.0" -authors = ["Alex Crichton "] -publish = false -edition = "2021" - -[build-dependencies] -ctest-next = { path = "../ctest-next" } -cc = "1.0" - -[dev-dependencies] -ctest-next = { path = "../ctest-next" } - -[dependencies] -libc = { path = ".." } - -[[bin]] -name = "t1-next" -test = false - -[[bin]] -name = "t2-next" -test = false - -# FIXME(msrv): These should be moved to the root Cargo.toml as `[workspace.lints.*]` -# once MSRV is above 1.64 and replaced with `[lints] workspace=true` - -[lints.rust] -# FIXME(cleanup): make ident usage consistent in each file -unused_qualifications = "allow" - -[lints.clippy] diff --git a/ctest-next-test/build.rs b/ctest-next-test/build.rs deleted file mode 100644 index 301f708454035..0000000000000 --- a/ctest-next-test/build.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::env; - -use ctest_next::{generate_test, TestGenerator}; - -fn main() { - let opt_level = env::var("OPT_LEVEL") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - - let profile = env::var("PROFILE").unwrap_or_default(); - if profile == "release" || opt_level >= 2 { - println!("cargo:rustc-cfg=optimized"); - } - - // FIXME(ctest): The .c files are ignored right now, I'm not sure if they - // were used or how they were used before. - cc::Build::new() - .include("src") - .warnings(false) - .file("src/t1.c") - .compile("libt1.a"); - println!("cargo:rerun-if-changed=src/t1.c"); - println!("cargo:rerun-if-changed=src/t1.h"); - - cc::Build::new() - .warnings(false) - .file("src/t2.c") - .compile("libt2.a"); - println!("cargo:rerun-if-changed=src/t2.c"); - println!("cargo:rerun-if-changed=src/t2.h"); - - let mut t1gen = TestGenerator::new(); - t1gen - .header("t1.h") - .include("src") - .skip_private(true) - .rename_fn(|f| f.link_name().unwrap_or(f.ident()).to_string().into()) - .rename_union_ty(|ty| (ty == "T1Union").then_some(ty.to_string())) - .rename_struct_ty(|ty| (ty == "Transparent").then_some(ty.to_string())) - .volatile_field(|s, f| s.ident() == "V" && f.ident() == "v") - .volatile_static(|s| s.ident() == "vol_ptr") - .volatile_static(|s| s.ident() == "T1_fn_ptr_vol") - .volatile_fn_arg(|f, p| f.ident() == "T1_vol0" && p.ident() == "arg0") - .volatile_fn_arg(|f, p| f.ident() == "T1_vol2" && p.ident() == "arg1") - .volatile_fn_return_type(|f| f.ident() == "T1_vol1") - .volatile_fn_return_type(|f| f.ident() == "T1_vol2") - // The parameter `a` of the functions `T1r`, `T1s`, `T1t`, `T1v` is an array. - .array_arg(|f, p| matches!(f.ident(), "T1r" | "T1s" | "T1t" | "T1v") && p.ident() == "a") - .skip_roundtrip(|n| n == "Arr"); - generate_test(&mut t1gen, "src/t1.rs", "t1gen.rs").unwrap(); - - let mut t2gen = TestGenerator::new(); - t2gen - .header("t2.h") - .include("src") - // public C typedefs have to manually be specified because they are identical to normal - // structs on the Rust side. - .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) - .skip_roundtrip(|_| true); - generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap(); -} diff --git a/ctest-next-test/src/bin/t1-next.rs b/ctest-next-test/src/bin/t1-next.rs deleted file mode 100644 index 1cbeabd1d00a1..0000000000000 --- a/ctest-next-test/src/bin/t1-next.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![cfg(not(test))] -#![deny(warnings)] - -use ctest_next_test::t1::*; - -include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest-next-test/src/bin/t2-next.rs b/ctest-next-test/src/bin/t2-next.rs deleted file mode 100644 index 04bc0563d9d5e..0000000000000 --- a/ctest-next-test/src/bin/t2-next.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![cfg(not(test))] -#![deny(warnings)] - -use ctest_next_test::t2::*; - -include!(concat!(env!("OUT_DIR"), "/t2gen.rs")); diff --git a/ctest-next-test/src/lib.rs b/ctest-next-test/src/lib.rs deleted file mode 100644 index 8bb86699f30c7..0000000000000 --- a/ctest-next-test/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! `ctest-next-test` is a test crate for testing `ctest-next`. It consists -//! of two test binaries, t1 and t2 that test various aspects of `ctest-next`, -//! such as validation and generation of tests. - -pub mod t1; -pub mod t2; diff --git a/ctest-next-test/src/t1.c b/ctest-next-test/src/t1.c deleted file mode 100644 index 24f9fe52bf215..0000000000000 --- a/ctest-next-test/src/t1.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "t1.h" -#include -#include - -void T1a(void) {} -void *T1b(void) { return NULL; } -void *T1c(void *a) { return NULL; } -int32_t T1d(unsigned a) { return 0; } -void T1e(unsigned a, const struct T1Bar *b) {} -void T1f(void) {} -void T1g(int32_t *a) {} -void T1h(const int32_t *b) {} -void T1i(int32_t a[4]) {} -void T1j(const int32_t b[4]) {} -void T1o(int32_t (*a)[4]) {} -void T1p(int32_t (*const a)[4]) {} - -void T1r(Arr a) {} -void T1s(const Arr a) {} -void T1t(Arr *a) {} -void T1v(const Arr *a) {} - -unsigned T1static = 3; - -const uint8_t T1_static_u8 = 42; -uint8_t T1_static_mut_u8 = 37; - -uint8_t foo(uint8_t a, uint8_t b) { return a + b; } -void bar(uint8_t a) { return; } -void baz(void) { return; } - -uint32_t (*nested(uint8_t arg))(uint16_t) -{ - return NULL; -} - -uint32_t (*nested2(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) -{ - return NULL; -} - -uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t) = foo; -uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t) = foo; -void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t) = bar; -void (*const T1_static_const_fn_ptr_unsafe3)(void) = baz; - -const uint8_t T1_static_right = 7; -uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; - -uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; -uint32_t (*(*T1_fn_ptr_s2)(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) = nested2; - -const int32_t T1_arr0[2] = {0, 0}; -const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; -const int32_t T1_arr2[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; - -int32_t T1_arr3[2] = {0, 0}; -int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; -int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; - -int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; -const int16_t *T1_sref = (void *)(1337); - -volatile uint8_t *vol_ptr = NULL; -void *T1_vol0(volatile void *x, void *a) { return a ? a : (void *)x; } -volatile void *T1_vol1(void *x, void *b) { return b ? (volatile void *)x : (volatile void *)x; } -volatile void *T1_vol2(void *c, volatile void *x) { return c ? x : x; } - -// FIXME(#4365): duplicate symbol errors when enabled -// uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; diff --git a/ctest-next-test/src/t1.h b/ctest-next-test/src/t1.h deleted file mode 100644 index 08800525651d7..0000000000000 --- a/ctest-next-test/src/t1.h +++ /dev/null @@ -1,187 +0,0 @@ -#include - -typedef int32_t T1Foo; - -#define T1N 5 -#define T1S "foo" - -struct T1Bar -{ - int32_t a; - uint32_t b; - T1Foo c; - uint8_t d; - int64_t e[T1N]; - int64_t f[T1N][2]; -}; - -struct T1Baz -{ - uint64_t a; - struct T1Bar b; -}; - -typedef union -{ - uint64_t a; - uint32_t b; -} T1Union; - -union T1NoTypedefUnion -{ - uint64_t a; - uint32_t b; -}; - -struct T1StructWithUnion -{ - union T1NoTypedefUnion u; -}; - -typedef double T1TypedefDouble; -typedef int *T1TypedefPtr; -typedef struct T1Bar T1TypedefStruct; - -void T1a(void); -void *T1b(void); -void *T1c(void *); -int32_t T1d(unsigned); -void T1e(unsigned, const struct T1Bar *); -void T1f(void); -void T1g(int32_t *a); -void T1h(const int32_t *b); -void T1i(int32_t a[4]); -void T1j(const int32_t b[4]); -void T1o(int32_t (*a)[4]); -void T1p(int32_t (*const a)[4]); - -typedef int32_t(Arr)[4]; -typedef int32_t Transparent; - -void T1r(Arr a); -void T1s(const Arr a); -void T1t(Arr *a); -void T1v(const Arr *a); - -#define T1C 4 - -extern uint32_t T1static; -extern const uint8_t T1_static_u8; -/* FIXME(#4365): duplicate symbol errors when enabled -// uint8_t T1_static_mut_u8; -// uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t); -extern uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t); -*/ -extern void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t); -extern void (*const T1_static_const_fn_ptr_unsafe3)(void); - -extern const uint8_t T1_static_right; -/* FIXME(#4365): duplicate symbol errors when enabled -// uint8_t (*T1_static_right2)(uint8_t, uint8_t); - -// T1_fn_ptr_nested: function pointer to a function, taking a uint8_t, and -// returning a function pointer to a function taking a uint16_t and returning a -// uint32_t -uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t); - -// T1_fn_ptr_nested: function pointer to a function, taking a function pointer -// uint8_t -> uint8_t, and returning a function pointer to a function taking a -// uint16_t and returning a uint32_t -uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); -*/ - -extern const int32_t T1_arr0[2]; -extern const int32_t T1_arr1[2][3]; -extern const int32_t T1_arr2[1][2][3]; - -extern int32_t T1_arr3[2]; -extern int32_t T1_arr4[2][3]; -extern int32_t T1_arr5[1][2][3]; - -extern int32_t T1_arr42[1][2][3]; - -extern const int16_t *T1_sref; - -struct Q -{ - uint8_t *q0; - uint8_t **q1; - uint8_t q2; -}; - -struct T1_conflict_foo -{ - int a; -}; - -struct T1_conflict -{ - int foo; -}; - -// test packed structs -// -// on msvc there is only pragma pack -// on clang and gcc there is a packed attribute - -#pragma pack(push, 1) - -struct Pack -{ - uint8_t a; - uint16_t b; -}; - -#pragma pack(pop) - -#pragma pack(push, 4) - -struct Pack4 -{ - uint8_t a; - uint32_t b; -}; - -#pragma pack(pop) - -// volatile pointers in struct fields: -struct V -{ - volatile uint8_t *v; -}; - -// volatile pointers in externs: -extern volatile uint8_t *vol_ptr; - -// volatile pointers in function arguments: -void *T1_vol0(volatile void *, void *); -volatile void *T1_vol1(void *, void *); -volatile void *T1_vol2(void *, volatile void *); - -/* FIXME(#4365): duplicate symbol errors when enabled -// volatile function pointers: -uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); -*/ - -#define LOG_MAX_LINE_LENGTH (1400) - -typedef struct -{ - long tv_sec; - int tv_usec; -} timeval; - -typedef struct -{ - long level; - char const *file; - long line; - char const *module; - timeval tv; - char message[LOG_MAX_LINE_LENGTH]; -} log_record_t; - -typedef struct -{ - long double inner; -} LongDoubleWrap; diff --git a/ctest-next-test/src/t1.rs b/ctest-next-test/src/t1.rs deleted file mode 100644 index 9d8f5bbb30172..0000000000000 --- a/ctest-next-test/src/t1.rs +++ /dev/null @@ -1,205 +0,0 @@ -#![allow(non_camel_case_types)] - -use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void}; - -pub type T1Foo = i32; -pub const T1S: *const c_char = c"foo".as_ptr(); - -pub const T1N: i32 = 5; - -macro_rules! i { - ($i:item) => { - $i - }; -} - -#[repr(C)] -pub struct T1Bar { - pub a: i32, - pub b: u32, - pub c: T1Foo, - pub d: u8, - pub e: [i64; T1N as usize], - pub f: [[i64; 2]; T1N as usize], -} - -#[repr(C)] -pub struct T1Baz { - pub a: u64, - pub b: T1Bar, -} - -#[repr(C)] -pub union T1Union { - pub a: u64, - pub b: u32, -} - -#[repr(C)] -pub union T1NoTypedefUnion { - pub a: u64, - pub b: u32, -} - -#[repr(C)] -pub struct T1StructWithUnion { - pub u: T1NoTypedefUnion, -} - -#[repr(transparent)] -pub struct Transparent(i32); - -pub type T1TypedefDouble = c_double; -pub type T1TypedefPtr = *mut c_int; -pub type T1TypedefStruct = T1Bar; - -i! { - pub const T1C: u32 = 4; -} - -#[expect(unused)] -const NOT_PRESENT: u32 = 5; - -pub type Arr = [i32; 4]; - -extern "C" { - pub fn T1a(); - pub fn T1b() -> *mut c_void; - pub fn T1c(a: *mut c_void) -> *mut c_void; - pub fn T1d(a: c_uint) -> i32; - pub fn T1e(a: c_uint, b: *const T1Bar); - - #[link_name = "T1f"] - #[allow(clippy::unused_unit)] - pub fn f() -> (); - - pub fn T1g(a: *mut [i32; 4]); - pub fn T1h(a: *const [i32; 4]) -> !; - pub fn T1i(a: *mut [i32; 4]); - pub fn T1j(a: *const [i32; 4]) -> !; - pub fn T1o(a: *mut *mut [i32; 4]); - pub fn T1p(a: *const *const [i32; 4]) -> !; - - pub fn T1r(a: *mut Arr); - pub fn T1s(a: *const Arr) -> !; - pub fn T1t(a: *mut *mut Arr); - pub fn T1v(a: *const *const Arr) -> !; - - pub static T1static: c_uint; -} - -pub fn foo() { - let x = 1; - assert_eq!(x, 1); -} - -extern "C" { - pub static T1_static_u8: u8; - /* FIXME(#4365): duplicate symbol errors when enabled - // pub static mut T1_static_mut_u8: u8; - // pub static mut T1_static_mut_fn_ptr: extern "C" fn(u8, u8) -> u8; - pub static T1_static_const_fn_ptr_unsafe: unsafe extern "C" fn(u8, u8) -> u8; - */ - pub static T1_static_const_fn_ptr_unsafe2: unsafe extern "C" fn(u8) -> (); - pub static T1_static_const_fn_ptr_unsafe3: unsafe extern "C" fn() -> (); - - #[link_name = "T1_static_right"] - pub static T1_static_wrong: u8; - /* FIXME(#4365): duplicate symbol errors when enabled - // #[link_name = "T1_static_right2"] - // pub static mut T1_static_wrong2: extern "C" fn(u8, u8) -> u8; - - pub static T1_fn_ptr_s: unsafe extern "C" fn(u8) -> extern "C" fn(u16) -> u32; - pub static T1_fn_ptr_s2: unsafe extern "C" fn( - extern "C" fn(u8) -> u8, - extern "C" fn(u16) -> u16, - ) -> extern "C" fn(u16) -> u32; - */ - - pub static T1_arr0: [i32; 2]; - pub static T1_arr1: [[i32; 3]; 2]; - pub static T1_arr2: [[[i32; 3]; 2]; 1]; - - pub static mut T1_arr3: [i32; 2]; - pub static mut T1_arr4: [[i32; 3]; 2]; - pub static mut T1_arr5: [[[i32; 3]; 2]; 1]; - - #[link_name = "T1_arr42"] - pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; - - pub static mut T1_sref: &'static i16; -} - -#[repr(C)] -pub struct Q { - pub q0: *mut u8, - pub q1: *mut *mut u8, - pub q2: u8, -} - -#[repr(C)] -pub struct T1_conflict_foo { - a: i32, -} - -#[repr(C)] -pub struct T1_conflict { - pub foo: i32, -} - -#[repr(C, packed)] -pub struct Pack { - pub a: u8, - pub b: u16, -} - -#[repr(C, packed(4))] -pub struct Pack4 { - pub a: u8, - pub b: u32, -} - -#[repr(C)] -pub struct V { - pub v: *mut u8, -} - -extern "C" { - pub static mut vol_ptr: *mut u8; - pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; - pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; - pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; -} - -pub const LOG_MAX_LINE_LENGTH: usize = 1400; - -#[repr(C)] -struct timeval { - tv_sec: c_long, - tv_usec: c_int, -} - -#[expect(unused)] -#[repr(C)] -struct log_record_t { - level: c_long, - file: *const c_char, - line: c_long, - module: *const c_char, - tv: timeval, - message: [c_char; LOG_MAX_LINE_LENGTH], -} - -#[expect(unused)] -#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] -#[repr(C, align(16))] -struct LongDoubleWrap { - inner: u128, -} - -#[expect(unused)] -#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] -#[repr(C)] -struct LongDoubleWrap { - inner: c_double, -} diff --git a/ctest-next-test/src/t2.c b/ctest-next-test/src/t2.c deleted file mode 100644 index ceeddfcf509ea..0000000000000 --- a/ctest-next-test/src/t2.c +++ /dev/null @@ -1,2 +0,0 @@ - -void T2a() {} diff --git a/ctest-next-test/src/t2.h b/ctest-next-test/src/t2.h deleted file mode 100644 index 9fd4fc3b5aa6d..0000000000000 --- a/ctest-next-test/src/t2.h +++ /dev/null @@ -1,26 +0,0 @@ -#include - -typedef int32_t T2Foo; -typedef int8_t T2Bar; - -typedef T2Foo T2TypedefFoo; -typedef unsigned T2TypedefInt; - -struct T2Baz -{ - int8_t _a; - int64_t a; - uint32_t b; -}; - -typedef struct -{ - uint32_t a; - int64_t b; -} T2Union; - -// FIXME(ctest): Will fail as unused function until extern functions are tested. -// static void T2a(void) {} - -#define T2C 4 -#define T2S "a" diff --git a/ctest-next-test/src/t2.rs b/ctest-next-test/src/t2.rs deleted file mode 100644 index cfd3bfb65b418..0000000000000 --- a/ctest-next-test/src/t2.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::ffi::{c_char, c_int}; - -pub type T2Foo = u32; -pub type T2Bar = u32; - -pub type T2TypedefFoo = T2Foo; -pub type T2TypedefInt = c_int; - -macro_rules! i { - ($i:item) => { - $i - }; -} - -#[repr(C)] -#[derive(Debug)] -pub struct T2Baz { - pub a: i64, - pub b: u32, -} - -#[repr(C)] -pub union T2Union { - pub a: u32, - pub b: i64, -} - -pub const T2C: i32 = 5; - -i! { - pub const T2S: *const c_char = c"b".as_ptr(); -} - -// FIXME(ctest): Will fail as unused function until extern functions are tested. -// extern "C" { -// pub fn T2a(); -// } diff --git a/ctest-next-test/tests/all.rs b/ctest-next-test/tests/all.rs deleted file mode 100644 index 327693a53d485..0000000000000 --- a/ctest-next-test/tests/all.rs +++ /dev/null @@ -1,86 +0,0 @@ -// FIXME(ctest): this test doesn't work when cross compiling. -#![cfg(target_arch = "x86_64")] - -use std::collections::HashSet; -use std::env; -use std::process::{Command, ExitStatus}; - -/// Create a command that starts in the `target/debug` or `target/release` directory. -fn cmd(name: &str) -> Command { - let mut path = env::current_exe().unwrap(); - path.pop(); - if path.file_name().unwrap().to_str() == Some("deps") { - path.pop(); - } - path.push(name); - Command::new(path) -} - -/// Executes a command, returning stdout and stderr combined and it's status. -fn output(cmd: &mut Command) -> (String, ExitStatus) { - eprintln!("command: {cmd:?}"); - let output = cmd.output().unwrap(); - let stdout = String::from_utf8(output.stdout).unwrap(); - let stderr = String::from_utf8(output.stderr).unwrap(); - - (stdout + &stderr, output.status) -} - -#[test] -fn t1_next() { - // t1 must run to completion without any errors. - let (output, status) = output(&mut cmd("t1-next")); - assert!(status.success(), "output: {output}"); - assert!(!output.contains("bad "), "{output}"); - eprintln!("output: {output}"); -} - -#[test] -fn t2_next() { - // t2 must fail to run to completion, and only have the errors we expect it to have. - let (output, status) = output(&mut cmd("t2-next")); - assert!(!status.success(), "output: {output}"); - - // FIXME(ctest): Errors currently commented out are not tested. - let errors = [ - "bad T2Foo signed", - "bad T2TypedefFoo signed", - "bad T2TypedefInt signed", - "bad T2Bar size", - "bad T2Bar align", - "bad T2Bar signed", - "bad T2Baz size", - "bad field offset a of T2Baz", - "bad field type a of T2Baz", - "bad field offset b of T2Baz", - "bad field type b of T2Baz", - // "bad T2a function pointer", - "bad T2C value at byte 0", - "bad T2S string", - "bad T2Union size", - "bad field type b of T2Union", - "bad field offset b of T2Union", - ]; - let mut errors = errors.iter().cloned().collect::>(); - - // Extract any errors that are not contained within the known error set. - let mut bad = false; - for line in output.lines().filter(|l| l.starts_with("bad ")) { - let msg = &line[..line.find(":").unwrap()]; - if !errors.remove(&msg) { - println!("unknown error: {msg:#?}"); - bad = true; - } - } - - // If any errors are left over, t2 did not run properly. - for error in errors { - println!("didn't find error: {error}"); - bad = true; - } - - if bad { - println!("output was:\n\n{output:#?}"); - panic!(); - } -} diff --git a/ctest-next/Cargo.lock b/ctest-next/Cargo.lock new file mode 100644 index 0000000000000..388fd3b08cb6d --- /dev/null +++ b/ctest-next/Cargo.lock @@ -0,0 +1,485 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cc" +version = "1.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "ctest-next" +version = "0.1.0" +dependencies = [ + "askama", + "cc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", + "tempfile", + "thiserror", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/ctest-next/Cargo.toml b/ctest-next/Cargo.toml index e56b3dd5cfe33..52182fc7a450f 100644 --- a/ctest-next/Cargo.toml +++ b/ctest-next/Cargo.toml @@ -10,7 +10,6 @@ publish = false [dependencies] askama = "0.14.0" cc = "1.2.29" -either = "1.15.0" proc-macro2 = { version = "1.0.95", features = ["span-locations"] } quote = "1.0.40" syn = { version = "2.0.104", features = ["full", "visit", "extra-traits"] } diff --git a/ctest-next/src/ast/structure.rs b/ctest-next/src/ast/structure.rs index b9fae7fd574e5..346e942bbd564 100644 --- a/ctest-next/src/ast/structure.rs +++ b/ctest-next/src/ast/structure.rs @@ -15,7 +15,7 @@ impl Struct { } /// Return the public fields of the struct. - pub fn public_fields(&self) -> Vec<&Field> { - self.fields.iter().filter(|f| f.public).collect() + pub fn public_fields(&self) -> impl Iterator { + self.fields.iter().filter(|f| f.public) } } diff --git a/ctest-next/src/ast/union.rs b/ctest-next/src/ast/union.rs index 09abaf9e746f1..96016c77a0f6a 100644 --- a/ctest-next/src/ast/union.rs +++ b/ctest-next/src/ast/union.rs @@ -16,7 +16,7 @@ impl Union { } /// Return the public fields of the union. - pub(crate) fn public_fields(&self) -> Vec<&Field> { - self.fields.iter().filter(|f| f.public).collect() + pub(crate) fn public_fields(&self) -> impl Iterator { + self.fields.iter().filter(|f| f.public) } } diff --git a/ctest-next/src/ffi_items.rs b/ctest-next/src/ffi_items.rs index 9529c0ccc9b33..b02466e98d89e 100644 --- a/ctest-next/src/ffi_items.rs +++ b/ctest-next/src/ffi_items.rs @@ -122,21 +122,26 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi syn::ReturnType::Type(_, ty) => Some(ty.deref().clone()), }; - let link_name = i + let mut link_name_iter = i .attrs .iter() - .find(|attr| attr.path().is_ident("link_name")) - .and_then(|attr| match &attr.meta { - syn::Meta::NameValue(nv) => { - if let syn::Expr::Lit(expr_lit) = &nv.value { - if let syn::Lit::Str(lit_str) = &expr_lit.lit { - return Some(lit_str.value().into_boxed_str()); - } + .filter(|attr| attr.path().is_ident("link_name")); + + let link_name = link_name_iter.next().and_then(|attr| match &attr.meta { + syn::Meta::NameValue(nv) => { + if let syn::Expr::Lit(expr_lit) = &nv.value { + if let syn::Lit::Str(lit_str) = &expr_lit.lit { + return Some(lit_str.value().into_boxed_str()); } - None } - _ => None, - }); + None + } + _ => None, + }); + + if link_name_iter.next().is_some() { + panic!("multiple #[link_name = ...] attributes found"); + } table.foreign_functions.push(Fn { public, diff --git a/ctest-next/src/generator.rs b/ctest-next/src/generator.rs index 974d773f019a6..7d586a89a0779 100644 --- a/ctest-next/src/generator.rs +++ b/ctest-next/src/generator.rs @@ -4,7 +4,6 @@ use std::io::Write; use std::path::{Path, PathBuf}; use askama::Template; -use either::Either; use syn::visit::Visit; use thiserror::Error; @@ -331,7 +330,7 @@ impl TestGenerator { f: impl Fn(&Struct, &Field) -> bool + 'static, ) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::Field(Either::Left(struct_), field) = item { + if let MapInput::StructField(struct_, field) = item { f(struct_, field) } else { false @@ -354,7 +353,7 @@ impl TestGenerator { /// ``` pub fn skip_union_field(&mut self, f: impl Fn(&Union, &Field) -> bool + 'static) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::Field(Either::Right(union_), field) = item { + if let MapInput::UnionField(union_, field) = item { f(union_, field) } else { false @@ -386,7 +385,7 @@ impl TestGenerator { f: impl Fn(&Struct, &Field) -> bool + 'static, ) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::FieldType(Either::Left(struct_), field) = item { + if let MapInput::StructFieldType(struct_, field) = item { f(struct_, field) } else { false @@ -418,7 +417,7 @@ impl TestGenerator { f: impl Fn(&Union, &Field) -> bool + 'static, ) -> &mut Self { self.skips.push(Box::new(move |item| { - if let MapInput::FieldType(Either::Right(union_), field) = item { + if let MapInput::UnionFieldType(union_, field) = item { f(union_, field) } else { false @@ -597,7 +596,7 @@ impl TestGenerator { f: impl Fn(&Struct, &Field) -> Option + 'static, ) -> &mut Self { self.mapped_names.push(Box::new(move |item| { - if let MapInput::Field(Either::Left(s), c) = item { + if let MapInput::StructField(s, c) = item { f(s, c) } else { None @@ -623,7 +622,7 @@ impl TestGenerator { f: impl Fn(&Union, &Field) -> Option + 'static, ) -> &mut Self { self.mapped_names.push(Box::new(move |item| { - if let MapInput::Field(Either::Right(u), c) = item { + if let MapInput::UnionField(u, c) = item { f(u, c) } else { None @@ -733,6 +732,9 @@ impl TestGenerator { /// /// # Examples /// ```no_run + /// use ctest_next::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); /// cfg.skip_roundtrip(|s| { /// s.starts_with("foo_") /// }); @@ -841,10 +843,12 @@ impl TestGenerator { MapInput::Struct(s) => s.ident().to_string(), MapInput::Union(u) => u.ident().to_string(), MapInput::Alias(t) => t.ident().to_string(), - MapInput::Field(_, f) => f.ident().to_string(), + MapInput::StructField(_, f) => f.ident().to_string(), + MapInput::UnionField(_, f) => f.ident().to_string(), MapInput::StructType(ty) => format!("struct {ty}"), MapInput::UnionType(ty) => format!("union {ty}"), - MapInput::FieldType(_, f) => f.ident().to_string(), + MapInput::StructFieldType(_, f) => f.ident().to_string(), + MapInput::UnionFieldType(_, f) => f.ident().to_string(), MapInput::Type(ty) => ty.to_string(), } } diff --git a/ctest-next/src/lib.rs b/ctest-next/src/lib.rs index 7c9cc89d87094..81e73d0327c2f 100644 --- a/ctest-next/src/lib.rs +++ b/ctest-next/src/lib.rs @@ -20,7 +20,6 @@ mod template; mod translator; pub use ast::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union}; -use either::Either; pub use generator::TestGenerator; pub use macro_expansion::expand; pub use runner::{__compile_test, __run_test, generate_test}; @@ -59,7 +58,8 @@ pub(crate) enum MapInput<'a> { Struct(&'a Struct), Union(&'a Union), Fn(&'a crate::Fn), - Field(Either<&'a Struct, &'a Union>, &'a Field), + StructField(&'a Struct, &'a Field), + UnionField(&'a Union, &'a Field), Alias(&'a Type), Const(&'a Const), Static(&'a Static), @@ -67,7 +67,8 @@ pub(crate) enum MapInput<'a> { /// This variant is used for renaming the struct type. StructType(&'a str), UnionType(&'a str), - FieldType(Either<&'a Struct, &'a Union>, &'a Field), + StructFieldType(&'a Struct, &'a Field), + UnionFieldType(&'a Union, &'a Field), } /* The From impls make it easier to write code in the test templates. */ diff --git a/ctest-next/src/template.rs b/ctest-next/src/template.rs index 86df820e3049f..57637ea6b371a 100644 --- a/ctest-next/src/template.rs +++ b/ctest-next/src/template.rs @@ -1,7 +1,6 @@ use std::ops::Deref; use askama::Template; -use either::Either; use quote::ToTokens; use crate::ffi_items::FfiItems; @@ -60,7 +59,8 @@ impl<'a> CTestTemplate<'a> { let (ident, ty) = match item { MapInput::Const(c) => (c.ident(), self.translator.translate_type(&c.ty)?), - MapInput::Field(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), + MapInput::StructField(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), + MapInput::UnionField(_, f) => (f.ident(), self.translator.translate_type(&f.ty)?), MapInput::Static(s) => (s.ident(), self.translator.translate_type(&s.ty)?), MapInput::Fn(_) => unimplemented!(), // For structs/unions/aliases, their type is the same as their identifier. @@ -70,7 +70,8 @@ impl<'a> CTestTemplate<'a> { MapInput::StructType(_) => panic!("MapInput::StructType is not allowed!"), MapInput::UnionType(_) => panic!("MapInput::UnionType is not allowed!"), - MapInput::FieldType(_, _) => panic!("MapInput::FieldType is not allowed!"), + MapInput::StructFieldType(_, _) => panic!("MapInput::StructFieldType is not allowed!"), + MapInput::UnionFieldType(_, _) => panic!("MapInput::UnionFieldType is not allowed!"), MapInput::Type(_) => panic!("MapInput::Type is not allowed!"), }; @@ -174,19 +175,31 @@ pub(crate) fn should_roundtrip(gen: &TestGenerator, ident: &str) -> bool { } /// Determine whether a struct field should be skipped for tests. -pub(crate) fn should_skip_field( - gen: &TestGenerator, - e: Either<&Struct, &Union>, - field: &Field, -) -> bool { - gen.skips.iter().any(|f| f(&MapInput::Field(e, field))) +pub(crate) fn should_skip_struct_field(gen: &TestGenerator, s: &Struct, field: &Field) -> bool { + gen.skips + .iter() + .any(|f| f(&MapInput::StructField(s, field))) +} + +/// Determine whether a union field should be skipped for tests. +pub(crate) fn should_skip_union_field(gen: &TestGenerator, u: &Union, field: &Field) -> bool { + gen.skips.iter().any(|f| f(&MapInput::UnionField(u, field))) } /// Determine whether a struct field type should be skipped for tests. -pub(crate) fn should_skip_field_type( +pub(crate) fn should_skip_struct_field_type( gen: &TestGenerator, - e: Either<&Struct, &Union>, + s: &Struct, field: &Field, ) -> bool { - gen.skips.iter().any(|f| f(&MapInput::FieldType(e, field))) + gen.skips + .iter() + .any(|f| f(&MapInput::StructFieldType(s, field))) +} + +/// Determine whether a union field type should be skipped for tests. +pub(crate) fn should_skip_union_field_type(gen: &TestGenerator, u: &Union, field: &Field) -> bool { + gen.skips + .iter() + .any(|f| f(&MapInput::UnionFieldType(u, field))) } diff --git a/ctest-next/src/translator.rs b/ctest-next/src/translator.rs index b00556575b265..8c90a89c1110f 100644 --- a/ctest-next/src/translator.rs +++ b/ctest-next/src/translator.rs @@ -334,13 +334,13 @@ impl Translator { } /// Determine whether a C type is a signed type. - pub(crate) fn has_sign(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { + pub(crate) fn is_signed(&self, ffi_items: &FfiItems, ty: &syn::Type) -> bool { match ty { syn::Type::Path(path) => { let ident = path.path.segments.last().unwrap().ident.clone(); // The only thing other than a primitive that can be signed is an alias. if let Some(aliased) = ffi_items.aliases().iter().find(|a| ident == a.ident()) { - return self.has_sign(ffi_items, &aliased.ty); + return self.is_signed(ffi_items, &aliased.ty); } match self.translate_primitive_type(&ident).as_str() { "char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" diff --git a/ctest-next/templates/common/test_roundtrip.c b/ctest-next/templates/common/test_roundtrip.c new file mode 100644 index 0000000000000..d97b0bfb9d8c5 --- /dev/null +++ b/ctest-next/templates/common/test_roundtrip.c @@ -0,0 +1,48 @@ +{#- Requires the presence of an `ident` and `c_type` variable. +#} + +#ifdef _MSC_VER +// Disable signed/unsigned conversion warnings on MSVC. +// These trigger even if the conversion is explicit. +# pragma warning(disable:4365) +#endif + +// Tests whether the struct/union/alias `x` when passed 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. +{{ c_type }} __test_roundtrip_{{ ident }}( + int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad +) { + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({{ c_type }}); + if (size != rust_size) { + fprintf( + stderr, + "size of {{ c_type }} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + } + int i = 0; + for (i = 0; i < size; ++i) { + if (pad[i]) { continue; } + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) { + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", + i, (int)p[i], (int)c + ); + } + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + } + return value; +} + +#ifdef _MSC_VER +# pragma warning(default:4365) +#endif \ No newline at end of file diff --git a/ctest-next/templates/common/test_roundtrip.rs b/ctest-next/templates/common/test_roundtrip.rs new file mode 100644 index 0000000000000..b94dc5a0794d5 --- /dev/null +++ b/ctest-next/templates/common/test_roundtrip.rs @@ -0,0 +1,54 @@ + {#- Requires the presence of an `ident` variable +#} + + /// Tests whether the alias/struct/union `x` 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. + pub fn roundtrip_{{ ident }}() { + type U = {{ ident }}; + extern "C" { + fn __test_roundtrip_{{ ident }}( + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool + ) -> U; + } + let pad = roundtrip_padding_{{ ident }}(); + assert_eq!(pad.len(), size_of::()); + unsafe { + let mut error: c_int = 0; + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz { + let c: u8 = (i % 256) as u8; + let c = if c == 0 { 42 } else { c }; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 { 42 } else { d }; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + } + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_{{ ident }}(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { + FAILED.store(true, Ordering::Relaxed); + return; + } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } + let rust = (*y_ptr.add(i)) as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; + if rust != c { + eprintln!( + "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", + ); + FAILED.store(true, Ordering::Relaxed); + } + } + } + } \ No newline at end of file diff --git a/ctest-next/templates/common/test_size_align.c b/ctest-next/templates/common/test_size_align.c new file mode 100644 index 0000000000000..95f8ae80646a2 --- /dev/null +++ b/ctest-next/templates/common/test_size_align.c @@ -0,0 +1,7 @@ +{#- Requires the presence of an `ident` and `c_type` variable. +#} + +// Return the size of a type. +uint64_t ctest_size_of__{{ident}}(void) { return sizeof({{c_type}}); } + +// Return the alignment of a type. +uint64_t ctest_align_of__{{ident}}(void) { return _Alignof({{c_type}}); } \ No newline at end of file diff --git a/ctest-next/templates/common/test_size_align.rs b/ctest-next/templates/common/test_size_align.rs new file mode 100644 index 0000000000000..7e3caa8d475db --- /dev/null +++ b/ctest-next/templates/common/test_size_align.rs @@ -0,0 +1,18 @@ + {#- Requires the presence of an `ident` variable. +#} + + /// Test that the size and alignment of the aliased type is the same in both Rust and C. + /// + /// This can fail if a different type is used on one side, and uses the built in size and + /// alignment functions to check. + pub fn size_align_{{ ident }}() { + extern "C" { + fn ctest_size_of__{{ ident }}() -> u64; + fn ctest_align_of__{{ ident }}() -> u64; + } + unsafe { + check_same(size_of::<{{ ident }}>() as u64, + ctest_size_of__{{ ident }}(), "{{ ident }} size"); + check_same(align_of::<{{ ident }}>() as u64, + ctest_align_of__{{ ident }}(), "{{ ident }} align"); + } + } \ No newline at end of file diff --git a/ctest-next/templates/test.c b/ctest-next/templates/test.c index f6b9b9eba6118..314c27a568aaa 100644 --- a/ctest-next/templates/test.c +++ b/ctest-next/templates/test.c @@ -28,77 +28,19 @@ static {{ c_type }} __test_const_{{ ident }}_val = {{ c_ident }}; {%- let ident = alias.ident() +%} {%- let c_type = self.c_type(*alias).unwrap() +%} -// Return the size of a type. -uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } +{%- include "common/test_size_align.c" +%} -// Return the alignment of a type. -uint64_t __test_align_{{ ident }}(void) { - typedef struct { - unsigned char c; - {{ c_type }} v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} - -{%- if translator.has_sign(ffi_items, alias.ty) +%} +{%- if translator.is_signed(ffi_items, alias.ty) +%} // Return `1` if the type is signed, otherwise return `0`. -uint32_t __test_signed_{{ ident }}(void) { - return ((({{ c_type }}) -1) < 0); +uint32_t ctest_{{ ident }}_is_signed(void) { + return (({{ c_type }}) -1) < 0; } {%- endif +%} {%- if self::should_roundtrip(generator, ident) +%} -#ifdef _MSC_VER -// Disable signed/unsigned conversion warnings on MSVC. -// These trigger even if the conversion is explicit. -# pragma warning(disable:4365) -#endif - -// Tests whether the type alias `x` 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. -{{ c_type }} __test_roundtrip_{{ ident }}( - int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad -) { - volatile unsigned char* p = (volatile unsigned char*)&value; - int size = (int)sizeof({{ c_type }}); - if (size != rust_size) { - fprintf( - stderr, - "size of {{ c_type }} is %d in C and %d in Rust\n", - (int)size, (int)rust_size - ); - *error = 1; - return value; - } - int i = 0; - for (i = 0; i < size; ++i) { - if (pad[i]) { continue; } - unsigned char c = (unsigned char)(i % 256); - c = c == 0? 42 : c; - if (p[i] != c) { - *error = 1; - fprintf( - stderr, - "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", - i, (int)p[i], (int)c - ); - } - unsigned char d - = (unsigned char)(255) - (unsigned char)(i % 256); - d = d == 0? 42: d; - p[i] = d; - } - return value; -} - -#ifdef _MSC_VER -# pragma warning(default:4365) -#endif +{%- include "common/test_roundtrip.c" +%} {%- endif +%} {%- endfor +%} @@ -106,36 +48,23 @@ uint32_t __test_signed_{{ ident }}(void) { {%- let ident = structure.ident() +%} {%- let c_type = self.c_type(*structure).unwrap() +%} -// Return the size of a type. -uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } - -// Return the alignment of a type. -uint64_t __test_align_{{ ident }}(void) { - typedef struct { - unsigned char c; - {{ c_type }} v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +{%- include "common/test_size_align.c" +%} {%- for field in structure.fields +%} -{%- if !self::should_skip_field(generator, Either::Left(structure), field) +%} +{%- if !self::should_skip_struct_field(generator, structure, field) +%} {%- let rust_field_name = field.ident() +%} -{%- let c_field_name = self.c_ident(MapInput::Field(Either::Left(structure), field)) +%} +{%- let c_field_name = self.c_ident(MapInput::StructField(structure, field)) +%} -uint64_t __test_offset_{{ ident }}_{{ rust_field_name }}(void) { +uint64_t ctest_offset_of__{{ ident }}__{{ rust_field_name }}(void) { return offsetof({{ c_type }}, {{ c_field_name }}); } -uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { +uint64_t ctest_field_size__{{ ident }}__{{ rust_field_name }}(void) { {{ c_type }}* foo = NULL; return sizeof(foo->{{ c_field_name }}); } -{%- if !self::should_skip_field_type(generator, Either::Left(structure), field) +%} +{%- if !self::should_skip_struct_field_type(generator, structure, field) +%} {%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} {%- let volatile = self.emit_volatile(VolatileItemKind::StructField(structure.clone(), field.clone())) +%} @@ -147,54 +76,8 @@ uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { {%- endfor +%} {%- if self::should_roundtrip(generator, ident) +%} -{%- let c_type = self.c_type(*structure).unwrap() +%} - -#ifdef _MSC_VER -// Disable signed/unsigned conversion warnings on MSVC. -// These trigger even if the conversion is explicit. -# pragma warning(disable:4365) -#endif -// Tests whether the struct/union `x` 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. -{{ c_type }} __test_roundtrip_{{ ident }}( - int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad -) { - volatile unsigned char* p = (volatile unsigned char*)&value; - int size = (int)sizeof({{ c_type }}); - if (size != rust_size) { - fprintf( - stderr, - "size of {{ c_type }} is %d in C and %d in Rust\n", - (int)size, (int)rust_size - ); - *error = 1; - return value; - } - int i = 0; - for (i = 0; i < size; ++i) { - if (pad[i]) { continue; } - unsigned char c = (unsigned char)(i % 256); - c = c == 0? 42 : c; - if (p[i] != c) { - *error = 1; - fprintf( - stderr, - "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", - i, (int)p[i], (int)c - ); - } - unsigned char d - = (unsigned char)(255) - (unsigned char)(i % 256); - d = d == 0? 42: d; - p[i] = d; - } - return value; -} - -#ifdef _MSC_VER -# pragma warning(default:4365) -#endif +{%- include "common/test_roundtrip.c" +%} {%- endif +%} {%- endfor +%} @@ -202,36 +85,23 @@ uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { {%- let ident = union_.ident() +%} {%- let c_type = self.c_type(*union_).unwrap() +%} -// Return the size of a type. -uint64_t __test_size_{{ ident }}(void) { return sizeof({{ c_type }}); } - -// Return the alignment of a type. -uint64_t __test_align_{{ ident }}(void) { - typedef struct { - unsigned char c; - {{ c_type }} v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +{%- include "common/test_size_align.c" +%} {%- for field in union_.fields +%} -{%- if !self::should_skip_field(generator, Either::Right(union_), field) +%} +{%- if !self::should_skip_union_field(generator, union_, field) +%} {%- let rust_field_name = field.ident() +%} -{%- let c_field_name = self.c_ident(MapInput::Field(Either::Right(union_), field)) +%} +{%- let c_field_name = self.c_ident(MapInput::UnionField(union_, field)) +%} -uint64_t __test_offset_{{ ident }}_{{ rust_field_name }}(void) { +uint64_t ctest_offset_of__{{ ident }}__{{ rust_field_name }}(void) { return offsetof({{ c_type }}, {{ c_field_name }}); } -uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { +uint64_t ctest_field_size__{{ ident }}__{{ rust_field_name }}(void) { {{ c_type }}* foo = NULL; return sizeof(foo->{{ c_field_name }}); } -{%- if !self::should_skip_field_type(generator, Either::Right(union_), field) +%} +{%- if !self::should_skip_union_field_type(generator, union_, field) +%} {%- let signature = self.c_signature(field.ty, &format!("__test_field_type_{ident}_{rust_field_name}({c_type}* b)")).unwrap() +%} {%- let volatile = self.emit_volatile(VolatileItemKind::UnionField(union_.clone(), field.clone())) +%} @@ -243,54 +113,8 @@ uint64_t __test_fsize_{{ ident }}_{{ rust_field_name }}(void) { {%- endfor +%} {%- if self::should_roundtrip(generator, ident) +%} -{%- let c_type = self.c_type(*union_).unwrap() +%} - -#ifdef _MSC_VER -// Disable signed/unsigned conversion warnings on MSVC. -// These trigger even if the conversion is explicit. -# pragma warning(disable:4365) -#endif - -// Tests whether the struct/union `x` 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. -{{ c_type }} __test_roundtrip_{{ ident }}( - int32_t rust_size, {{ c_type }} value, int* error, unsigned char* pad -) { - volatile unsigned char* p = (volatile unsigned char*)&value; - int size = (int)sizeof({{ c_type }}); - if (size != rust_size) { - fprintf( - stderr, - "size of {{ c_type }} is %d in C and %d in Rust\n", - (int)size, (int)rust_size - ); - *error = 1; - return value; - } - int i = 0; - for (i = 0; i < size; ++i) { - if (pad[i]) { continue; } - unsigned char c = (unsigned char)(i % 256); - c = c == 0? 42 : c; - if (p[i] != c) { - *error = 1; - fprintf( - stderr, - "rust[%d] = %d != %d (C): Rust \"{{ ident }}\" -> C\n", - i, (int)p[i], (int)c - ); - } - unsigned char d - = (unsigned char)(255) - (unsigned char)(i % 256); - d = d == 0? 42: d; - p[i] = d; - } - return value; -} -#ifdef _MSC_VER -# pragma warning(default:4365) -#endif +{%- include "common/test_roundtrip.c" +%} {%- endif +%} {%- endfor +%} diff --git a/ctest-next/templates/test.rs b/ctest-next/templates/test.rs index 9d560378f328c..804c3a9c6fda3 100644 --- a/ctest-next/templates/test.rs +++ b/ctest-next/templates/test.rs @@ -5,12 +5,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - use std::mem::offset_of; + use std::{ptr, slice}; use super::*; @@ -51,7 +52,6 @@ mod generated_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_{{ ident }}() { - use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_{{ ident }}() -> *const *const u8; } @@ -60,12 +60,11 @@ mod generated_tests { let ptr = *__test_const_{{ ident }}(); let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const {{ ident }} not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const {{ ident }} not utf8"); check_same(val, c, "{{ ident }} string"); } } - {%- else +%} // Test that the value of the constant is the same in both Rust and C. @@ -78,8 +77,8 @@ mod generated_tests { unsafe { let ptr1 = ptr::from_ref(&val).cast::(); let ptr2 = __test_const_{{ ident }}().cast::(); - let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::<{{ ty }}>()); - let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::<{{ ty }}>()); + let ptr1_bytes = slice::from_raw_parts(ptr1, size_of::<{{ ty }}>()); + let ptr2_bytes = slice::from_raw_parts(ptr2, size_of::<{{ ty }}>()); for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. @@ -93,37 +92,24 @@ mod generated_tests { {%- for alias in ffi_items.aliases() +%} {%- let ident = alias.ident() +%} - /// Test that the size and alignment of the aliased type is the same in both Rust and C. - /// - /// This can fail if a different type is used on one side, and uses the built in size and - /// alignment functions to check. - pub fn size_align_{{ ident }}() { - extern "C" { - fn __test_size_{{ ident }}() -> u64; - fn __test_align_{{ ident }}() -> u64; - } - unsafe { - check_same(mem::size_of::<{{ ident }}>() as u64, - __test_size_{{ ident }}(), "{{ ident }} size"); - check_same(mem::align_of::<{{ ident }}>() as u64, - __test_align_{{ ident }}(), "{{ ident }} align"); - } - } + {%- include "common/test_size_align.rs" +%} - {%- if translator.has_sign(ffi_items, alias.ty) +%} + {%- if translator.is_signed(ffi_items, alias.ty) +%} - /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. /// /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value /// depending on whether a signed or unsigned type is used. This is simply checked on both /// Rust and C sides to see if they are equal. pub fn sign_{{ ident }}() { extern "C" { - fn __test_signed_{{ ident }}() -> u32; + fn ctest_{{ ident }}_is_signed() -> u32; } + let all_ones = !(0 as {{ ident }}); + let all_zeros = 0 as {{ ident }}; unsafe { - check_same(((!(0 as {{ ident }})) < (0 as {{ ident }})) as u32, - __test_signed_{{ ident }}(), "{{ ident }} signed"); + check_same((all_ones < all_zeros) as u32, + ctest_{{ ident }}_is_signed(), "{{ ident }} signed"); } } {%- endif +%} @@ -137,119 +123,53 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_{{ ident }}() -> Vec { - vec![0; mem::size_of::<{{ ident }}>()] + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { + [false; size_of::<{{ ident }}>()] } - /// Tests whether the type alias `x` 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. - pub fn roundtrip_{{ ident }}() { - use std::ffi::c_int; - - type U = {{ ident }}; - extern "C" { - fn __test_roundtrip_{{ ident }}( - size: i32, x: U, e: *mut c_int, pad: *const u8 - ) -> U; - } - let pad = roundtrip_padding_{{ ident }}(); - unsafe { - use std::mem::{MaybeUninit, size_of}; - let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); - let x_ptr = x.as_mut_ptr().cast::(); - let y_ptr = y.as_mut_ptr().cast::(); - let sz = size_of::(); - for i in 0..sz { - let c: u8 = (i % 256) as u8; - let c = if c == 0 { 42 } else { c }; - let d: u8 = 255_u8 - (i % 256) as u8; - let d = if d == 0 { 42 } else { d }; - x_ptr.add(i).write_volatile(c); - y_ptr.add(i).write_volatile(d); - } - let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { - FAILED.store(true, Ordering::Relaxed); - return; - } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } - let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; - if rust != c { - eprintln!( - "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", - ); - FAILED.store(true, Ordering::Relaxed); - } - } - } - } + {%- include "common/test_roundtrip.rs" +%} {%- endif +%} {%- endfor +%} {%- for structure in ffi_items.structs() +%} {%- let ident = structure.ident() +%} - {%- let fields = structure.public_fields() +%} + {%- let fields = structure.public_fields().collect::>() +%} - /// Test that the size and alignment of the aliased type is the same in both Rust and C. - /// - /// This can fail if a different type is used on one side, and uses the built in size and - /// alignment functions to check. - pub fn size_align_{{ ident }}() { - extern "C" { - fn __test_size_{{ ident }}() -> u64; - fn __test_align_{{ ident }}() -> u64; - } - unsafe { - check_same(mem::size_of::<{{ ident }}>() as u64, - __test_size_{{ ident }}(), "{{ ident }} size"); - check_same(mem::align_of::<{{ ident }}>() as u64, - __test_align_{{ ident }}(), "{{ ident }} align"); - } - } + {%- include "common/test_size_align.rs" +%} - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_{{ ident }}() { {%- for field in structure.public_fields() +%} - {%- if !self::should_skip_field(generator, Either::Left(structure), field) +%} + {%- if !self::should_skip_struct_field(generator, structure, field) +%} extern "C" { - fn __test_offset_{{ ident }}_{{ field.ident() }}() -> u64; - fn __test_fsize_{{ ident }}_{{ field.ident() }}() -> u64; + fn ctest_offset_of__{{ ident }}__{{ field.ident() }}() -> u64; + fn ctest_field_size__{{ ident }}__{{ field.ident() }}() -> u64; } unsafe { - let uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let ty_ptr = &raw const ((*uninit_ty).{{ field.ident() }}); let val = ty_ptr.read_unaligned(); check_same(offset_of!({{ ident }}, {{ field.ident() }}), - __test_offset_{{ ident }}_{{ field.ident() }}() as usize, + ctest_offset_of__{{ ident }}__{{ field.ident() }}() as usize, "field offset {{ field.ident() }} of {{ ident }}"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_{{ ident }}_{{ field.ident() }}(), + check_same(size_of_val(&val) as u64, + ctest_field_size__{{ ident }}__{{ field.ident() }}(), "field size {{ field.ident() }} of {{ ident }}"); } - {%- if !self::should_skip_field_type(generator, Either::Left(structure), field) +%} + {%- if !self::should_skip_struct_field_type(generator, structure, field) +%} extern "C" { - fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *mut {{ ident }}) -> *mut u8; + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *const {{ ident }}) -> *mut u8; } unsafe { - let mut uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).{{ field.ident() }}); check_same(field_ptr as *mut _, - __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr_mut), + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr), "field type {{ field.ident() }} of {{ ident }}"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } {%- endif +%} {%- endif +%} @@ -265,151 +185,86 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_{{ ident }}() -> Vec { + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { // stores (offset, size) for each field {#- If there is no public field these become unused. +#} {%- if fields.len() > 0 +%} let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let bar = MaybeUninit::<{{ ident }}>::zeroed(); let bar = bar.as_ptr(); {%- else +%} let v = Vec::<(usize, usize)>::new(); {%- endif +%} {%- for field in fields +%} unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).{{ field.ident() }}); + let ty_ptr = &raw const ((*bar).{{ field.ident() }}); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!({{ ident }}, {{ field.ident() }}); v.push((off, size)); } {%- endfor +%} // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::<{{ ident }}>()]; // 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 - pad.resize(mem::size_of::<{{ ident }}>(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` 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. - pub fn roundtrip_{{ ident }}() { - use std::ffi::c_int; - - type U = {{ ident }}; - extern "C" { - fn __test_roundtrip_{{ ident }}( - size: i32, x: U, e: *mut c_int, pad: *const u8 - ) -> U; - } - let pad = roundtrip_padding_{{ ident }}(); - unsafe { - use std::mem::{MaybeUninit, size_of}; - let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); - let x_ptr = x.as_mut_ptr().cast::(); - let y_ptr = y.as_mut_ptr().cast::(); - let sz = size_of::(); - for i in 0..sz { - let c: u8 = (i % 256) as u8; - let c = if c == 0 { 42 } else { c }; - let d: u8 = 255_u8 - (i % 256) as u8; - let d = if d == 0 { 42 } else { d }; - x_ptr.add(i).write_volatile(c); - y_ptr.add(i).write_volatile(d); - } - let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { - FAILED.store(true, Ordering::Relaxed); - return; - } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } - let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; - if rust != c { - eprintln!( - "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", - ); - FAILED.store(true, Ordering::Relaxed); - } - } - } - } + {%- include "common/test_roundtrip.rs" +%} {%- endif +%} {%- endfor +%} {%- for union_ in ffi_items.unions() +%} {%- let ident = union_.ident() +%} - {%- let fields = union_.public_fields() +%} + {%- let fields = union_.public_fields().collect::>() +%} - /// Test that the size and alignment of the aliased type is the same in both Rust and C. - /// - /// This can fail if a different type is used on one side, and uses the built in size and - /// alignment functions to check. - pub fn size_align_{{ ident }}() { - extern "C" { - fn __test_size_{{ ident }}() -> u64; - fn __test_align_{{ ident }}() -> u64; - } - unsafe { - check_same(mem::size_of::<{{ ident }}>() as u64, - __test_size_{{ ident }}(), "{{ ident }} size"); - check_same(mem::align_of::<{{ ident }}>() as u64, - __test_align_{{ ident }}(), "{{ ident }} align"); - } - } + {%- include "common/test_size_align.rs" +%} - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_{{ ident }}() { {%- for field in union_.public_fields() +%} - {%- if !self::should_skip_field(generator, Either::Right(union_), field) +%} + {%- if !self::should_skip_union_field(generator, union_, field) +%} extern "C" { - fn __test_offset_{{ ident }}_{{ field.ident() }}() -> u64; - fn __test_fsize_{{ ident }}_{{ field.ident() }}() -> u64; + fn ctest_offset_of__{{ ident }}__{{ field.ident() }}() -> u64; + fn ctest_field_size__{{ ident }}__{{ field.ident() }}() -> u64; } + // Check that the offset and size are the same. unsafe { - let uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let ty_ptr = &raw const ((*uninit_ty).{{ field.ident() }}); let val = ty_ptr.read_unaligned(); check_same(offset_of!({{ ident }}, {{ field.ident() }}), - __test_offset_{{ ident }}_{{ field.ident() }}() as usize, + ctest_offset_of__{{ ident }}__{{ field.ident() }}() as usize, "field offset {{ field.ident() }} of {{ ident }}"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_{{ ident }}_{{ field.ident() }}(), + check_same(size_of_val(&val) as u64, + ctest_field_size__{{ ident }}__{{ field.ident() }}(), "field size {{ field.ident() }} of {{ ident }}"); } - {%- if !self::should_skip_field_type(generator, Either::Right(union_), field) +%} + {%- if !self::should_skip_union_field_type(generator, union_, field) +%} extern "C" { - fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *mut {{ ident }}) -> *mut u8; + fn __test_field_type_{{ ident }}_{{ field.ident() }}(a: *const {{ ident }}) -> *mut u8; } + // Check that the type of the field is the same. unsafe { - let mut uninit_ty = std::mem::MaybeUninit::<{{ ident }}>::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).{{ field.ident() }}); + let uninit_ty = MaybeUninit::<{{ ident }}>::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).{{ field.ident() }}); check_same(field_ptr as *mut _, - __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr_mut), + __test_field_type_{{ ident }}_{{ field.ident() }}(ty_ptr), "field type {{ field.ident() }} of {{ ident }}"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } {%- endif +%} {%- endif +%} @@ -425,91 +280,42 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_{{ ident }}() -> Vec { + fn roundtrip_padding_{{ ident }}() -> [bool; size_of::<{{ ident }}>()] { // stores (offset, size) for each field {#- If there is no public field these become unused. +#} {%- if fields.len() > 0 +%} let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::<{{ ident }}>::uninit(); + let bar = MaybeUninit::<{{ ident }}>::zeroed(); let bar = bar.as_ptr(); {%- else +%} let v = Vec::<(usize, usize)>::new(); {%- endif +%} {%- for field in fields +%} unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).{{ field.ident() }}); + let ty_ptr = &raw const ((*bar).{{ field.ident() }}); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!({{ ident }}, {{ field.ident() }}); v.push((off, size)); } {%- endfor +%} // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::<{{ ident }}>()]; // 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 - pad.resize(mem::size_of::<{{ ident }}>(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` 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. - pub fn roundtrip_{{ ident }}() { - use std::ffi::c_int; - - type U = {{ ident }}; - extern "C" { - fn __test_roundtrip_{{ ident }}( - size: i32, x: U, e: *mut c_int, pad: *const u8 - ) -> U; - } - let pad = roundtrip_padding_{{ ident }}(); - unsafe { - use std::mem::{MaybeUninit, size_of}; - let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); - let x_ptr = x.as_mut_ptr().cast::(); - let y_ptr = y.as_mut_ptr().cast::(); - let sz = size_of::(); - for i in 0..sz { - let c: u8 = (i % 256) as u8; - let c = if c == 0 { 42 } else { c }; - let d: u8 = 255_u8 - (i % 256) as u8; - let d = if d == 0 { 42 } else { d }; - x_ptr.add(i).write_volatile(c); - y_ptr.add(i).write_volatile(d); - } - let r: U = __test_roundtrip_{{ ident }}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { - FAILED.store(true, Ordering::Relaxed); - return; - } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } - let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; - if rust != c { - eprintln!( - "rust [{i}] = {rust} != {c} (C): C \"{{ ident }}\" -> Rust", - ); - FAILED.store(true, Ordering::Relaxed); - } - } - } - } + {%- include "common/test_roundtrip.rs" +%} {%- endif +%} {%- endfor +%} } @@ -538,7 +344,7 @@ fn run_all() { {%- for alias in ffi_items.aliases() +%} {%- let ident = alias.ident() +%} size_align_{{ ident }}(); - {%- if translator.has_sign(ffi_items, alias.ty) +%} + {%- if translator.is_signed(ffi_items, alias.ty) +%} sign_{{ ident }}(); {%- endif +%} {%- if self::should_roundtrip(generator, ident) +%} diff --git a/ctest-next/tests/input/hierarchy.out.c b/ctest-next/tests/input/hierarchy.out.c index 993d8829e1f4c..09ce1097ab7e3 100644 --- a/ctest-next/tests/input/hierarchy.out.c +++ b/ctest-next/tests/input/hierarchy.out.c @@ -15,23 +15,14 @@ bool* __test_const_ON(void) { } // Return the size of a type. -uint64_t __test_size_in6_addr(void) { return sizeof(in6_addr); } +uint64_t ctest_size_of__in6_addr(void) { return sizeof(in6_addr); } // Return the alignment of a type. -uint64_t __test_align_in6_addr(void) { - typedef struct { - unsigned char c; - in6_addr v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__in6_addr(void) { return _Alignof(in6_addr); } // Return `1` if the type is signed, otherwise return `0`. -uint32_t __test_signed_in6_addr(void) { - return (((in6_addr) -1) < 0); +uint32_t ctest_in6_addr_is_signed(void) { + return ((in6_addr) -1) < 0; } #ifdef _MSC_VER @@ -40,10 +31,10 @@ uint32_t __test_signed_in6_addr(void) { # pragma warning(disable:4365) #endif -// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. in6_addr __test_roundtrip_in6_addr( - int32_t rust_size, in6_addr value, int* error, unsigned char* pad + int32_t rust_size, in6_addr value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(in6_addr); diff --git a/ctest-next/tests/input/hierarchy.out.rs b/ctest-next/tests/input/hierarchy.out.rs index e3c98e94b966a..028963ed5b92c 100644 --- a/ctest-next/tests/input/hierarchy.out.rs +++ b/ctest-next/tests/input/hierarchy.out.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - use std::mem::offset_of; + use std::{ptr, slice}; use super::*; @@ -51,8 +52,8 @@ mod generated_tests { unsafe { let ptr1 = ptr::from_ref(&val).cast::(); let ptr2 = __test_const_ON().cast::(); - let ptr1_bytes = slice::from_raw_parts(ptr1, mem::size_of::()); - let ptr2_bytes = slice::from_raw_parts(ptr2, mem::size_of::()); + let ptr1_bytes = slice::from_raw_parts(ptr1, size_of::()); + let ptr2_bytes = slice::from_raw_parts(ptr2, size_of::()); for (i, (&b1, &b2)) in ptr1_bytes.iter().zip(ptr2_bytes.iter()).enumerate() { // HACK: This may read uninitialized data! We do this because // there isn't a good way to recursively iterate all fields. @@ -67,29 +68,31 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_in6_addr() { extern "C" { - fn __test_size_in6_addr() -> u64; - fn __test_align_in6_addr() -> u64; + fn ctest_size_of__in6_addr() -> u64; + fn ctest_align_of__in6_addr() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_in6_addr(), "in6_addr size"); - check_same(mem::align_of::() as u64, - __test_align_in6_addr(), "in6_addr align"); + check_same(size_of::() as u64, + ctest_size_of__in6_addr(), "in6_addr size"); + check_same(align_of::() as u64, + ctest_align_of__in6_addr(), "in6_addr align"); } } - /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. /// /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value /// depending on whether a signed or unsigned type is used. This is simply checked on both /// Rust and C sides to see if they are equal. pub fn sign_in6_addr() { extern "C" { - fn __test_signed_in6_addr() -> u32; + fn ctest_in6_addr_is_signed() -> u32; } + let all_ones = !(0 as in6_addr); + let all_zeros = 0 as in6_addr; unsafe { - check_same(((!(0 as in6_addr)) < (0 as in6_addr)) as u32, - __test_signed_in6_addr(), "in6_addr signed"); + check_same((all_ones < all_zeros) as u32, + ctest_in6_addr_is_signed(), "in6_addr signed"); } } @@ -100,29 +103,31 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_in6_addr() -> Vec { - vec![0; mem::size_of::()] + fn roundtrip_padding_in6_addr() -> [bool; size_of::()] { + [false; size_of::()] } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_in6_addr() { - use std::ffi::c_int; - type U = in6_addr; extern "C" { fn __test_roundtrip_in6_addr( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_in6_addr(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -134,16 +139,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_in6_addr(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_in6_addr(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"in6_addr\" -> Rust", diff --git a/ctest-next/tests/input/macro.out.c b/ctest-next/tests/input/macro.out.c index 8f79857b05095..1c485bad6bb59 100644 --- a/ctest-next/tests/input/macro.out.c +++ b/ctest-next/tests/input/macro.out.c @@ -7,19 +7,10 @@ #include // Return the size of a type. -uint64_t __test_size_string(void) { return sizeof(string); } +uint64_t ctest_size_of__string(void) { return sizeof(string); } // Return the alignment of a type. -uint64_t __test_align_string(void) { - typedef struct { - unsigned char c; - string v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__string(void) { return _Alignof(string); } #ifdef _MSC_VER // Disable signed/unsigned conversion warnings on MSVC. @@ -27,10 +18,10 @@ uint64_t __test_align_string(void) { # pragma warning(disable:4365) #endif -// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. string __test_roundtrip_string( - int32_t rust_size, string value, int* error, unsigned char* pad + int32_t rust_size, string value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(string); @@ -69,25 +60,16 @@ string __test_roundtrip_string( #endif // Return the size of a type. -uint64_t __test_size_VecU8(void) { return sizeof(struct VecU8); } +uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); } // Return the alignment of a type. -uint64_t __test_align_VecU8(void) { - typedef struct { - unsigned char c; - struct VecU8 v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__VecU8(void) { return _Alignof(struct VecU8); } -uint64_t __test_offset_VecU8_x(void) { +uint64_t ctest_offset_of__VecU8__x(void) { return offsetof(struct VecU8, x); } -uint64_t __test_fsize_VecU8_x(void) { +uint64_t ctest_field_size__VecU8__x(void) { struct VecU8* foo = NULL; return sizeof(foo->x); } @@ -96,11 +78,11 @@ uint8_t* __test_field_type_VecU8_x(struct VecU8* b) { return &b->x; } -uint64_t __test_offset_VecU8_y(void) { +uint64_t ctest_offset_of__VecU8__y(void) { return offsetof(struct VecU8, y); } -uint64_t __test_fsize_VecU8_y(void) { +uint64_t ctest_field_size__VecU8__y(void) { struct VecU8* foo = NULL; return sizeof(foo->y); } @@ -115,10 +97,10 @@ uint8_t* __test_field_type_VecU8_y(struct VecU8* b) { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. struct VecU8 __test_roundtrip_VecU8( - int32_t rust_size, struct VecU8 value, int* error, unsigned char* pad + int32_t rust_size, struct VecU8 value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(struct VecU8); @@ -157,25 +139,16 @@ struct VecU8 __test_roundtrip_VecU8( #endif // Return the size of a type. -uint64_t __test_size_VecU16(void) { return sizeof(struct VecU16); } +uint64_t ctest_size_of__VecU16(void) { return sizeof(struct VecU16); } // Return the alignment of a type. -uint64_t __test_align_VecU16(void) { - typedef struct { - unsigned char c; - struct VecU16 v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__VecU16(void) { return _Alignof(struct VecU16); } -uint64_t __test_offset_VecU16_x(void) { +uint64_t ctest_offset_of__VecU16__x(void) { return offsetof(struct VecU16, x); } -uint64_t __test_fsize_VecU16_x(void) { +uint64_t ctest_field_size__VecU16__x(void) { struct VecU16* foo = NULL; return sizeof(foo->x); } @@ -184,11 +157,11 @@ uint16_t* __test_field_type_VecU16_x(struct VecU16* b) { return &b->x; } -uint64_t __test_offset_VecU16_y(void) { +uint64_t ctest_offset_of__VecU16__y(void) { return offsetof(struct VecU16, y); } -uint64_t __test_fsize_VecU16_y(void) { +uint64_t ctest_field_size__VecU16__y(void) { struct VecU16* foo = NULL; return sizeof(foo->y); } @@ -203,10 +176,10 @@ uint16_t* __test_field_type_VecU16_y(struct VecU16* b) { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. struct VecU16 __test_roundtrip_VecU16( - int32_t rust_size, struct VecU16 value, int* error, unsigned char* pad + int32_t rust_size, struct VecU16 value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(struct VecU16); diff --git a/ctest-next/tests/input/macro.out.rs b/ctest-next/tests/input/macro.out.rs index 61117d8be65d6..fea8d66173817 100644 --- a/ctest-next/tests/input/macro.out.rs +++ b/ctest-next/tests/input/macro.out.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - use std::mem::offset_of; + use std::{ptr, slice}; use super::*; @@ -47,14 +48,14 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_string() { extern "C" { - fn __test_size_string() -> u64; - fn __test_align_string() -> u64; + fn ctest_size_of__string() -> u64; + fn ctest_align_of__string() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_string(), "string size"); - check_same(mem::align_of::() as u64, - __test_align_string(), "string align"); + check_same(size_of::() as u64, + ctest_size_of__string(), "string size"); + check_same(align_of::() as u64, + ctest_align_of__string(), "string align"); } } @@ -65,29 +66,31 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_string() -> Vec { - vec![0; mem::size_of::()] + fn roundtrip_padding_string() -> [bool; size_of::()] { + [false; size_of::()] } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_string() { - use std::ffi::c_int; - type U = string; extern "C" { fn __test_roundtrip_string( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_string(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -99,16 +102,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_string(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_string(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"string\" -> Rust", @@ -125,18 +129,18 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_VecU8() { extern "C" { - fn __test_size_VecU8() -> u64; - fn __test_align_VecU8() -> u64; + fn ctest_size_of__VecU8() -> u64; + fn ctest_align_of__VecU8() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_VecU8(), "VecU8 size"); - check_same(mem::align_of::() as u64, - __test_align_VecU8(), "VecU8 align"); + check_same(size_of::() as u64, + ctest_size_of__VecU8(), "VecU8 size"); + check_same(align_of::() as u64, + ctest_align_of__VecU8(), "VecU8 align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_VecU8() { } @@ -147,45 +151,46 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_VecU8() -> Vec { + fn roundtrip_padding_VecU8() -> [bool; size_of::()] { // stores (offset, size) for each field let v = Vec::<(usize, usize)>::new(); // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_VecU8() { - use std::ffi::c_int; - type U = VecU8; extern "C" { fn __test_roundtrip_VecU8( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_VecU8(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -197,16 +202,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_VecU8(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_VecU8(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"VecU8\" -> Rust", @@ -223,18 +229,18 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_VecU16() { extern "C" { - fn __test_size_VecU16() -> u64; - fn __test_align_VecU16() -> u64; + fn ctest_size_of__VecU16() -> u64; + fn ctest_align_of__VecU16() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_VecU16(), "VecU16 size"); - check_same(mem::align_of::() as u64, - __test_align_VecU16(), "VecU16 align"); + check_same(size_of::() as u64, + ctest_size_of__VecU16(), "VecU16 size"); + check_same(align_of::() as u64, + ctest_align_of__VecU16(), "VecU16 align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_VecU16() { } @@ -245,45 +251,46 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_VecU16() -> Vec { + fn roundtrip_padding_VecU16() -> [bool; size_of::()] { // stores (offset, size) for each field let v = Vec::<(usize, usize)>::new(); // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_VecU16() { - use std::ffi::c_int; - type U = VecU16; extern "C" { fn __test_roundtrip_VecU16( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_VecU16(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -295,16 +302,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_VecU16(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_VecU16(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"VecU16\" -> Rust", diff --git a/ctest-next/tests/input/simple.out.with-renames.c b/ctest-next/tests/input/simple.out.with-renames.c index 03a19b3b34cae..3a0c5160ee9e0 100644 --- a/ctest-next/tests/input/simple.out.with-renames.c +++ b/ctest-next/tests/input/simple.out.with-renames.c @@ -23,23 +23,14 @@ char const** __test_const_B(void) { } // Return the size of a type. -uint64_t __test_size_Byte(void) { return sizeof(Byte); } +uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } // Return the alignment of a type. -uint64_t __test_align_Byte(void) { - typedef struct { - unsigned char c; - Byte v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); } // Return `1` if the type is signed, otherwise return `0`. -uint32_t __test_signed_Byte(void) { - return (((Byte) -1) < 0); +uint32_t ctest_Byte_is_signed(void) { + return ((Byte) -1) < 0; } #ifdef _MSC_VER @@ -48,10 +39,10 @@ uint32_t __test_signed_Byte(void) { # pragma warning(disable:4365) #endif -// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. Byte __test_roundtrip_Byte( - int32_t rust_size, Byte value, int* error, unsigned char* pad + int32_t rust_size, Byte value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(Byte); @@ -90,25 +81,16 @@ Byte __test_roundtrip_Byte( #endif // Return the size of a type. -uint64_t __test_size_Person(void) { return sizeof(struct Person); } +uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } // Return the alignment of a type. -uint64_t __test_align_Person(void) { - typedef struct { - unsigned char c; - struct Person v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Person(void) { return _Alignof(struct Person); } -uint64_t __test_offset_Person_name(void) { +uint64_t ctest_offset_of__Person__name(void) { return offsetof(struct Person, name); } -uint64_t __test_fsize_Person_name(void) { +uint64_t ctest_field_size__Person__name(void) { struct Person* foo = NULL; return sizeof(foo->name); } @@ -117,11 +99,11 @@ char const** __test_field_type_Person_name(struct Person* b) { return &b->name; } -uint64_t __test_offset_Person_age(void) { +uint64_t ctest_offset_of__Person__age(void) { return offsetof(struct Person, age); } -uint64_t __test_fsize_Person_age(void) { +uint64_t ctest_field_size__Person__age(void) { struct Person* foo = NULL; return sizeof(foo->age); } @@ -136,10 +118,10 @@ uint8_t* __test_field_type_Person_age(struct Person* b) { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. struct Person __test_roundtrip_Person( - int32_t rust_size, struct Person value, int* error, unsigned char* pad + int32_t rust_size, struct Person value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(struct Person); @@ -178,25 +160,16 @@ struct Person __test_roundtrip_Person( #endif // Return the size of a type. -uint64_t __test_size_Word(void) { return sizeof(union Word); } +uint64_t ctest_size_of__Word(void) { return sizeof(union Word); } // Return the alignment of a type. -uint64_t __test_align_Word(void) { - typedef struct { - unsigned char c; - union Word v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Word(void) { return _Alignof(union Word); } -uint64_t __test_offset_Word_word(void) { +uint64_t ctest_offset_of__Word__word(void) { return offsetof(union Word, word); } -uint64_t __test_fsize_Word_word(void) { +uint64_t ctest_field_size__Word__word(void) { union Word* foo = NULL; return sizeof(foo->word); } @@ -205,11 +178,11 @@ uint16_t* __test_field_type_Word_word(union Word* b) { return &b->word; } -uint64_t __test_offset_Word_byte(void) { +uint64_t ctest_offset_of__Word__byte(void) { return offsetof(union Word, byte); } -uint64_t __test_fsize_Word_byte(void) { +uint64_t ctest_field_size__Word__byte(void) { union Word* foo = NULL; return sizeof(foo->byte); } @@ -224,10 +197,10 @@ Byte(*__test_field_type_Word_byte(union Word* b))[2] { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. union Word __test_roundtrip_Word( - int32_t rust_size, union Word value, int* error, unsigned char* pad + int32_t rust_size, union Word value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(union Word); diff --git a/ctest-next/tests/input/simple.out.with-renames.rs b/ctest-next/tests/input/simple.out.with-renames.rs index a2e038012ca4c..768d967a45532 100644 --- a/ctest-next/tests/input/simple.out.with-renames.rs +++ b/ctest-next/tests/input/simple.out.with-renames.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - use std::mem::offset_of; + use std::{ptr, slice}; use super::*; @@ -44,7 +45,6 @@ mod generated_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_A() { - use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_A() -> *const *const u8; } @@ -53,7 +53,7 @@ mod generated_tests { let ptr = *__test_const_A(); let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); check_same(val, c, "A string"); } @@ -62,7 +62,6 @@ mod generated_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_B() { - use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_B() -> *const *const u8; } @@ -71,7 +70,7 @@ mod generated_tests { let ptr = *__test_const_B(); let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const B not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const B not utf8"); check_same(val, c, "B string"); } @@ -83,29 +82,31 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Byte() { extern "C" { - fn __test_size_Byte() -> u64; - fn __test_align_Byte() -> u64; + fn ctest_size_of__Byte() -> u64; + fn ctest_align_of__Byte() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Byte(), "Byte size"); - check_same(mem::align_of::() as u64, - __test_align_Byte(), "Byte align"); + check_same(size_of::() as u64, + ctest_size_of__Byte(), "Byte size"); + check_same(align_of::() as u64, + ctest_align_of__Byte(), "Byte align"); } } - /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. /// /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value /// depending on whether a signed or unsigned type is used. This is simply checked on both /// Rust and C sides to see if they are equal. pub fn sign_Byte() { extern "C" { - fn __test_signed_Byte() -> u32; + fn ctest_Byte_is_signed() -> u32; } + let all_ones = !(0 as Byte); + let all_zeros = 0 as Byte; unsafe { - check_same(((!(0 as Byte)) < (0 as Byte)) as u32, - __test_signed_Byte(), "Byte signed"); + check_same((all_ones < all_zeros) as u32, + ctest_Byte_is_signed(), "Byte signed"); } } @@ -116,29 +117,31 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Byte() -> Vec { - vec![0; mem::size_of::()] + fn roundtrip_padding_Byte() -> [bool; size_of::()] { + [false; size_of::()] } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Byte() { - use std::ffi::c_int; - type U = Byte; extern "C" { fn __test_roundtrip_Byte( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Byte(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -150,16 +153,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Byte(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Byte(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", @@ -176,50 +180,47 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Person() { extern "C" { - fn __test_size_Person() -> u64; - fn __test_align_Person() -> u64; + fn ctest_size_of__Person() -> u64; + fn ctest_align_of__Person() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Person(), "Person size"); - check_same(mem::align_of::() as u64, - __test_align_Person(), "Person align"); + check_same(size_of::() as u64, + ctest_size_of__Person(), "Person size"); + check_same(align_of::() as u64, + ctest_align_of__Person(), "Person align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_Person() { extern "C" { - fn __test_offset_Person_name() -> u64; - fn __test_fsize_Person_name() -> u64; + fn ctest_offset_of__Person__name() -> u64; + fn ctest_field_size__Person__name() -> u64; } unsafe { - let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).name); + let ty_ptr = &raw const ((*uninit_ty).name); let val = ty_ptr.read_unaligned(); check_same(offset_of!(Person, name), - __test_offset_Person_name() as usize, + ctest_offset_of__Person__name() as usize, "field offset name of Person"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_Person_name(), + check_same(size_of_val(&val) as u64, + ctest_field_size__Person__name(), "field size name of Person"); } extern "C" { - fn __test_field_type_Person_name(a: *mut Person) -> *mut u8; + fn __test_field_type_Person_name(a: *const Person) -> *mut u8; } unsafe { - let mut uninit_ty = std::mem::MaybeUninit::::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).name); + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).name); check_same(field_ptr as *mut _, - __test_field_type_Person_name(ty_ptr_mut), + __test_field_type_Person_name(ty_ptr), "field type name of Person"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } } @@ -230,54 +231,55 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Person() -> Vec { + fn roundtrip_padding_Person() -> [bool; size_of::()] { // stores (offset, size) for each field let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::::uninit(); + let bar = MaybeUninit::::zeroed(); let bar = bar.as_ptr(); unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).name); + let ty_ptr = &raw const ((*bar).name); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!(Person, name); v.push((off, size)); } // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Person() { - use std::ffi::c_int; - type U = Person; extern "C" { fn __test_roundtrip_Person( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Person(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -289,16 +291,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Person(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Person(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", @@ -315,50 +318,49 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Word() { extern "C" { - fn __test_size_Word() -> u64; - fn __test_align_Word() -> u64; + fn ctest_size_of__Word() -> u64; + fn ctest_align_of__Word() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Word(), "Word size"); - check_same(mem::align_of::() as u64, - __test_align_Word(), "Word align"); + check_same(size_of::() as u64, + ctest_size_of__Word(), "Word size"); + check_same(align_of::() as u64, + ctest_align_of__Word(), "Word align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_Word() { extern "C" { - fn __test_offset_Word_word() -> u64; - fn __test_fsize_Word_word() -> u64; + fn ctest_offset_of__Word__word() -> u64; + fn ctest_field_size__Word__word() -> u64; } + // Check that the offset and size are the same. unsafe { - let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).word); + let ty_ptr = &raw const ((*uninit_ty).word); let val = ty_ptr.read_unaligned(); check_same(offset_of!(Word, word), - __test_offset_Word_word() as usize, + ctest_offset_of__Word__word() as usize, "field offset word of Word"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_Word_word(), + check_same(size_of_val(&val) as u64, + ctest_field_size__Word__word(), "field size word of Word"); } extern "C" { - fn __test_field_type_Word_word(a: *mut Word) -> *mut u8; + fn __test_field_type_Word_word(a: *const Word) -> *mut u8; } + // Check that the type of the field is the same. unsafe { - let mut uninit_ty = std::mem::MaybeUninit::::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).word); + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).word); check_same(field_ptr as *mut _, - __test_field_type_Word_word(ty_ptr_mut), + __test_field_type_Word_word(ty_ptr), "field type word of Word"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } } @@ -369,54 +371,55 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Word() -> Vec { + fn roundtrip_padding_Word() -> [bool; size_of::()] { // stores (offset, size) for each field let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::::uninit(); + let bar = MaybeUninit::::zeroed(); let bar = bar.as_ptr(); unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).word); + let ty_ptr = &raw const ((*bar).word); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!(Word, word); v.push((off, size)); } // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Word() { - use std::ffi::c_int; - type U = Word; extern "C" { fn __test_roundtrip_Word( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Word(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -428,16 +431,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Word(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Word(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", diff --git a/ctest-next/tests/input/simple.out.with-skips.c b/ctest-next/tests/input/simple.out.with-skips.c index 2205307767fa9..5d9c0e5172f4b 100644 --- a/ctest-next/tests/input/simple.out.with-skips.c +++ b/ctest-next/tests/input/simple.out.with-skips.c @@ -15,23 +15,14 @@ char const** __test_const_A(void) { } // Return the size of a type. -uint64_t __test_size_Byte(void) { return sizeof(Byte); } +uint64_t ctest_size_of__Byte(void) { return sizeof(Byte); } // Return the alignment of a type. -uint64_t __test_align_Byte(void) { - typedef struct { - unsigned char c; - Byte v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Byte(void) { return _Alignof(Byte); } // Return `1` if the type is signed, otherwise return `0`. -uint32_t __test_signed_Byte(void) { - return (((Byte) -1) < 0); +uint32_t ctest_Byte_is_signed(void) { + return ((Byte) -1) < 0; } #ifdef _MSC_VER @@ -40,10 +31,10 @@ uint32_t __test_signed_Byte(void) { # pragma warning(disable:4365) #endif -// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. Byte __test_roundtrip_Byte( - int32_t rust_size, Byte value, int* error, unsigned char* pad + int32_t rust_size, Byte value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(Byte); @@ -82,25 +73,16 @@ Byte __test_roundtrip_Byte( #endif // Return the size of a type. -uint64_t __test_size_Person(void) { return sizeof(struct Person); } +uint64_t ctest_size_of__Person(void) { return sizeof(struct Person); } // Return the alignment of a type. -uint64_t __test_align_Person(void) { - typedef struct { - unsigned char c; - struct Person v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Person(void) { return _Alignof(struct Person); } -uint64_t __test_offset_Person_name(void) { +uint64_t ctest_offset_of__Person__name(void) { return offsetof(struct Person, name); } -uint64_t __test_fsize_Person_name(void) { +uint64_t ctest_field_size__Person__name(void) { struct Person* foo = NULL; return sizeof(foo->name); } @@ -109,11 +91,11 @@ char const** __test_field_type_Person_name(struct Person* b) { return &b->name; } -uint64_t __test_offset_Person_age(void) { +uint64_t ctest_offset_of__Person__age(void) { return offsetof(struct Person, age); } -uint64_t __test_fsize_Person_age(void) { +uint64_t ctest_field_size__Person__age(void) { struct Person* foo = NULL; return sizeof(foo->age); } @@ -128,10 +110,10 @@ uint8_t* __test_field_type_Person_age(struct Person* b) { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. struct Person __test_roundtrip_Person( - int32_t rust_size, struct Person value, int* error, unsigned char* pad + int32_t rust_size, struct Person value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(struct Person); @@ -170,25 +152,16 @@ struct Person __test_roundtrip_Person( #endif // Return the size of a type. -uint64_t __test_size_Word(void) { return sizeof(union Word); } +uint64_t ctest_size_of__Word(void) { return sizeof(union Word); } // Return the alignment of a type. -uint64_t __test_align_Word(void) { - typedef struct { - unsigned char c; - union Word v; - } type; - type t; - size_t t_addr = (size_t)(unsigned char*)(&t); - size_t v_addr = (size_t)(unsigned char*)(&t.v); - return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; -} +uint64_t ctest_align_of__Word(void) { return _Alignof(union Word); } -uint64_t __test_offset_Word_word(void) { +uint64_t ctest_offset_of__Word__word(void) { return offsetof(union Word, word); } -uint64_t __test_fsize_Word_word(void) { +uint64_t ctest_field_size__Word__word(void) { union Word* foo = NULL; return sizeof(foo->word); } @@ -197,11 +170,11 @@ uint16_t* __test_field_type_Word_word(union Word* b) { return &b->word; } -uint64_t __test_offset_Word_byte(void) { +uint64_t ctest_offset_of__Word__byte(void) { return offsetof(union Word, byte); } -uint64_t __test_fsize_Word_byte(void) { +uint64_t ctest_field_size__Word__byte(void) { union Word* foo = NULL; return sizeof(foo->byte); } @@ -216,10 +189,10 @@ Byte(*__test_field_type_Word_byte(union Word* b))[2] { # pragma warning(disable:4365) #endif -// Tests whether the struct/union `x` when passed to C and back to Rust remains unchanged. +// Tests whether the struct/union/alias `x` 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. union Word __test_roundtrip_Word( - int32_t rust_size, union Word value, int* error, unsigned char* pad + int32_t rust_size, union Word value, int* error, unsigned char* pad ) { volatile unsigned char* p = (volatile unsigned char*)&value; int size = (int)sizeof(union Word); diff --git a/ctest-next/tests/input/simple.out.with-skips.rs b/ctest-next/tests/input/simple.out.with-skips.rs index cb7d7d46af7c6..ba832f8ad9342 100644 --- a/ctest-next/tests/input/simple.out.with-skips.rs +++ b/ctest-next/tests/input/simple.out.with-skips.rs @@ -4,12 +4,13 @@ /// are not allowed at the top-level, so we hack around this by keeping it /// inside of a module. mod generated_tests { - #![allow(non_snake_case)] + #![allow(non_snake_case, unused_imports)] #![deny(improper_ctypes_definitions)] + use std::ffi::{CStr, c_char, c_int}; use std::fmt::{Debug, LowerHex}; + use std::mem::{MaybeUninit, offset_of, align_of}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::{mem, ptr, slice}; - use std::mem::offset_of; + use std::{ptr, slice}; use super::*; @@ -44,7 +45,6 @@ mod generated_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_A() { - use std::ffi::{CStr, c_char}; extern "C" { fn __test_const_A() -> *const *const u8; } @@ -53,7 +53,7 @@ mod generated_tests { let ptr = *__test_const_A(); let val = CStr::from_ptr(val.cast::()); let val = val.to_str().expect("const A not utf8"); - let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = CStr::from_ptr(ptr as *const _); let c = c.to_str().expect("const A not utf8"); check_same(val, c, "A string"); } @@ -65,29 +65,31 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Byte() { extern "C" { - fn __test_size_Byte() -> u64; - fn __test_align_Byte() -> u64; + fn ctest_size_of__Byte() -> u64; + fn ctest_align_of__Byte() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Byte(), "Byte size"); - check_same(mem::align_of::() as u64, - __test_align_Byte(), "Byte align"); + check_same(size_of::() as u64, + ctest_size_of__Byte(), "Byte size"); + check_same(align_of::() as u64, + ctest_align_of__Byte(), "Byte align"); } } - /// Test that the aliased type has the same sign (signed or unsigned) in both Rust and C. + /// Test that the aliased type has the same signedness (signed or unsigned) in both Rust and C. /// /// This check can be performed because `!(0 as _)` yields either -1 or the maximum value /// depending on whether a signed or unsigned type is used. This is simply checked on both /// Rust and C sides to see if they are equal. pub fn sign_Byte() { extern "C" { - fn __test_signed_Byte() -> u32; + fn ctest_Byte_is_signed() -> u32; } + let all_ones = !(0 as Byte); + let all_zeros = 0 as Byte; unsafe { - check_same(((!(0 as Byte)) < (0 as Byte)) as u32, - __test_signed_Byte(), "Byte signed"); + check_same((all_ones < all_zeros) as u32, + ctest_Byte_is_signed(), "Byte signed"); } } @@ -98,29 +100,31 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Byte() -> Vec { - vec![0; mem::size_of::()] + fn roundtrip_padding_Byte() -> [bool; size_of::()] { + [false; size_of::()] } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Byte() { - use std::ffi::c_int; - type U = Byte; extern "C" { fn __test_roundtrip_Byte( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Byte(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -132,16 +136,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Byte(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Byte(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Byte\" -> Rust", @@ -158,50 +163,47 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Person() { extern "C" { - fn __test_size_Person() -> u64; - fn __test_align_Person() -> u64; + fn ctest_size_of__Person() -> u64; + fn ctest_align_of__Person() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Person(), "Person size"); - check_same(mem::align_of::() as u64, - __test_align_Person(), "Person align"); + check_same(size_of::() as u64, + ctest_size_of__Person(), "Person size"); + check_same(align_of::() as u64, + ctest_align_of__Person(), "Person align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_Person() { extern "C" { - fn __test_offset_Person_name() -> u64; - fn __test_fsize_Person_name() -> u64; + fn ctest_offset_of__Person__name() -> u64; + fn ctest_field_size__Person__name() -> u64; } unsafe { - let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).name); + let ty_ptr = &raw const ((*uninit_ty).name); let val = ty_ptr.read_unaligned(); check_same(offset_of!(Person, name), - __test_offset_Person_name() as usize, + ctest_offset_of__Person__name() as usize, "field offset name of Person"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_Person_name(), + check_same(size_of_val(&val) as u64, + ctest_field_size__Person__name(), "field size name of Person"); } extern "C" { - fn __test_field_type_Person_name(a: *mut Person) -> *mut u8; + fn __test_field_type_Person_name(a: *const Person) -> *mut u8; } unsafe { - let mut uninit_ty = std::mem::MaybeUninit::::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).name); + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).name); check_same(field_ptr as *mut _, - __test_field_type_Person_name(ty_ptr_mut), + __test_field_type_Person_name(ty_ptr), "field type name of Person"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } } @@ -212,54 +214,55 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Person() -> Vec { + fn roundtrip_padding_Person() -> [bool; size_of::()] { // stores (offset, size) for each field let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::::uninit(); + let bar = MaybeUninit::::zeroed(); let bar = bar.as_ptr(); unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).name); + let ty_ptr = &raw const ((*bar).name); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!(Person, name); v.push((off, size)); } // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Person() { - use std::ffi::c_int; - type U = Person; extern "C" { fn __test_roundtrip_Person( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Person(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -271,16 +274,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Person(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Person(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Person\" -> Rust", @@ -297,50 +301,49 @@ mod generated_tests { /// alignment functions to check. pub fn size_align_Word() { extern "C" { - fn __test_size_Word() -> u64; - fn __test_align_Word() -> u64; + fn ctest_size_of__Word() -> u64; + fn ctest_align_of__Word() -> u64; } unsafe { - check_same(mem::size_of::() as u64, - __test_size_Word(), "Word size"); - check_same(mem::align_of::() as u64, - __test_align_Word(), "Word align"); + check_same(size_of::() as u64, + ctest_size_of__Word(), "Word size"); + check_same(align_of::() as u64, + ctest_align_of__Word(), "Word align"); } } - /// No idea what this does. + /// Check that offsets, sizes, and types of each field in a struct are the same in Rust and C. pub fn field_offset_size_Word() { extern "C" { - fn __test_offset_Word_word() -> u64; - fn __test_fsize_Word_word() -> u64; + fn ctest_offset_of__Word__word() -> u64; + fn ctest_field_size__Word__word() -> u64; } + // Check that the offset and size are the same. unsafe { - let uninit_ty = std::mem::MaybeUninit::::uninit(); + let uninit_ty = MaybeUninit::::zeroed(); let uninit_ty = uninit_ty.as_ptr(); - let ty_ptr = std::ptr::addr_of!((*uninit_ty).word); + let ty_ptr = &raw const ((*uninit_ty).word); let val = ty_ptr.read_unaligned(); check_same(offset_of!(Word, word), - __test_offset_Word_word() as usize, + ctest_offset_of__Word__word() as usize, "field offset word of Word"); - check_same(mem::size_of_val(&val) as u64, - __test_fsize_Word_word(), + check_same(size_of_val(&val) as u64, + ctest_field_size__Word__word(), "field size word of Word"); } extern "C" { - fn __test_field_type_Word_word(a: *mut Word) -> *mut u8; + fn __test_field_type_Word_word(a: *const Word) -> *mut u8; } + // Check that the type of the field is the same. unsafe { - let mut uninit_ty = std::mem::MaybeUninit::::uninit(); - let uninit_ty = uninit_ty.as_mut_ptr(); - let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); - let field_ptr = std::ptr::addr_of!((*uninit_ty).word); + let uninit_ty = MaybeUninit::::zeroed(); + let ty_ptr = uninit_ty.as_ptr(); + let field_ptr = &raw const ((*ty_ptr).word); check_same(field_ptr as *mut _, - __test_field_type_Word_word(ty_ptr_mut), + __test_field_type_Word_word(ty_ptr), "field type word of Word"); - #[allow(unknown_lints, forgetting_copy_types)] - mem::forget(uninit_ty); } } @@ -351,54 +354,55 @@ mod generated_tests { /// and `0` if the byte is not padding. /// /// For type aliases, the padding map is all zeroes. - fn roundtrip_padding_Word() -> Vec { + fn roundtrip_padding_Word() -> [bool; size_of::()] { // stores (offset, size) for each field let mut v = Vec::<(usize, usize)>::new(); - let bar = std::mem::MaybeUninit::::uninit(); + let bar = MaybeUninit::::zeroed(); let bar = bar.as_ptr(); unsafe { - let ty_ptr = std::ptr::addr_of!((*bar).word); + let ty_ptr = &raw const ((*bar).word); let val = ty_ptr.read_unaligned(); - let size = mem::size_of_val(&val); + let size = size_of_val(&val); let off = offset_of!(Word, word); v.push((off, size)); } // This vector contains `1` if the byte is padding // and `0` if the byte is not padding. - let mut pad = Vec::::new(); + let mut pad = [true; size_of::()]; // 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 - pad.resize(mem::size_of::(), 1); for (off, size) in &v { for i in 0..*size { - pad[off + i] = 0; + pad[off + i] = false; } } pad } - /// Tests whether the type alias `x` when passed to C and back to Rust remains unchanged. + /// Tests whether the alias/struct/union `x` 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. pub fn roundtrip_Word() { - use std::ffi::c_int; - type U = Word; extern "C" { fn __test_roundtrip_Word( - size: i32, x: U, e: *mut c_int, pad: *const u8 + size: i32, x: MaybeUninit, e: *mut c_int, pad: *const bool ) -> U; } let pad = roundtrip_padding_Word(); + assert_eq!(pad.len(), size_of::()); unsafe { - use std::mem::{MaybeUninit, size_of}; let mut error: c_int = 0; - let mut y = MaybeUninit::::uninit(); - let mut x = MaybeUninit::::uninit(); + // Fill both x and y with non-zero, deterministic test patterns + // Here the pattern is every byte that is a multiple of 256 is set to 42, + // and the rest are filled incrementally for c, decrementally for d. + // We use volatile writes to prevent compiler optimization. + let mut y = MaybeUninit::::zeroed(); + let mut x = MaybeUninit::::zeroed(); let x_ptr = x.as_mut_ptr().cast::(); let y_ptr = y.as_mut_ptr().cast::(); let sz = size_of::(); @@ -410,16 +414,17 @@ mod generated_tests { x_ptr.add(i).write_volatile(c); y_ptr.add(i).write_volatile(d); } - let r: U = __test_roundtrip_Word(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); - if error == 1 { + // Now we test that the data sent from Rust to C is the same, and from C to Rust is + // also the same. + let r: U = __test_roundtrip_Word(sz as i32, x, &mut error, pad.as_ptr()); + if error != 0 { FAILED.store(true, Ordering::Relaxed); return; } - for (i, elem) in pad.iter().enumerate().take(size_of::()) { - if *elem == 1 { continue; } + for (i, elem) in pad.iter().enumerate() { + if *elem { continue; } let rust = (*y_ptr.add(i)) as usize; - let c = (&r as *const _ as *const u8) - .add(i).read_volatile() as usize; + let c = (&raw const r).cast::().add(i).read_volatile() as usize; if rust != c { eprintln!( "rust [{i}] = {rust} != {c} (C): C \"Word\" -> Rust", diff --git a/ctest-test/Cargo.toml b/ctest-test/Cargo.toml index 64a12e7835040..1edddb3ba1a12 100644 --- a/ctest-test/Cargo.toml +++ b/ctest-test/Cargo.toml @@ -6,14 +6,13 @@ publish = false edition = "2021" [build-dependencies] -ctest = { path = "../ctest" } -cc = "1.2" +ctest-next = { path = "../ctest-next" } +cc = "1.0" [dev-dependencies] -ctest = { path = "../ctest" } +ctest-next = { path = "../ctest-next" } [dependencies] -cfg-if = "1.0.1" libc = { path = ".." } [[bin]] @@ -24,14 +23,6 @@ test = false name = "t2" test = false -[[bin]] -name = "t1_cxx" -test = false - -[[bin]] -name = "t2_cxx" -test = false - # FIXME(msrv): These should be moved to the root Cargo.toml as `[workspace.lints.*]` # once MSRV is above 1.64 and replaced with `[lints] workspace=true` diff --git a/ctest-test/build.rs b/ctest-test/build.rs index b67c2eaaa4639..301f708454035 100644 --- a/ctest-test/build.rs +++ b/ctest-test/build.rs @@ -1,15 +1,20 @@ -use std::process::Command; +use std::env; + +use ctest_next::{generate_test, TestGenerator}; fn main() { - use std::env; let opt_level = env::var("OPT_LEVEL") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(0); + let profile = env::var("PROFILE").unwrap_or_default(); if profile == "release" || opt_level >= 2 { println!("cargo:rustc-cfg=optimized"); } + + // FIXME(ctest): The .c files are ignored right now, I'm not sure if they + // were used or how they were used before. cc::Build::new() .include("src") .warnings(false) @@ -17,91 +22,41 @@ fn main() { .compile("libt1.a"); println!("cargo:rerun-if-changed=src/t1.c"); println!("cargo:rerun-if-changed=src/t1.h"); + cc::Build::new() .warnings(false) .file("src/t2.c") .compile("libt2.a"); println!("cargo:rerun-if-changed=src/t2.c"); println!("cargo:rerun-if-changed=src/t2.h"); - ctest::TestGenerator::new() + + let mut t1gen = TestGenerator::new(); + t1gen .header("t1.h") .include("src") - .fn_cname(|a, b| b.unwrap_or(a).to_string()) - .type_name(move |ty, is_struct, is_union| match ty { - "T1Union" => ty.to_string(), - "Transparent" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .volatile_item(t1_volatile) - .array_arg(t1_arrays) - .skip_roundtrip(|n| n == "Arr") - .generate("src/t1.rs", "t1gen.rs"); - ctest::TestGenerator::new() + .skip_private(true) + .rename_fn(|f| f.link_name().unwrap_or(f.ident()).to_string().into()) + .rename_union_ty(|ty| (ty == "T1Union").then_some(ty.to_string())) + .rename_struct_ty(|ty| (ty == "Transparent").then_some(ty.to_string())) + .volatile_field(|s, f| s.ident() == "V" && f.ident() == "v") + .volatile_static(|s| s.ident() == "vol_ptr") + .volatile_static(|s| s.ident() == "T1_fn_ptr_vol") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol0" && p.ident() == "arg0") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol2" && p.ident() == "arg1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol2") + // The parameter `a` of the functions `T1r`, `T1s`, `T1t`, `T1v` is an array. + .array_arg(|f, p| matches!(f.ident(), "T1r" | "T1s" | "T1t" | "T1v") && p.ident() == "a") + .skip_roundtrip(|n| n == "Arr"); + generate_test(&mut t1gen, "src/t1.rs", "t1gen.rs").unwrap(); + + let mut t2gen = TestGenerator::new(); + t2gen .header("t2.h") .include("src") - .type_name(move |ty, is_struct, is_union| match ty { - "T2Union" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .skip_roundtrip(|_| true) - .generate("src/t2.rs", "t2gen.rs"); - - println!("cargo::rustc-check-cfg=cfg(has_cxx)"); - if !cfg!(unix) || Command::new("c++").arg("v").output().is_ok() { - // A C compiler is always available, but these are only run if a C++ compiler is - // also available. - println!("cargo::rustc-cfg=has_cxx"); - - ctest::TestGenerator::new() - .header("t1.h") - .language(ctest::Lang::CXX) - .include("src") - .fn_cname(|a, b| b.unwrap_or(a).to_string()) - .type_name(move |ty, is_struct, is_union| match ty { - "T1Union" => ty.to_string(), - "Transparent" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .volatile_item(t1_volatile) - .array_arg(t1_arrays) - .skip_roundtrip(|n| n == "Arr") - .generate("src/t1.rs", "t1gen_cxx.rs"); - ctest::TestGenerator::new() - .header("t2.h") - .language(ctest::Lang::CXX) - .include("src") - .type_name(move |ty, is_struct, is_union| match ty { - "T2Union" => ty.to_string(), - t if is_struct => format!("struct {t}"), - t if is_union => format!("union {t}"), - t => t.to_string(), - }) - .skip_roundtrip(|_| true) - .generate("src/t2.rs", "t2gen_cxx.rs"); - } else { - println!("cargo::warning=skipping C++ tests"); - } -} - -fn t1_volatile(i: ctest::VolatileItemKind) -> bool { - use ctest::VolatileItemKind::*; - match i { - StructField(ref n, ref f) if n == "V" && f == "v" => true, - Static(ref n) if n == "vol_ptr" => true, - FunctionArg(ref n, 0) if n == "T1_vol0" => true, - FunctionArg(ref n, 1) if n == "T1_vol2" => true, - FunctionRet(ref n) if n == "T1_vol1" || n == "T1_vol2" => true, - Static(ref n) if n == "T1_fn_ptr_vol" => true, - _ => false, - } -} - -fn t1_arrays(n: &str, i: usize) -> bool { - i == 0 && matches!(n, "T1r" | "T1s" | "T1t" | "T1v") + // public C typedefs have to manually be specified because they are identical to normal + // structs on the Rust side. + .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) + .skip_roundtrip(|_| true); + generate_test(&mut t2gen, "src/t2.rs", "t2gen.rs").unwrap(); } diff --git a/ctest-test/src/bin/t1.rs b/ctest-test/src/bin/t1.rs index cbe9090eecd4b..a788bf924c918 100644 --- a/ctest-test/src/bin/t1.rs +++ b/ctest-test/src/bin/t1.rs @@ -2,6 +2,5 @@ #![deny(warnings)] use ctest_test::t1::*; -use libc::*; include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest-test/src/bin/t1_cxx.rs b/ctest-test/src/bin/t1_cxx.rs deleted file mode 100644 index 2e1e192a1e210..0000000000000 --- a/ctest-test/src/bin/t1_cxx.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(not(test))] - -cfg_if::cfg_if! { - if #[cfg(has_cxx)] { - use ctest_test::t1::*; - use libc::*; - - include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs")); - } else { - fn main() {} - } -} diff --git a/ctest-test/src/bin/t2_cxx.rs b/ctest-test/src/bin/t2_cxx.rs deleted file mode 100644 index 7ef46bb6a004a..0000000000000 --- a/ctest-test/src/bin/t2_cxx.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![cfg(not(test))] - -cfg_if::cfg_if! { - if #[cfg(has_cxx)] { - use ctest_test::t2::*; - - include!(concat!(env!("OUT_DIR"), "/t2gen_cxx.rs")); - } else { - fn main() {} - } -} diff --git a/ctest-test/src/lib.rs b/ctest-test/src/lib.rs index d54b4ede501b1..8bb86699f30c7 100644 --- a/ctest-test/src/lib.rs +++ b/ctest-test/src/lib.rs @@ -1,5 +1,6 @@ -// src/** is mostly dummy files -#![allow(clippy::style, clippy::correctness)] +//! `ctest-next-test` is a test crate for testing `ctest-next`. It consists +//! of two test binaries, t1 and t2 that test various aspects of `ctest-next`, +//! such as validation and generation of tests. pub mod t1; pub mod t2; diff --git a/ctest-test/src/t1.c b/ctest-test/src/t1.c index 81cd7d7915cd1..24f9fe52bf215 100644 --- a/ctest-test/src/t1.c +++ b/ctest-test/src/t1.c @@ -1,15 +1,15 @@ -#include -#include #include "t1.h" +#include +#include void T1a(void) {} -void* T1b(void) { return NULL; } -void* T1c(void* a) { return NULL; } -int32_t T1d(unsigned a ) { return 0; } -void T1e(unsigned a, const struct T1Bar* b) { } +void *T1b(void) { return NULL; } +void *T1c(void *a) { return NULL; } +int32_t T1d(unsigned a) { return 0; } +void T1e(unsigned a, const struct T1Bar *b) {} void T1f(void) {} -void T1g(int32_t* a) {} -void T1h(const int32_t* b) {} +void T1g(int32_t *a) {} +void T1h(const int32_t *b) {} void T1i(int32_t a[4]) {} void T1j(const int32_t b[4]) {} void T1o(int32_t (*a)[4]) {} @@ -17,8 +17,8 @@ void T1p(int32_t (*const a)[4]) {} void T1r(Arr a) {} void T1s(const Arr a) {} -void T1t(Arr* a) {} -void T1v(const Arr* a) {} +void T1t(Arr *a) {} +void T1v(const Arr *a) {} unsigned T1static = 3; @@ -29,11 +29,13 @@ uint8_t foo(uint8_t a, uint8_t b) { return a + b; } void bar(uint8_t a) { return; } void baz(void) { return; } -uint32_t (*nested(uint8_t arg))(uint16_t) { +uint32_t (*nested(uint8_t arg))(uint16_t) +{ return NULL; } -uint32_t (*nested2(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) { +uint32_t (*nested2(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) +{ return NULL; } @@ -46,7 +48,7 @@ const uint8_t T1_static_right = 7; uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; -uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; +uint32_t (*(*T1_fn_ptr_s2)(uint8_t (*arg0)(uint8_t), uint16_t (*arg1)(uint16_t)))(uint16_t) = nested2; const int32_t T1_arr0[2] = {0, 0}; const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; @@ -57,21 +59,12 @@ int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; -const int16_t* T1_sref = (void*)(1337); - -const int32_t* T1_mut_opt_ref = NULL; -int32_t* T1_mut_opt_mut_ref = NULL; -const int32_t* T1_const_opt_const_ref = NULL; - -void (*const T1_opt_fn1)(void) = baz; -uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t) = nested; -uint32_t (*(*T1_opt_fn3)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; +const int16_t *T1_sref = (void *)(1337); -volatile uint8_t* vol_ptr = NULL; -void* T1_vol0(volatile void* x, void* a) { return a? a: (void*)x; } -volatile void* T1_vol1(void* x, void* b) { return b? (volatile void*)x : (volatile void*)x; } -volatile void* T1_vol2(void* c, volatile void* x) { return c? x : x; } +volatile uint8_t *vol_ptr = NULL; +void *T1_vol0(volatile void *x, void *a) { return a ? a : (void *)x; } +volatile void *T1_vol1(void *x, void *b) { return b ? (volatile void *)x : (volatile void *)x; } +volatile void *T1_vol2(void *c, volatile void *x) { return c ? x : x; } -/* FIXME(#4365): duplicate symbol errors when enabled -uint8_t (* volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; -*/ +// FIXME(#4365): duplicate symbol errors when enabled +// uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; diff --git a/ctest-test/src/t1.cpp b/ctest-test/src/t1.cpp deleted file mode 120000 index 1627f65e030cd..0000000000000 --- a/ctest-test/src/t1.cpp +++ /dev/null @@ -1 +0,0 @@ -t1.c \ No newline at end of file diff --git a/ctest-test/src/t1.h b/ctest-test/src/t1.h index e610bb10d053a..08800525651d7 100644 --- a/ctest-test/src/t1.h +++ b/ctest-test/src/t1.h @@ -5,7 +5,8 @@ typedef int32_t T1Foo; #define T1N 5 #define T1S "foo" -struct T1Bar { +struct T1Bar +{ int32_t a; uint32_t b; T1Foo c; @@ -14,49 +15,53 @@ struct T1Bar { int64_t f[T1N][2]; }; -struct T1Baz { +struct T1Baz +{ uint64_t a; struct T1Bar b; }; -typedef union { +typedef union +{ uint64_t a; uint32_t b; } T1Union; -union T1NoTypedefUnion { - uint64_t a; - uint32_t b; +union T1NoTypedefUnion +{ + uint64_t a; + uint32_t b; }; -struct T1StructWithUnion { - union T1NoTypedefUnion u; +struct T1StructWithUnion +{ + union T1NoTypedefUnion u; }; typedef double T1TypedefDouble; -typedef int* T1TypedefPtr; +typedef int *T1TypedefPtr; typedef struct T1Bar T1TypedefStruct; void T1a(void); -void* T1b(void); -void* T1c(void*); +void *T1b(void); +void *T1c(void *); int32_t T1d(unsigned); -void T1e(unsigned, const struct T1Bar*); +void T1e(unsigned, const struct T1Bar *); void T1f(void); -void T1g(int32_t* a); -void T1h(const int32_t* b); +void T1g(int32_t *a); +void T1h(const int32_t *b); void T1i(int32_t a[4]); void T1j(const int32_t b[4]); void T1o(int32_t (*a)[4]); void T1p(int32_t (*const a)[4]); -typedef int32_t (Arr)[4]; +typedef int32_t(Arr)[4]; typedef int32_t Transparent; void T1r(Arr a); void T1s(const Arr a); -void T1t(Arr* a); -void T1v(const Arr* a); +void T1t(Arr *a); +void T1v(const Arr *a); #define T1C 4 @@ -95,31 +100,22 @@ extern int32_t T1_arr5[1][2][3]; extern int32_t T1_arr42[1][2][3]; -extern const int16_t* T1_sref; - -extern const int32_t* T1_mut_opt_ref; -extern int32_t* T1_mut_opt_mut_ref; -extern const int32_t* T1_const_opt_const_ref; - -extern void (*const T1_opt_fn1)(void); -/* FIXME(#4365): duplicate symbol errors when enabled -// uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t); -// uint32_t (*(*T1_opt_fn3)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); -*/ +extern const int16_t *T1_sref; - -struct Q { - uint8_t* q0; - uint8_t** q1; +struct Q +{ + uint8_t *q0; + uint8_t **q1; uint8_t q2; }; - -struct T1_conflict_foo { +struct T1_conflict_foo +{ int a; }; -struct T1_conflict{ +struct T1_conflict +{ int foo; }; @@ -128,36 +124,39 @@ struct T1_conflict{ // on msvc there is only pragma pack // on clang and gcc there is a packed attribute -# pragma pack(push,1) +#pragma pack(push, 1) -struct Pack { +struct Pack +{ uint8_t a; uint16_t b; }; -# pragma pack(pop) +#pragma pack(pop) -# pragma pack(push,4) +#pragma pack(push, 4) -struct Pack4 { +struct Pack4 +{ uint8_t a; uint32_t b; }; -# pragma pack(pop) +#pragma pack(pop) // volatile pointers in struct fields: -struct V { - volatile uint8_t* v; +struct V +{ + volatile uint8_t *v; }; // volatile pointers in externs: -extern volatile uint8_t* vol_ptr; +extern volatile uint8_t *vol_ptr; // volatile pointers in function arguments: -void* T1_vol0(volatile void*, void*); -volatile void* T1_vol1(void*, void*); -volatile void* T1_vol2(void*, volatile void*); +void *T1_vol0(volatile void *, void *); +volatile void *T1_vol1(void *, void *); +volatile void *T1_vol2(void *, volatile void *); /* FIXME(#4365): duplicate symbol errors when enabled // volatile function pointers: @@ -166,7 +165,8 @@ uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); #define LOG_MAX_LINE_LENGTH (1400) -typedef struct { +typedef struct +{ long tv_sec; int tv_usec; } timeval; diff --git a/ctest-test/src/t1.rs b/ctest-test/src/t1.rs index 77a2873204c5f..9d8f5bbb30172 100644 --- a/ctest-test/src/t1.rs +++ b/ctest-test/src/t1.rs @@ -1,9 +1,9 @@ -#![allow(dead_code)] +#![allow(non_camel_case_types)] -use libc::*; +use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void}; pub type T1Foo = i32; -pub const T1S: &str = "foo"; +pub const T1S: *const c_char = c"foo".as_ptr(); pub const T1N: i32 = 5; @@ -57,6 +57,7 @@ i! { pub const T1C: u32 = 4; } +#[expect(unused)] const NOT_PRESENT: u32 = 5; pub type Arr = [i32; 4]; @@ -88,7 +89,8 @@ extern "C" { } pub fn foo() { - assert_eq!(1, 1); + let x = 1; + assert_eq!(x, 1); } extern "C" { @@ -126,21 +128,6 @@ extern "C" { pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; pub static mut T1_sref: &'static i16; - - pub static mut T1_mut_opt_ref: Option<&'static i32>; - pub static mut T1_mut_opt_mut_ref: Option<&'static mut i32>; - pub static T1_const_opt_const_ref: Option<&'static i32>; - - pub static T1_opt_fn1: Option ()>; - /* FIXME(#4365): duplicate symbol errors when enabled - pub static T1_opt_fn2: Option extern "C" fn(u16) -> u32>; - pub static T1_opt_fn3: Option< - unsafe extern "C" fn( - extern "C" fn(u8) -> u8, - extern "C" fn(u16) -> u16, - ) -> extern "C" fn(u16) -> u32, - >; - */ } #[repr(C)] @@ -182,9 +169,6 @@ extern "C" { pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; - /* FIXME(#4365): duplicate symbol errors when enabled - pub static T1_fn_ptr_vol: Option u8>; - */ } pub const LOG_MAX_LINE_LENGTH: usize = 1400; @@ -195,6 +179,7 @@ struct timeval { tv_usec: c_int, } +#[expect(unused)] #[repr(C)] struct log_record_t { level: c_long, @@ -205,11 +190,14 @@ struct log_record_t { message: [c_char; LOG_MAX_LINE_LENGTH], } +#[expect(unused)] #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] #[repr(C, align(16))] struct LongDoubleWrap { inner: u128, } + +#[expect(unused)] #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] #[repr(C)] struct LongDoubleWrap { diff --git a/ctest-test/src/t2.cpp b/ctest-test/src/t2.cpp deleted file mode 120000 index 02be41dad0810..0000000000000 --- a/ctest-test/src/t2.cpp +++ /dev/null @@ -1 +0,0 @@ -t2.c \ No newline at end of file diff --git a/ctest-test/src/t2.h b/ctest-test/src/t2.h index 9f99e11a1e79d..9fd4fc3b5aa6d 100644 --- a/ctest-test/src/t2.h +++ b/ctest-test/src/t2.h @@ -6,18 +6,21 @@ typedef int8_t T2Bar; typedef T2Foo T2TypedefFoo; typedef unsigned T2TypedefInt; -struct T2Baz { +struct T2Baz +{ int8_t _a; int64_t a; uint32_t b; }; -typedef struct { +typedef struct +{ uint32_t a; int64_t b; } T2Union; -static void T2a(void) {} +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// static void T2a(void) {} #define T2C 4 #define T2S "a" diff --git a/ctest-test/src/t2.rs b/ctest-test/src/t2.rs index bafeaef7cd897..cfd3bfb65b418 100644 --- a/ctest-test/src/t2.rs +++ b/ctest-test/src/t2.rs @@ -1,4 +1,4 @@ -use libc::*; +use std::ffi::{c_char, c_int}; pub type T2Foo = u32; pub type T2Bar = u32; @@ -28,9 +28,10 @@ pub union T2Union { pub const T2C: i32 = 5; i! { - pub const T2S: &str = "b"; + pub const T2S: *const c_char = c"b".as_ptr(); } -extern "C" { - pub fn T2a(); -} +// FIXME(ctest): Will fail as unused function until extern functions are tested. +// extern "C" { +// pub fn T2a(); +// } diff --git a/ctest-test/tests/all.rs b/ctest-test/tests/all.rs index b8f29e6799737..327693a53d485 100644 --- a/ctest-test/tests/all.rs +++ b/ctest-test/tests/all.rs @@ -5,16 +5,18 @@ use std::collections::HashSet; use std::env; use std::process::{Command, ExitStatus}; +/// Create a command that starts in the `target/debug` or `target/release` directory. fn cmd(name: &str) -> Command { - let mut p = env::current_exe().unwrap(); - p.pop(); - if p.file_name().unwrap().to_str() == Some("deps") { - p.pop(); + let mut path = env::current_exe().unwrap(); + path.pop(); + if path.file_name().unwrap().to_str() == Some("deps") { + path.pop(); } - p.push(name); - Command::new(p) + path.push(name); + Command::new(path) } +/// Executes a command, returning stdout and stderr combined and it's status. fn output(cmd: &mut Command) -> (String, ExitStatus) { eprintln!("command: {cmd:?}"); let output = cmd.output().unwrap(); @@ -25,25 +27,21 @@ fn output(cmd: &mut Command) -> (String, ExitStatus) { } #[test] -fn t1() { - let (o, status) = output(&mut cmd("t1")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); - eprintln!("o: {o}"); +fn t1_next() { + // t1 must run to completion without any errors. + let (output, status) = output(&mut cmd("t1-next")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); + eprintln!("output: {output}"); } #[test] -#[cfg(has_cxx)] -fn t1_cxx() { - let (o, status) = output(&mut cmd("t1_cxx")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); -} +fn t2_next() { + // t2 must fail to run to completion, and only have the errors we expect it to have. + let (output, status) = output(&mut cmd("t2-next")); + assert!(!status.success(), "output: {output}"); -#[test] -fn t2() { - let (o, status) = output(&mut cmd("t2")); - assert!(!status.success(), "output: {o}"); + // FIXME(ctest): Errors currently commented out are not tested. let errors = [ "bad T2Foo signed", "bad T2TypedefFoo signed", @@ -56,7 +54,7 @@ fn t2() { "bad field type a of T2Baz", "bad field offset b of T2Baz", "bad field type b of T2Baz", - "bad T2a function pointer", + // "bad T2a function pointer", "bad T2C value at byte 0", "bad T2S string", "bad T2Union size", @@ -65,137 +63,24 @@ fn t2() { ]; let mut errors = errors.iter().cloned().collect::>(); + // Extract any errors that are not contained within the known error set. let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { + for line in output.lines().filter(|l| l.starts_with("bad ")) { let msg = &line[..line.find(":").unwrap()]; if !errors.remove(&msg) { - println!("unknown error: {msg}"); + println!("unknown error: {msg:#?}"); bad = true; } } + // If any errors are left over, t2 did not run properly. for error in errors { println!("didn't find error: {error}"); bad = true; } - if bad { - println!("output was:\n\n{o}"); - panic!(); - } -} - -#[test] -#[cfg(has_cxx)] -fn t2_cxx() { - let (o, status) = output(&mut cmd("t2_cxx")); - assert!(!status.success(), "output: {o}"); - let errors = [ - "bad T2Foo signed", - "bad T2TypedefFoo signed", - "bad T2TypedefInt signed", - "bad T2Bar size", - "bad T2Bar align", - "bad T2Bar signed", - "bad T2Baz size", - "bad field offset a of T2Baz", - "bad field type a of T2Baz", - "bad field offset b of T2Baz", - "bad field type b of T2Baz", - "bad T2a function pointer", - "bad T2C value at byte 0", - "bad T2S string", - "bad T2Union size", - "bad field type b of T2Union", - "bad field offset b of T2Union", - ]; - let mut errors = errors.iter().cloned().collect::>(); - - let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { - let msg = &line[..line.find(":").unwrap()]; - if !errors.remove(&msg) { - println!("unknown error: {msg}"); - bad = true; - } - } - for error in errors { - println!("didn't find error: {error}"); - bad = true; - } if bad { - println!("output was:\n\n{o}"); + println!("output was:\n\n{output:#?}"); panic!(); } } - -#[test] -fn test_missing_out_dir() { - // Save original OUT_DIR - let orig_out_dir = env::var_os("OUT_DIR"); - env::remove_var("OUT_DIR"); - - // Test error handling for OUT_DIR missing - let result = ctest::TestGenerator::new() - .header("t1.h") - .try_generate("src/t1.rs", "out_dir_gen.rs"); - - // Restore OUT_DIR - if let Some(dir) = orig_out_dir { - env::set_var("OUT_DIR", dir); - } - - assert!(result.is_err(), "Expected error when OUT_DIR is missing"); -} - -#[test] -fn test_invalid_output_path() { - // Test error handling for invalid output path - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("src") - .out_dir("/nonexistent_dir") // Should fail with permission error - .try_generate("src/t1.rs", "out_path_gen.rs"); - - assert!(err.is_err(), "Expected error with invalid output path"); -} - -#[test] -fn test_parsing_error() { - // Test parsing error - // Create a temporary file with invalid Rust syntax - let temp_dir = env::temp_dir(); - let invalid_file = temp_dir.join("invalid.rs"); - std::fs::write(&invalid_file, "fn invalid_syntax {").unwrap(); - - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("src") - .target("x86_64-unknown-linux-gnu") - .try_generate(&invalid_file, "parse_gen.rs"); - - assert!(err.is_err(), "Expected error when parsing invalid syntax"); - let _ = std::fs::remove_file(invalid_file); -} - -#[test] -fn test_non_existent_header() { - // Test non-existent header - let err = ctest::TestGenerator::new() - .header("nonexistent_header.h") - .include("src") - .try_generate("src/t1.rs", "missing_header_gen.rs"); - - assert!(err.is_err(), "Expected error with non-existent header"); -} - -#[test] -fn test_invalid_include_path() { - // Test invalid include path - let err = ctest::TestGenerator::new() - .header("t1.h") - .include("nonexistent_directory") - .try_generate("src/t1.rs", "invalid_include_gen.rs"); - - assert!(err.is_err(), "Expected error with invalid include path"); -}