Skip to content

Commit 30831f5

Browse files
mbyxtgross35
authored andcommitted
ctest: add foreign func tests
1 parent 9e26bd8 commit 30831f5

File tree

18 files changed

+225
-75
lines changed

18 files changed

+225
-75
lines changed

ctest-next/src/ast/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ mod structure;
77
mod type_alias;
88
mod union;
99

10+
use std::fmt;
11+
1012
pub use constant::Const;
1113
pub use field::Field;
1214
pub use function::Fn;
@@ -37,6 +39,16 @@ impl From<&str> for Abi {
3739
}
3840
}
3941

42+
impl fmt::Display for Abi {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
match self {
45+
Abi::C => write!(f, "C"),
46+
Abi::Rust => write!(f, "Rust"),
47+
Abi::Other(s) => write!(f, "{s}"),
48+
}
49+
}
50+
}
51+
4052
/// Things that can appear directly inside of a module or scope.
4153
///
4254
/// This is not an exhaustive list and only contains variants directly useful
@@ -47,7 +59,7 @@ pub(crate) enum Item {
4759
/// Represents a constant defined in Rust.
4860
Const(Const),
4961
/// Represents a function defined in Rust.
50-
Fn(Fn),
62+
Fn(Box<Fn>),
5163
/// Represents a static variable defined in Rust.
5264
Static(Static),
5365
/// Represents a type alias defined in Rust.

ctest-next/src/ffi_items.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ impl FfiItems {
5757
}
5858

5959
/// Return a list of all foreign functions found mapped by their ABI.
60-
#[cfg_attr(not(test), expect(unused))]
6160
pub(crate) fn foreign_functions(&self) -> &Vec<Fn> {
6261
&self.foreign_functions
6362
}

ctest-next/src/generator.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ pub struct TestGenerator {
4242
pub(crate) skips: Vec<Skip>,
4343
verbose_skip: bool,
4444
pub(crate) volatile_items: Vec<VolatileItem>,
45-
array_arg: Option<ArrayArg>,
45+
pub(crate) array_arg: Option<ArrayArg>,
4646
skip_private: bool,
4747
pub(crate) skip_roundtrip: Option<SkipTest>,
4848
pub(crate) skip_signededness: Option<SkipTest>,
49+
pub(crate) skip_fn_ptrcheck: Option<SkipTest>,
4950
}
5051

5152
#[derive(Debug, Error)]
@@ -856,6 +857,30 @@ impl TestGenerator {
856857
self
857858
}
858859

860+
/// Configures whether tests for a function pointer's value are generated.
861+
///
862+
/// The closure is given a Rust FFI function and returns whether
863+
/// the test will be generated.
864+
///
865+
/// By default generated tests will ensure that the function pointer in C
866+
/// corresponds to the same function pointer in Rust. This can often
867+
/// uncover subtle symbol naming issues where a header file is referenced
868+
/// through the C identifier `foo` but the underlying symbol is mapped to
869+
/// something like `__foo_compat`.
870+
///
871+
/// # Examples
872+
///
873+
/// ```no_run
874+
/// use ctest_next::TestGenerator;
875+
///
876+
/// let mut cfg = TestGenerator::new();
877+
/// cfg.skip_fn_ptrcheck(|name| name == "T1p");
878+
/// ```
879+
pub fn skip_fn_ptrcheck(&mut self, f: impl Fn(&str) -> bool + 'static) -> &mut Self {
880+
self.skip_fn_ptrcheck = Some(Box::new(f));
881+
self
882+
}
883+
859884
/// Generate the Rust and C testing files.
860885
///
861886
/// Returns the path to the generated file.

ctest-next/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type BoxStr = Box<str>;
3737
///
3838
/// This is necessary because `ctest` does not parse the header file, so it
3939
/// does not know which items are volatile.
40-
#[derive(Debug)]
40+
#[derive(Debug, Clone)]
4141
#[non_exhaustive]
4242
pub enum VolatileItemKind {
4343
/// A struct field.

ctest-next/src/template.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use quote::ToTokens;
44
use syn::spanned::Spanned;
55

66
use crate::ffi_items::FfiItems;
7-
use crate::translator::Translator;
7+
use crate::translator::{TranslationErrorKind, Translator};
88
use crate::{
99
BoxStr, Field, MapInput, Result, TestGenerator, TranslationError, VolatileItemKind, cdecl,
1010
};
@@ -53,6 +53,7 @@ pub(crate) struct TestTemplate {
5353
pub field_ptr_tests: Vec<TestFieldPtr>,
5454
pub field_size_offset_tests: Vec<TestFieldSizeOffset>,
5555
pub roundtrip_tests: Vec<TestRoundtrip>,
56+
pub foreign_fn_tests: Vec<TestForeignFn>,
5657
pub signededness_tests: Vec<TestSignededness>,
5758
pub size_align_tests: Vec<TestSizeAlign>,
5859
pub const_cstr_tests: Vec<TestCStr>,
@@ -75,6 +76,7 @@ impl TestTemplate {
7576
template.populate_field_size_offset_tests(&helper)?;
7677
template.populate_field_ptr_tests(&helper)?;
7778
template.populate_roundtrip_tests(&helper)?;
79+
template.populate_foreign_fn_tests(&helper)?;
7880

7981
Ok(template)
8082
}
@@ -359,7 +361,7 @@ impl TestTemplate {
359361
)
360362
.map_err(|_| {
361363
TranslationError::new(
362-
crate::translator::TranslationErrorKind::InvalidReturn,
364+
TranslationErrorKind::InvalidReturn,
363365
&field.ty.to_token_stream().to_string(),
364366
field.ty.span(),
365367
)
@@ -380,6 +382,38 @@ impl TestTemplate {
380382

381383
Ok(())
382384
}
385+
386+
/// Populates tests for extern functions.
387+
///
388+
/// It also keeps track of the names of each test.
389+
fn populate_foreign_fn_tests(
390+
&mut self,
391+
helper: &TranslateHelper,
392+
) -> Result<(), TranslationError> {
393+
let should_skip_fn_test = |ident| {
394+
helper
395+
.generator
396+
.skip_fn_ptrcheck
397+
.as_ref()
398+
.is_some_and(|skip| skip(ident))
399+
};
400+
for func in helper.ffi_items.foreign_functions() {
401+
if should_skip_fn_test(func.ident()) {
402+
continue;
403+
}
404+
405+
let item = TestForeignFn {
406+
test_name: foreign_fn_test_ident(func.ident()),
407+
id: func.ident().into(),
408+
c_val: helper.c_ident(func).into_boxed_str(),
409+
};
410+
411+
self.foreign_fn_tests.push(item.clone());
412+
self.test_idents.push(item.test_name);
413+
}
414+
415+
Ok(())
416+
}
383417
}
384418

385419
/* Many test structures have the following fields:
@@ -457,6 +491,13 @@ pub(crate) struct TestRoundtrip {
457491
pub is_alias: bool,
458492
}
459493

494+
#[derive(Clone, Debug)]
495+
pub(crate) struct TestForeignFn {
496+
pub test_name: BoxStr,
497+
pub c_val: BoxStr,
498+
pub id: BoxStr,
499+
}
500+
460501
fn signededness_test_ident(ident: &str) -> BoxStr {
461502
format!("ctest_signededness_{ident}").into()
462503
}
@@ -485,6 +526,10 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr {
485526
format!("ctest_roundtrip_{ident}").into()
486527
}
487528

529+
fn foreign_fn_test_ident(ident: &str) -> BoxStr {
530+
format!("ctest_foreign_fn_{ident}").into()
531+
}
532+
488533
/// Wrap methods that depend on both ffi items and the generator.
489534
pub(crate) struct TranslateHelper<'a> {
490535
ffi_items: &'a FfiItems,
@@ -533,7 +578,7 @@ impl<'a> TranslateHelper<'a> {
533578

534579
let ty = cdecl::cdecl(&ty, "".to_string()).map_err(|_| {
535580
TranslationError::new(
536-
crate::translator::TranslationErrorKind::InvalidReturn,
581+
TranslationErrorKind::InvalidReturn,
537582
ident,
538583
Span::call_site(),
539584
)

ctest-next/src/tests.rs

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use syn::spanned::Spanned;
22
use syn::visit::Visit;
33

44
use crate::ffi_items::FfiItems;
5-
use crate::translator::Translator;
5+
use crate::translator::{TranslationErrorKind, Translator};
66
use crate::{Result, TestGenerator, TranslationError, cdecl};
77

88
const ALL_ITEMS: &str = r#"
@@ -44,13 +44,13 @@ fn r2cdecl(s: &str, name: &str) -> Result<String, TranslationError> {
4444
let translator = Translator::new(&ffi_items, &generator);
4545
let ty: syn::Type = syn::parse_str(s).unwrap();
4646
let translated = translator.translate_type(&ty)?;
47-
cdecl::cdecl(&translated, name.to_string()).map_err(|_| {
48-
TranslationError::new(
49-
crate::translator::TranslationErrorKind::InvalidReturn,
50-
s,
51-
ty.span(),
52-
)
53-
})
47+
cdecl::cdecl(&translated, name.to_string())
48+
.map_err(|_| TranslationError::new(TranslationErrorKind::InvalidReturn, s, ty.span()))
49+
}
50+
51+
#[track_caller]
52+
fn assert_r2cdecl(rust: &str, expected: &str) {
53+
assert_eq!(r2cdecl(rust, "foo").unwrap(), expected)
5454
}
5555

5656
#[test]
@@ -70,55 +70,37 @@ fn test_extraction_ffi_items() {
7070

7171
#[test]
7272
fn test_translation_type_ptr() {
73-
assert_eq!(
74-
r2cdecl("*const *mut i32", "").unwrap(),
75-
"int32_t *const *".to_string()
76-
);
77-
assert_eq!(
78-
r2cdecl("*const [u128; 2 + 3]", "").unwrap(),
79-
"unsigned __int128 (*)[2 + 3]".to_string()
80-
);
81-
assert_eq!(
82-
r2cdecl("*const *mut [u8; 5]", "").unwrap(),
83-
"uint8_t (*const *)[5]".to_string()
84-
);
73+
assert_r2cdecl("*const *mut i32", "int32_t *const *foo");
74+
assert_r2cdecl("*const [u128; 2 + 3]", "unsigned __int128 (*foo)[2 + 3]");
75+
assert_r2cdecl("*const *mut [u8; 5]", "uint8_t (*const *foo)[5]");
76+
assert_r2cdecl("*mut *const [u8; 5]", "uint8_t (**foo)[5]");
77+
assert_r2cdecl("*const *const [u8; 5]", "uint8_t (*const *foo)[5]");
78+
assert_r2cdecl("*mut *mut [u8; 5]", "uint8_t (**foo)[5]");
8579
}
8680

8781
#[test]
8882
fn test_translation_type_reference() {
89-
assert_eq!(r2cdecl("&u8", "").unwrap(), "const uint8_t *".to_string());
90-
assert_eq!(
91-
r2cdecl("&&u8", "").unwrap(),
92-
"const uint8_t *const *".to_string()
93-
);
94-
assert_eq!(
95-
r2cdecl("*mut &u8", "").unwrap(),
96-
"const uint8_t **".to_string()
97-
);
98-
assert_eq!(
99-
r2cdecl("& &mut u8", "").unwrap(),
100-
"uint8_t *const *".to_string()
101-
);
83+
assert_r2cdecl("&u8", "const uint8_t *foo");
84+
assert_r2cdecl("&&u8", "const uint8_t *const *foo");
85+
assert_r2cdecl("*mut &u8", "const uint8_t **foo");
86+
assert_r2cdecl("& &mut u8", "uint8_t *const *foo");
10287
}
10388

10489
#[test]
10590
fn test_translation_type_bare_fn() {
106-
assert_eq!(
107-
r2cdecl("fn(*mut u8, i16) -> *const char", "").unwrap(),
108-
"const char *(*)(uint8_t *, int16_t)".to_string()
91+
assert_r2cdecl(
92+
"fn(*mut u8, i16) -> *const char",
93+
"const char *(*foo)(uint8_t *, int16_t)",
10994
);
110-
assert_eq!(
111-
r2cdecl("*const fn(*mut u8, &mut [u8; 16]) -> &mut *mut u8", "").unwrap(),
112-
"uint8_t **(*const *)(uint8_t *, uint8_t (*)[16])".to_string()
95+
assert_r2cdecl(
96+
"*const fn(*mut u8, &mut [u8; 16]) -> &mut *mut u8",
97+
"uint8_t **(*const *foo)(uint8_t *, uint8_t (*)[16])",
11398
);
11499
}
115100

116101
#[test]
117102
fn test_translation_type_array() {
118-
assert_eq!(
119-
r2cdecl("[&u8; 2 + 2]", "").unwrap(),
120-
"const uint8_t *[2 + 2]".to_string()
121-
);
103+
assert_r2cdecl("[&u8; 2 + 2]", "const uint8_t *foo[2 + 2]");
122104
}
123105

124106
#[test]
@@ -129,25 +111,19 @@ fn test_translation_fails_for_unsupported() {
129111

130112
#[test]
131113
fn test_translate_helper_function_pointer() {
132-
assert_eq!(
133-
r2cdecl("extern \"C\" fn(c_int) -> *const c_void", "test_make_cdecl").unwrap(),
134-
"const void *(*test_make_cdecl)(int)"
114+
assert_r2cdecl(
115+
"extern \"C\" fn(c_int) -> *const c_void",
116+
"const void *(*foo)(int)",
135117
);
136118
// FIXME(ctest): Reimplement support for ABI in a more robust way.
137-
// assert_eq!(
138-
// cdecl("Option<extern \"stdcall\" fn(*const c_char, [u32; 16]) -> u8>").unwrap(),
139-
// "uint8_t (__stdcall **test_make_cdecl)(const char *, uint32_t [16])"
119+
// assert_r2cdecl(
120+
// "Option<extern \"stdcall\" fn(*const c_char, [u32; 16]) -> u8>",
121+
// "uint8_t (__stdcall **foo)(const char *, uint32_t [16])"
140122
// );
141123
}
142124

143125
#[test]
144126
fn test_translate_helper_array_1d_2d() {
145-
assert_eq!(
146-
r2cdecl("[u8; 10]", "test_make_cdecl").unwrap(),
147-
"uint8_t test_make_cdecl[10]",
148-
);
149-
assert_eq!(
150-
r2cdecl("[[u8; 64]; 32]", "test_make_cdecl").unwrap(),
151-
"uint8_t test_make_cdecl[32][64]"
152-
);
127+
assert_r2cdecl("[u8; 10]", "uint8_t foo[10]");
128+
assert_r2cdecl("[[u8; 64]; 32]", "uint8_t foo[32][64]");
153129
}

ctest-next/src/translator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl<'a> Translator<'a> {
178178
}
179179

180180
/// Translate a Rust function pointer type to its C equivalent.
181-
fn translate_bare_fn(
181+
pub(crate) fn translate_bare_fn(
182182
&self,
183183
function: &syn::TypeBareFn,
184184
) -> Result<cdecl::CTy, TranslationError> {

ctest-next/templates/test.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <{{ header }}>
1414
{%- endfor +%}
1515

16+
typedef void (*ctest_void_func)(void);
17+
1618
{%- for const_cstr in ctx.const_cstr_tests +%}
1719

1820
static char *ctest_const_{{ const_cstr.id }}_val_static = {{ const_cstr.c_val }};
@@ -118,3 +120,20 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
118120
#ifdef _MSC_VER
119121
# pragma warning(default:4365)
120122
#endif
123+
124+
#ifdef _MSC_VER
125+
// Disable function pointer type conversion warnings on MSVC.
126+
// The conversion may fail only if we call that function, however we only check its address.
127+
# pragma warning(disable:4191)
128+
#endif
129+
130+
{%- for item in ctx.foreign_fn_tests +%}
131+
132+
ctest_void_func ctest_foreign_fn__{{ item.id }}(void) {
133+
return (ctest_void_func){{ item.c_val }};
134+
}
135+
{%- endfor +%}
136+
137+
#ifdef _MSC_VER
138+
# pragma warning(default:4191)
139+
#endif

ctest-next/templates/test.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,19 @@ mod generated_tests {
314314
}
315315
}
316316
{%- endfor +%}
317+
318+
{%- for item in ctx.foreign_fn_tests +%}
319+
320+
/// Check if the Rust and C side function pointers point to the same underlying function.
321+
pub fn {{ item.test_name }}() {
322+
extern "C" {
323+
fn ctest_foreign_fn__{{ item.id }}() -> unsafe extern "C" fn();
324+
}
325+
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() } as u64;
326+
let expected = {{ item.id }} as u64;
327+
check_same(actual, expected, "{{ item.id }} function pointer");
328+
}
329+
{%- endfor +%}
317330
}
318331

319332
use generated_tests::*;

0 commit comments

Comments
 (0)