From b8bd5c42e104618203167c92f4f4587235a40fe4 Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 20 Aug 2026 16:51:16 +0200 Subject: [PATCH 01/14] transpile: Add tests for unused volatile reads --- .../snapshots__transpile@volatile.c.2021.clang15.snap | 2 ++ .../snapshots__transpile@volatile.c.2024.clang15.snap | 2 ++ c2rust-transpile/tests/snapshots/volatile.c | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap index b056a038c8..8b2eb340d0 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap @@ -34,4 +34,6 @@ pub unsafe extern "C" fn test_volatile() { ::core::ptr::read_volatile::<*mut ::core::ffi::c_int>(&raw const volatile_global_struct.p) .offset(-1), ); + -::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); + *pvi; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap index 012d33f8d9..585e828cff 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap @@ -34,4 +34,6 @@ pub unsafe extern "C" fn test_volatile() { ::core::ptr::read_volatile::<*mut ::core::ffi::c_int>(&raw const volatile_global_struct.p) .offset(-1), ); + -::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); + *pvi; } diff --git a/c2rust-transpile/tests/snapshots/volatile.c b/c2rust-transpile/tests/snapshots/volatile.c index a838187716..6b9a3f12da 100644 --- a/c2rust-transpile/tests/snapshots/volatile.c +++ b/c2rust-transpile/tests/snapshots/volatile.c @@ -11,4 +11,10 @@ void test_volatile(void) { // https://github.com/immunant/c2rust/issues/1237 --(volatile_global_struct.p); + + // Unused reads, should be included as side effects. + vi; + -vi; + vi + 1; + *pvi; } From 123d9261aea5cd9ad0ca515aeefca939dcaae81b Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 20 Aug 2026 15:30:15 +0200 Subject: [PATCH 02/14] transpile: Move `LValueToRValue` handling to `convert_cast` --- c2rust-transpile/src/translator/mod.rs | 95 ++++++++++++++------------ 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 16fe7a87cd..33deb9385c 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3923,15 +3923,59 @@ impl<'c> Translation<'c> { opt_field_id: Option, is_explicit: bool, ) -> TranslationResult>> { - if matches!( - kind, - CastKind::IntegralToBoolean | CastKind::FloatingToBoolean | CastKind::PointerToBoolean - ) { - return self.convert_condition(ctx, true, expr); - } + let source_ty = if let Some(func_decl) = self + .ast_context + .fn_declref_decl(expr) + .filter(|_| is_explicit) + { + // If we're casting a function, look for its declared ty to use as a more + // precise source type. The AST node's type will not preserve typedef arg types + // but the function's declaration will. + let kind_with_declared_args = self.ast_context.fn_decl_ty_with_declared_args(func_decl); + let func_ty = self.ast_context.type_for_kind(&kind_with_declared_args); + let func_ptr_ty = self + .ast_context + .type_for_kind(&CTypeKind::Pointer(CQualTypeId::new(func_ty))); + CQualTypeId::new(func_ptr_ty) + } else { + self.ast_context + .index_unwrap_parens(expr) + .kind + .get_qual_type() + .ok_or_else(|| format_err!("bad source type"))? + }; let target_ty = override_ty.unwrap_or(ty); + match kind { + CastKind::LValueToRValue => { + let val = self.convert_expr(ctx, expr, None)?; + let mut val = if source_ty.qualifiers.is_volatile { + // If the expression is volatile and used as something that isn't an LValue, + // this constitutes a volatile read. + val.try_map(|val| self.volatile_read(val, target_ty))? + } else { + val + }; + + // if the context wants a different type, add a cast + if target_ty.ctype != source_ty.ctype { + let ty = self.convert_type(target_ty.ctype)?; + val = val.map(|val| mk().cast_expr(val, ty)); + } + + return Ok(val); + } + + CastKind::IntegralToBoolean + | CastKind::FloatingToBoolean + | CastKind::PointerToBoolean => { + return self.convert_condition(ctx, true, expr); + } + + _ => {} + } + // In general, if we are casting the result of an expression, then the inner // expression should be translated to whatever type it normally would. // But for some expression types, if we don't absolutely have to cast, @@ -3968,29 +4012,6 @@ impl<'c> Translation<'c> { return Ok(val); } - let source_ty = if let Some(func_decl) = self - .ast_context - .fn_declref_decl(expr) - .filter(|_| is_explicit) - { - // If we're casting a function, look for its declared ty to use as a more - // precise source type. The AST node's type will not preserve typedef arg types - // but the function's declaration will. - let kind_with_declared_args = self.ast_context.fn_decl_ty_with_declared_args(func_decl); - let func_ty = self.ast_context.type_for_kind(&kind_with_declared_args); - let func_ptr_ty = self - .ast_context - .type_for_kind(&CTypeKind::Pointer(CQualTypeId::new(func_ty))); - - CQualTypeId::new(func_ptr_ty) - } else { - self.ast_context - .index_unwrap_parens(expr) - .kind - .get_qual_type() - .ok_or_else(|| format_err!("bad source type"))? - }; - self.make_cast_full( ctx, source_ty, @@ -4206,21 +4227,7 @@ impl<'c> Translation<'c> { } CastKind::LValueToRValue => { - let mut val = if source_cty.qualifiers.is_volatile { - // If the expression is volatile and used as something that isn't an LValue, - // this constitutes a volatile read. - val.try_map(|val| self.volatile_read(val, target_cty))? - } else { - val - }; - - // if the context wants a different type, add a cast - if target_cty.ctype != source_cty.ctype { - let ty = self.convert_type(target_cty.ctype)?; - val = val.map(|val| mk().cast_expr(val, ty)); - } - - Ok(val) + panic!("LValueToRValue casts must be handled in convert_cast") } CastKind::ToVoid | CastKind::ConstCast => Ok(val), From 2e45f0cff51d8a334150d9fab153b37451c525b4 Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 20 Aug 2026 17:09:40 +0200 Subject: [PATCH 03/14] transpile: Use `make_cast` in `LValueToRValue` cast handling --- c2rust-transpile/src/c_ast/mod.rs | 14 ++++++++++++++ c2rust-transpile/src/translator/mod.rs | 9 ++------- ...hots__transpile@auto_type.c.2021.clang15.snap | 4 +--- ...hots__transpile@auto_type.c.2021.clang22.snap | 4 +--- ...hots__transpile@auto_type.c.2024.clang15.snap | 4 +--- ...hots__transpile@auto_type.c.2024.clang22.snap | 4 +--- ...ots__transpile@conditions.c.2021.clang15.snap | 16 ++++++++-------- ...ots__transpile@conditions.c.2024.clang15.snap | 16 ++++++++-------- ...shots__transpile@generics.c.2021.clang15.snap | 2 +- ...shots__transpile@generics.c.2024.clang15.snap | 2 +- 10 files changed, 38 insertions(+), 37 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 8491835111..9a6b8ea488 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -2565,6 +2565,13 @@ impl Qualifiers { false => Mutability::Mutable, } } + + pub(crate) fn not_volatile(self) -> Self { + Self { + is_volatile: false, + ..self + } + } } /// Qualified type @@ -2589,6 +2596,13 @@ impl CQualTypeId { pub fn mutability(self) -> Mutability { self.qualifiers.mutability() } + + pub(crate) fn not_volatile(self) -> Self { + Self { + qualifiers: self.qualifiers.not_volatile(), + ..self + } + } } // TODO: these may be interesting, but I'm not sure if they fit here: diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 33deb9385c..0587fdbc2c 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3950,7 +3950,7 @@ impl<'c> Translation<'c> { match kind { CastKind::LValueToRValue => { let val = self.convert_expr(ctx, expr, None)?; - let mut val = if source_ty.qualifiers.is_volatile { + let val = if source_ty.qualifiers.is_volatile { // If the expression is volatile and used as something that isn't an LValue, // this constitutes a volatile read. val.try_map(|val| self.volatile_read(val, target_ty))? @@ -3959,12 +3959,7 @@ impl<'c> Translation<'c> { }; // if the context wants a different type, add a cast - if target_ty.ctype != source_ty.ctype { - let ty = self.convert_type(target_ty.ctype)?; - val = val.map(|val| mk().cast_expr(val, ty)); - } - - return Ok(val); + return self.make_cast(ctx, source_ty.not_volatile(), target_ty, val); } CastKind::IntegralToBoolean diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang15.snap index 763bc00213..aafd92073e 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang15.snap @@ -23,8 +23,6 @@ pub unsafe extern "C" fn foo() -> ::core::ffi::c_int { let mut px: *mut ::core::ffi::c_int = &raw mut x; let mut sx: ::core::ffi::c_ulong = ::core::mem::size_of::<::core::ffi::c_int>() as ::core::ffi::c_ulong; - let mut y: bar = bar { - x: x as ::core::ffi::c_int, - } as bar; + let mut y: bar = bar { x: x }; return y.x; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang22.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang22.snap index 1cf710a387..58ae4ab36c 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang22.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2021.clang22.snap @@ -22,8 +22,6 @@ pub unsafe extern "C" fn foo() -> ::core::ffi::c_int { let mut x: ::core::ffi::c_int = 42 as ::core::ffi::c_int; let mut px: *mut ::core::ffi::c_int = &raw mut x; let mut sx: usize = ::core::mem::size_of::<::core::ffi::c_int>(); - let mut y: bar = bar { - x: x as ::core::ffi::c_int, - } as bar; + let mut y: bar = bar { x: x }; return y.x; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang15.snap index 2ba87b9141..ea60ec488b 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang15.snap @@ -23,8 +23,6 @@ pub unsafe extern "C" fn foo() -> ::core::ffi::c_int { let mut px: *mut ::core::ffi::c_int = &raw mut x; let mut sx: ::core::ffi::c_ulong = ::core::mem::size_of::<::core::ffi::c_int>() as ::core::ffi::c_ulong; - let mut y: bar = bar { - x: x as ::core::ffi::c_int, - } as bar; + let mut y: bar = bar { x: x }; return y.x; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang22.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang22.snap index 4118c1258c..c264026048 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang22.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@auto_type.c.2024.clang22.snap @@ -22,8 +22,6 @@ pub unsafe extern "C" fn foo() -> ::core::ffi::c_int { let mut x: ::core::ffi::c_int = 42 as ::core::ffi::c_int; let mut px: *mut ::core::ffi::c_int = &raw mut x; let mut sx: usize = ::core::mem::size_of::<::core::ffi::c_int>(); - let mut y: bar = bar { - x: x as ::core::ffi::c_int, - } as bar; + let mut y: bar = bar { x: x }; return y.x; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2021.clang15.snap index 54acc88eee..f8cbf10e7c 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2021.clang15.snap @@ -101,12 +101,12 @@ pub unsafe extern "C" fn conditional_operator( let mut x: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut y: ::core::ffi::c_int = 1 as ::core::ffi::c_int; *if false { &raw mut y } else { &raw mut x } = 10 as ::core::ffi::c_int; - *(buf as *mut ::core::ffi::c_int).offset(2isize) = if true { + *buf.offset(2isize) = if true { 2 as ::core::ffi::c_int } else { 3 as ::core::ffi::c_int }; - *(buf as *mut ::core::ffi::c_int).offset(3isize) = if false { + *buf.offset(3isize) = if false { 2 as ::core::ffi::c_int } else { 3 as ::core::ffi::c_int @@ -129,37 +129,37 @@ pub unsafe extern "C" fn binary_conditional_operator( buf: *mut ::core::ffi::c_int, ) { let c2rust_fresh0 = id(0 as ::core::ffi::c_int); - *(buf as *mut ::core::ffi::c_int).offset(0isize) = if c2rust_fresh0 != 0 { + *buf.offset(0isize) = if c2rust_fresh0 != 0 { c2rust_fresh0 } else { id(1 as ::core::ffi::c_int) }; let c2rust_fresh1 = id(2 as ::core::ffi::c_int); - *(buf as *mut ::core::ffi::c_int).offset(1isize) = if c2rust_fresh1 != 0 { + *buf.offset(1isize) = if c2rust_fresh1 != 0 { c2rust_fresh1 } else { id(3 as ::core::ffi::c_int) }; if add( - (buf as *mut ::core::ffi::c_int).offset(2 as ::core::ffi::c_int as isize), + buf.offset(2 as ::core::ffi::c_int as isize), 2 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ) == 0 { add( - (buf as *mut ::core::ffi::c_int).offset(3 as ::core::ffi::c_int as isize), + buf.offset(3 as ::core::ffi::c_int as isize), 3 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ); } if add( - (buf as *mut ::core::ffi::c_int).offset(4 as ::core::ffi::c_int as isize), + buf.offset(4 as ::core::ffi::c_int as isize), 4 as ::core::ffi::c_int, 1 as ::core::ffi::c_int, ) == 0 { add( - (buf as *mut ::core::ffi::c_int).offset(5 as ::core::ffi::c_int as isize), + buf.offset(5 as ::core::ffi::c_int as isize), 5 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ); diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2024.clang15.snap index 15b5fab745..0d2b164d49 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@conditions.c.2024.clang15.snap @@ -101,12 +101,12 @@ pub unsafe extern "C" fn conditional_operator( let mut x: ::core::ffi::c_int = 0 as ::core::ffi::c_int; let mut y: ::core::ffi::c_int = 1 as ::core::ffi::c_int; *if false { &raw mut y } else { &raw mut x } = 10 as ::core::ffi::c_int; - *(buf as *mut ::core::ffi::c_int).offset(2isize) = if true { + *buf.offset(2isize) = if true { 2 as ::core::ffi::c_int } else { 3 as ::core::ffi::c_int }; - *(buf as *mut ::core::ffi::c_int).offset(3isize) = if false { + *buf.offset(3isize) = if false { 2 as ::core::ffi::c_int } else { 3 as ::core::ffi::c_int @@ -129,37 +129,37 @@ pub unsafe extern "C" fn binary_conditional_operator( buf: *mut ::core::ffi::c_int, ) { let c2rust_fresh0 = id(0 as ::core::ffi::c_int); - *(buf as *mut ::core::ffi::c_int).offset(0isize) = if c2rust_fresh0 != 0 { + *buf.offset(0isize) = if c2rust_fresh0 != 0 { c2rust_fresh0 } else { id(1 as ::core::ffi::c_int) }; let c2rust_fresh1 = id(2 as ::core::ffi::c_int); - *(buf as *mut ::core::ffi::c_int).offset(1isize) = if c2rust_fresh1 != 0 { + *buf.offset(1isize) = if c2rust_fresh1 != 0 { c2rust_fresh1 } else { id(3 as ::core::ffi::c_int) }; if add( - (buf as *mut ::core::ffi::c_int).offset(2 as ::core::ffi::c_int as isize), + buf.offset(2 as ::core::ffi::c_int as isize), 2 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ) == 0 { add( - (buf as *mut ::core::ffi::c_int).offset(3 as ::core::ffi::c_int as isize), + buf.offset(3 as ::core::ffi::c_int as isize), 3 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ); } if add( - (buf as *mut ::core::ffi::c_int).offset(4 as ::core::ffi::c_int as isize), + buf.offset(4 as ::core::ffi::c_int as isize), 4 as ::core::ffi::c_int, 1 as ::core::ffi::c_int, ) == 0 { add( - (buf as *mut ::core::ffi::c_int).offset(5 as ::core::ffi::c_int as isize), + buf.offset(5 as ::core::ffi::c_int as isize), 5 as ::core::ffi::c_int, 0 as ::core::ffi::c_int, ); diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2021.clang15.snap index 848a9963db..a25092060c 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2021.clang15.snap @@ -36,5 +36,5 @@ pub unsafe extern "C" fn foo() { let mut t1: *mut t = p1 as *mut t; let mut t2: *const t = p2 as *const t; let cx: ::core::ffi::c_int = n; - let mut ut_y: ::core::ffi::c_int = cx as ::core::ffi::c_int; + let mut ut_y: ::core::ffi::c_int = cx; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2024.clang15.snap index 1d848bace9..ff96db37f8 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@generics.c.2024.clang15.snap @@ -36,5 +36,5 @@ pub unsafe extern "C" fn foo() { let mut t1: *mut t = p1 as *mut t; let mut t2: *const t = p2 as *const t; let cx: ::core::ffi::c_int = n; - let mut ut_y: ::core::ffi::c_int = cx as ::core::ffi::c_int; + let mut ut_y: ::core::ffi::c_int = cx; } From 476a77cbf76ffa0e72626e3d9f371616bb10eb22 Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 20 Aug 2026 16:53:13 +0200 Subject: [PATCH 04/14] transpile: Consider volatile reads a side effect --- c2rust-transpile/src/translator/mod.rs | 15 +++++++++++---- ...pshots__transpile@volatile.c.2021.clang15.snap | 4 +++- ...pshots__transpile@volatile.c.2024.clang15.snap | 4 +++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 0587fdbc2c..21057adb88 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3949,13 +3949,20 @@ impl<'c> Translation<'c> { match kind { CastKind::LValueToRValue => { - let val = self.convert_expr(ctx, expr, None)?; let val = if source_ty.qualifiers.is_volatile { // If the expression is volatile and used as something that isn't an LValue, - // this constitutes a volatile read. - val.try_map(|val| self.volatile_read(val, target_ty))? + // this constitutes a volatile read. A volatile read is a side effect, so it + // needs to be included even if the expression is unused. + let val = self + .convert_expr(ctx.used(), expr, None)? + .try_map(|val| self.volatile_read(val, source_ty))?; + self.convert_side_effects_expr( + ctx, + val, + "LValueToRValue value is not supposed to be used", + ) } else { - val + self.convert_expr(ctx, expr, None)? }; // if the context wants a different type, add a cast diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap index 8b2eb340d0..158a1b9ed3 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2021.clang15.snap @@ -34,6 +34,8 @@ pub unsafe extern "C" fn test_volatile() { ::core::ptr::read_volatile::<*mut ::core::ffi::c_int>(&raw const volatile_global_struct.p) .offset(-1), ); + ::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); -::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); - *pvi; + ::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); + ::core::ptr::read_volatile::<::core::ffi::c_int>(pvi); } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap index 585e828cff..2268a628dd 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@volatile.c.2024.clang15.snap @@ -34,6 +34,8 @@ pub unsafe extern "C" fn test_volatile() { ::core::ptr::read_volatile::<*mut ::core::ffi::c_int>(&raw const volatile_global_struct.p) .offset(-1), ); + ::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); -::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); - *pvi; + ::core::ptr::read_volatile::<::core::ffi::c_int>(&raw const vi); + ::core::ptr::read_volatile::<::core::ffi::c_int>(pvi); } From 02af88647a7b2996bc540dd8d67cf50c243f659c Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 13:45:21 +0200 Subject: [PATCH 05/14] transpile: Add `bypass_cast_with_typedef` snapshot test --- c2rust-transpile/tests/snapshots/exprs.c | 12 ++++++++++++ .../snapshots__transpile@exprs.c.2021.clang15.snap | 9 +++++++++ .../snapshots__transpile@exprs.c.2024.clang15.snap | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/c2rust-transpile/tests/snapshots/exprs.c b/c2rust-transpile/tests/snapshots/exprs.c index 09a3d8eab0..5dbb8ed041 100644 --- a/c2rust-transpile/tests/snapshots/exprs.c +++ b/c2rust-transpile/tests/snapshots/exprs.c @@ -114,3 +114,15 @@ void assign_result(void) { size_t s1 = l = 1; size_t s2 = l += 2; } + +void bypass_cast_with_typedef(void) { + // Primitive + int i = 0; + int_t t_implicit = i; + int_t t_explicit = (int_t) i; + + // Pointer + int *pi = 0; + int_t *pt_implicit = pi; + int_t *pt_explicit = (int_t *) pi; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap index 25c4af8ec1..57ff24bdea 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap @@ -152,3 +152,12 @@ pub unsafe extern "C" fn assign_result() { l = l.wrapping_add(2 as ::core::ffi::c_ulong); let mut s2: size_t = l as size_t; } +#[no_mangle] +pub unsafe extern "C" fn bypass_cast_with_typedef() { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut t_implicit: int_t = i; + let mut t_explicit: int_t = i; + let mut pi: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>(); + let mut pt_implicit: *mut int_t = pi as *mut int_t; + let mut pt_explicit: *mut int_t = pi as *mut int_t; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap index a7abaf5f3b..67482d9e62 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap @@ -152,3 +152,12 @@ pub unsafe extern "C" fn assign_result() { l = l.wrapping_add(2 as ::core::ffi::c_ulong); let mut s2: size_t = l as size_t; } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn bypass_cast_with_typedef() { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut t_implicit: int_t = i; + let mut t_explicit: int_t = i; + let mut pi: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>(); + let mut pt_implicit: *mut int_t = pi as *mut int_t; + let mut pt_explicit: *mut int_t = pi as *mut int_t; +} From ed40e9df6db0a40c8d07d45178f0d5d1ade23d87 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 13:46:45 +0200 Subject: [PATCH 06/14] transpile: Deep comparison of types in `make_cast` --- c2rust-transpile/src/c_ast/mod.rs | 149 ++++++++++++++++++ c2rust-transpile/src/translator/mod.rs | 7 +- ...shots__transpile@exprs.c.2021.clang15.snap | 4 +- ...shots__transpile@exprs.c.2024.clang15.snap | 4 +- 4 files changed, 159 insertions(+), 5 deletions(-) diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index 9a6b8ea488..f412c31234 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -841,6 +841,155 @@ impl TypedAstContext { self.index(resolved_typ_id) } + /// Returns whether `type_id1` and `type_id2` describe the same type with the same qualifiers. + /// `resolver` is called on every `CTypeId` before recursing. + pub(crate) fn qual_types_eq( + &self, + qual_type_id1: CQualTypeId, + qual_type_id2: CQualTypeId, + resolver: &impl Fn(&Self, CTypeId) -> CTypeId, + ) -> bool { + qual_type_id1.qualifiers == qual_type_id2.qualifiers + && self.types_eq(qual_type_id1.ctype, qual_type_id2.ctype, resolver) + } + + /// Returns whether `type_id1` and `type_id2` describe the same type. + /// `resolver` is called on every `CTypeId` before recursing. + pub(crate) fn types_eq( + &self, + mut type_id1: CTypeId, + mut type_id2: CTypeId, + resolver: &impl Fn(&Self, CTypeId) -> CTypeId, + ) -> bool { + type_id1 = resolver(self, type_id1); + type_id2 = resolver(self, type_id2); + self.type_kinds_eq(&self[type_id1].kind, &self[type_id2].kind, resolver) + } + + /// Returns whether `kind1` and `kind2` describe the same type. + /// `resolver` is called on every `CTypeId` before recursing. + pub(crate) fn type_kinds_eq( + &self, + kind1: &CTypeKind, + kind2: &CTypeKind, + resolver: &impl Fn(&Self, CTypeId) -> CTypeId, + ) -> bool { + use CTypeKind::*; + + match (kind1, kind2) { + (Void, Void) + | (Bool, Bool) + | (SChar, SChar) + | (Short, Short) + | (Int, Int) + | (Long, Long) + | (LongLong, LongLong) + | (Int8, Int8) + | (Int16, Int16) + | (Int32, Int32) + | (Int64, Int64) + | (Int128, Int128) + | (IntPtr, IntPtr) + | (IntMax, IntMax) + | (SSize, SSize) + | (UChar, UChar) + | (UShort, UShort) + | (UInt, UInt) + | (ULong, ULong) + | (ULongLong, ULongLong) + | (UInt8, UInt8) + | (UInt16, UInt16) + | (UInt32, UInt32) + | (UInt64, UInt64) + | (UInt128, UInt128) + | (UIntPtr, UIntPtr) + | (UIntMax, UIntMax) + | (Size, Size) + | (Char, Char) + | (WChar, WChar) + | (PtrDiff, PtrDiff) + | (Half, Half) + | (BFloat16, BFloat16) + | (Float, Float) + | (Double, Double) + | (LongDouble, LongDouble) + | (Float128, Float128) + | (BuiltinFn, BuiltinFn) + | (UnhandledSveType, UnhandledSveType) => true, + + (Enum(decl_id1), Enum(decl_id2)) + | (Struct(decl_id1), Struct(decl_id2)) + | (Typedef(decl_id1), Typedef(decl_id2)) + | (Union(decl_id1), Union(decl_id2)) => decl_id1 == decl_id2, + + (Auto(type_id1), Auto(type_id2)) + | (Complex(type_id1), Complex(type_id2)) + | (Decayed(type_id1), Decayed(type_id2)) + | (Elaborated(type_id1), Elaborated(type_id2)) + | (Paren(type_id1), Paren(type_id2)) + | (TypeOf(type_id1), TypeOf(type_id2)) + | (IncompleteArray(type_id1), IncompleteArray(type_id2)) => { + self.types_eq(*type_id1, *type_id2, resolver) + } + + (Atomic(type_id1), Atomic(type_id2)) + | (BlockPointer(type_id1), BlockPointer(type_id2)) + | (Pointer(type_id1), Pointer(type_id2)) + | (Reference(type_id1), Reference(type_id2)) => { + self.qual_types_eq(*type_id1, *type_id2, resolver) + } + + (ConstantArray(type_id1, len1), ConstantArray(type_id2, len2)) => { + self.types_eq(*type_id1, *type_id2, resolver) && len1 == len2 + } + + (VariableArray(type_id1, expr_id1), VariableArray(type_id2, expr_id2)) => { + self.types_eq(*type_id1, *type_id2, resolver) && expr_id1 == expr_id2 + } + + (Vector(type_id1, len1), Vector(type_id2, len2)) => { + self.qual_types_eq(*type_id1, *type_id2, resolver) && len1 == len2 + } + + ( + Function( + result_type_id1, + ref argument_type_ids1, + is_variable_argument1, + is_noreturn1, + has_prototype1, + ), + Function( + result_type_id2, + ref argument_type_ids2, + is_variable_argument2, + is_noreturn2, + has_prototype2, + ), + ) => { + self.qual_types_eq(*result_type_id1, *result_type_id2, resolver) + && argument_type_ids1.len() == argument_type_ids2.len() + && argument_type_ids1 + .iter() + .zip(argument_type_ids2.iter()) + .all(|(&type_id1, &type_id2)| { + self.qual_types_eq(type_id1, type_id2, resolver) + }) + && is_variable_argument1 == is_variable_argument2 + && is_noreturn1 == is_noreturn2 + && has_prototype1 == has_prototype2 + } + + (Attributed(type_id1, ref attribute1), Attributed(type_id2, ref attribute2)) => { + self.qual_types_eq(*type_id1, *type_id2, resolver) && attribute1 == attribute2 + } + + (TypeOfExpr(expr_id1), TypeOfExpr(expr_id2)) => expr_id1 == expr_id2, + + _ => false, + } + } + /// Extract decl of referenced function. /// Looks for ImplicitCast(FunctionToPointerDecay, DeclRef(function_decl)) pub fn fn_declref_decl(&self, func_expr: CExprId) -> Option<&CDeclKind> { diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 21057adb88..fac7441975 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -4147,7 +4147,12 @@ impl<'c> Translation<'c> { }) }); - if source_ty_kind == target_ty_kind && kind != CastKind::LValueToRValue { + if self.ast_context.type_kinds_eq( + source_ty_kind, + target_ty_kind, + &TypedAstContext::resolve_type_id, + ) && kind != CastKind::LValueToRValue + { return Ok(val); } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap index 57ff24bdea..31f027042e 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2021.clang15.snap @@ -158,6 +158,6 @@ pub unsafe extern "C" fn bypass_cast_with_typedef() { let mut t_implicit: int_t = i; let mut t_explicit: int_t = i; let mut pi: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>(); - let mut pt_implicit: *mut int_t = pi as *mut int_t; - let mut pt_explicit: *mut int_t = pi as *mut int_t; + let mut pt_implicit: *mut int_t = pi; + let mut pt_explicit: *mut int_t = pi; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap index 67482d9e62..ebfd341e21 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.2024.clang15.snap @@ -158,6 +158,6 @@ pub unsafe extern "C" fn bypass_cast_with_typedef() { let mut t_implicit: int_t = i; let mut t_explicit: int_t = i; let mut pi: *mut ::core::ffi::c_int = ::core::ptr::null_mut::<::core::ffi::c_int>(); - let mut pt_implicit: *mut int_t = pi as *mut int_t; - let mut pt_explicit: *mut int_t = pi as *mut int_t; + let mut pt_implicit: *mut int_t = pi; + let mut pt_explicit: *mut int_t = pi; } From c4314ee3027df7f2c38f947607f365837557c190 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 18:39:09 +0200 Subject: [PATCH 07/14] transpile: Add `decay_of_deref` snapshot test --- c2rust-transpile/tests/snapshots/arrays.c | 8 ++++++++ .../snapshots__transpile@arrays.c.2021.clang15.snap | 11 +++++++++++ .../snapshots__transpile@arrays.c.2024.clang15.snap | 11 +++++++++++ 3 files changed, 30 insertions(+) diff --git a/c2rust-transpile/tests/snapshots/arrays.c b/c2rust-transpile/tests/snapshots/arrays.c index fabd5afea2..8b0881ba22 100644 --- a/c2rust-transpile/tests/snapshots/arrays.c +++ b/c2rust-transpile/tests/snapshots/arrays.c @@ -94,3 +94,11 @@ extern void (*funcs[1])(void); void func_ptr_array(void) { (*funcs[0])(); } + +extern int my_nested_array[2][2]; + +void decay_of_deref(void) { + int *decayed = my_nested_array[1]; + int (*ptr)[2] = &my_nested_array[1]; + int *decayed_ptr = ptr[1]; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap index d9b2bce721..3f69bbbdba 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap @@ -17,6 +17,7 @@ extern "C" { static mut my_indexes: [::core::ffi::c_int; 1]; fn my_index() -> ::core::ffi::c_ulong; static mut funcs: [Option ()>; 1]; + static mut my_nested_array: [[::core::ffi::c_int; 2]; 2]; } pub type ptrdiff_t = isize; pub type size_t = usize; @@ -178,3 +179,13 @@ pub unsafe extern "C" fn addr_of_subscript() { pub unsafe extern "C" fn func_ptr_array() { Some(funcs[0usize].expect("non-null function pointer")).expect("non-null function pointer")(); } +#[no_mangle] +pub unsafe extern "C" fn decay_of_deref() { + let mut decayed: *mut ::core::ffi::c_int = &raw mut *(&raw mut my_nested_array + as *mut [::core::ffi::c_int; 2]) + .offset(1isize) as *mut ::core::ffi::c_int; + let mut ptr: *mut [::core::ffi::c_int; 2] = + (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]).offset(1isize); + let mut decayed_ptr: *mut ::core::ffi::c_int = + &raw mut *ptr.offset(1isize) as *mut ::core::ffi::c_int; +} diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap index 640c1c76dc..a15c0dceb2 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap @@ -17,6 +17,7 @@ unsafe extern "C" { static mut my_indexes: [::core::ffi::c_int; 1]; unsafe fn my_index() -> ::core::ffi::c_ulong; static mut funcs: [Option ()>; 1]; + static mut my_nested_array: [[::core::ffi::c_int; 2]; 2]; } pub type ptrdiff_t = isize; pub type size_t = usize; @@ -178,3 +179,13 @@ pub unsafe extern "C" fn addr_of_subscript() { pub unsafe extern "C" fn func_ptr_array() { Some(funcs[0usize].expect("non-null function pointer")).expect("non-null function pointer")(); } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn decay_of_deref() { + let mut decayed: *mut ::core::ffi::c_int = &raw mut *(&raw mut my_nested_array + as *mut [::core::ffi::c_int; 2]) + .offset(1isize) as *mut ::core::ffi::c_int; + let mut ptr: *mut [::core::ffi::c_int; 2] = + (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]).offset(1isize); + let mut decayed_ptr: *mut ::core::ffi::c_int = + &raw mut *ptr.offset(1isize) as *mut ::core::ffi::c_int; +} From 2704fd8c5c5e4ec10ef18fe46be94cb95fc6edd8 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 24 Aug 2026 14:59:40 +0200 Subject: [PATCH 08/14] transpile: Use `ptr::from_ref` to cast references to pointers --- c2rust-transpile/src/translator/mod.rs | 2 +- c2rust-transpile/src/translator/pointers.rs | 33 ++++++++----------- ...hots__transpile@arrays.c.2021.clang15.snap | 6 ++-- ...hots__transpile@arrays.c.2024.clang15.snap | 4 +-- ...pile@compound_literals.c.2021.clang15.snap | 9 +++-- ...pile@compound_literals.c.2024.clang15.snap | 7 ++-- ...hots__transpile@macros.c.2021.clang15.snap | 4 +-- ...hots__transpile@macros.c.2024.clang15.snap | 2 +- 8 files changed, 30 insertions(+), 37 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index fac7441975..820d19a668 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -1730,7 +1730,7 @@ impl<'c> Translation<'c> { pub fn use_feature(&self, feature: &'static str) { if matches!( feature, - "asm" | "inline_const" | "label_break_value" | "raw_ref_op" + "asm" | "inline_const" | "label_break_value" | "ptr_from_ref" | "raw_ref_op" ) && self.tcfg.edition >= Edition2024 { return; diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index dc89b7c9f0..21c7c0e1be 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -108,7 +108,6 @@ impl<'c> Translation<'c> { let arg_is_macro = arg.map_or(false, |arg| self.expr_is_expanded_macro(ctx, arg, None)); let mut needs_cast = false; - let mut ref_cast_pointee_ty = None; let mutbl = if ctx.is_const && !pointee_cty.qualifiers.is_const { // const contexts aren't able to use &mut, so we work around that // by using & and an extra cast through & to *const to *mut @@ -121,17 +120,17 @@ impl<'c> Translation<'c> { // Narrow string literals are translated directly as `[u8; N]` literals when their address // is taken, without the transmute. String/byte literals are already references in Rust. - if let ( - Some(&CExprKind::Literal(literal_cty, CLiteral::String(_, element_size @ 1))), - false, - ) = (arg_expr_kind, arg_is_macro) + if matches!( + arg_expr_kind, + Some(&CExprKind::Literal(_, CLiteral::String(_, 1))) + ) && !arg_is_macro { if is_array_decay { val = val.map(|val| mk().method_call_expr(val, "as_ptr", vec![])); } else { - let size = self.ast_context.array_len(literal_cty.ctype) * element_size as usize; - ref_cast_pointee_ty = - Some(mk().array_ty(mk().ident_ty("u8"), mk().lit_expr(size as u128))); + self.use_feature("ptr_from_ref"); + let func = mk().abs_path_expr(vec!["core", "ptr", "from_ref"]); + val = val.map(|val| mk().call_expr(func, vec![val])) } needs_cast = true; } @@ -157,11 +156,13 @@ impl<'c> Translation<'c> { } else { val = val.map(|val| mk().set_mutbl(mutbl).borrow_expr(val)); - // Add an intermediate reference-to-pointer cast if the context needs - // reference-to-pointer decay, or if another cast follows. - if ctx.decay_ref.is_yes() || needs_cast { - ref_cast_pointee_ty = Some(self.convert_pointee_type(arg_cty.ctype)?); - } + self.use_feature("ptr_from_ref"); + let func = match mutbl { + Mutability::Mutable => "from_mut", + Mutability::Immutable => "from_ref", + }; + let func = mk().abs_path_expr(vec!["core", "ptr", func]); + val = val.map(|val| mk().call_expr(func, vec![val])); } } else { self.use_feature("raw_ref_op"); @@ -174,12 +175,6 @@ impl<'c> Translation<'c> { } } - // Perform an intermediate reference-to-pointer cast if needed. - // TODO: Rust 1.76: Use `ptr::from_ref`. - if let Some(pointee_ty) = ref_cast_pointee_ty { - val = val.map(|val| mk().cast_expr(val, mk().set_mutbl(mutbl).ptr_ty(pointee_ty))); - } - // Perform a final cast to the target type if needed. if needs_cast { let pointer_ty = self.convert_type(pointer_cty.ctype)?; diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap index 3f69bbbdba..2d769c5114 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap @@ -11,7 +11,7 @@ expression: cat tests/snapshots/arrays.2021.clang15.rs unused_assignments, unused_mut )] -#![feature(raw_ref_op)] +#![feature(ptr_from_ref, raw_ref_op)] extern "C" { static mut my_array: [::core::ffi::c_int; 1]; static mut my_indexes: [::core::ffi::c_int; 1]; @@ -101,10 +101,10 @@ pub unsafe extern "C" fn entry() { let mut const_char_lit_ptr: *const ::core::ffi::c_char = b"abc\0".as_ptr() as *const ::core::ffi::c_char; let mut const_char_lit_array_ptr: *const [::core::ffi::c_char; 4] = - b"abc\0" as *const [u8; 4] as *const [::core::ffi::c_char; 4]; + ::core::ptr::from_ref(b"abc\0") as *const [::core::ffi::c_char; 4]; let mut char_lit_ptr: *mut ::core::ffi::c_char = b"abc\0".as_ptr() as *const ::core::ffi::c_char as *mut ::core::ffi::c_char; - let mut char_lit_array_ptr: *mut [::core::ffi::c_char; 4] = b"abc\0" as *const [u8; 4] + let mut char_lit_array_ptr: *mut [::core::ffi::c_char; 4] = ::core::ptr::from_ref(b"abc\0") as *const [::core::ffi::c_char; 4] as *mut [::core::ffi::c_char; 4]; let mut past_end: *mut ::core::ffi::c_char = (&raw mut static_char_array diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap index a15c0dceb2..4482861750 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap @@ -101,10 +101,10 @@ pub unsafe extern "C" fn entry() { let mut const_char_lit_ptr: *const ::core::ffi::c_char = b"abc\0".as_ptr() as *const ::core::ffi::c_char; let mut const_char_lit_array_ptr: *const [::core::ffi::c_char; 4] = - b"abc\0" as *const [u8; 4] as *const [::core::ffi::c_char; 4]; + ::core::ptr::from_ref(b"abc\0") as *const [::core::ffi::c_char; 4]; let mut char_lit_ptr: *mut ::core::ffi::c_char = b"abc\0".as_ptr() as *const ::core::ffi::c_char as *mut ::core::ffi::c_char; - let mut char_lit_array_ptr: *mut [::core::ffi::c_char; 4] = b"abc\0" as *const [u8; 4] + let mut char_lit_array_ptr: *mut [::core::ffi::c_char; 4] = ::core::ptr::from_ref(b"abc\0") as *const [::core::ffi::c_char; 4] as *mut [::core::ffi::c_char; 4]; let mut past_end: *mut ::core::ffi::c_char = (&raw mut static_char_array diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2021.clang15.snap index f9a7cb7388..e46c588b06 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2021.clang15.snap @@ -11,7 +11,7 @@ expression: cat tests/snapshots/compound_literals.2021.clang15.rs unused_assignments, unused_mut )] -#![feature(raw_ref_op)] +#![feature(ptr_from_ref, raw_ref_op)] #[no_mangle] pub static mut static_single_int: ::core::ffi::c_int = 42; static mut c2rust_lvalue: ::core::ffi::c_int = 42; @@ -65,14 +65,13 @@ pub unsafe extern "C" fn local_compound_literals() { ::core::mem::transmute::<[u8; 6], [::core::ffi::c_char; 6]>(*b"hello\0"); let mut char_array_ptr: *mut [::core::ffi::c_char; 6] = &raw mut c2rust_lvalue_9; let mut macro_single_int: ::core::ffi::c_int = SINGLE_INT; - let mut macro_single_int_ptr: *mut ::core::ffi::c_int = - &mut SINGLE_INT as *mut ::core::ffi::c_int; + let mut macro_single_int_ptr: *mut ::core::ffi::c_int = ::core::ptr::from_mut(&mut SINGLE_INT); let mut macro_int_ptr_to_array: *mut ::core::ffi::c_int = INT_ARRAY.as_mut_ptr(); let mut macro_char_ptr_to_array: *mut ::core::ffi::c_char = CHAR_ARRAY.as_mut_ptr(); let mut macro_int_array_ptr: *mut [::core::ffi::c_int; 2] = - &mut INT_ARRAY as *mut [::core::ffi::c_int; 2]; + ::core::ptr::from_mut(&mut INT_ARRAY); let mut macro_char_array_ptr: *mut [::core::ffi::c_char; 6] = - &mut CHAR_ARRAY as *mut [::core::ffi::c_char; 6]; + ::core::ptr::from_mut(&mut CHAR_ARRAY); } #[no_mangle] pub unsafe extern "C" fn deref_addrof() { diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2024.clang15.snap index c005935de1..0b144a6fe2 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@compound_literals.c.2024.clang15.snap @@ -65,14 +65,13 @@ pub unsafe extern "C" fn local_compound_literals() { ::core::mem::transmute::<[u8; 6], [::core::ffi::c_char; 6]>(*b"hello\0"); let mut char_array_ptr: *mut [::core::ffi::c_char; 6] = &raw mut c2rust_lvalue_9; let mut macro_single_int: ::core::ffi::c_int = SINGLE_INT; - let mut macro_single_int_ptr: *mut ::core::ffi::c_int = - &mut SINGLE_INT as *mut ::core::ffi::c_int; + let mut macro_single_int_ptr: *mut ::core::ffi::c_int = ::core::ptr::from_mut(&mut SINGLE_INT); let mut macro_int_ptr_to_array: *mut ::core::ffi::c_int = INT_ARRAY.as_mut_ptr(); let mut macro_char_ptr_to_array: *mut ::core::ffi::c_char = CHAR_ARRAY.as_mut_ptr(); let mut macro_int_array_ptr: *mut [::core::ffi::c_int; 2] = - &mut INT_ARRAY as *mut [::core::ffi::c_int; 2]; + ::core::ptr::from_mut(&mut INT_ARRAY); let mut macro_char_array_ptr: *mut [::core::ffi::c_char; 6] = - &mut CHAR_ARRAY as *mut [::core::ffi::c_char; 6]; + ::core::ptr::from_mut(&mut CHAR_ARRAY); } #[unsafe(no_mangle)] pub unsafe extern "C" fn deref_addrof() { diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2021.clang15.snap index 7e82464c90..a34f4aa420 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2021.clang15.snap @@ -11,7 +11,7 @@ expression: cat tests/snapshots/macros.2021.clang15.rs unused_assignments, unused_mut )] -#![feature(raw_ref_op)] +#![feature(ptr_from_ref, raw_ref_op)] extern "C" { fn extern_fn() -> ::core::ffi::c_int; } @@ -82,7 +82,7 @@ pub const REF_MACRO: *const ::core::ffi::c_char = unsafe { .as_ptr() .offset(LITERAL_FLOAT as ::core::ffi::c_int as isize) }; -pub const REF_LITERAL: *mut S = &LITERAL_STRUCT as *const S as *mut S; +pub const REF_LITERAL: *mut S = ::core::ptr::from_ref(&LITERAL_STRUCT) as *mut S; pub const TERNARY: ::core::ffi::c_int = if LITERAL_BOOL != 0 { 1 as ::core::ffi::c_int } else { diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2024.clang15.snap index 1515d75618..8294df1678 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@macros.c.2024.clang15.snap @@ -82,7 +82,7 @@ pub const REF_MACRO: *const ::core::ffi::c_char = unsafe { .as_ptr() .offset(LITERAL_FLOAT as ::core::ffi::c_int as isize) }; -pub const REF_LITERAL: *mut S = &LITERAL_STRUCT as *const S as *mut S; +pub const REF_LITERAL: *mut S = ::core::ptr::from_ref(&LITERAL_STRUCT) as *mut S; pub const TERNARY: ::core::ffi::c_int = if LITERAL_BOOL != 0 { 1 as ::core::ffi::c_int } else { From 283ec0f232a46ec19790624260e0e16bd61e1032 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 17:05:44 +0200 Subject: [PATCH 09/14] transpile: Remove `decay_ref`, which is now written but never read --- c2rust-transpile/src/translator/functions.rs | 5 +- c2rust-transpile/src/translator/mod.rs | 74 +++----------------- c2rust-transpile/src/translator/operators.rs | 19 ----- c2rust-transpile/src/translator/pointers.rs | 14 +--- docs/source_walkthrough.md | 5 -- 5 files changed, 13 insertions(+), 104 deletions(-) diff --git a/c2rust-transpile/src/translator/functions.rs b/c2rust-transpile/src/translator/functions.rs index 01593fb623..15f9910621 100644 --- a/c2rust-transpile/src/translator/functions.rs +++ b/c2rust-transpile/src/translator/functions.rs @@ -352,7 +352,7 @@ impl<'c> Translation<'c> { pub fn convert_function_call( &self, - mut ctx: ExprContext, + ctx: ExprContext, func: CExprId, args: &[CExprId], call_expr_ty: CQualTypeId, @@ -443,9 +443,6 @@ impl<'c> Translation<'c> { }; let call = func.and_then_try(|func| { - // We want to decay refs only when function is variadic - ctx.decay_ref = DecayRef::from(is_variadic); - let args = self.convert_call_args(ctx.used(), args, arg_tys.as_deref(), is_variadic)?; let call_expr = args.map(|args| mk().call_expr(func, args)); diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 820d19a668..04575c55ef 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -74,45 +74,6 @@ struct Import { ident_name: String, } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum DecayRef { - Yes, - Default, - No, -} - -impl DecayRef { - // Here we give intrinsic meaning to default to equate to yes/true - // when actually evaluated - pub fn is_yes(&self) -> bool { - match self { - DecayRef::Yes => true, - DecayRef::Default => true, - DecayRef::No => false, - } - } - - #[inline] - pub fn is_no(&self) -> bool { - !self.is_yes() - } - - pub fn set_default_to_no(&mut self) { - if *self == DecayRef::Default { - *self = DecayRef::No; - } - } -} - -impl From for DecayRef { - fn from(b: bool) -> Self { - match b { - true => DecayRef::Yes, - false => DecayRef::No, - } - } -} - #[derive(Debug, Copy, Clone)] pub enum ReplaceMode { None, @@ -138,7 +99,6 @@ pub struct ExprContext { #[allow(dead_code)] is_static: bool, - decay_ref: DecayRef, is_bitfield_write: bool, /// We will be referring to the expression by address. In this context we @@ -167,12 +127,7 @@ impl ExprContext { pub fn is_unused(&self) -> bool { !self.used } - pub fn decay_ref(self) -> Self { - ExprContext { - decay_ref: DecayRef::Yes, - ..self - } - } + pub fn const_(self) -> Self { ExprContext { is_const: true, @@ -875,7 +830,6 @@ pub fn translate( is_const: false, is_pattern: false, is_static: false, - decay_ref: DecayRef::Default, is_bitfield_write: false, needs_address: false, ternary_needs_parens: false, @@ -2472,7 +2426,7 @@ impl<'c> Translation<'c> { let null_pointer_case = |ptr: CExprId, is_null: bool| -> TranslationResult>> { - let val = self.convert_expr(ctx.used().decay_ref(), ptr, None)?; + let val = self.convert_expr(ctx.used(), ptr, None)?; let ptr_type = self .ast_context .index_unwrap_parens(ptr) @@ -2523,11 +2477,7 @@ impl<'c> Translation<'c> { } _ => { - // DecayRef could (and probably should) be Default instead of Yes here; however, as noted - // in https://github.com/rust-lang/rust/issues/53772, you cant compare a reference (lhs) to - // a ptr (rhs) (even though the reverse works!). We could also be smarter here and just - // specify Yes for that particular case, given enough analysis. - let val = self.convert_expr(ctx.used().decay_ref(), cond_id, None)?; + let val = self.convert_expr(ctx.used(), cond_id, None)?; val.try_map(|e| self.match_bool(ctx, target, ty_id, e)) } } @@ -2856,7 +2806,6 @@ impl<'c> Translation<'c> { }; } - // ref decayed ptrs generally need a type annotation if let Some(CExprKind::Unary(_, CUnOp::AddressOf, _, _)) = initializer_kind { return true; @@ -3987,18 +3936,13 @@ impl<'c> Translation<'c> { return self.convert_expr(ctx, expr, Some(target_ty)); } - match kind { - // A reference must be decayed if a bitcast is required. Const casts in - // LLVM 8 are now NoOp casts, so we need to include it as well. - CastKind::BitCast | CastKind::PointerToIntegral | CastKind::NoOp => { - ctx.decay_ref = DecayRef::Yes - } + if matches!( + kind, CastKind::ArrayToPointerDecay - | CastKind::FunctionToPointerDecay - | CastKind::BuiltinFnToFnPtr => { - ctx.needs_address = true; - } - _ => {} + | CastKind::FunctionToPointerDecay + | CastKind::BuiltinFnToFnPtr + ) { + ctx.needs_address = true; } let mut val = self.convert_expr(ctx, expr, None)?; diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 31dc38b668..738de27d82 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -59,14 +59,6 @@ impl<'c> Translation<'c> { ), _ => { - // Comparing references to pointers isn't consistently supported by rust - // and so we need to decay references to pointers to do so. See - // https://github.com/rust-lang/rust/issues/53772. This might be removable - // once the above issue is resolved. - if op == CBinOp::EqualEqual || op == CBinOp::NotEqual { - ctx = ctx.decay_ref(); - } - let lhs_kind = &self.ast_context.index_unwrap_parens(lhs).kind; let mut lhs_type_id = lhs_kind.get_qual_type().ok_or_else(|| { format_translation_err!( @@ -127,17 +119,6 @@ impl<'c> Translation<'c> { } else { let rhs_ctx = ctx; - // When we use methods on pointers (ie wrapping_offset_from or offset) - // we must ensure we have an explicit raw ptr for the self param, as - // self references do not decay - if op.is_pointer_arithmetic() { - let ty_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind; - - if let CTypeKind::Pointer(_) = ty_kind { - ctx = ctx.decay_ref(); - } - } - // Using `.is_none()` and `.is_some()` for null comparison means we don't // have to rely on `trait PartialEq` as much and it is also more idiomatic. if matches!(op, CBinOp::EqualEqual | CBinOp::NotEqual) { diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 21c7c0e1be..7da16c4c65 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -18,7 +18,7 @@ use crate::{ impl<'c> Translation<'c> { pub fn convert_address_of( &self, - mut ctx: ExprContext, + ctx: ExprContext, cqual_type: CQualTypeId, arg: CExprId, ) -> TranslationResult>> { @@ -40,10 +40,6 @@ impl<'c> Translation<'c> { false, // don't deref, keep as pointer ); } - // An AddrOf DeclRef/Member is safe to not decay - // if the translator isn't already giving a hard yes to decaying (ie, BitCasts). - // So we only convert default to no decay. - CExprKind::DeclRef(..) | CExprKind::Member(..) => ctx.decay_ref.set_default_to_no(), _ => (), } @@ -326,12 +322,8 @@ impl<'c> Translation<'c> { } }; - // LHS must be ref decayed for the offset method call's self param - let pointer_rs = self.convert_expr( - ctx.used().set_needs_address(false).decay_ref(), - pointer_id, - None, - )?; + let pointer_rs = + self.convert_expr(ctx.used().set_needs_address(false), pointer_id, None)?; let target_type_id = self.ast_context.type_for_kind(&CTypeKind::SSize); let offset_rs = self.convert_expr_with_cast( ctx.used().set_needs_address(false), diff --git a/docs/source_walkthrough.md b/docs/source_walkthrough.md index 9b07d228fa..9eae39d6d4 100644 --- a/docs/source_walkthrough.md +++ b/docs/source_walkthrough.md @@ -218,11 +218,6 @@ This allows emitting different code in such situations, to account for the different lifetimes of compound literals for example. `is_static` usually implies `is_const`, but not always. -The `decay_ref` attribute keeps track of whether or not -we're in a context in which Rust will infer that a reference can decay in to a pointer. -This can happen at method calls, variable initializers, and possibly more locations. -This allows the translation to omit some otherwise superfluous casts. - The `va_decl` attribute indicates which, if any, declaration corresponds to the variable-argument list for the current variadic function. This enables us to drop the associated declaration, From 246c8092d34b8ff50270d9c4989da72a74e6f2d3 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 16:35:16 +0200 Subject: [PATCH 10/14] transpile: Refactor `convert_address_of_common` --- c2rust-transpile/src/translator/pointers.rs | 53 +++++++++++---------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 7da16c4c65..9ac9997843 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -114,43 +114,44 @@ impl<'c> Translation<'c> { pointee_cty.mutability() }; - // Narrow string literals are translated directly as `[u8; N]` literals when their address - // is taken, without the transmute. String/byte literals are already references in Rust. - if matches!( - arg_expr_kind, - Some(&CExprKind::Literal(_, CLiteral::String(_, 1))) - ) && !arg_is_macro - { - if is_array_decay { - val = val.map(|val| mk().method_call_expr(val, "as_ptr", vec![])); - } else { - self.use_feature("ptr_from_ref"); - let func = mk().abs_path_expr(vec!["core", "ptr", "from_ref"]); - val = val.map(|val| mk().call_expr(func, vec![val])) - } - needs_cast = true; - } // Values that translate into const temporaries can't be raw-borrowed in Rust. // They must be regular-borrowed first, which will extend the lifetime to static. - else if arg_is_macro || matches!(arg_expr_kind, Some(CExprKind::Literal(..))) { - let arg_cty_kind = &self.ast_context.resolve_type(arg_cty.ctype).kind; + if arg_is_macro || matches!(arg_expr_kind, Some(CExprKind::Literal(..))) { + // Narrow string literals are translated directly as `[u8; N]` literals + // when their address is taken, without the transmute. + // String/byte literals are already references in Rust. + let is_byte_string_literal = matches!( + arg_expr_kind, + Some(&CExprKind::Literal(_, CLiteral::String(_, 1))) + ) && !arg_is_macro; if is_array_decay { + if is_byte_string_literal { + needs_cast = true; + } else { + let arg_type_kind = &self.ast_context.resolve_type(arg_cty.ctype).kind; + let arg_element_type_id = arg_type_kind.element_ty().ok_or_else(|| { + TranslationError::generic("Array decay should have array argument") + })?; + + // If the target pointee type is different from the source element type, + // then we need to cast the ptr type as well. + if arg_element_type_id != pointee_cty.ctype { + needs_cast = true; + } + } + let method = match mutbl { Mutability::Mutable => "as_mut_ptr", Mutability::Immutable => "as_ptr", }; val = val.map(|val| mk().method_call_expr(val, method, vec![])); - - // If the target pointee type is different from the source element type, - // then we need to cast the ptr type as well. - if arg_cty_kind.element_ty().map_or(false, |arg_element_cty| { - arg_element_cty != pointee_cty.ctype - }) { + } else { + if is_byte_string_literal { needs_cast = true; + } else { + val = val.map(|val| mk().set_mutbl(mutbl).borrow_expr(val)); } - } else { - val = val.map(|val| mk().set_mutbl(mutbl).borrow_expr(val)); self.use_feature("ptr_from_ref"); let func = match mutbl { From ac449ba41ecaf789eb93879a0336a7158441a2ab Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 16:53:14 +0200 Subject: [PATCH 11/14] transpile: In `convert_address_of_common`, cast based on type kinds --- c2rust-ast-builder/src/builder.rs | 2 +- c2rust-transpile/src/c_ast/mod.rs | 12 ++++- c2rust-transpile/src/translator/pointers.rs | 55 ++++++++++++--------- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/c2rust-ast-builder/src/builder.rs b/c2rust-ast-builder/src/builder.rs index 89bc092b2c..98c270ebd9 100644 --- a/c2rust-ast-builder/src/builder.rs +++ b/c2rust-ast-builder/src/builder.rs @@ -17,7 +17,7 @@ pub mod properties { fn to_token(&self) -> Option; } - #[derive(Debug, Copy, Clone)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Mutability { Mutable, Immutable, diff --git a/c2rust-transpile/src/c_ast/mod.rs b/c2rust-transpile/src/c_ast/mod.rs index f412c31234..97fc067f1c 100644 --- a/c2rust-transpile/src/c_ast/mod.rs +++ b/c2rust-transpile/src/c_ast/mod.rs @@ -3355,9 +3355,17 @@ impl CTypeKind { } /// Return the element type of a pointer or array - pub fn element_ty(&self) -> Option { + pub(crate) fn array_element_type(&self) -> Option { Some(match *self { - Self::Pointer(ty) => ty.ctype, + Self::ConstantArray(ty, _) => ty, + Self::IncompleteArray(ty) => ty, + Self::VariableArray(ty, _) => ty, + _ => return None, + }) + } + + pub(crate) fn array_element_type_mut(&mut self) -> Option<&mut CTypeId> { + Some(match self { Self::ConstantArray(ty, _) => ty, Self::IncompleteArray(ty) => ty, Self::VariableArray(ty, _) => ty, diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 9ac9997843..53ad252941 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -1,10 +1,12 @@ +use std::borrow::Cow; + use c2rust_ast_builder::{mk, properties::Mutability}; use c2rust_ast_exporter::clang_ast::LRValue; use c2rust_rust_tools::RustEdition; use failure::{err_msg, format_err}; use syn::{BinOp, Expr, Type, UnOp}; -use crate::c_ast::CUnOp; +use crate::c_ast::{CUnOp, TypedAstContext}; use crate::{ diagnostics::{TranslationError, TranslationErrorKind, TranslationResult}, format_translation_err, @@ -103,17 +105,18 @@ impl<'c> Translation<'c> { .ok_or_else(|| TranslationError::generic("Address-of should return a pointer"))?; let arg_is_macro = arg.map_or(false, |arg| self.expr_is_expanded_macro(ctx, arg, None)); - let mut needs_cast = false; let mutbl = if ctx.is_const && !pointee_cty.qualifiers.is_const { // const contexts aren't able to use &mut, so we work around that // by using & and an extra cast through & to *const to *mut // TODO: Rust 1.83: Allowed, so this can be removed. - needs_cast = true; Mutability::Immutable } else { pointee_cty.mutability() }; + let mut cast_source_type_kind = + Cow::Borrowed(&self.ast_context.resolve_type(arg_cty.ctype).kind); + // Values that translate into const temporaries can't be raw-borrowed in Rust. // They must be regular-borrowed first, which will extend the lifetime to static. if arg_is_macro || matches!(arg_expr_kind, Some(CExprKind::Literal(..))) { @@ -127,18 +130,15 @@ impl<'c> Translation<'c> { if is_array_decay { if is_byte_string_literal { - needs_cast = true; + cast_source_type_kind = Cow::Borrowed(&CTypeKind::UInt8); } else { - let arg_type_kind = &self.ast_context.resolve_type(arg_cty.ctype).kind; - let arg_element_type_id = arg_type_kind.element_ty().ok_or_else(|| { - TranslationError::generic("Array decay should have array argument") - })?; - - // If the target pointee type is different from the source element type, - // then we need to cast the ptr type as well. - if arg_element_type_id != pointee_cty.ctype { - needs_cast = true; - } + let Some(element_type_id) = cast_source_type_kind.array_element_type() else { + return Err(TranslationError::generic( + "Argument of array decay should have array type" + )); + }; + cast_source_type_kind = + Cow::Borrowed(&self.ast_context.resolve_type(element_type_id).kind); } let method = match mutbl { @@ -148,7 +148,14 @@ impl<'c> Translation<'c> { val = val.map(|val| mk().method_call_expr(val, method, vec![])); } else { if is_byte_string_literal { - needs_cast = true; + let Some(element_type_id) = + cast_source_type_kind.to_mut().array_element_type_mut() + else { + return Err(TranslationError::generic( + "String literal should have array type", + )); + }; + *element_type_id = self.ast_context.type_for_kind(&CTypeKind::UInt8); } else { val = val.map(|val| mk().set_mutbl(mutbl).borrow_expr(val)); } @@ -164,16 +171,18 @@ impl<'c> Translation<'c> { } else { self.use_feature("raw_ref_op"); val = val.map(|val| mk().set_mutbl(mutbl).raw_borrow_expr(val)); - - if is_array_decay { - // TODO: Call `ptr::as_[mut]_ptr` instead once that is available. - // (`array_ptr_get` feature added to nightly in January 2024) - needs_cast = true; - } } - // Perform a final cast to the target type if needed. - if needs_cast { + let pointee_type_kind = &self.ast_context.resolve_type(pointee_cty.ctype).kind; + + // If the target pointee type is different from the source type, + // then we need to cast the pointer type. + if !self.ast_context.type_kinds_eq( + pointee_type_kind, + &cast_source_type_kind, + &TypedAstContext::resolve_type_id, + ) || pointee_cty.mutability() != mutbl + { let pointer_ty = self.convert_type(pointer_cty.ctype)?; val = val.map(|val| mk().cast_expr(val, pointer_ty)); } From 1c64223e4b93ba7cd5e2c0ec6ce330349b152698 Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 17:16:23 +0200 Subject: [PATCH 12/14] transpile: Merge `convert_array_to_pointer_decay` and `convert_address_of_common` --- c2rust-transpile/src/translator/mod.rs | 2 +- c2rust-transpile/src/translator/pointers.rs | 51 +++++++++------------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 04575c55ef..8138246e16 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -4188,7 +4188,7 @@ impl<'c> Translation<'c> { } CastKind::ArrayToPointerDecay => { - self.convert_array_to_pointer_decay(ctx, source_cty, target_cty, val, expr) + self.make_address_of(ctx, target_cty, source_cty, expr, val, true) } CastKind::NullToPointer => { diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 53ad252941..bce3a777f8 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -56,45 +56,36 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad source type"))?; - self.convert_address_of_common(ctx, Some(arg), arg_cty, cqual_type, val, false) + self.make_address_of(ctx, cqual_type, arg_cty, Some(arg), val, false) } - pub fn convert_array_to_pointer_decay( + pub(crate) fn make_address_of( &self, ctx: ExprContext, - source_cty: CQualTypeId, - target_cty: CQualTypeId, - val: WithStmts>, - expr: Option, + pointer_cty: CQualTypeId, + arg_cty: CQualTypeId, + arg: Option, + mut val: WithStmts>, + is_array_decay: bool, ) -> TranslationResult>> { - // Because va_list is sometimes defined as a single-element - // array in order for it to allocate memory as a local variable - // and to be a pointer as a function argument we would get - // spurious casts when trying to treat it like a VaList which - // has reference semantics. - if self.ast_context.is_va_list(target_cty.ctype) { - return Ok(val); - } + if is_array_decay { + // Because va_list is sometimes defined as a single-element + // array in order for it to allocate memory as a local variable + // and to be a pointer as a function argument we would get + // spurious casts when trying to treat it like a VaList which + // has reference semantics. + if self.ast_context.is_va_list(pointer_cty.ctype) { + return Ok(val); + } - let source_ty_kind = &self.ast_context.resolve_type(source_cty.ctype).kind; + let source_ty_kind = &self.ast_context.resolve_type(arg_cty.ctype).kind; - // Variable length arrays are already represented as pointers. - if let CTypeKind::VariableArray(..) = source_ty_kind { - return Ok(val); + // Variable length arrays are already represented as pointers. + if let CTypeKind::VariableArray(..) = source_ty_kind { + return Ok(val); + } } - self.convert_address_of_common(ctx, expr, source_cty, target_cty, val, true) - } - - fn convert_address_of_common( - &self, - ctx: ExprContext, - arg: Option, - arg_cty: CQualTypeId, - pointer_cty: CQualTypeId, - mut val: WithStmts>, - is_array_decay: bool, - ) -> TranslationResult>> { let arg_expr_kind = arg.map(|arg| { let arg = self.ast_context.unwrap_predefined_ident(arg); &self.ast_context.index_unwrap_parens(arg).kind From 2f12e6b5a0dc6a48b083978d110b57b68911485a Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 18:30:46 +0200 Subject: [PATCH 13/14] transpile: Move `is_function_pointer` check to `make_address_of` --- c2rust-transpile/src/translator/pointers.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index bce3a777f8..5b133ee76e 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -47,11 +47,6 @@ impl<'c> Translation<'c> { let val = self.convert_expr(ctx.used().set_needs_address(true), arg, None)?; - // & becomes a no-op when applied to a function. - if self.ast_context.is_function_pointer(cqual_type.ctype) { - return Ok(val.map(|x| mk().call_expr(mk().ident_expr("Some"), vec![x]))); - } - let arg_cty = arg_kind .get_qual_type() .ok_or_else(|| format_err!("bad source type"))?; @@ -84,6 +79,11 @@ impl<'c> Translation<'c> { if let CTypeKind::VariableArray(..) = source_ty_kind { return Ok(val); } + } else { + // & becomes a no-op when applied to a function. + if self.ast_context.is_function_pointer(pointer_cty.ctype) { + return Ok(val.map(|x| mk().call_expr(mk().ident_expr("Some"), vec![x]))); + } } let arg_expr_kind = arg.map(|arg| { From 509de7a9421e5a825dea5a7d115d8e206f2938eb Mon Sep 17 00:00:00 2001 From: Rua Date: Tue, 25 Aug 2026 18:55:20 +0200 Subject: [PATCH 14/14] transpile: Use `convert_address_of` for array decay too --- c2rust-transpile/src/translator/mod.rs | 8 +++++--- c2rust-transpile/src/translator/operators.rs | 2 +- c2rust-transpile/src/translator/pointers.rs | 5 +++-- .../snapshots__transpile@arrays.c.2021.clang15.snap | 5 ++--- .../snapshots__transpile@arrays.c.2024.clang15.snap | 5 ++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 8138246e16..b2c87543f8 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -3918,6 +3918,10 @@ impl<'c> Translation<'c> { return self.make_cast(ctx, source_ty.not_volatile(), target_ty, val); } + CastKind::ArrayToPointerDecay => { + return self.convert_address_of(ctx, target_ty, expr, true); + } + CastKind::IntegralToBoolean | CastKind::FloatingToBoolean | CastKind::PointerToBoolean => { @@ -3938,9 +3942,7 @@ impl<'c> Translation<'c> { if matches!( kind, - CastKind::ArrayToPointerDecay - | CastKind::FunctionToPointerDecay - | CastKind::BuiltinFnToFnPtr + CastKind::FunctionToPointerDecay | CastKind::BuiltinFnToFnPtr ) { ctx.needs_address = true; } diff --git a/c2rust-transpile/src/translator/operators.rs b/c2rust-transpile/src/translator/operators.rs index 738de27d82..b1a0a4e68b 100644 --- a/c2rust-transpile/src/translator/operators.rs +++ b/c2rust-transpile/src/translator/operators.rs @@ -560,7 +560,7 @@ impl<'c> Translation<'c> { ) -> TranslationResult>> { let expr_type_id = expected_type_id.unwrap_or(result_type_id); let mut unary = match op { - CUnOp::AddressOf => self.convert_address_of(ctx, expr_type_id, arg), + CUnOp::AddressOf => self.convert_address_of(ctx, expr_type_id, arg, false), CUnOp::PreIncrement | CUnOp::PreDecrement diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 5b133ee76e..7c883d78bb 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -23,13 +23,14 @@ impl<'c> Translation<'c> { ctx: ExprContext, cqual_type: CQualTypeId, arg: CExprId, + is_array_decay: bool, ) -> TranslationResult>> { let arg_kind = &self.ast_context.index_unwrap_parens(arg).kind; match arg_kind { // C99 6.5.3.2 para 4 CExprKind::Unary(_, CUnOp::Deref, target, _) => { - return self.convert_expr(ctx, *target, None) + return self.convert_expr(ctx, *target, Some(cqual_type)) } // Array subscript functions as a deref too. &CExprKind::ArraySubscript(_, lhs, rhs, _) => { @@ -51,7 +52,7 @@ impl<'c> Translation<'c> { .get_qual_type() .ok_or_else(|| format_err!("bad source type"))?; - self.make_address_of(ctx, cqual_type, arg_cty, Some(arg), val, false) + self.make_address_of(ctx, cqual_type, arg_cty, Some(arg), val, is_array_decay) } pub(crate) fn make_address_of( diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap index 2d769c5114..2b92fb215f 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2021.clang15.snap @@ -181,11 +181,10 @@ pub unsafe extern "C" fn func_ptr_array() { } #[no_mangle] pub unsafe extern "C" fn decay_of_deref() { - let mut decayed: *mut ::core::ffi::c_int = &raw mut *(&raw mut my_nested_array + let mut decayed: *mut ::core::ffi::c_int = (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]) .offset(1isize) as *mut ::core::ffi::c_int; let mut ptr: *mut [::core::ffi::c_int; 2] = (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]).offset(1isize); - let mut decayed_ptr: *mut ::core::ffi::c_int = - &raw mut *ptr.offset(1isize) as *mut ::core::ffi::c_int; + let mut decayed_ptr: *mut ::core::ffi::c_int = ptr.offset(1isize) as *mut ::core::ffi::c_int; } diff --git a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap index 4482861750..051454ce86 100644 --- a/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap +++ b/c2rust-transpile/tests/snapshots/snapshots__transpile@arrays.c.2024.clang15.snap @@ -181,11 +181,10 @@ pub unsafe extern "C" fn func_ptr_array() { } #[unsafe(no_mangle)] pub unsafe extern "C" fn decay_of_deref() { - let mut decayed: *mut ::core::ffi::c_int = &raw mut *(&raw mut my_nested_array + let mut decayed: *mut ::core::ffi::c_int = (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]) .offset(1isize) as *mut ::core::ffi::c_int; let mut ptr: *mut [::core::ffi::c_int; 2] = (&raw mut my_nested_array as *mut [::core::ffi::c_int; 2]).offset(1isize); - let mut decayed_ptr: *mut ::core::ffi::c_int = - &raw mut *ptr.offset(1isize) as *mut ::core::ffi::c_int; + let mut decayed_ptr: *mut ::core::ffi::c_int = ptr.offset(1isize) as *mut ::core::ffi::c_int; }