From e7dc88914b267f91b6386727699d82940466863d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 2 Jun 2026 09:15:54 +0100 Subject: [PATCH 1/2] Avoid collision between tag and typedef identifier --- cpp2rust/converter/converter_lib.cpp | 25 ++++ cpp2rust/converter/converter_lib.h | 2 + cpp2rust/converter/mapper.cpp | 20 ++- .../cross_tu_tag_collision/CMakeLists.txt | 3 + tests/multi-file/cross_tu_tag_collision/a.c | 19 +++ tests/multi-file/cross_tu_tag_collision/b.c | 6 + .../out/refcount/cross_tu_tag_collision.rs | 57 ++++++++ .../out/unsafe/cross_tu_tag_collision.rs | 50 +++++++ tests/unit/out/refcount/anonymous_enum_c.rs | 22 +-- tests/unit/out/refcount/enum_int_interop_c.rs | 32 ++--- tests/unit/out/unsafe/anonymous_enum_c.rs | 22 +-- tests/unit/out/unsafe/enum_int_interop_c.rs | 30 ++-- .../out/unsafe/tag_vs_identifier_collision.rs | 134 ++++++++++++++++++ .../unit/out/unsafe/union_tagged_many_arms.rs | 32 ++--- tests/unit/out/unsafe/union_tagged_simple.rs | 22 +-- .../out/unsafe/union_tagged_struct_arms.rs | 24 ++-- .../out/unsafe/union_void_ptr_sized_deref.rs | 30 ++-- tests/unit/tag_vs_identifier_collision.c | 77 ++++++++++ 18 files changed, 496 insertions(+), 111 deletions(-) create mode 100644 tests/multi-file/cross_tu_tag_collision/CMakeLists.txt create mode 100644 tests/multi-file/cross_tu_tag_collision/a.c create mode 100644 tests/multi-file/cross_tu_tag_collision/b.c create mode 100644 tests/multi-file/cross_tu_tag_collision/out/refcount/cross_tu_tag_collision.rs create mode 100644 tests/multi-file/cross_tu_tag_collision/out/unsafe/cross_tu_tag_collision.rs create mode 100644 tests/unit/out/unsafe/tag_vs_identifier_collision.rs create mode 100644 tests/unit/tag_vs_identifier_collision.c diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 2d5731c9..2e394370 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -358,6 +358,31 @@ std::string GetID(const clang::Decl *decl) { return GetLocationID(decl) + GetParamSignature(decl); } +std::string DisambiguateAnonymousTag(const clang::TagDecl *tag) { + if (!tag) { + return ""; + } + // C++ does not allow collision between tags and typedef identifiers. + if (tag->getASTContext().getLangOpts().CPlusPlus) { + return ""; + } + // Tag has an identifier, nothing to disambiguate. + if (tag->getIdentifier()) { + return ""; + } + // The anonymous decl is named through a typedef; guards getName() below. + auto typedef_decl = tag->getTypedefNameForAnonDecl(); + if (!typedef_decl || !typedef_decl->getDeclName().isIdentifier()) { + return ""; + } + // Only disambiguate user-defined types. + if (tag->getASTContext().getSourceManager().isInSystemHeader( + tag->getLocation())) { + return ""; + } + return typedef_decl->getName().str() + "_" + tag->getKindName().str(); +} + static std::unordered_map type_mapping; static size_t GetDeclId(const clang::NamedDecl *decl, bool internal) { diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index 6a6a70ec..91ee3107 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -89,6 +89,8 @@ std::string GetID(const clang::Decl *decl); std::string GetNamedDeclAsString(const clang::NamedDecl *decl); +std::string DisambiguateAnonymousTag(const clang::TagDecl *tag); + const char *AccessSpecifierAsString(clang::AccessSpecifier spec); template llvm::SmallString<16> GetNumAsString(const T &num) { diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index fc8779fa..d332b180 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -736,6 +736,11 @@ std::string ToString(clang::QualType qual_type) { return ToString(clang::cast(tag)); } + if (auto renamed = DisambiguateAnonymousTag(qual_type->getAsTagDecl()); + !renamed.empty()) { + return renamed; + } + std::string type; llvm::raw_string_ostream os(type); normalizeQualType(qual_type).print(os, getPrintPolicy()); @@ -745,16 +750,23 @@ std::string ToString(clang::QualType qual_type) { std::string ToString(const clang::NamedDecl *decl) { if (auto *record = clang::dyn_cast(decl); record && !record->getIdentifier()) { + if (auto renamed = DisambiguateAnonymousTag(record); !renamed.empty()) { + return renamed; + } if (auto *typedef_decl = record->getTypedefNameForAnonDecl()) { return ToString(clang::cast(typedef_decl)); } return GetNamedDeclAsString(record); } - if (auto *enum_decl = clang::dyn_cast(decl); - enum_decl && !enum_decl->getIdentifier() && - !enum_decl->getTypedefNameForAnonDecl()) { - return std::format("anon_enum_{}", GetLineNumber(enum_decl)); + if (auto *enum_decl = clang::dyn_cast(decl)) { + if (auto renamed = DisambiguateAnonymousTag(enum_decl); !renamed.empty()) { + return renamed; + } + if (!enum_decl->getIdentifier() && + !enum_decl->getTypedefNameForAnonDecl()) { + return std::format("anon_enum_{}", GetLineNumber(enum_decl)); + } } std::string out; diff --git a/tests/multi-file/cross_tu_tag_collision/CMakeLists.txt b/tests/multi-file/cross_tu_tag_collision/CMakeLists.txt new file mode 100644 index 00000000..ab89791a --- /dev/null +++ b/tests/multi-file/cross_tu_tag_collision/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +project(cross_tu_tag_collision LANGUAGES C) +add_executable(app a.c b.c) diff --git a/tests/multi-file/cross_tu_tag_collision/a.c b/tests/multi-file/cross_tu_tag_collision/a.c new file mode 100644 index 00000000..1a5edbca --- /dev/null +++ b/tests/multi-file/cross_tu_tag_collision/a.c @@ -0,0 +1,19 @@ +#include + +struct widget { + int id; +}; + +int b_value(void); + +int a_value(void) { + struct widget w; + w.id = 11; + return w.id; +} + +int main(void) { + assert(a_value() == 11); + assert(b_value() == 2); + return 0; +} diff --git a/tests/multi-file/cross_tu_tag_collision/b.c b/tests/multi-file/cross_tu_tag_collision/b.c new file mode 100644 index 00000000..e49669a2 --- /dev/null +++ b/tests/multi-file/cross_tu_tag_collision/b.c @@ -0,0 +1,6 @@ +typedef enum { WIDGET_A, WIDGET_B, WIDGET_C } widget; + +int b_value(void) { + widget w = WIDGET_C; + return (int)w; +} diff --git a/tests/multi-file/cross_tu_tag_collision/out/refcount/cross_tu_tag_collision.rs b/tests/multi-file/cross_tu_tag_collision/out/refcount/cross_tu_tag_collision.rs new file mode 100644 index 00000000..5fd97a8f --- /dev/null +++ b/tests/multi-file/cross_tu_tag_collision/out/refcount/cross_tu_tag_collision.rs @@ -0,0 +1,57 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Default)] +pub struct widget { + pub id: Value, +} +impl ByteRepr for widget { + fn to_bytes(&self, buf: &mut [u8]) { + (*self.id.borrow()).to_bytes(&mut buf[0..4]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + id: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + } + } +} +pub fn a_value_0() -> i32 { + let w: Value = >::default(); + (*(*w.borrow()).id.borrow_mut()) = 11; + return (*(*w.borrow()).id.borrow()); +} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + assert!((((({ a_value_0() }) == 11) as i32) != 0)); + assert!((((({ b_value_1() }) == 2) as i32) != 0)); + return 0; +} +#[derive(Clone, Copy, PartialEq, Debug, Default)] +enum widget_enum { + #[default] + WIDGET_A = 0, + WIDGET_B = 1, + WIDGET_C = 2, +} +impl From for widget_enum { + fn from(n: i32) -> widget_enum { + match n { + 0 => widget_enum::WIDGET_A, + 1 => widget_enum::WIDGET_B, + 2 => widget_enum::WIDGET_C, + _ => panic!("invalid widget_enum value: {}", n), + } + } +} +libcc2rs::impl_enum_inc_dec!(widget_enum); +pub fn b_value_1() -> i32 { + let w: Value = Rc::new(RefCell::new(widget_enum::WIDGET_C)); + return ((*w.borrow()) as i32).clone(); +} diff --git a/tests/multi-file/cross_tu_tag_collision/out/unsafe/cross_tu_tag_collision.rs b/tests/multi-file/cross_tu_tag_collision/out/unsafe/cross_tu_tag_collision.rs new file mode 100644 index 00000000..cc671218 --- /dev/null +++ b/tests/multi-file/cross_tu_tag_collision/out/unsafe/cross_tu_tag_collision.rs @@ -0,0 +1,50 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct widget { + pub id: i32, +} +pub unsafe fn a_value_0() -> i32 { + let mut w: widget = ::default(); + w.id = 11; + return w.id; +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + assert!(((((unsafe { a_value_0() }) == (11)) as i32) != 0)); + assert!(((((unsafe { b_value_1() }) == (2)) as i32) != 0)); + return 0; +} +#[derive(Clone, Copy, PartialEq, Debug, Default)] +enum widget_enum { + #[default] + WIDGET_A = 0, + WIDGET_B = 1, + WIDGET_C = 2, +} +impl From for widget_enum { + fn from(n: i32) -> widget_enum { + match n { + 0 => widget_enum::WIDGET_A, + 1 => widget_enum::WIDGET_B, + 2 => widget_enum::WIDGET_C, + _ => panic!("invalid widget_enum value: {}", n), + } + } +} +libcc2rs::impl_enum_inc_dec!(widget_enum); +pub unsafe fn b_value_1() -> i32 { + let mut w: widget_enum = widget_enum::WIDGET_C; + return (w as i32); +} diff --git a/tests/unit/out/refcount/anonymous_enum_c.rs b/tests/unit/out/refcount/anonymous_enum_c.rs index 66829762..d4d31b73 100644 --- a/tests/unit/out/refcount/anonymous_enum_c.rs +++ b/tests/unit/out/refcount/anonymous_enum_c.rs @@ -53,21 +53,21 @@ impl ByteRepr for S { } } #[derive(Clone, Copy, PartialEq, Debug, Default)] -enum TdEnum { +enum TdEnum_enum { #[default] TD_A = 0, TD_B = 1, } -impl From for TdEnum { - fn from(n: i32) -> TdEnum { +impl From for TdEnum_enum { + fn from(n: i32) -> TdEnum_enum { match n { - 0 => TdEnum::TD_A, - 1 => TdEnum::TD_B, - _ => panic!("invalid TdEnum value: {}", n), + 0 => TdEnum_enum::TD_A, + 1 => TdEnum_enum::TD_B, + _ => panic!("invalid TdEnum_enum value: {}", n), } } } -libcc2rs::impl_enum_inc_dec!(TdEnum); +libcc2rs::impl_enum_inc_dec!(TdEnum_enum); #[derive(Clone, Copy, PartialEq, Debug, Default)] enum anon_enum_24 { #[default] @@ -113,10 +113,10 @@ fn main_0() -> i32 { assert!(((((anon_enum_3::FIRST_A as i32) != (anon_enum_3::FIRST_B as i32)) as i32) != 0)); assert!(((((anon_enum_11::SECOND_A as i32) != (anon_enum_11::SECOND_B as i32)) as i32) != 0)); assert!(((((anon_enum_31::THIRD_A as i32) != (anon_enum_31::THIRD_B as i32)) as i32) != 0)); - let td: Value = Rc::new(RefCell::new(TdEnum::TD_A)); - assert!((((((*td.borrow()) as u32) == ((TdEnum::TD_A as i32) as u32)) as i32) != 0)); - (*td.borrow_mut()) = TdEnum::TD_B; - assert!((((((*td.borrow()) as u32) == ((TdEnum::TD_B as i32) as u32)) as i32) != 0)); + let td: Value = Rc::new(RefCell::new(TdEnum_enum::TD_A)); + assert!((((((*td.borrow()) as u32) == ((TdEnum_enum::TD_A as i32) as u32)) as i32) != 0)); + (*td.borrow_mut()) = TdEnum_enum::TD_B; + assert!((((((*td.borrow()) as u32) == ((TdEnum_enum::TD_B as i32) as u32)) as i32) != 0)); let w: Value = >::default(); (*(*w.borrow()).field.borrow_mut()) = anon_enum_24::FIELD_A; assert!( diff --git a/tests/unit/out/refcount/enum_int_interop_c.rs b/tests/unit/out/refcount/enum_int_interop_c.rs index 3cefe5ff..721a1cd1 100644 --- a/tests/unit/out/refcount/enum_int_interop_c.rs +++ b/tests/unit/out/refcount/enum_int_interop_c.rs @@ -45,23 +45,23 @@ impl From for Option { } libcc2rs::impl_enum_inc_dec!(Option); #[derive(Clone, Copy, PartialEq, Debug, Default)] -enum Tag { +enum Tag_enum { #[default] TAG_ZERO = 0, TAG_ONE = 1, TAG_TWO = 2, } -impl From for Tag { - fn from(n: i32) -> Tag { +impl From for Tag_enum { + fn from(n: i32) -> Tag_enum { match n { - 0 => Tag::TAG_ZERO, - 1 => Tag::TAG_ONE, - 2 => Tag::TAG_TWO, - _ => panic!("invalid Tag value: {}", n), + 0 => Tag_enum::TAG_ZERO, + 1 => Tag_enum::TAG_ONE, + 2 => Tag_enum::TAG_TWO, + _ => panic!("invalid Tag_enum value: {}", n), } } } -libcc2rs::impl_enum_inc_dec!(Tag); +libcc2rs::impl_enum_inc_dec!(Tag_enum); #[derive(Default)] pub struct Entry { pub name: Value>, @@ -76,7 +76,7 @@ thread_local!( pub static global_opt_1: Value