Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions c2rust-ast-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ categories.workspace = true
[dependencies]
itertools = "0.10.0"
proc-macro2 = { version = "1.0", features = ["span-locations"]}
quote = "1"
syn = { version = "2.0", features = ["full", "extra-traits", "printing", "clone-impls"]}
11 changes: 8 additions & 3 deletions c2rust-ast-builder/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ where
intersperse(items, comma).collect()
}

fn str_to_ident(s: &str, span: Span) -> Ident {
// NB: We cannot use `Ident::new` here because it does not support raw identifiers.
quote::format_ident!("{}", s, span = span)
}

pub trait Make<T> {
fn make(self, mk: &Builder) -> T;
}
Expand All @@ -192,19 +197,19 @@ impl<T> Make<T> for T {

impl Make<Ident> for &str {
fn make(self, mk: &Builder) -> Ident {
Ident::new(self, mk.span)
str_to_ident(self, mk.span)
}
}

impl Make<Ident> for String {
fn make(self, mk: &Builder) -> Ident {
Ident::new(&self, mk.span)
str_to_ident(self.as_str(), mk.span)
}
}

impl Make<Ident> for &String {
fn make(self, mk: &Builder) -> Ident {
Ident::new(self, mk.span)
str_to_ident(self.as_str(), mk.span)
}
}

Expand Down
9 changes: 7 additions & 2 deletions c2rust-bitfields-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ fn bitfield_struct_impl(struct_item: ItemStruct) -> Result<TokenStream, Error> {
let field_types_setter_arg = &field_types;
let method_names: Vec<_> = bitfields
.iter()
.map(|field| Ident::new(&field.name, Span::call_site()))
.map(|field| {
// We cannot use `Ident::new` here because it does not support raw identifiers
quote::format_ident!("{}", field.name, span = Span::call_site())
})
.collect();
let field_names: Vec<_> = bitfields.iter().map(|field| &field.field_name).collect();
let field_names_setters = &field_names;
Expand All @@ -196,7 +199,9 @@ fn bitfield_struct_impl(struct_item: ItemStruct) -> Result<TokenStream, Error> {
.iter()
.map(|field_ident| {
let span = Span::call_site();
let setter_name = &format!("set_{}", field_ident);
let raw_ident = &field_ident.to_string();
let ident = raw_ident.strip_prefix("r#").unwrap_or(raw_ident);
let setter_name = &format!("set_{}", ident);

Ident::new(setter_name, span)
})
Expand Down
38 changes: 38 additions & 0 deletions c2rust-bitfields/c2rust-tests/test_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,41 @@ fn test_bool_bits() {
assert!(bool_bits.y());
assert!(bool_bits.z());
}

#[repr(C)]
#[derive(BitfieldStruct)]
struct ReservedNames {
#[bitfield(name = "r#use", ty = "bool", bits = "0..=0")]
#[bitfield(name = "r#as", ty = "bool", bits = "1..=1")]
use_as: [u8; 1],
}

#[test]
fn test_reserved_names() {
let mut reserved_names = ReservedNames {
use_as: [u8::max_value(); 1],
};

assert!(reserved_names.r#use());
assert!(reserved_names.r#as());

reserved_names.set_as(false);

assert!(reserved_names.r#use());
assert!(!reserved_names.r#as());

reserved_names.set_use(false);

assert!(!reserved_names.r#use());
assert!(!reserved_names.r#as());

reserved_names.set_use(true);

assert!(reserved_names.r#use());
assert!(!reserved_names.r#as());

reserved_names.set_as(true);

assert!(reserved_names.r#use());
assert!(reserved_names.r#as());
}
48 changes: 47 additions & 1 deletion c2rust-transpile/src/renamer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ pub const RUST_KEYWORDS: &[&str] = &[
"yield",
];

/// Keywords that cannot be expressed as a raw identifier [0] because they can be used as path
/// segments. The list of path segment keywords can be found here [1]. For discussion of this
/// topic see [2].
/// [0] https://doc.rust-lang.org/edition-guide/rust-2018/module-system/raw-identifiers.html
/// [1] https://github.com/rust-lang/rust/blob/e96c36b6f76833388c519561d145492d2c08db4e/compiler/rustc_span/src/symbol.rs#L2892
/// [2] https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094
#[rustfmt::skip]
const PATH_SEGMENT_KEYWORDS: [&str; 4] = [
"super",
"self",
"Self",
"crate",
];

const PRELUDE_TYPE_NAMESPACE: &[&str] = &[
"Copy",
"Send",
Expand Down Expand Up @@ -224,7 +238,8 @@ impl<T: Clone + Eq + Hash> Renamer<T> {
/// Assigns a name that doesn't collide with anything in the context of a particular
/// scope, defaulting to the current scope if None is provided
fn pick_name_in_scope(&mut self, basename: &str, scope: Option<usize>) -> String {
let mut target = basename.to_string();
let mut target =
Self::raw_identifier_if_reserved_name(basename).unwrap_or_else(|| basename.to_string());

for i in 0.. {
if self.is_target_used(&target) {
Expand Down Expand Up @@ -316,6 +331,14 @@ impl<T: Clone + Eq + Hash> Renamer<T> {
self.next_fresh += 1;
self.pick_name(&format!("c2rust_fresh{fresh}"))
}

fn raw_identifier_if_reserved_name(basename: &str) -> Option<String> {
if RUST_KEYWORDS.contains(&basename) && !PATH_SEGMENT_KEYWORDS.contains(&basename) {
Some(format!("r#{}", basename))
} else {
None
}
}
}

fn check_c2rust_name(basename: &str) {
Expand Down Expand Up @@ -370,4 +393,27 @@ mod tests {
renamer.drop_scope();
assert_eq!(renamer.get(&1), None);
}

#[test]
fn raw_identifier() {
let mut renamer = Renamer::new(&[RUST_KEYWORDS]);

// A reserved keyword that can be expressed as a raw identifier
let reserved1 = renamer.insert(1, "dyn").unwrap();
let reserved2 = renamer.get(&1).unwrap();
assert_eq!(reserved1, "r#dyn");
assert_eq!(reserved2, "r#dyn");

// A reserved keyword that is already bound and therefore does not need the "#r" prefix
let reserved1 = renamer.insert(2, "dyn").unwrap();
let reserved2 = renamer.get(&2).unwrap();
assert_eq!(reserved1, "dyn_0");
assert_eq!(reserved2, "dyn_0");

// A reserved that cannot be used as a raw identifier because it can be used in a path
let reserved1 = renamer.insert(3, "self").unwrap();
let reserved2 = renamer.get(&3).unwrap();
assert_eq!(reserved1, "self_0");
assert_eq!(reserved2, "self_0");
}
}
27 changes: 23 additions & 4 deletions c2rust-transpile/src/translator/structs_unions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ impl<'a> Translation<'a> {
field_name,
bytes,
attrs,
..
} => {
let ty = mk().array_ty(
mk().ident_ty("u8"),
Expand Down Expand Up @@ -527,7 +528,8 @@ impl<'a> Translation<'a> {

// Now we must use the bitfield methods to initialize bitfields
for (field_name, val) in bitfield_inits {
let field_name_setter = format!("set_{}", field_name);
let plain_field_name = field_name.strip_prefix("r#").unwrap_or(&field_name);
let field_name_setter = format!("set_{}", plain_field_name);
let struct_ident = mk().ident_expr("init");
is_unsafe |= val.is_unsafe();
let val = val
Expand Down Expand Up @@ -719,7 +721,8 @@ impl<'a> Translation<'a> {
.borrow()
.resolve_field_name(None, field_id)
.ok_or("Could not find bitfield name")?;
let setter_name = format!("set_{}", field_name);
let plain_field_name = field_name.strip_prefix("r#").unwrap_or(&field_name);
let setter_name = format!("set_{}", plain_field_name);
let lhs_expr_read = mk().method_call_expr(lhs_expr.clone(), field_name, Vec::new());
// Allow the value of this assignment to be used as the RHS of other assignments
let val = lhs_expr_read.clone();
Expand Down Expand Up @@ -904,11 +907,13 @@ impl<'a> Translation<'a> {
Some(FieldType::BitfieldGroup {
start_bit,
field_name: ref mut name,
ref mut single_bitfield,
ref mut bytes,
ref mut attrs,
}) => {
*single_bitfield = false;
name.push('_');
name.push_str(&field_name);
name.push_str(field_name.strip_prefix("r#").unwrap_or(&field_name));

let end_bit = platform_bit_offset + bitfield_width;

Expand Down Expand Up @@ -951,6 +956,7 @@ impl<'a> Translation<'a> {
last_bitfield_group = Some(FieldType::BitfieldGroup {
start_bit: platform_bit_offset,
field_name,
single_bitfield: true,
bytes,
attrs,
});
Expand All @@ -962,7 +968,19 @@ impl<'a> Translation<'a> {
}

// Find leftover bitfield group at end: it's all set
if let Some(field_group) = last_bitfield_group.take() {
if let Some(mut field_group) = last_bitfield_group.take() {
if let FieldType::BitfieldGroup {
ref mut field_name,
single_bitfield,
..
} = field_group
{
if !single_bitfield {
if let Some(s) = field_name.strip_prefix("r#") {
*field_name = s.to_string();
}
}
};
reorganized_fields.push(field_group);

// Packed structs can cause platform_byte_size < next_byte_pos
Expand Down Expand Up @@ -1085,6 +1103,7 @@ enum FieldType {
BitfieldGroup {
start_bit: u64,
field_name: String,
single_bitfield: bool,
bytes: u64,
attrs: Vec<(String, Box<Type>, String)>,
}, // 64 bytes
Expand Down
5 changes: 5 additions & 0 deletions c2rust-transpile/tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,11 @@ fn test_predefined() {
transpile("predefined.c").run();
}

#[test]
fn test_raw_keywords() {
transpile("raw_keywords.c").run();
}

#[test]
fn test_records() {
transpile("records.c").run();
Expand Down
5 changes: 5 additions & 0 deletions c2rust-transpile/tests/snapshots/bitfields.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ struct PacketHeader {
unsigned short length : 6;
};

struct SingleRawIdent {
unsigned char as : 3;
};

void test_bitfields(void) {
struct PacketHeader h = {0};
h.version = 5;
h.sequence = 513;
printf("version=%u sequence=%u\n", h.version, h.sequence);
struct SingleRawIdent s = {0};
}
30 changes: 30 additions & 0 deletions c2rust-transpile/tests/snapshots/raw_keywords.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
typedef int type;

enum as {
async,
await
};

struct dyn {
type false
};

union fn {
type where;
type final;
};

type super = 0;

void pub(type ref) {
}

void impl(type in) {
type trait = super;
enum as let = async;
struct dyn mod;
mod.false = 0;
union fn mut;
mut.where = 0;
pub(mut.where);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@ pub struct PacketHeader {
#[bitfield(padding)]
pub c2rust_padding: [u8; 1],
#[bitfield(name = "version", ty = "::core::ffi::c_uchar", bits = "0..=2")]
#[bitfield(name = "type_0", ty = "::core::ffi::c_uchar", bits = "3..=4")]
#[bitfield(name = "r#type", ty = "::core::ffi::c_uchar", bits = "3..=4")]
#[bitfield(name = "flags", ty = "::core::ffi::c_uchar", bits = "5..=7")]
#[bitfield(name = "sequence", ty = "::core::ffi::c_ushort", bits = "16..=25")]
#[bitfield(name = "length", ty = "::core::ffi::c_ushort", bits = "26..=31")]
pub version_type_0_flags_sequence_length: [u8; 3],
pub version_type_flags_sequence_length: [u8; 3],
}
#[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)]
#[repr(C)]
pub struct SingleRawIdent {
#[bitfield(name = "r#as", ty = "::core::ffi::c_uchar", bits = "0..=2")]
pub r#as: [u8; 1],
}
#[no_mangle]
pub unsafe extern "C" fn test_bitfields() {
let mut h: PacketHeader = {
let mut init = PacketHeader {
c2rust_padding: [0; 1],
version_type_0_flags_sequence_length: [0; 3],
version_type_flags_sequence_length: [0; 3],
};
init.set_version(0 as ::core::ffi::c_uchar);
init.set_type_0(0);
init.set_type(0);
init.set_flags(0);
init.set_sequence(0);
init.set_length(0);
Expand All @@ -47,4 +53,9 @@ pub unsafe extern "C" fn test_bitfields() {
h.version() as ::core::ffi::c_int,
h.sequence() as ::core::ffi::c_int,
);
let mut s: SingleRawIdent = {
let mut init = SingleRawIdent { r#as: [0; 1] };
init.set_as(0 as ::core::ffi::c_uchar);
init
};
}
Loading
Loading