Skip to content

Commit 6df4113

Browse files
committed
ctest: add foreign static test
1 parent cf82fdf commit 6df4113

File tree

14 files changed

+202
-49
lines changed

14 files changed

+202
-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 signededness_tests: Vec<TestSignededness>,
@@ -76,6 +76,7 @@ impl TestTemplate {
7676
template.populate_signededness_tests(&helper)?;
7777
template.populate_field_size_offset_tests(&helper)?;
7878
template.populate_field_ptr_tests(&helper)?;
79+
template.populate_foreign_static_tests(&helper)?;
7980

8081
Ok(template)
8182
}
@@ -325,6 +326,48 @@ impl TestTemplate {
325326

326327
Ok(())
327328
}
329+
330+
/// Populates tests for foreign statics, keeping track of the names of each test.
331+
fn populate_foreign_static_tests(
332+
&mut self,
333+
helper: &TranslateHelper,
334+
) -> Result<(), TranslationError> {
335+
for static_ in helper.ffi_items.foreign_statics() {
336+
let is_volatile = helper
337+
.generator
338+
.volatile_items
339+
.iter()
340+
.any(|is_volatile| is_volatile(VolatileItemKind::Static(static_.clone())));
341+
let c_ty = helper.c_type(static_)?.into_boxed_str();
342+
343+
let item = TestForeignStatic {
344+
test_name: static_test_ident(static_.ident()),
345+
id: static_.ident().into(),
346+
c_val: helper.c_ident(static_).into_boxed_str(),
347+
rust_ty: static_.ty.to_token_stream().to_string().into_boxed_str(),
348+
mutable: if static_.mutable { "mut" } else { "const" }.into(),
349+
volatile_keyword: { if is_volatile { "volatile " } else { "" }.into() },
350+
c_mutable: if c_ty.contains("const") || static_.mutable {
351+
""
352+
} else {
353+
"const "
354+
}
355+
.into(),
356+
return_type: helper
357+
.make_cdecl(
358+
&format!("ctest_static_ty__{}", static_.ident()),
359+
&static_.ty,
360+
)?
361+
.replacen("**ctest_", "*ctest_", 1)
362+
.into(),
363+
};
364+
365+
self.foreign_static_tests.push(item.clone());
366+
self.test_idents.push(item.test_name);
367+
}
368+
369+
Ok(())
370+
}
328371
}
329372

