From 31365946f7dfa8ad8e6324a6c3a6fcd1b28eb9cd Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 26 May 2026 11:40:09 +0100 Subject: [PATCH 1/7] Handle non-uniform C++ st_mtim.tv_sec with Rust st_mtime --- cpp2rust/converter/converter.cpp | 54 +++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 8616123..68ae511 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2556,6 +2556,45 @@ bool Converter::VisitMemberExpr(clang::MemberExpr *expr) { return false; } +// Returns the inner member and the replacement string. +static std::pair +replaceNonUniformLibcField(clang::MemberExpr *expr) { + // Example: ::struct stat::st_mtim::tv_sec -> ::libc::stat::st_mtime + struct Mapping { + const char *record; + const char *inner_field; + const char *leaf_field; + const char *replacement; + }; + static constexpr Mapping kFields[] = { + {"stat", "st_mtim", "tv_sec", "st_mtime"}, + {"stat", "st_mtim", "tv_nsec", "st_mtime_nsec"}, + }; + + auto getNamedIdentifierOrNull = [](auto *decl) { + return decl && decl->getDeclName().isIdentifier() ? decl : nullptr; + }; + + if (auto leaf = getNamedIdentifierOrNull(expr->getMemberDecl())) { + if (auto inner = clang::dyn_cast( + expr->getBase()->IgnoreParenImpCasts())) { + if (auto field = getNamedIdentifierOrNull( + clang::dyn_cast(inner->getMemberDecl()))) { + if (getNamedIdentifierOrNull(field->getParent())) { + for (const auto &m : kFields) { + if (field->getParent()->getName() == m.record && + field->getName() == m.inner_field && + leaf->getName() == m.leaf_field) { + return {inner, m.replacement}; + } + } + } + } + } + } + return {nullptr, ""}; +} + void Converter::ConvertMemberExpr(clang::MemberExpr *expr) { if (auto mapped = GetMappedAsString(expr); !mapped.empty()) { if (Mapper::ReturnsPointer(expr)) { @@ -2567,6 +2606,11 @@ void Converter::ConvertMemberExpr(clang::MemberExpr *expr) { } auto *member = expr->getMemberDecl(); + auto [inner, name_override] = replaceNonUniformLibcField(expr); + if (inner) { + expr = inner; + } + auto *base = expr->getBase(); bool base_is_this = clang::isa(base->IgnoreCasts()); PushExprKind push(*this, isLValue() ? ExprKind::LValue : ExprKind::RValue); @@ -2580,11 +2624,11 @@ void Converter::ConvertMemberExpr(clang::MemberExpr *expr) { method && IsOverloadedMethod(method)) { StrCat(token::kDot); StrCat(GetOverloadedFunctionName(method)); - } else { - if (member->getDeclName().isIdentifier()) { - StrCat(token::kDot); - StrCat(GetNamedDeclAsString(member)); - } + } else if (!name_override.empty()) { + StrCat(token::kDot, name_override); + } else if (member->getDeclName().isIdentifier()) { + StrCat(token::kDot); + StrCat(GetNamedDeclAsString(member)); } } From 48bc14bb49185668bb9163bca622a1a3205b9897 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 26 May 2026 12:22:49 +0100 Subject: [PATCH 2/7] Trigger CI From d286031db24d0f4776975713cccf8f546e2257a4 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Tue, 26 May 2026 20:18:34 +0100 Subject: [PATCH 3/7] Trigger CI From eb2a952c729d1e51b0f61a14e484556cd42957c4 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 14:23:32 +0100 Subject: [PATCH 4/7] Add sec and nsec tests for st_mtime --- tests/unit/out/unsafe/sys_stat.rs | 4 ++++ tests/unit/sys_stat.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/unit/out/unsafe/sys_stat.rs b/tests/unit/out/unsafe/sys_stat.rs index 1f04dab..3e649dc 100644 --- a/tests/unit/out/unsafe/sys_stat.rs +++ b/tests/unit/out/unsafe/sys_stat.rs @@ -21,6 +21,8 @@ pub unsafe fn test_stat_0() { let mut st: stat = unsafe { std::mem::zeroed::() }; assert!(((((libc::stat(path as *const i8, (&mut st as *mut stat))) == (0)) as i32) != 0)); assert!(((((st.st_size) == (5_i64)) as i32) != 0)); + assert!(((((st.st_mtime) > (0_i64)) as i32) != 0)); + assert!(((((st.st_mtime_nsec) > (0_i64)) as i32) != 0)); libc::unlink(path as *const i8); } pub unsafe fn test_fstat_1() { @@ -39,6 +41,8 @@ pub unsafe fn test_fstat_1() { let mut st: stat = unsafe { std::mem::zeroed::() }; assert!(((((libc::fstat(fd, (&mut st as *mut stat))) == (0)) as i32) != 0)); assert!(((((st.st_size) == (11_i64)) as i32) != 0)); + assert!(((((st.st_mtime) > (0_i64)) as i32) != 0)); + assert!(((((st.st_mtime_nsec) > (0_i64)) as i32) != 0)); assert!(((((libc::fclose(fp)) == (0)) as i32) != 0)); libc::unlink(path as *const i8); } diff --git a/tests/unit/sys_stat.c b/tests/unit/sys_stat.c index a7e86ef..b1d6a1c 100644 --- a/tests/unit/sys_stat.c +++ b/tests/unit/sys_stat.c @@ -14,6 +14,8 @@ static void test_stat(void) { struct stat st; assert(stat(path, &st) == 0); assert(st.st_size == 5); + assert(st.st_mtime > 0); + assert(st.st_mtim.tv_nsec > 0); unlink(path); } @@ -27,6 +29,8 @@ static void test_fstat(void) { struct stat st; assert(fstat(fd, &st) == 0); assert(st.st_size == 11); + assert(st.st_mtime > 0); + assert(st.st_mtim.tv_nsec > 0); assert(fclose(fp) == 0); unlink(path); } From 7f62c5ea6ec75c9da0d040f2b48201dd92c36be0 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 14:31:55 +0100 Subject: [PATCH 5/7] Delete non-portable st_mtim from tests --- cpp2rust/converter/converter.cpp | 1 - tests/unit/out/unsafe/sys_stat.rs | 2 -- tests/unit/sys_stat.c | 2 -- 3 files changed, 5 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 68ae511..b789532 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2568,7 +2568,6 @@ replaceNonUniformLibcField(clang::MemberExpr *expr) { }; static constexpr Mapping kFields[] = { {"stat", "st_mtim", "tv_sec", "st_mtime"}, - {"stat", "st_mtim", "tv_nsec", "st_mtime_nsec"}, }; auto getNamedIdentifierOrNull = [](auto *decl) { diff --git a/tests/unit/out/unsafe/sys_stat.rs b/tests/unit/out/unsafe/sys_stat.rs index 3e649dc..6094a5f 100644 --- a/tests/unit/out/unsafe/sys_stat.rs +++ b/tests/unit/out/unsafe/sys_stat.rs @@ -22,7 +22,6 @@ pub unsafe fn test_stat_0() { assert!(((((libc::stat(path as *const i8, (&mut st as *mut stat))) == (0)) as i32) != 0)); assert!(((((st.st_size) == (5_i64)) as i32) != 0)); assert!(((((st.st_mtime) > (0_i64)) as i32) != 0)); - assert!(((((st.st_mtime_nsec) > (0_i64)) as i32) != 0)); libc::unlink(path as *const i8); } pub unsafe fn test_fstat_1() { @@ -42,7 +41,6 @@ pub unsafe fn test_fstat_1() { assert!(((((libc::fstat(fd, (&mut st as *mut stat))) == (0)) as i32) != 0)); assert!(((((st.st_size) == (11_i64)) as i32) != 0)); assert!(((((st.st_mtime) > (0_i64)) as i32) != 0)); - assert!(((((st.st_mtime_nsec) > (0_i64)) as i32) != 0)); assert!(((((libc::fclose(fp)) == (0)) as i32) != 0)); libc::unlink(path as *const i8); } diff --git a/tests/unit/sys_stat.c b/tests/unit/sys_stat.c index b1d6a1c..7c86e3d 100644 --- a/tests/unit/sys_stat.c +++ b/tests/unit/sys_stat.c @@ -15,7 +15,6 @@ static void test_stat(void) { assert(stat(path, &st) == 0); assert(st.st_size == 5); assert(st.st_mtime > 0); - assert(st.st_mtim.tv_nsec > 0); unlink(path); } @@ -30,7 +29,6 @@ static void test_fstat(void) { assert(fstat(fd, &st) == 0); assert(st.st_size == 11); assert(st.st_mtime > 0); - assert(st.st_mtim.tv_nsec > 0); assert(fclose(fp) == 0); unlink(path); } From ade3a5aef4a1682044fe825a9718844beadd4e0a Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 14:46:50 +0100 Subject: [PATCH 6/7] Update tests --- tests/unit/out/unsafe/union_tagged_struct_arms.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/out/unsafe/union_tagged_struct_arms.rs b/tests/unit/out/unsafe/union_tagged_struct_arms.rs index a1f5955..6bf0689 100644 --- a/tests/unit/out/unsafe/union_tagged_struct_arms.rs +++ b/tests/unit/out/unsafe/union_tagged_struct_arms.rs @@ -73,7 +73,7 @@ pub fn main() { } } unsafe fn main_0() -> i32 { - static mut items_0: [*mut u8; 3] = unsafe { + static mut items_4: [*mut u8; 3] = unsafe { [ b"a\0".as_ptr().cast_mut(), b"b\0".as_ptr().cast_mut(), @@ -83,7 +83,7 @@ unsafe fn main_0() -> i32 { let mut p_list: Branch = ::default(); p_list.choice = Choice::C_LIST; p_list.index = 0; - p_list.v.list.items = items_0.as_mut_ptr(); + p_list.v.list.items = items_4.as_mut_ptr(); p_list.v.list.count = 3_i64; p_list.v.list.cursor = 1_i64; assert!(((((p_list.v.list.count) == (3_i64)) as i32) != 0)); From edc68b15a5d10cb6d0918a2b1ae8e9c3a1b01da8 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 27 May 2026 15:23:43 +0100 Subject: [PATCH 7/7] Normalize output on macOS as well --- cpp2rust/converter/converter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index b789532..c895347 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2567,7 +2567,8 @@ replaceNonUniformLibcField(clang::MemberExpr *expr) { const char *replacement; }; static constexpr Mapping kFields[] = { - {"stat", "st_mtim", "tv_sec", "st_mtime"}, + {"stat", "st_mtim", "tv_sec", "st_mtime"}, // Linux + {"stat", "st_mtimespec", "tv_sec", "st_mtime"}, // macOS }; auto getNamedIdentifierOrNull = [](auto *decl) {