Skip to content

Commit 4455b42

Browse files
committed
ctest: add foreign static test
1 parent e634372 commit 4455b42

File tree

14 files changed

+197
-49
lines changed

14 files changed

+197
-49
lines changed

ctest-next/src/ast/static_variable.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct Static {
1212
pub(crate) ident: BoxStr,
1313
pub(crate) link_name: Option<BoxStr>,
1414
pub(crate) ty: syn::Type,
15+
pub(crate) mutable: bool,
1516
}
1617

1718
impl Static {

ctest-next/src/ffi_items.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ impl FfiItems {
6363
}
6464

6565
/// Return a list of all foreign statics found mapped by their ABI.
66-
#[cfg_attr(not(test), expect(unused))]
6766
pub(crate) fn foreign_statics(&self) -> &Vec<Static> {
6867
&self.foreign_statics
6968
}
@@ -159,13 +158,15 @@ fn visit_foreign_item_static(table: &mut FfiItems, i: &syn::ForeignItemStatic, a
159158
let ident = i.ident.to_string().into_boxed_str();
160159
let ty = i.ty.deref().clone();
161160
let link_name = extract_single_link_name(&i.attrs);
161+
let mutable = matches!(i.mutability, syn::StaticMutability::Mut(_));
162162

163163
table.foreign_statics.push(Static {
164164
public,
165165
abi,
166166
ident,
167167
link_name,
168168
ty,
169+
mutable,
169170
});
170171
}
171172

ctest-next/src/template.rs

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use std::ops::Deref;
22

33
use askama::Template;
44
use quote::ToTokens;
5-
use syn::spanned::Spanned;
65

76
use crate::ffi_items::FfiItems;
8-
use crate::translator::{TranslationErrorKind, Translator, translate_abi, translate_expr};
7+
use crate::translator::{Translator, extract_ffi_safe_option, translate_abi, translate_expr};
98
use crate::{BoxStr, Field, MapInput, Result, TestGenerator, TranslationError, VolatileItemKind};
109

1110
/// Represents the Rust side of the generated testing suite.
@@ -49,6 +48,7 @@ impl CTestTemplate {
4948
/// Stores all information necessary for generation of tests for all items.
5049
#[derive(Clone, Debug, Default)]
5150
pub(crate) struct TestTemplate {
51+
pub foreign_static_tests: Vec<TestForeignStatic>,
5252
pub field_ptr_tests: Vec<TestFieldPtr>,
5353
pub field_size_offset_tests: Vec<TestFieldSizeOffset>,
5454
pub roundtrip_tests: Vec<TestRoundtrip>,
@@ -78,6 +78,7 @@ impl TestTemplate {
7878
template.populate_field_size_offset_tests(&helper)?;
7979
template.populate_field_ptr_tests(&helper)?;
8080
template.populate_roundtrip_tests(&helper)?;
81+
template.populate_foreign_static_tests(&helper)?;
8182

8283
Ok(template)
8384
}
@@ -376,6 +377,48 @@ impl TestTemplate {
376377

377378
Ok(())
378379
}
380+
381+
/// Populates tests for foreign statics, keeping track of the names of each test.
382+
fn populate_foreign_static_tests(
383+
&mut self,
384+
helper: &TranslateHelper,
385+
) -> Result<(), TranslationError> {
386+
for static_ in helper.ffi_items.foreign_statics() {
387+
let is_volatile = helper
388+
.generator
389+
.volatile_items
390+
.iter()
391+
.any(|is_volatile| is_volatile(VolatileItemKind::Static(static_.clone())));
392+
let c_ty = helper.c_type(static_)?.into_boxed_str();
393+
394+
let item = TestForeignStatic {
395+
test_name: static_test_ident(static_.ident()),
396+
id: static_.ident().into(),
397+
c_val: helper.c_ident(static_).into_boxed_str(),
398+
rust_ty: static_.ty.to_token_stream().to_string().into_boxed_str(),
399+
mutable: if static_.mutable { "mut" } else { "const" }.into(),
400+
volatile_keyword: { if is_volatile { "volatile " } else { "" }.into() },
401+
c_mutable: if c_ty.contains("const") || static_.mutable {
402+
""
403+
} else {
404+
"const "
405+
}
406+
.into(),
407+
return_type: helper
408+
.make_cdecl(
409+
&format!("ctest_static_ty__{}", static_.ident()),
410+
&static_.ty,
411+
)?
412+
.replacen("**ctest_", "*ctest_", 1)
413+
.into(),
414+
};
415+
416+
self.foreign_static_tests.push(item.clone());
417+
self.test_idents.push(item.test_name);
418+
}
419+
420+
Ok(())
421+
}
379422
}
380423

381424
/* Many test structures have the following fields:
@@ -453,6 +496,18 @@ pub(crate) struct TestRoundtrip {
453496
pub is_alias: bool,
454497
}
455498

499+
#[derive(Clone, Debug)]
500+
pub(crate) struct TestForeignStatic {
501+
pub test_name: BoxStr,
502+
pub id: BoxStr,
503+
pub c_val: BoxStr,
504+
pub rust_ty: BoxStr,
505+
pub mutable: BoxStr,
506+
pub c_mutable: BoxStr,
507+
pub return_type: BoxStr,
508+
pub volatile_keyword: BoxStr,
509+
}
510+
456511
fn signededness_test_ident(ident: &str) -> BoxStr {
457512
format!("ctest_signededness_{ident}").into()
458513
}
@@ -481,6 +536,10 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr {
481536
format!("ctest_roundtrip_{ident}").into()
482537
}
483538

539+
fn static_test_ident(ident: &str) -> BoxStr {
540+
format!("ctest_static_{ident}").into()
541+
}
542+
484543
/// Wrap methods that depend on both ffi items and the generator.
485544
pub(crate) struct TranslateHelper<'a> {
486545
ffi_items: &'a FfiItems,
@@ -600,26 +659,11 @@ impl<'a> TranslateHelper<'a> {
600659
syn::Type::Path(p) => {
601660
let last = p.path.segments.last().unwrap();
602661
let ident = last.ident.to_string();
603-
if ident != "Option" {
662+
if ident == "Option" {
663+
self.make_cdecl(name, &extract_ffi_safe_option(p)?)
664+
} else {
604665
let mapped_type = self.basic_c_type(ty)?;
605-
return Ok(format!("{mapped_type}* {name}"));
606-
}
607-
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
608-
if let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap() {
609-
// Option<T> is ONLY ffi-safe if it contains a function pointer, or a reference.
610-
match inner_ty {
611-
syn::Type::Reference(_) | syn::Type::BareFn(_) => {
612-
return self.make_cdecl(name, inner_ty);
613-
}
614-
_ => {
615-
return Err(TranslationError::new(
616-
TranslationErrorKind::NotFfiCompatible,
617-
&p.to_token_stream().to_string(),
618-
inner_ty.span(),
619-
));
620-
}
621-
}
622-
}
666+
Ok(format!("{mapped_type}* {name}"))
623667
}
624668
}
625669
syn::Type::BareFn(f) => {
@@ -643,29 +687,28 @@ impl<'a> TranslateHelper<'a> {
643687
args.push("void".to_string());
644688
}
645689

646-
return Ok(format!("{} ({}**{})({})", ret, abi, name, args.join(", ")));
690+
Ok(format!("{} ({}**{})({})", ret, abi, name, args.join(", ")))
647691
}
648-
// Arrays are supported only up to 2D arrays.
649-
syn::Type::Array(outer) => {
650-
let elem = outer.elem.deref();
651-
let len_outer = translate_expr(&outer.len);
652-
653-
if let syn::Type::Array(inner) = elem {
654-
let inner_type = self.basic_c_type(inner.elem.deref())?;
655-
let len_inner = translate_expr(&inner.len);
656-
return Ok(format!("{inner_type} (*{name})[{len_outer}][{len_inner}]",));
657-
} else {
658-
let elem_type = self.basic_c_type(elem)?;
659-
return Ok(format!("{elem_type} (*{name})[{len_outer}]"));
692+
syn::Type::Array(_) => {
693+
let mut lens = Vec::new();
694+
let mut elem = ty;
695+
696+
while let syn::Type::Array(array) = elem {
697+
lens.push(translate_expr(&array.len));
698+
elem = array.elem.deref();
660699
}
700+
701+
let elem_type = self.basic_c_type(elem)?;
702+
let dims = lens
703+
.into_iter()
704+
.map(|len| format!("[{len}]"))
705+
.collect::<String>();
706+
Ok(format!("{elem_type} (*{name}){dims}"))
661707
}
662708
_ => {
663709
let elem_type = self.basic_c_type(ty)?;
664-
return Ok(format!("{elem_type} *{name}"));
710+
Ok(format!("{elem_type} *{name}"))
665711
}
666712
}
667-
668-
let mapped_type = self.basic_c_type(ty)?;
669-
Ok(format!("{mapped_type}* {name}"))
670713
}
671714
}

ctest-next/src/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ fn test_translation_type_bare_fn() {
111111
fn test_translation_type_array() {
112112
assert_eq!(
113113
ty("[&u8; 2 + 2]").unwrap(),
114-
"const uint8_t*[2 + 2]".to_string()
114+
"const uint8_t* [2 + 2]".to_string()
115115
);
116116
}
117117

@@ -129,7 +129,7 @@ fn test_translate_helper_function_pointer() {
129129
);
130130
assert_eq!(
131131
cdecl("Option<extern \"stdcall\" fn(*const c_char, [u32; 16]) -> u8>").unwrap(),
132-
"uint8_t (__stdcall **test_make_cdecl)(char const*, uint32_t[16])"
132+
"uint8_t (__stdcall **test_make_cdecl)(char const*, uint32_t [16])"
133133
);
134134
}
135135

ctest-next/src/translator.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,17 @@ impl Translator {
264264
/// Translate a Rust path into its C equivalent.
265265
fn translate_path(&self, path: &syn::TypePath) -> Result<String, TranslationError> {
266266
let last = path.path.segments.last().unwrap();
267-
Ok(self.translate_primitive_type(&last.ident))
267+
if last.ident == "Option" {
268+
self.translate_type(&extract_ffi_safe_option(path)?)
269+
} else {
270+
Ok(self.translate_primitive_type(&last.ident))
271+
}
268272
}
269273

270274
/// Translate a Rust array declaration into its C equivalent.
271275
fn translate_array(&self, array: &syn::TypeArray) -> Result<String, TranslationError> {
272276
Ok(format!(
273-
"{}[{}]",
277+
"{} [{}]",
274278
self.translate_type(array.elem.deref())?,
275279
translate_expr(&array.len)
276280
))
@@ -373,3 +377,34 @@ pub(crate) fn translate_abi(abi: &syn::Abi, target: &str) -> &'static str {
373377
Some(a) => panic!("unknown ABI: {a}"),
374378
}
375379
}
380+
381+
pub(crate) fn extract_ffi_safe_option(p: &syn::TypePath) -> Result<syn::Type, TranslationError> {
382+
let last = p.path.segments.last().unwrap();
383+
let ident = last.ident.to_string();
384+
385+
if ident != "Option" {
386+
return Err(TranslationError::new(
387+
TranslationErrorKind::NotFfiCompatible,
388+
&p.to_token_stream().to_string(),
389+
p.span(),
390+
));
391+
}
392+
393+
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
394+
if let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap() {
395+
// Option<T> is ONLY ffi-safe if it contains a function pointer, or a reference.
396+
match inner_ty {
397+
syn::Type::Reference(_) | syn::Type::BareFn(_) => {
398+
return Ok(inner_ty.clone());
399+
}
400+
_ => (),
401+
}
402+
}
403+
}
404+
405+
Err(TranslationError::new(
406+
TranslationErrorKind::NotFfiCompatible,
407+
&p.to_token_stream().to_string(),
408+
p.span(),
409+
))
410+
}

ctest-next/templates/test.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,19 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
118118
#ifdef _MSC_VER
119119
# pragma warning(default:4365)
120120
#endif
121+
122+
{%- for static_ in ctx.foreign_static_tests +%}
123+
124+
// Return a pointer to the static variable content.
125+
{%- if static_.rust_ty.contains("extern") +%}
126+
typedef {{ static_.return_type }};
127+
ctest_static_ty__{{ static_.id }} ctest_static__{{ static_.id }}(void) {
128+
return {{ static_.c_val }};
129+
}
130+
{%- else +%}
131+
typedef {{ static_.volatile_keyword }}{{ static_.c_mutable }}{{ static_.return_type }};
132+
ctest_static_ty__{{ static_.id }} ctest_static__{{ static_.id }}(void) {
133+
return &{{ static_.c_val }};
134+
}
135+
{%- endif +%}
136+
{%- endfor +%}

ctest-next/templates/test.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod generated_tests {
1010
#![allow(non_snake_case)]
1111
#![deny(improper_ctypes_definitions)]
1212
#[allow(unused_imports)]
13-
use std::ffi::{CStr, c_int, c_char};
13+
use std::ffi::{CStr, c_int, c_char, c_uint};
1414
use std::fmt::{Debug, LowerHex};
1515
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1616
#[allow(unused_imports)]
@@ -314,6 +314,37 @@ mod generated_tests {
314314
}
315315
}
316316
{%- endfor +%}
317+
318+
{%- for static_ in ctx.foreign_static_tests +%}
319+
320+
// Tests if the pointer to the static variable matches in both Rust and C.
321+
pub fn {{ static_.test_name }}() {
322+
{%- if static_.rust_ty.contains("extern") +%}
323+
extern "C" {
324+
fn ctest_static__{{ static_.id }}() -> {{ static_.rust_ty }};
325+
}
326+
unsafe {
327+
// We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447
328+
let actual = std::mem::transmute_copy::<_, usize>(&*std::ptr::addr_of!({{ static_.id }}));
329+
let expected = {
330+
let val = ctest_static__{{ static_.id }}();
331+
std::mem::transmute_copy::<_, usize>(&val)
332+
};
333+
check_same(actual, expected, "{{ static_.id }} static");
334+
}
335+
{%- else +%}
336+
extern "C" {
337+
fn ctest_static__{{ static_.id }}() -> *{{ static_.mutable }} {{ static_.rust_ty }};
338+
}
339+
unsafe {
340+
// We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447
341+
check_same(std::ptr::addr_of!({{ static_.id }}) as usize,
342+
ctest_static__{{ static_.id }}() as usize,
343+
"{{ static_.id }} static");
344+
}
345+
{%- endif +%}
346+
}
347+
{%- endfor +%}
317348
}
318349

319350
use generated_tests::*;

ctest-next/tests/input/hierarchy.out.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ in6_addr ctest_roundtrip__in6_addr(
6464
#ifdef _MSC_VER
6565
# pragma warning(default:4365)
6666
#endif
67+
68+
// Return a pointer to the static variable content.
69+
typedef const in6_addr* ctest_static_ty__in6addr_any;
70+
ctest_static_ty__in6addr_any ctest_static__in6addr_any(void) {
71+
return &in6addr_any;
72+
}

0 commit comments

Comments
 (0)