From c19d2b559c0977507ed1ca9187f589cd31747427 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:29:25 +0000 Subject: [PATCH 01/17] Initial plan From c61fdd42bac0447271284e308b187d0e5f945099 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:37:03 +0000 Subject: [PATCH 02/17] Fix Rust 2024 static mut reference emission --- cpp2rust/converter/converter.cpp | 28 +++++++++++++++---- tests/lit/lit/formats/Cpp2RustTest.py | 2 -- .../unit/out/unsafe/bool_condition_logical.rs | 2 +- .../out/unsafe/bool_condition_logical_c.rs | 2 +- tests/unit/out/unsafe/void_cast.rs | 2 +- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 3c96df3c..1e9d5d78 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2322,31 +2322,40 @@ bool Converter::IsReferenceType(const clang::Expr *expr) const { bool Converter::ConvertIncAndDec(clang::UnaryOperator *expr) { auto opcode = expr->getOpcode(); auto *sub_expr = expr->getSubExpr(); + auto emit_target = [&]() { + if (IsGlobalVar(sub_expr)) { + StrCat("(*(&raw", keyword_mut_); + Convert(sub_expr); + StrCat("))"); + } else { + Convert(sub_expr); + } + }; switch (opcode) { case clang::UO_PostInc: { PushExprKind push(*this, ExprKind::RValue); - Convert(sub_expr); + emit_target(); StrCat(".postfix_inc()"); SetFresh(); return true; } case clang::UO_PostDec: { PushExprKind push(*this, ExprKind::RValue); - Convert(sub_expr); + emit_target(); StrCat(".postfix_dec()"); SetFresh(); return true; } case clang::UO_PreInc: { PushExprKind push(*this, ExprKind::RValue); - Convert(sub_expr); + emit_target(); StrCat(".prefix_inc()"); SetFresh(); return true; } case clang::UO_PreDec: { PushExprKind push(*this, ExprKind::RValue); - Convert(sub_expr); + emit_target(); StrCat(".prefix_dec()"); SetFresh(); return true; @@ -2540,8 +2549,15 @@ bool Converter::VisitDeclRefExpr(clang::DeclRefExpr *expr) { } if (!decl->getType()->getAs() && isAddrOf()) { - StrCat(token::kRef, decl->getType().isConstQualified() ? "" : keyword_mut_, - str); + if (IsGlobalVar(expr)) { + StrCat("&raw", decl->getType().isConstQualified() ? keyword::kConst + : keyword_mut_, + str); + } else { + StrCat(token::kRef, decl->getType().isConstQualified() ? "" + : keyword_mut_, + str); + } return false; } diff --git a/tests/lit/lit/formats/Cpp2RustTest.py b/tests/lit/lit/formats/Cpp2RustTest.py index f13cff6b..63a07cf2 100644 --- a/tests/lit/lit/formats/Cpp2RustTest.py +++ b/tests/lit/lit/formats/Cpp2RustTest.py @@ -224,8 +224,6 @@ def build_rust(self): "strip=symbols", "-C", "panic=abort", - "-W", ## TODO: remove this once we fix the errors in the generated code - "static_mut_refs", "--out-dir", str(self.tmp_dir), "-L", diff --git a/tests/unit/out/unsafe/bool_condition_logical.rs b/tests/unit/out/unsafe/bool_condition_logical.rs index dd1dc5de..d154a7e8 100644 --- a/tests/unit/out/unsafe/bool_condition_logical.rs +++ b/tests/unit/out/unsafe/bool_condition_logical.rs @@ -26,7 +26,7 @@ impl From for Code { libcc2rs::impl_enum_inc_dec!(Code); pub static mut side_effect_0: i32 = unsafe { 0 }; pub unsafe fn observe_1(mut v: i32) -> i32 { - side_effect_0.prefix_inc(); + (*(&raw mut side_effect_0)).prefix_inc(); return v; } pub unsafe fn returns_one_2() -> i32 { diff --git a/tests/unit/out/unsafe/bool_condition_logical_c.rs b/tests/unit/out/unsafe/bool_condition_logical_c.rs index 05279a31..87953cf4 100644 --- a/tests/unit/out/unsafe/bool_condition_logical_c.rs +++ b/tests/unit/out/unsafe/bool_condition_logical_c.rs @@ -26,7 +26,7 @@ impl From for Code { libcc2rs::impl_enum_inc_dec!(Code); pub static mut side_effect_0: i32 = unsafe { 0 }; pub unsafe fn observe_1(mut v: i32) -> i32 { - side_effect_0.prefix_inc(); + (*(&raw mut side_effect_0)).prefix_inc(); return v; } pub unsafe fn returns_one_2() -> i32 { diff --git a/tests/unit/out/unsafe/void_cast.rs b/tests/unit/out/unsafe/void_cast.rs index 1b4b5978..10eccb01 100644 --- a/tests/unit/out/unsafe/void_cast.rs +++ b/tests/unit/out/unsafe/void_cast.rs @@ -22,7 +22,7 @@ pub unsafe fn unused_ptr_param_2(mut p: *const NonTrivial) { } pub static mut side_effect_counter_3: i32 = unsafe { 0 }; pub unsafe fn bump_and_return_4() -> i32 { - side_effect_counter_3.prefix_inc(); + (*(&raw mut side_effect_counter_3)).prefix_inc(); return side_effect_counter_3; } #[repr(C)] From 0e8a426d96d07818d465b3fe32d756bcf96b1b92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:46:59 +0000 Subject: [PATCH 03/17] Apply clang-format to converter.cpp --- cpp2rust/converter/converter.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 1e9d5d78..26d06052 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2550,13 +2550,13 @@ bool Converter::VisitDeclRefExpr(clang::DeclRefExpr *expr) { if (!decl->getType()->getAs() && isAddrOf()) { if (IsGlobalVar(expr)) { - StrCat("&raw", decl->getType().isConstQualified() ? keyword::kConst - : keyword_mut_, + StrCat("&raw", + decl->getType().isConstQualified() ? keyword::kConst + : keyword_mut_, str); } else { - StrCat(token::kRef, decl->getType().isConstQualified() ? "" - : keyword_mut_, - str); + StrCat(token::kRef, + decl->getType().isConstQualified() ? "" : keyword_mut_, str); } return false; } From be00acfe631f0837fed7a61548644961d8d835ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:50:42 +0000 Subject: [PATCH 04/17] Apply static mut array decay fix --- cpp2rust/converter/converter.cpp | 23 ++++++++++++------- .../out/unsafe/union_tagged_struct_arms.rs | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 26d06052..433fec58 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1986,15 +1986,22 @@ bool Converter::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) { StrCat(keyword::kAs, dest_pointee_const ? "*const u8" : "*mut u8"); break; } - Convert(sub_expr); - if (clang::isa(sub_expr) || - clang::isa(sub_expr)) { - StrCat(".as_ptr()"); - if (!dest_pointee_const) { - StrCat(".cast_mut()"); - } + if (IsGlobalVar(sub_expr)) { + StrCat("(&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); + Convert(sub_expr); + StrCat(").cast::<", GetUnsafeTypeAsString(expr->getType()->getPointeeType()), + ">()"); } else { - StrCat(dest_pointee_const ? ".as_ptr()" : ".as_mut_ptr()"); + Convert(sub_expr); + if (clang::isa(sub_expr) || + clang::isa(sub_expr)) { + StrCat(".as_ptr()"); + if (!dest_pointee_const) { + StrCat(".cast_mut()"); + } + } else { + StrCat(dest_pointee_const ? ".as_ptr()" : ".as_mut_ptr()"); + } } break; } diff --git a/tests/unit/out/unsafe/union_tagged_struct_arms.rs b/tests/unit/out/unsafe/union_tagged_struct_arms.rs index 7c578e28..49dbd4d4 100644 --- a/tests/unit/out/unsafe/union_tagged_struct_arms.rs +++ b/tests/unit/out/unsafe/union_tagged_struct_arms.rs @@ -83,7 +83,7 @@ unsafe fn main_0() -> i32 { let mut p_list: Branch = ::default(); p_list.choice = Choice_enum::C_LIST; p_list.index = 0; - p_list.v.list.items = items_4.as_mut_ptr(); + p_list.v.list.items = (&raw mut items_4).cast::<*mut u8>(); 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 167627eface8d571c406673e19cad7b612744446 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:53:35 +0000 Subject: [PATCH 05/17] Format converter static-array decay fix --- cpp2rust/converter/converter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 433fec58..dd17717a 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1989,8 +1989,8 @@ bool Converter::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) { if (IsGlobalVar(sub_expr)) { StrCat("(&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); Convert(sub_expr); - StrCat(").cast::<", GetUnsafeTypeAsString(expr->getType()->getPointeeType()), - ">()"); + StrCat(").cast::<", + GetUnsafeTypeAsString(expr->getType()->getPointeeType()), ">()"); } else { Convert(sub_expr); if (clang::isa(sub_expr) || From 05258a5afd2c7cfd03086691bf46a8ecf7a8685c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:21:37 +0000 Subject: [PATCH 06/17] Use PushParen in global array decay emission --- cpp2rust/converter/converter.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index dd17717a..25cd4e8d 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1987,9 +1987,10 @@ bool Converter::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) { break; } if (IsGlobalVar(sub_expr)) { - StrCat("(&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); + PushParen paren(*this); + StrCat("&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); Convert(sub_expr); - StrCat(").cast::<", + StrCat(".cast::<", GetUnsafeTypeAsString(expr->getType()->getPointeeType()), ">()"); } else { Convert(sub_expr); From 413c515734cb9cf78ab47673663e185a6aa43ae8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:29:57 +0000 Subject: [PATCH 07/17] Avoid static-mut shared refs in global fn ptr null checks --- cpp2rust/converter/converter.cpp | 20 +++++++++++++++++--- tests/unit/out/unsafe/fn_ptr_global.rs | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 25cd4e8d..6aa557c1 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3535,13 +3535,27 @@ void Converter::ConvertUnsignedArithOperand(clang::Expr *expr, } void Converter::ConvertEqualsNullPtr(clang::Expr *expr) { - StrCat('('); + if (IsGlobalVar(expr) && + (IsUniquePtr(expr->getType()) || + expr->getType()->isFunctionPointerType())) { + StrCat(keyword_unsafe_); + PushBrace unsafe_brace(*this); + { + PushParen paren(*this); + StrCat("&raw", keyword::kConst); + Convert(expr); + } + StrCat(".as_ref().unwrap().is_none()"); + return; + } + + PushParen paren(*this); Convert(expr); if (IsUniquePtr(expr->getType()) || expr->getType()->isFunctionPointerType()) { - StrCat(").is_none()"); + StrCat(".is_none()"); } else { - StrCat(").is_null()"); + StrCat(".is_null()"); } } diff --git a/tests/unit/out/unsafe/fn_ptr_global.rs b/tests/unit/out/unsafe/fn_ptr_global.rs index 9e77d34a..e35b7407 100644 --- a/tests/unit/out/unsafe/fn_ptr_global.rs +++ b/tests/unit/out/unsafe/fn_ptr_global.rs @@ -17,7 +17,7 @@ pub unsafe fn set_op_3(mut fn_: Option i32>) { g_op_2 = fn_; } pub unsafe fn call_op_4(mut x: i32) -> i32 { - if !(g_op_2).is_none() { + if !(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() }) { return (unsafe { let _arg0: i32 = x; (g_op_2).unwrap()(_arg0) @@ -41,7 +41,7 @@ unsafe fn main_0() -> i32 { let _fn: Option i32> = Some(double_it_0); set_op_3(_fn) }); - assert!(!((g_op_2).is_none())); + assert!(!(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() })); assert!(((g_op_2) == (Some(double_it_0)))); assert!( ((unsafe { @@ -64,7 +64,7 @@ unsafe fn main_0() -> i32 { let _fn: Option i32> = None; set_op_3(_fn) }); - assert!((g_op_2).is_none()); + assert!(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() }); assert!( ((unsafe { let _x: i32 = 5; From 7e9aea59b77417ab6410d83517792c795f9c2c06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:35:57 +0000 Subject: [PATCH 08/17] Fix PushParen scope --- cpp2rust/converter/converter.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 6aa557c1..969893b0 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1987,9 +1987,11 @@ bool Converter::VisitImplicitCastExpr(clang::ImplicitCastExpr *expr) { break; } if (IsGlobalVar(sub_expr)) { - PushParen paren(*this); - StrCat("&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); - Convert(sub_expr); + { + PushParen paren(*this); + StrCat("&raw", dest_pointee_const ? keyword::kConst : keyword_mut_); + Convert(sub_expr); + } StrCat(".cast::<", GetUnsafeTypeAsString(expr->getType()->getPointeeType()), ">()"); } else { From 55c480d4c1f889a93a249e6f5c383e46a03aa53e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:45:51 +0000 Subject: [PATCH 09/17] Fix null-check parenthesis scope --- cpp2rust/converter/converter.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 969893b0..71491ab9 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3551,8 +3551,10 @@ void Converter::ConvertEqualsNullPtr(clang::Expr *expr) { return; } - PushParen paren(*this); - Convert(expr); + { + PushParen paren(*this); + Convert(expr); + } if (IsUniquePtr(expr->getType()) || expr->getType()->isFunctionPointerType()) { StrCat(".is_none()"); From 6df6b8162cd0df63a2b6ccd4d3c3c643eb7b2602 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:54:20 +0000 Subject: [PATCH 10/17] Fix conflict markers in fn_ptr_global.rs: keep Rust 2024-safe raw pointer pattern --- tests/unit/out/unsafe/fn_ptr_global.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/unit/out/unsafe/fn_ptr_global.rs b/tests/unit/out/unsafe/fn_ptr_global.rs index 3efd8212..3152bfc5 100644 --- a/tests/unit/out/unsafe/fn_ptr_global.rs +++ b/tests/unit/out/unsafe/fn_ptr_global.rs @@ -49,7 +49,6 @@ unsafe fn main_0() -> i32 { let _fn: Option i32> = None; set_op_3(_fn) }); -<<<<<<< HEAD assert!(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() }); assert!( ((unsafe { @@ -57,9 +56,5 @@ unsafe fn main_0() -> i32 { call_op_4(_x) }) == (5)) ); -======= - assert!((g_op_2).is_none()); - assert!(((unsafe { call_op_4(5,) }) == (5))); ->>>>>>> origin/master return 0; } From ac5ef5893b5614ece651400af83f124fad8ba609 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:08:01 +0000 Subject: [PATCH 11/17] Update expected outputs for default_in_statics and fn_ptr_global unsafe tests --- tests/unit/out/unsafe/default_in_statics.rs | 4 ++-- tests/unit/out/unsafe/fn_ptr_global.rs | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/unit/out/unsafe/default_in_statics.rs b/tests/unit/out/unsafe/default_in_statics.rs index 4694da33..986d71c1 100644 --- a/tests/unit/out/unsafe/default_in_statics.rs +++ b/tests/unit/out/unsafe/default_in_statics.rs @@ -127,7 +127,7 @@ pub unsafe fn check_local_static_5() { static mut local_p_8: *mut i32 = unsafe { std::ptr::null_mut() };; assert!((local_outer_6.p1).is_null()); assert!((local_outer_6.fn_).is_none()); - assert!((local_fn_7).is_none()); + assert!(unsafe { (&raw const local_fn_7).as_ref().unwrap().is_none() }); assert!((local_p_8).is_null()); } pub fn main() { @@ -136,7 +136,7 @@ pub fn main() { } } unsafe fn main_0() -> i32 { - assert!((static_fn_0).is_none()); + assert!(unsafe { (&raw const static_fn_0).as_ref().unwrap().is_none() }); assert!((static_outer_1.p1).is_null()); assert!((static_outer_1.p2).is_null()); assert!((static_outer_1.cp).is_null()); diff --git a/tests/unit/out/unsafe/fn_ptr_global.rs b/tests/unit/out/unsafe/fn_ptr_global.rs index 3152bfc5..3e277bbf 100644 --- a/tests/unit/out/unsafe/fn_ptr_global.rs +++ b/tests/unit/out/unsafe/fn_ptr_global.rs @@ -17,7 +17,7 @@ pub unsafe fn set_op_3(mut fn_: Option i32>) { g_op_2 = fn_; } pub unsafe fn call_op_4(mut x: i32) -> i32 { - if !(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() }) { + if !unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() } { return (unsafe { let _arg0: i32 = x; (g_op_2).unwrap()(_arg0) @@ -50,11 +50,6 @@ unsafe fn main_0() -> i32 { set_op_3(_fn) }); assert!(unsafe { (&raw const g_op_2).as_ref().unwrap().is_none() }); - assert!( - ((unsafe { - let _x: i32 = 5; - call_op_4(_x) - }) == (5)) - ); + assert!(((unsafe { call_op_4(5,) }) == (5))); return 0; } From fbf98c57534a9ffa2d0109f5f4e04580e9de2736 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:15:02 +0000 Subject: [PATCH 12/17] Fix remaining Rust 2024 static-mut assertions in default_in_statics test output --- tests/unit/out/unsafe/default_in_statics.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/out/unsafe/default_in_statics.rs b/tests/unit/out/unsafe/default_in_statics.rs index 986d71c1..3bf22786 100644 --- a/tests/unit/out/unsafe/default_in_statics.rs +++ b/tests/unit/out/unsafe/default_in_statics.rs @@ -126,7 +126,7 @@ pub unsafe fn check_local_static_5() { static mut local_fn_7: Option i32> = unsafe { None };; static mut local_p_8: *mut i32 = unsafe { std::ptr::null_mut() };; assert!((local_outer_6.p1).is_null()); - assert!((local_outer_6.fn_).is_none()); + assert!(unsafe { (&raw const local_outer_6).as_ref().unwrap().fn_.is_none() }); assert!(unsafe { (&raw const local_fn_7).as_ref().unwrap().is_none() }); assert!((local_p_8).is_null()); } @@ -141,7 +141,7 @@ unsafe fn main_0() -> i32 { assert!((static_outer_1.p2).is_null()); assert!((static_outer_1.cp).is_null()); assert!((static_outer_1.pp).is_null()); - assert!((static_outer_1.fn_).is_none()); + assert!(unsafe { (&raw const static_outer_1).as_ref().unwrap().fn_.is_none() }); let mut i: i32 = 0; 'loop_: while ((i) < (3)) { assert!((static_outer_1.arr[(i) as usize]).is_null()); @@ -154,14 +154,14 @@ unsafe fn main_0() -> i32 { i.prefix_inc(); } assert!((static_foo_3.s2).is_null()); - assert!((static_foo_3.fn1).is_none()); - assert!((static_foo_3.fn2).is_none()); + assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn1.is_none() }); + assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn2.is_none() }); assert!(((static_foo_3.n) == (42))); let mut i: i32 = 0; 'loop_: while ((i) < (2)) { assert!((static_foo_array_4[(i) as usize].s2).is_null()); - assert!((static_foo_array_4[(i) as usize].fn1).is_none()); - assert!((static_foo_array_4[(i) as usize].fn2).is_none()); + assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn1.is_none() }); + assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn2.is_none() }); i.prefix_inc(); } (unsafe { check_local_static_5() }); From c3e4b41a65b3d87f6dc11912fdc5df204cea805a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:22:54 +0000 Subject: [PATCH 13/17] Fix default_in_statics expected output for function-pointer fields --- tests/unit/out/unsafe/default_in_statics.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/out/unsafe/default_in_statics.rs b/tests/unit/out/unsafe/default_in_statics.rs index 3bf22786..986d71c1 100644 --- a/tests/unit/out/unsafe/default_in_statics.rs +++ b/tests/unit/out/unsafe/default_in_statics.rs @@ -126,7 +126,7 @@ pub unsafe fn check_local_static_5() { static mut local_fn_7: Option i32> = unsafe { None };; static mut local_p_8: *mut i32 = unsafe { std::ptr::null_mut() };; assert!((local_outer_6.p1).is_null()); - assert!(unsafe { (&raw const local_outer_6).as_ref().unwrap().fn_.is_none() }); + assert!((local_outer_6.fn_).is_none()); assert!(unsafe { (&raw const local_fn_7).as_ref().unwrap().is_none() }); assert!((local_p_8).is_null()); } @@ -141,7 +141,7 @@ unsafe fn main_0() -> i32 { assert!((static_outer_1.p2).is_null()); assert!((static_outer_1.cp).is_null()); assert!((static_outer_1.pp).is_null()); - assert!(unsafe { (&raw const static_outer_1).as_ref().unwrap().fn_.is_none() }); + assert!((static_outer_1.fn_).is_none()); let mut i: i32 = 0; 'loop_: while ((i) < (3)) { assert!((static_outer_1.arr[(i) as usize]).is_null()); @@ -154,14 +154,14 @@ unsafe fn main_0() -> i32 { i.prefix_inc(); } assert!((static_foo_3.s2).is_null()); - assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn1.is_none() }); - assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn2.is_none() }); + assert!((static_foo_3.fn1).is_none()); + assert!((static_foo_3.fn2).is_none()); assert!(((static_foo_3.n) == (42))); let mut i: i32 = 0; 'loop_: while ((i) < (2)) { assert!((static_foo_array_4[(i) as usize].s2).is_null()); - assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn1.is_none() }); - assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn2.is_none() }); + assert!((static_foo_array_4[(i) as usize].fn1).is_none()); + assert!((static_foo_array_4[(i) as usize].fn2).is_none()); i.prefix_inc(); } (unsafe { check_local_static_5() }); From d9f29409d746b90dea110485e75511b5590f6de5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:40:44 +0000 Subject: [PATCH 14/17] Fix converter: emit raw-const pattern for fn-ptr fields of static mut structs --- cpp2rust/converter/converter.cpp | 66 ++++++++++++++++++--- cpp2rust/converter/converter.h | 2 + tests/unit/out/unsafe/default_in_statics.rs | 12 ++-- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 18a31a06..26a461a7 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3535,20 +3535,68 @@ void Converter::ConvertUnsignedArithOperand(clang::Expr *expr, } } -void Converter::ConvertEqualsNullPtr(clang::Expr *expr) { - if (IsGlobalVar(expr) && - (IsUniquePtr(expr->getType()) || - expr->getType()->isFunctionPointerType())) { - StrCat(keyword_unsafe_); - PushBrace unsafe_brace(*this); +void Converter::ConvertGlobalVarBaseSuffix(clang::Expr *expr) { + expr = expr->IgnoreImplicit(); + // Base case: this expression IS the global var root — no suffix to emit. + if (IsGlobalVar(expr)) { + return; + } + if (auto *member = clang::dyn_cast(expr)) { + ConvertGlobalVarBaseSuffix(member->getBase()); + StrCat(token::kDot); + StrCat(GetNamedDeclAsString(member->getMemberDecl())); + return; + } + if (auto *subscript = clang::dyn_cast(expr)) { + ConvertGlobalVarBaseSuffix(subscript->getBase()); + PushBracket bracket(*this); { PushParen paren(*this); - StrCat("&raw", keyword::kConst); - Convert(expr); + Convert(subscript->getIdx()); } - StrCat(".as_ref().unwrap().is_none()"); + StrCat(keyword::kAs, "usize"); return; } +} + +void Converter::ConvertEqualsNullPtr(clang::Expr *expr) { + if (IsUniquePtr(expr->getType()) || + expr->getType()->isFunctionPointerType()) { + // Walk up through member accesses and array subscripts to find the root + // global var (file-scope or static-local). Any fn-ptr field access on a + // static mut struct requires the raw-const pattern to avoid Rust 2024's + // shared-reference-to-static-mut restriction. + clang::Expr *root = expr; + while (root) { + if (IsGlobalVar(root)) { + break; + } + auto *r = root->IgnoreImplicit(); + if (auto *member = clang::dyn_cast(r)) { + root = member->getBase(); + continue; + } + if (auto *subscript = clang::dyn_cast(r)) { + root = subscript->getBase(); + continue; + } + root = nullptr; + break; + } + if (root != nullptr) { + StrCat(keyword_unsafe_); + PushBrace unsafe_brace(*this); + { + PushParen paren(*this); + StrCat("&raw", keyword::kConst); + Convert(root->IgnoreImplicit()); + } + StrCat(".as_ref().unwrap()"); + ConvertGlobalVarBaseSuffix(expr); + StrCat(".is_none()"); + return; + } + } { PushParen paren(*this); diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index 9d64ef22..e502faeb 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -476,6 +476,8 @@ class Converter : public clang::RecursiveASTVisitor { virtual void ConvertEqualsNullPtr(clang::Expr *expr); + void ConvertGlobalVarBaseSuffix(clang::Expr *expr); + virtual void ConvertPointerSubscript(clang::ArraySubscriptExpr *expr); virtual void ConvertPointerOffset(clang::Expr *base, clang::Expr *idx, diff --git a/tests/unit/out/unsafe/default_in_statics.rs b/tests/unit/out/unsafe/default_in_statics.rs index 986d71c1..3bf22786 100644 --- a/tests/unit/out/unsafe/default_in_statics.rs +++ b/tests/unit/out/unsafe/default_in_statics.rs @@ -126,7 +126,7 @@ pub unsafe fn check_local_static_5() { static mut local_fn_7: Option i32> = unsafe { None };; static mut local_p_8: *mut i32 = unsafe { std::ptr::null_mut() };; assert!((local_outer_6.p1).is_null()); - assert!((local_outer_6.fn_).is_none()); + assert!(unsafe { (&raw const local_outer_6).as_ref().unwrap().fn_.is_none() }); assert!(unsafe { (&raw const local_fn_7).as_ref().unwrap().is_none() }); assert!((local_p_8).is_null()); } @@ -141,7 +141,7 @@ unsafe fn main_0() -> i32 { assert!((static_outer_1.p2).is_null()); assert!((static_outer_1.cp).is_null()); assert!((static_outer_1.pp).is_null()); - assert!((static_outer_1.fn_).is_none()); + assert!(unsafe { (&raw const static_outer_1).as_ref().unwrap().fn_.is_none() }); let mut i: i32 = 0; 'loop_: while ((i) < (3)) { assert!((static_outer_1.arr[(i) as usize]).is_null()); @@ -154,14 +154,14 @@ unsafe fn main_0() -> i32 { i.prefix_inc(); } assert!((static_foo_3.s2).is_null()); - assert!((static_foo_3.fn1).is_none()); - assert!((static_foo_3.fn2).is_none()); + assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn1.is_none() }); + assert!(unsafe { (&raw const static_foo_3).as_ref().unwrap().fn2.is_none() }); assert!(((static_foo_3.n) == (42))); let mut i: i32 = 0; 'loop_: while ((i) < (2)) { assert!((static_foo_array_4[(i) as usize].s2).is_null()); - assert!((static_foo_array_4[(i) as usize].fn1).is_none()); - assert!((static_foo_array_4[(i) as usize].fn2).is_none()); + assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn1.is_none() }); + assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn2.is_none() }); i.prefix_inc(); } (unsafe { check_local_static_5() }); From b3def059246b92014634e097c34617dc14f51adf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:49:10 +0000 Subject: [PATCH 15/17] Fix expected output for default_in_statics.cpp/unsafe test --- tests/unit/out/unsafe/default_in_statics.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit/out/unsafe/default_in_statics.rs b/tests/unit/out/unsafe/default_in_statics.rs index 3bf22786..8d6c3391 100644 --- a/tests/unit/out/unsafe/default_in_statics.rs +++ b/tests/unit/out/unsafe/default_in_statics.rs @@ -160,8 +160,16 @@ unsafe fn main_0() -> i32 { let mut i: i32 = 0; 'loop_: while ((i) < (2)) { assert!((static_foo_array_4[(i) as usize].s2).is_null()); - assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn1.is_none() }); - assert!(unsafe { (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize].fn2.is_none() }); + assert!(unsafe { + (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize] + .fn1 + .is_none() + }); + assert!(unsafe { + (&raw const static_foo_array_4).as_ref().unwrap()[(i) as usize] + .fn2 + .is_none() + }); i.prefix_inc(); } (unsafe { check_local_static_5() }); From 8f677b7ae0f27c1f25f00c7bff242003ba7c4d94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Jun 2026 08:08:35 +0000 Subject: [PATCH 16/17] Fix ConvertVarInit: use &raw const/mut for global statics bound to reference params --- cpp2rust/converter/converter.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 26a461a7..3eebdba0 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3479,9 +3479,14 @@ std::string Converter::GetUnsafeTypeAsString(clang::QualType qual_type) const { void Converter::ConvertVarInit(clang::QualType qual_type, clang::Expr *expr) { if (qual_type->isReferenceType() && !IsReferenceType(expr)) { - StrCat(token::kRef); - if (IsMut(qual_type)) { - StrCat(keyword_mut_); + if (IsGlobalVar(expr)) { + StrCat("&raw"); + StrCat(IsMut(qual_type) ? keyword_mut_ : keyword::kConst); + } else { + StrCat(token::kRef); + if (IsMut(qual_type)) { + StrCat(keyword_mut_); + } } } if (qual_type->isFunctionPointerType()) { From 2e76b75ea220a61c30eed12f34f94364ef73caa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Jun 2026 08:34:38 +0000 Subject: [PATCH 17/17] Changes before error encountered Agent-Logs-Url: https://github.com/Cpp2Rust/cpp2rust/sessions/8e8af683-6260-496d-8c27-2a03ae69bf90 --- cpp2rust/converter/converter.cpp | 46 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 3eebdba0..e43025c5 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3479,14 +3479,9 @@ std::string Converter::GetUnsafeTypeAsString(clang::QualType qual_type) const { void Converter::ConvertVarInit(clang::QualType qual_type, clang::Expr *expr) { if (qual_type->isReferenceType() && !IsReferenceType(expr)) { - if (IsGlobalVar(expr)) { - StrCat("&raw"); - StrCat(IsMut(qual_type) ? keyword_mut_ : keyword::kConst); - } else { - StrCat(token::kRef); - if (IsMut(qual_type)) { - StrCat(keyword_mut_); - } + StrCat(token::kRef); + if (IsMut(qual_type)) { + StrCat(keyword_mut_); } } if (qual_type->isFunctionPointerType()) { @@ -4179,12 +4174,35 @@ std::string Converter::ConvertIRFragment( auto all_args = BuildUnifiedArgs(expr, args, num_args); std::string result; - for (auto &frag : fragments) { - if (auto *t = std::get_if(&frag)) { - result += t->text; - } else if (auto *g = std::get_if(&frag)) { + for (size_t i = 0; i < fragments.size(); ++i) { + const auto &frag = fragments[i]; + if (const auto *t = std::get_if(&frag)) { + std::string text = t->text; + // If this text fragment ends with a lone '&' (reference operator, not + // '&&' or '&mut') and the immediately following fragment is a placeholder + // for a global variable, upgrade '&' to '&raw const' or '&raw mut' to + // comply with Rust 2024's ban on shared references to mutable statics. + if (!text.empty() && text.back() == '&' && + (text.size() < 2 || text[text.size() - 2] != '&') && + i + 1 < fragments.size()) { + if (const auto *next_ph = + std::get_if(&fragments[i + 1])) { + auto next_idx = next_ph->n; + if (next_idx < all_args.size() && IsGlobalVar(all_args[next_idx])) { + const auto *decl_ref = clang::dyn_cast( + all_args[next_idx]->IgnoreImplicit()); + bool is_const = + decl_ref && + decl_ref->getDecl()->getType().isConstQualified(); + text.pop_back(); // remove '&' + text += is_const ? "&raw const " : std::string("&raw") + keyword_mut_; + } + } + } + result += text; + } else if (const auto *g = std::get_if(&frag)) { result += Mapper::InstantiateTemplate(GetCalleeOrExpr(expr), g->n); - } else if (auto *ph = std::get_if(&frag)) { + } else if (const auto *ph = std::get_if(&frag)) { auto arg_idx = ph->n; assert(arg_idx < all_args.size()); auto *arg = all_args[arg_idx]; @@ -4204,7 +4222,7 @@ std::string Converter::ConvertIRFragment( .is_index_base = ph->is_index_base, }; result += ConvertPlaceholder(expr, arg, ph_ctx); - } else if (auto *mc = + } else if (const auto *mc = std::get_if>(&frag)) { result += ConvertMappedMethodCall(expr, **mc, args, num_args, ctx); }