Skip to content

Commit 494e5e5

Browse files
committed
ctest: add foreign static test
1 parent 7f3d696 commit 494e5e5

File tree

13 files changed

+173
-8
lines changed

13 files changed

+173
-8
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/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use ast::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union};
2424
pub use generator::TestGenerator;
2525
pub use macro_expansion::expand;
2626
pub use runner::{__compile_test, __run_test, generate_test};
27+
use syn::visit::Visit;
2728
pub use translator::TranslationError;
2829

2930
/// A possible error that can be encountered in our library.
@@ -107,3 +108,21 @@ impl<'a> From<&'a Union> for MapInput<'a> {
107108
MapInput::Union(u)
108109
}
109110
}
111+
112+
/* A simple helper to find any nested bare fn types, such as `Option<unsafe extern "C" fn()>` */
113+
114+
struct BareFnFinder {
115+
found: bool,
116+
}
117+
118+
impl<'ast> Visit<'ast> for BareFnFinder {
119+
fn visit_type_bare_fn(&mut self, _: &'ast syn::TypeBareFn) {
120+
self.found = true;
121+
}
122+
}
123+
124+
pub(crate) fn contains_bare_fn(ty: &syn::Type) -> bool {
125+
let mut visitor = BareFnFinder { found: false };
126+
visitor.visit_type(ty);
127+
visitor.found
128+
}

ctest-next/src/template.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ 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,
10+
contains_bare_fn,
1011
};
1112

