Skip to content

Commit 2921463

Browse files
committed
ctest: allow different code generation according to edition
1 parent d58c1f4 commit 2921463

File tree

9 files changed

+819
-13
lines changed

9 files changed

+819
-13
lines changed

ctest/src/generator.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ use crate::{
3636
get_build_target,
3737
};
3838

39+
/// The default Rust edition used to generate the code.
40+
const DEFAULT_EDITION: u32 = 2021;
41+
3942
/// A function that takes a mappable input and returns its mapping as `Some`, otherwise
4043
/// use the default name if `None`.
4144
type MappedName = Box<dyn Fn(&MapInput) -> Option<String>>;
@@ -73,6 +76,8 @@ pub struct TestGenerator {
7376
pub(crate) skip_roundtrip: Option<SkipTest>,
7477
pub(crate) skip_signededness: Option<SkipTest>,
7578
pub(crate) skip_fn_ptrcheck: Option<SkipTest>,
79+
/// The Rust edition to generate code against.
80+
pub(crate) edition: Option<u32>,
7681
}
7782

7883
/// An error that occurs when generating the test files.
@@ -239,6 +244,21 @@ impl TestGenerator {
239244
self
240245
}
241246

247+
/// Set the Rust edition that the code is generated for.
248+
///
249+
/// # Examples
250+
///
251+
/// ```no_run
252+
/// use ctest::TestGenerator;
253+
///
254+
/// let mut cfg = TestGenerator::new();
255+
/// cfg.edition(2024);
256+
/// ```
257+
pub fn edition(&mut self, e: u32) -> &mut Self {
258+
self.edition = Some(e);
259+
self
260+
}
261+
242262
/// Configures the output directory of the generated Rust and C code.
243263
///
244264
/// # Examples
@@ -1048,6 +1068,7 @@ impl TestGenerator {
10481068
};
10491069

10501070
let mut rust_file = RustTestTemplate::new(&ffi_items, self)?
1071+
.edition(self.edition.unwrap_or(DEFAULT_EDITION))
10511072
.render()
10521073
.map_err(GenerationError::RustTemplateRender)?;
10531074
ensure_trailing_newline(&mut rust_file);