330373
/* Many test structures have the following fields:
@@ -393,6 +436,18 @@ pub(crate) struct TestFieldSizeOffset {
393436
pub c_ty: BoxStr,
394437
}
395438

439+
#[derive(Clone, Debug)]
440+
pub(crate) struct TestForeignStatic {
441+
pub test_name: BoxStr,
442+
pub id: BoxStr,
443+
pub c_val: BoxStr,
444+
pub rust_ty: BoxStr,
445+
pub mutable: BoxStr,
446+
pub c_mutable: BoxStr,
447+
pub return_type: BoxStr,
448+
pub volatile_keyword: BoxStr,
449+
}
450+
396451
fn signededness_test_ident(ident: &str) -> BoxStr {
397452
format!("ctest_signededness_{ident}").into()
398453
}
@@ -417,6 +472,10 @@ fn field_size_offset_test_ident(ident: &str, field_ident: &str) -> BoxStr {
417472
format!("ctest_field_size_offset_{ident}_{field_ident}").into()
418473
}
419474

475+
fn static_test_ident(ident: &str) -> BoxStr {
476+
format!("ctest_static_{ident}").into()
477+
}
478+
420479
/// Wrap methods that depend on both ffi items and the generator.
421480
pub(crate) struct TranslateHelper<'a> {
422481
ffi_items: &'a FfiItems,
@@ -536,26 +595,11 @@ impl<'a> TranslateHelper<'a> {
536595
syn::Type::Path(p) => {
537596
let last = p.path.segments.last().unwrap();
538597
let ident = last.ident.to_string();
539-
if ident != "Option" {
598+
if ident == "Option" {
599+
self.make_cdecl(name, &extract_ffi_safe_option(p)?)
600+
} else {
540601
let mapped_type = self.basic_c_type(ty)?;
541-
return Ok(format!("{mapped_type}* {name}"));
542-
}
543-
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
544-
if let syn::GenericArgument::Type(inner_ty) = args.args.first().unwrap() {
545-
// Option<T> is ONLY ffi-safe if it contains a function pointer, or a reference.
546-
match inner_ty {
547-
syn::Type::Reference(_) | syn::Type::BareFn(_) => {
548-
return self.make_cdecl(name, inner_ty);
549-
}
550-
_ => {
551-
return Err(TranslationError::new(
552-
TranslationErrorKind::NotFfiCompatible,
553-
&p.to_token_stream().to_string(),
554-
inner_ty.span(),
555-
));
556-
}
557-
}
558-
}
602+
Ok(format!("{mapped_type}* {name}"))
559603
}
560604
}
561605
syn::Type::BareFn(f) => {
@@ -579,29 +623,28 @@ impl<'a> TranslateHelper<'a> {
579623
args.push("void".to_string());
580624
}
581625

582-
return Ok(format!("{} ({}**{})({})", ret, abi, name, args.join(", ")));
626+
Ok(format!("{} ({}**{})({})", ret, abi, name, args.join(", ")))
583627
}
584-
// Arrays are supported only up to 2D arrays.
585-
syn::Type::Array(outer) => {
586-
let elem = outer.elem.deref();
587-
let len_outer = translate_expr(&outer.len);
588-
589-
if let syn::Type::Array(inner) = elem {
590-
let inner_type = self.basic_c_type(inner.elem.deref())?;
591-
let len_inner = translate_expr(&inner.len);
592-
return Ok(format!("{inner_type} (*{name})[{len_outer}][{len_inner}]",));
593-
} else {
594-
let elem_type = self.basic_c_type(elem)?;
595-
return Ok(format!("{elem_type} (*{name})[{len_outer}]"));
628+
syn::Type::Array(_) => {
629+
let mut lens = Vec::new();
630+
let mut elem = ty;
631+
632+
while let syn::Type::Array(array) = elem {
633+
lens.push(translate_expr(&array.len));
634+
elem = array.elem.deref();
596635
}
636+
637+
let elem_type = self.basic_c_type(elem)?;
638+
let dims = lens
639+
.into_iter()
640+
.map(|len| format!("[{len}]"))
641+
.collect::<String>();
642+
Ok(format!("{elem_type} (*{name}){dims}"))
597643
}
598644
_ => {
599645
let elem_type = self.basic_c_type(ty)?;
600-
return Ok(format!("{elem_type} *{name}"));
646+
Ok(format!("{elem_type} *{name}"))
601647
}
602648
}
603-
604-
let mapped_type = self.basic_c_type(ty)?;
605-
Ok(format!("{mapped_type}* {name}"))
606649
}
607650
}

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
@@ -78,3 +78,19 @@ ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_ty }} *b) {
7878
return &b->{{ item.c_field }};
7979
}
8080
{%- endfor +%}
81+
82+
{%- for static_ in ctx.foreign_static_tests +%}
83+
84+
// Return a pointer to the static variable content.
85+
{%- if static_.rust_ty.contains("extern") +%}
86+
typedef {{ static_.return_type }};
87+
ctest_static_ty__{{ static_.id }} ctest_static__{{ static_.id }}(void) {
88+
return {{ static_.c_val }};
89+
}
90+
{%- else +%}
91+
typedef {{ static_.volatile_keyword }}{{ static_.c_mutable }}{{ static_.return_type }};
92+
ctest_static_ty__{{ static_.id }} ctest_static__{{ static_.id }}(void) {
93+
return &{{ static_.c_val }};
94+
}
95+
{%- endif +%}
96+
{%- endfor +%}

ctest-next/templates/test.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
mod generated_tests {
1010
#![allow(non_snake_case)]
1111
#![deny(improper_ctypes_definitions)]
12-
use std::ffi::{CStr, c_char};
12+
#[allow(unused_imports)]
13+
use std::ffi::{CStr, c_char, c_uint};
1314
use std::fmt::{Debug, LowerHex};
1415
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1516
#[allow(unused_imports)]
@@ -190,6 +191,37 @@ mod generated_tests {
190191
"field type {{ item.field.ident() }} of {{ item.id }}");
191192
}
192193
{%- endfor +%}
194+
195+
{%- for static_ in ctx.foreign_static_tests +%}
196+
197+
// Tests if the pointer to the static variable matches in both Rust and C.
198+
pub fn {{ static_.test_name }}() {
199+
{%- if static_.rust_ty.contains("extern") +%}
200+
extern "C" {
201+
fn ctest_static__{{ static_.id }}() -> {{ static_.rust_ty }};
202+
}
203+
unsafe {
204+
// We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447
205+
let actual = std::mem::transmute_copy::<_, usize>(&*std::ptr::addr_of!({{ static_.id }}));
206+
let expected = {
207+
let val = ctest_static__{{ static_.id }}();
208+
std::mem::transmute_copy::<_, usize>(&val)
209+
};
210+
check_same(actual, expected, "{{ static_.id }} static");
211+
}
212+
{%- else +%}
213+
extern "C" {
214+
fn ctest_static__{{ static_.id }}() -> *{{ static_.mutable }} {{ static_.rust_ty }};
215+
}
216+
unsafe {
217+
// We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447
218+
check_same(std::ptr::addr_of!({{ static_.id }}) as usize,
219+
ctest_static__{{ static_.id }}() as usize,
220+
"{{ static_.id }} static");
221+
}
222+
{%- endif +%}
223+
}
224+
{%- endfor +%}
193225
}
194226

195227
use generated_tests::*;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,9 @@ uint32_t ctest_signededness_of__in6_addr(void) {
2727
in6_addr all_ones = (in6_addr) -1;
2828
return all_ones < 0;
2929
}
30+
31+
// Return a pointer to the static variable content.
32+
typedef const in6_addr* ctest_static_ty__in6addr_any;
33+
ctest_static_ty__in6addr_any ctest_static__in6addr_any(void) {
34+
return &in6addr_any;
35+
}

0 commit comments

Comments
 (0)