1213
/// Represents the Rust side of the generated testing suite.
@@ -50,6 +51,7 @@ impl CTestTemplate {
5051
/// Stores all information necessary for generation of tests for all items.
5152
#[derive(Clone, Debug, Default)]
5253
pub(crate) struct TestTemplate {
54+
pub foreign_static_tests: Vec<TestForeignStatic>,
5355
pub field_ptr_tests: Vec<TestFieldPtr>,
5456
pub field_size_offset_tests: Vec<TestFieldSizeOffset>,
5557
pub roundtrip_tests: Vec<TestRoundtrip>,
@@ -75,6 +77,7 @@ impl TestTemplate {
7577
template.populate_field_size_offset_tests(&helper)?;
7678
template.populate_field_ptr_tests(&helper)?;
7779
template.populate_roundtrip_tests(&helper)?;
80+
template.populate_foreign_static_tests(&helper)?;
7881

7982
Ok(template)
8083
}
@@ -380,6 +383,66 @@ impl TestTemplate {
380383

381384
Ok(())
382385
}
386+
387+
/// Populates tests for foreign statics, keeping track of the names of each test.
388+
fn populate_foreign_static_tests(
389+
&mut self,
390+
helper: &TranslateHelper,
391+
) -> Result<(), TranslationError> {
392+
for static_ in helper.ffi_items.foreign_statics() {
393+
let is_volatile = helper
394+
.generator
395+
.volatile_items
396+
.iter()
397+
.any(|is_volatile| is_volatile(VolatileItemKind::Static(static_.clone())));
398+
399+
let c_ty = helper.c_type(static_)?.into_boxed_str();
400+
let rust_ty = static_.ty.to_token_stream().to_string().into_boxed_str();
401+
let is_bare_fn = contains_bare_fn(&static_.ty);
402+
403+
let return_type = if is_bare_fn {
404+
helper.translator.translate_type(&static_.ty)?
405+
} else {
406+
cdecl::ptr(
407+
helper.translator.translate_type(&static_.ty)?,
408+
cdecl::Constness::Mut,
409+
)
410+
};
411+
let return_type = cdecl::cdecl(
412+
&return_type,
413+
format!("ctest_static_ty__{}", static_.ident()),
414+
)
415+
.map_err(|_| {
416+
TranslationError::new(
417+
TranslationErrorKind::InvalidReturn,
418+
&static_.ty.to_token_stream().to_string(),
419+
static_.ty.span(),
420+
)
421+
})?;
422+
423+
let item = TestForeignStatic {
424+
test_name: static_test_ident(static_.ident()),
425+
id: static_.ident().into(),
426+
c_val: helper.c_ident(static_).into_boxed_str(),
427+
rust_ty,
428+
is_bare_fn,
429+
mutable: if static_.mutable { "mut" } else { "const" }.into(),
430+
volatile_keyword: { if is_volatile { "volatile " } else { "" }.into() },
431+
c_mutable: if c_ty.contains("const") || static_.mutable {
432+
""
433+
} else {
434+
"const "
435+
}
436+
.into(),
437+
return_type: return_type.into(),
438+
};
439+
440+
self.foreign_static_tests.push(item.clone());
441+
self.test_idents.push(item.test_name);
442+
}
443+
444+
Ok(())
445+
}
383446
}
384447

385448
/* Many test structures have the following fields:
@@ -457,6 +520,19 @@ pub(crate) struct TestRoundtrip {
457520
pub is_alias: bool,
458521
}
459522

523+
#[derive(Clone, Debug)]
524+
pub(crate) struct TestForeignStatic {
525+
pub test_name: BoxStr,
526+
pub id: BoxStr,
527+
pub c_val: BoxStr,
528+
pub rust_ty: BoxStr,
529+
pub is_bare_fn: bool,
530+
pub mutable: BoxStr,
531+
pub c_mutable: BoxStr,
532+
pub return_type: BoxStr,
533+
pub volatile_keyword: BoxStr,
534+
}
535+
460536
fn signededness_test_ident(ident: &str) -> BoxStr {
461537
format!("ctest_signededness_{ident}").into()
462538
}
@@ -485,6 +561,10 @@ fn roundtrip_test_ident(ident: &str) -> BoxStr {
485561
format!("ctest_roundtrip_{ident}").into()
486562
}
487563

564+
fn static_test_ident(ident: &str) -> BoxStr {
565+
format!("ctest_static_{ident}").into()
566+
}
567+
488568
/// Wrap methods that depend on both ffi items and the generator.
489569
pub(crate) struct TranslateHelper<'a> {
490570
ffi_items: &'a FfiItems,

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: 29 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,34 @@ 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_.is_bare_fn +%}
323+
extern "C" {
324+
fn ctest_static__{{ static_.id }}() -> {{ static_.rust_ty }};
325+
}
326+
let actual = unsafe {
327+
std::mem::transmute::<_, *const ()>(*(&raw const {{ static_.id }})).addr()
328+
};
329+
let expected = unsafe {
330+
std::mem::transmute::<_, *const ()>(ctest_static__{{ static_.id }}()).addr()
331+
};
332+
check_same(actual, expected, "{{ static_.id }} static");
333+
{%- else +%}
334+
extern "C" {
335+
fn ctest_static__{{ static_.id }}() -> *{{ static_.mutable }} {{ static_.rust_ty }};
336+
}
337+
let actual = (&raw const {{ static_.id }}).addr();
338+
let expected = unsafe {
339+
ctest_static__{{ static_.id }}().addr()
340+
};
341+
check_same(actual, expected, "{{ static_.id }} static");
342+
{%- endif +%}
343+
}
344+
{%- endfor +%}
317345
}
318346

319347
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+
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod generated_tests {
77
#![allow(non_snake_case)]
88
#![deny(improper_ctypes_definitions)]
99
#[allow(unused_imports)]
10-
use std::ffi::{CStr, c_int, c_char};
10+
use std::ffi::{CStr, c_int, c_char, c_uint};
1111
use std::fmt::{Debug, LowerHex};
1212
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1313
#[allow(unused_imports)]
@@ -213,6 +213,18 @@ mod generated_tests {
213213
}
214214
}
215215
}
216+
217+
// Tests if the pointer to the static variable matches in both Rust and C.
218+
pub fn ctest_static_in6addr_any() {
219+
extern "C" {
220+
fn ctest_static__in6addr_any() -> *const in6_addr;
221+
}
222+
let actual = (&raw const in6addr_any).addr();
223+
let expected = unsafe {
224+
ctest_static__in6addr_any().addr()
225+
};
226+
check_same(actual, expected, "in6addr_any static");
227+
}
216228
}
217229

218230
use generated_tests::*;
@@ -236,4 +248,5 @@ fn run_all() {
236248
ctest_size_align_in6_addr();
237249
ctest_signededness_in6_addr();
238250
ctest_roundtrip_in6_addr();
251+
ctest_static_in6addr_any();
239252
}

ctest-next/tests/input/hierarchy/foo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ pub const ON: bool = true;
66
unsafe extern "C" {
77
fn malloc(size: usize) -> *mut c_void;
88

9-
static in6addr_any: in6_addr;
9+
pub static in6addr_any: in6_addr;
1010
}

ctest-next/tests/input/macro.out.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod generated_tests {
77
#![allow(non_snake_case)]
88
#![deny(improper_ctypes_definitions)]
99
#[allow(unused_imports)]
10-
use std::ffi::{CStr, c_int, c_char};
10+
use std::ffi::{CStr, c_int, c_char, c_uint};
1111
use std::fmt::{Debug, LowerHex};
1212
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1313
#[allow(unused_imports)]

0 commit comments

Comments
 (0)