From 870db1d07e8c547efa44515b84b14ea725a4cd58 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Tue, 23 Jun 2026 08:00:45 -0700 Subject: [PATCH 1/3] tests: make cleanup_attr trace statics thread-local to fix race The six tests share trace_buf/trace_n but libtest runs them in parallel. Marking the statics _Thread_local (faithfully transpiled to #[thread_local]) gives each test thread its own trace, so they no longer race. --- tests/unit/cleanup_attr/src/cleanup.c | 6 ++++-- tests/unit/cleanup_attr/src/test_cleanup.rs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/cleanup_attr/src/cleanup.c b/tests/unit/cleanup_attr/src/cleanup.c index 9e0643547c..e572929b1d 100644 --- a/tests/unit/cleanup_attr/src/cleanup.c +++ b/tests/unit/cleanup_attr/src/cleanup.c @@ -1,5 +1,7 @@ -static int trace_buf[16]; -static int trace_n; +/* Thread-local so the parallel test harness gives each test its own trace + * instead of racing on shared state (both here and in the transpiled Rust). */ +static _Thread_local int trace_buf[16]; +static _Thread_local int trace_n; static void reset(void) { trace_n = 0; diff --git a/tests/unit/cleanup_attr/src/test_cleanup.rs b/tests/unit/cleanup_attr/src/test_cleanup.rs index fb4d154568..286015d776 100644 --- a/tests/unit/cleanup_attr/src/test_cleanup.rs +++ b/tests/unit/cleanup_attr/src/test_cleanup.rs @@ -1,3 +1,4 @@ +//! feature_thread_local use crate::cleanup::{ rust_run_early_return, rust_run_goto, rust_run_multiple, rust_run_nested, rust_run_single, rust_run_typedef, From 509cea4d686165559410d63a828661567cfab980 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Tue, 23 Jun 2026 12:14:52 -0700 Subject: [PATCH 2/3] transpile: don't emit bitfield struct initializers as const macros --- c2rust-transpile/src/translator/macros.rs | 8 +++++ c2rust-transpile/src/translator/mod.rs | 39 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/c2rust-transpile/src/translator/macros.rs b/c2rust-transpile/src/translator/macros.rs index 2140801852..99645b2fb7 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -115,6 +115,14 @@ impl<'c> Translation<'c> { /// Determine if we're able to convert this const macro expansion. fn can_convert_const_macro_expansion(&self, expr_id: CExprId) -> TranslationResult<()> { + // Bitfield struct initializers are lowered to non-`const` setter calls, + // so they can't be emitted as a `const` item. + if self.expr_initializes_bitfield(expr_id) { + Err(format_err!( + "macro initializes a bitfield, which is not const" + ))?; + } + match self.tcfg.translate_const_macros { TranslateMacros::None => Err(format_err!("translate_const_macros is None"))?, TranslateMacros::Conservative => { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index f03409c5ed..881e1037d4 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -1768,6 +1768,45 @@ impl<'c> Translation<'c> { false } + /// Does this expression initialize a struct that contains a bitfield? + /// + /// Bitfield initialization is lowered to non-`const` setter method calls + /// Such an initializer cannot appear in a `const` or `static` item. + /// Const-like macros that expand to one must be left inlined at use sites. + pub(crate) fn expr_initializes_bitfield(&self, expr_id: CExprId) -> bool { + for i in DFExpr::new(&self.ast_context, expr_id.into()) { + let expr_id = match i { + SomeId::Expr(expr_id) => expr_id, + _ => continue, + }; + if let CExprKind::InitList(qtype, ..) = + self.ast_context.index_unwrap_parens(expr_id).kind + { + if let CTypeKind::Struct(decl_id) = self.ast_context.resolve_type(qtype.ctype).kind + { + if let CDeclKind::Struct { + fields: Some(fields), + .. + } = &self.ast_context[decl_id].kind + { + if fields.iter().any(|field_id| { + matches!( + self.ast_context[*field_id].kind, + CDeclKind::Field { + bitfield_width: Some(_), + .. + } + ) + }) { + return true; + } + } + } + } + } + false + } + fn add_static_initializer_to_section( &self, ctx: ExprContext, From c06ca49755fe1d0fecf9a8733466633032038812 Mon Sep 17 00:00:00 2001 From: Per Larsen Date: Tue, 23 Jun 2026 20:27:30 -0700 Subject: [PATCH 3/3] transpile: add snapshot test for bitfield const macros --- c2rust-transpile/tests/snapshots.rs | 7 ++++ .../tests/snapshots/const_macro_bitfield.c | 14 ++++++++ ...e@const_macro_bitfield.c.2021.clang15.snap | 35 ++++++++++++++++++ ...e@const_macro_bitfield.c.2024.clang15.snap | 36 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 c2rust-transpile/tests/snapshots/const_macro_bitfield.c create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2021.clang15.snap create mode 100644 c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2024.clang15.snap diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index ca9727c3eb..df95f3719f 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -354,6 +354,13 @@ fn test_compound_literals() { transpile("compound_literals.c").run(); } +#[test] +fn test_const_macro_bitfield() { + transpile("const_macro_bitfield.c") + .expect_compile_error(true) + .run(); +} + #[test] fn test_empty_init() { transpile("empty_init.c").run(); diff --git a/c2rust-transpile/tests/snapshots/const_macro_bitfield.c b/c2rust-transpile/tests/snapshots/const_macro_bitfield.c new file mode 100644 index 0000000000..e6cff7e617 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/const_macro_bitfield.c @@ -0,0 +1,14 @@ +// A const-like macro initializing a bitfield struct must be inlined at each +// use site, not emitted as a `const` (bitfield setters aren't `const`). + +struct indexwriter { + unsigned int should_write : 1; + int fd; +}; + +#define INDEXWRITER_INIT { 0, 0 } + +struct indexwriter make_writer(void) { + struct indexwriter writer = INDEXWRITER_INIT; + return writer; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2021.clang15.snap new file mode 100644 index 0000000000..60705c4395 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2021.clang15.snap @@ -0,0 +1,35 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/const_macro_bitfield.2021.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] +#[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)] +#[repr(C)] +pub struct indexwriter { + #[bitfield(name = "should_write", ty = "::core::ffi::c_uint", bits = "0..=0")] + pub should_write: [u8; 1], + #[bitfield(padding)] + pub c2rust_padding: [u8; 3], + pub fd: ::core::ffi::c_int, +} +#[no_mangle] +pub unsafe extern "C" fn make_writer() -> indexwriter { + let mut writer: indexwriter = { + let mut init = indexwriter { + should_write: [0; 1], + c2rust_padding: [0; 3], + fd: 0 as ::core::ffi::c_int, + }; + init.set_should_write(0 as ::core::ffi::c_uint); + init + }; + return writer; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2024.clang15.snap new file mode 100644 index 0000000000..c63fba54f5 --- /dev/null +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@const_macro_bitfield.c.2024.clang15.snap @@ -0,0 +1,36 @@ +--- +source: c2rust-transpile/tests/snapshots.rs +expression: cat tests/snapshots/const_macro_bitfield.2024.clang15.rs +--- +#![allow( + clippy::missing_safety_doc, + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unsafe_op_in_unsafe_fn, + unused_assignments, + unused_mut +)] +#[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)] +#[repr(C)] +pub struct indexwriter { + #[bitfield(name = "should_write", ty = "::core::ffi::c_uint", bits = "0..=0")] + pub should_write: [u8; 1], + #[bitfield(padding)] + pub c2rust_padding: [u8; 3], + pub fd: ::core::ffi::c_int, +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn make_writer() -> indexwriter { + let mut writer: indexwriter = { + let mut init = indexwriter { + should_write: [0; 1], + c2rust_padding: [0; 3], + fd: 0 as ::core::ffi::c_int, + }; + init.set_should_write(0 as ::core::ffi::c_uint); + init + }; + return writer; +}