ctest/src/template.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use crate::{
2727
#[template(path = "test.rs")]
2828
pub(crate) struct RustTestTemplate {
2929
pub template: TestTemplate,
30+
pub extern_keyword: BoxStr,
3031
}
3132

3233
impl RustTestTemplate {
@@ -36,8 +37,24 @@ impl RustTestTemplate {
3637
) -> Result<Self, TranslationError> {
3738
Ok(Self {
3839
template: TestTemplate::new(ffi_items, generator)?,
40+
extern_keyword: "extern".into(),
3941
})
4042
}
43+
44+
/// Modify the generated template such that it supports edition 2024.
45+
pub(crate) fn edition(&mut self, edition: u32) -> &Self {
46+
match edition {
47+
2021 => {
48+
self.extern_keyword = "extern".into();
49+
}
50+
2024 => {
51+
// For now we only use this to convert extern to unsafe extern.
52+
self.extern_keyword = "unsafe extern".into();
53+
}
54+
_ => panic!("unsupported or invalid edition: {edition}"),
55+
}
56+
self
57+
}
4158
}
4259

4360
/// Represents the C side of the generated testing suite.

ctest/templates/test.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{#- ↑ Doesn't apply here, this is the template! +#}
33

44
{%- let ctx = self.template +%}
5+
{%- let ctest_extern = self.extern_keyword +%}
56

67
/// As this file is sometimes built using rustc, crate level attributes
78
/// are not allowed at the top-level, so we hack around this by keeping it
@@ -73,7 +74,7 @@ mod generated_tests {
7374
// Test that the string constant is the same in both Rust and C.
7475
// While fat pointers can't be translated, we instead use * const c_char.
7576
pub fn {{ const_cstr.test_name }}() {
76-
extern "C" {
77+
{{ ctest_extern }} "C" {
7778
fn ctest_const_cstr__{{ const_cstr.id }}() -> *const c_char;
7879
}
7980

@@ -100,7 +101,7 @@ mod generated_tests {
100101
// This performs a byte by byte comparison of the constant value.
101102
pub fn {{ constant.test_name }}() {
102103
type T = {{ constant.rust_ty }};
103-
extern "C" {
104+
{{ ctest_extern }} "C" {
104105
fn ctest_const__{{ constant.id }}() -> *const T;
105106
}
106107

@@ -125,7 +126,7 @@ mod generated_tests {
125126

126127
/// Compare the size and alignment of the type in Rust and C, making sure they are the same.
127128
pub fn {{ item.test_name }}() {
128-
extern "C" {
129+
{{ ctest_extern }} "C" {
129130
fn ctest_size_of__{{ item.id }}() -> u64;
130131
fn ctest_align_of__{{ item.id }}() -> u64;
131132
}
@@ -149,7 +150,7 @@ mod generated_tests {
149150
/// this would result in a value larger than zero. For signed types, this results in a value
150151
/// smaller than 0.
151152
pub fn {{ alias.test_name }}() {
152-
extern "C" {
153+
{{ ctest_extern }} "C" {
153154
fn ctest_signededness_of__{{ alias.id }}() -> u32;
154155
}
155156
let all_ones = !(0 as {{ alias.id }});
@@ -164,7 +165,7 @@ mod generated_tests {
164165

165166
/// Make sure that the offset and size of a field in a struct/union is the same.
166167
pub fn {{ item.test_name }}() {
167-
extern "C" {
168+
{{ ctest_extern }} "C" {
168169
fn ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() -> u64;
169170
fn ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() -> u64;
170171
}
@@ -193,7 +194,7 @@ mod generated_tests {
193194

194195
/// Tests if the pointer to the field is the same in Rust and C.
195196
pub fn {{ item.test_name }}() {
196-
extern "C" {
197+
{{ ctest_extern }} "C" {
197198
fn ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(a: *const {{ item.id }}) -> *mut u8;
198199
}
199200

@@ -264,7 +265,7 @@ mod generated_tests {
264265
/// correct place. For this test to be sound, `T` must be valid for any bitpattern.
265266
pub fn {{ item.test_name }}() {
266267
type U = {{ item.id }};
267-
extern "C" {
268+
{{ ctest_extern }} "C" {
268269
fn ctest_size_of__{{ item.id }}() -> u64;
269270
fn ctest_roundtrip__{{ item.id }}(
270271
input: MaybeUninit<U>, is_padding_byte: *const bool, value_bytes: *mut u8
@@ -337,7 +338,7 @@ mod generated_tests {
337338

338339
/// Check if the Rust and C side function pointers point to the same underlying function.
339340
pub fn {{ item.test_name }}() {
340-
extern "C" {
341+
{{ ctest_extern }} "C" {
341342
fn ctest_foreign_fn__{{ item.id }}() -> unsafe extern "C" fn();
342343
}
343344
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() } as u64;
@@ -350,7 +351,7 @@ mod generated_tests {
350351

351352
// Tests if the pointer to the static variable matches in both Rust and C.
352353
pub fn {{ static_.test_name }}() {
353-
extern "C" {
354+
{{ ctest_extern }} "C" {
354355
fn ctest_static__{{ static_.id }}() -> *const {{ static_.rust_ty }};
355356
}
356357
let actual = (&raw const {{ static_.id }}).addr();

ctest/tests/basic.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ fn test_entrypoint_macro() {
150150
check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path);
151151
}
152152

153+
/// Test if generated code for macro.rs passes requirements for edition 2024.
154+
#[test]
155+
fn test_edition_2024_macro() {
156+
let include_path = PathBuf::from("tests/input");
157+
let crate_path = include_path.join("macro.rs");
158+
let library_path = "macro.out.edition-2024.a";
159+
160+
let (mut gen_, out_dir) = default_generator(1, None).unwrap();
161+
gen_.edition(2024)
162+
.header_with_defines("macro.h", vec!["SUPPRESS_ERROR"]);
163+
164+
check_entrypoint(&mut gen_, out_dir, crate_path, library_path, include_path);
165+
}
166+
153167
/// Test if a file with invalid syntax fails to generate tests.
154168
#[test]
155169
fn test_entrypoint_invalid_syntax() {

ctest/tests/input/hierarchy.out.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ mod generated_tests {
112112
/// this would result in a value larger than zero. For signed types, this results in a value
113113
/// smaller than 0.
114114
pub fn ctest_signededness_in6_addr() {
115-
extern "C" {
115+
extern "C" {
116116
fn ctest_signededness_of__in6_addr() -> u32;
117117
}
118118
let all_ones = !(0 as in6_addr);
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/* This file was autogenerated by ctest; do not modify directly */
2+
3+
#include <stdbool.h>
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
#include <stdio.h>
7+
8+
#define SUPPRESS_ERROR
9+
#include <macro.h>
10+
#undef SUPPRESS_ERROR
11+
12+
#if defined(__cplusplus)
13+
#define CTEST_ALIGNOF(T) alignof(T)
14+
#define CTEST_EXTERN extern "C"
15+
#else
16+
#define CTEST_ALIGNOF(T) _Alignof(T)
17+
#define CTEST_EXTERN
18+
#endif
19+
20+
typedef void (*ctest_void_func)(void);
21+
22+
// Return the size of a type.
23+
CTEST_EXTERN uint64_t ctest_size_of__VecU8(void) { return sizeof(struct VecU8); }
24+
25+
// Return the alignment of a type.
26+
CTEST_EXTERN uint64_t ctest_align_of__VecU8(void) { return CTEST_ALIGNOF(struct VecU8); }
27+
28+
// Return the size of a type.
29+
CTEST_EXTERN uint64_t ctest_size_of__VecU16(void) { return sizeof(struct VecU16); }
30+
31+
// Return the alignment of a type.
32+
CTEST_EXTERN uint64_t ctest_align_of__VecU16(void) { return CTEST_ALIGNOF(struct VecU16); }
33+
34+
// Return the offset of a struct/union field.
35+
CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) {
36+
return offsetof(struct VecU8, x);
37+
}
38+
39+
// Return the size of a struct/union field.
40+
CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) {
41+
return sizeof(((struct VecU8){}).x);
42+
}
43+
44+
// Return the offset of a struct/union field.
45+
CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) {
46+
return offsetof(struct VecU8, y);
47+
}
48+
49+
// Return the size of a struct/union field.
50+
CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) {
51+
return sizeof(((struct VecU8){}).y);
52+
}
53+
54+
// Return the offset of a struct/union field.
55+
CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) {
56+
return offsetof(struct VecU16, x);
57+
}
58+
59+
// Return the size of a struct/union field.
60+
CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) {
61+
return sizeof(((struct VecU16){}).x);
62+
}
63+
64+
// Return the offset of a struct/union field.
65+
CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) {
66+
return offsetof(struct VecU16, y);
67+
}
68+
69+
// Return the size of a struct/union field.
70+
CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) {
71+
return sizeof(((struct VecU16){}).y);
72+
}
73+
74+
// Return a pointer to a struct/union field.
75+
// This field can have a normal data type, or it could be a function pointer or an array, which
76+
// have different syntax. A typedef is used for convenience, but the syntax must be precomputed.
77+
typedef uint8_t *ctest_field_ty__VecU8__x;
78+
CTEST_EXTERN ctest_field_ty__VecU8__x
79+
ctest_field_ptr__VecU8__x(struct VecU8 *b) {
80+
return &b->x;
81+
}
82+
83+
// Return a pointer to a struct/union field.
84+
// This field can have a normal data type, or it could be a function pointer or an array, which
85+
// have different syntax. A typedef is used for convenience, but the syntax must be precomputed.
86+
typedef uint8_t *ctest_field_ty__VecU8__y;
87+
CTEST_EXTERN ctest_field_ty__VecU8__y
88+
ctest_field_ptr__VecU8__y(struct VecU8 *b) {
89+
return &b->y;
90+
}
91+
92+
// Return a pointer to a struct/union field.
93+
// This field can have a normal data type, or it could be a function pointer or an array, which
94+
// have different syntax. A typedef is used for convenience, but the syntax must be precomputed.
95+
typedef uint16_t *ctest_field_ty__VecU16__x;
96+
CTEST_EXTERN ctest_field_ty__VecU16__x
97+
ctest_field_ptr__VecU16__x(struct VecU16 *b) {
98+
return &b->x;
99+
}
100+
101+
// Return a pointer to a struct/union field.
102+
// This field can have a normal data type, or it could be a function pointer or an array, which
103+
// have different syntax. A typedef is used for convenience, but the syntax must be precomputed.
104+
typedef uint16_t *ctest_field_ty__VecU16__y;
105+
CTEST_EXTERN ctest_field_ty__VecU16__y
106+
ctest_field_ptr__VecU16__y(struct VecU16 *b) {
107+
return &b->y;
108+
}
109+
110+
#ifdef _MSC_VER
111+
// Disable signed/unsigned conversion warnings on MSVC.
112+
// These trigger even if the conversion is explicit.
113+
#pragma warning(disable:4365)
114+
#endif
115+
116+
#ifdef __GNUC__
117+
// GCC emits a warning with `-Wextra` if we return a typedef to a type marked `volatile`.
118+
#pragma GCC diagnostic push
119+
#pragma GCC diagnostic ignored "-Wignored-qualifiers"
120+
#endif
121+
122+
// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
123+
// remains unchanged.
124+
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
125+
CTEST_EXTERN struct VecU8 ctest_roundtrip__VecU8(
126+
struct VecU8 value,
127+
const uint8_t is_padding_byte[sizeof(struct VecU8)],
128+
uint8_t value_bytes[sizeof(struct VecU8)]
129+
) {
130+
int size = (int)sizeof(struct VecU8);
131+
// Mark `p` as volatile so that the C compiler does not optimize away the pattern we create.
132+
// Otherwise the Rust side would not be able to see it.
133+
volatile uint8_t* p = (volatile uint8_t*)&value;
134+
int i = 0;
135+
for (i = 0; i < size; ++i) {
136+
// We skip padding bytes in both Rust and C because writing to it is undefined.
137+
// Instead we just make sure the the placement of the padding bytes remains the same.
138+
if (is_padding_byte[i]) { continue; }
139+
value_bytes[i] = p[i];
140+
// After we check that the pattern remained unchanged from Rust to C, we invert the pattern
141+
// and send it back to Rust to make sure that it remains unchanged from C to Rust.
142+
uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256);
143+
d = d == 0 ? 42: d;
144+
p[i] = d;
145+
}
146+
return value;
147+
}
148+
149+
// Tests whether the struct/union/alias `x` when passed by value to C and back to Rust
150+
// remains unchanged.
151+
// It checks if the size is the same as well as if the padding bytes are all in the correct place.
152+
CTEST_EXTERN struct VecU16 ctest_roundtrip__VecU16(
153+
struct VecU16 value,
154+
const uint8_t is_padding_byte[sizeof(struct VecU16)],
155+
uint8_t value_bytes[sizeof(struct VecU16)]
156+
) {
157+
int size = (int)sizeof(struct VecU16);
158+
// Mark `p` as volatile so that the C compiler does not optimize away the pattern we create.
159+
// Otherwise the Rust side would not be able to see it.
160+
volatile uint8_t* p = (volatile uint8_t*)&value;
161+
int i = 0;
162+
for (i = 0; i < size; ++i) {
163+
// We skip padding bytes in both Rust and C because writing to it is undefined.
164+
// Instead we just make sure the the placement of the padding bytes remains the same.
165+
if (is_padding_byte[i]) { continue; }
166+
value_bytes[i] = p[i];
167+
// After we check that the pattern remained unchanged from Rust to C, we invert the pattern
168+
// and send it back to Rust to make sure that it remains unchanged from C to Rust.
169+
uint8_t d = (uint8_t)(255) - (uint8_t)(i % 256);
170+
d = d == 0 ? 42: d;
171+
p[i] = d;
172+
}
173+
return value;
174+
}
175+
176+
#ifdef __GNUC__
177+
// Pop allow for `-Wignored-qualifiers`
178+
#pragma GCC diagnostic pop
179+
#endif
180+
181+
#ifdef _MSC_VER
182+
// Pop allow for 4365
183+
#pragma warning(default:4365)
184+
#endif
185+
186+
#ifdef _MSC_VER
187+
// Disable function pointer type conversion warnings on MSVC.
188+
// The conversion may fail only if we call that function, however we only check its address.
189+
#pragma warning(disable:4191)
190+
#endif
191+
192+
#ifdef _MSC_VER
193+
// Pop allow for 4191
194+
#pragma warning(default:4191)
195+
#endif

0 commit comments

Comments
 (0)