From e75e87d03750bf0889632c409fd1562fc025ed5b Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:08:45 +0100 Subject: [PATCH 01/15] Add dummy ByteRepr for opaque structs --- cpp2rust/converter/converter.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 65929016..d2cce119 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -58,9 +58,8 @@ use std::rc::Rc; std::string Converter::EmitOpaqueRecords() { std::string out; record_decls_.ForEachUndefined([&](const std::string &name) { - out += "pub struct "; - out += name; - out += ";\n"; + out += std::format("pub struct {};\n", name); + out += std::format("impl ByteRepr for {} {{}}\n", name); }); return out; } From 7e10a1d0f17bdcd5c30d48c0e6d3d27d6f709199 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:11:27 +0100 Subject: [PATCH 02/15] Add ByteRepr for Ptr and AnyPtr --- libcc2rs/src/rc.rs | 108 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index f13be101..696c38ee 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -3,7 +3,7 @@ use crate::{PostfixDec, PostfixInc, PrefixDec, PrefixInc}; use std::any::{Any, TypeId}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::{ cell::{Ref, RefCell, RefMut}, @@ -1208,8 +1208,110 @@ impl AsPointerDyn for Rc> { } } -impl ByteRepr for Ptr {} -impl ByteRepr for AnyPtr {} +type RealAddr = usize; +type SyntheticAddr = usize; +type ByteLen = usize; + +struct RangeAllocator { + cursor: SyntheticAddr, + bases: HashMap, +} + +impl RangeAllocator { + fn new() -> Self { + Self { + // Increase this if you need higher alignment. In general, malloc returns + // 16-bits-aligned pointers, but that's not relevant for now. + cursor: 1, + bases: HashMap::new(), + } + } + + fn base_for(&mut self, real_addr: RealAddr, byte_len: ByteLen) -> SyntheticAddr { + if let Some(&(base, capacity)) = self.bases.get(&real_addr) { + if byte_len <= capacity { + return base; + } + } + let base = self.cursor; + self.cursor = base + byte_len + 1; + self.bases.insert(real_addr, (base, byte_len)); + base + } +} + +thread_local! { + static PTR_RANGE_ALLOC: RefCell = RefCell::new(RangeAllocator::new()); + static PTR_REGISTRY: RefCell> = + const { RefCell::new(BTreeMap::new()) }; +} + +impl ByteRepr for Ptr { + fn byte_size() -> usize { + std::mem::size_of::() + } + + fn to_bytes(&self, buf: &mut [u8]) { + if self.is_null() { + 0usize.to_bytes(buf); + return; + } + let (byte_off, byte_len) = match &self.kind { + PtrKind::Reinterpreted(data) => (self.offset, data.alloc.total_byte_len()), + _ => ( + self.offset.wrapping_mul(T::byte_size()), + self.len() * T::byte_size(), + ), + }; + let base = + PTR_RANGE_ALLOC.with(|a| a.borrow_mut().base_for(self.kind.address(), byte_len)); + let rebased = Ptr { + offset: 0, + kind: self.kind.clone(), + }; + PTR_REGISTRY.with(|r| { + r.borrow_mut().insert(base, (rebased.to_any(), byte_len)); + }); + base.wrapping_add(byte_off).to_bytes(buf); + } + + fn from_bytes(buf: &[u8]) -> Self { + let addr = usize::from_bytes(buf); + if addr == 0 { + return Ptr::null(); + } + let entry = PTR_REGISTRY.with(|r| { + r.borrow() + .range(..=addr) + .next_back() + .map(|(base, (any, len))| (*base, any.clone(), *len)) + }); + let Some((base, any, byte_len)) = entry else { + panic!("ub: cast of invalid address 0x{addr:x} to pointer"); + }; + let delta = addr - base; + if delta > byte_len { + panic!("ub: cast of invalid address 0x{addr:x} to pointer"); + } + let elem_size = T::byte_size(); + assert_eq!(delta % elem_size, 0, "ub: misaligned pointer"); + any.reinterpret_cast::().offset(delta / elem_size) + } +} + +impl ByteRepr for AnyPtr { + fn byte_size() -> usize { + std::mem::size_of::() + } + + fn to_bytes(&self, buf: &mut [u8]) { + self.ptr.as_bytes().to_bytes(buf); + } + + fn from_bytes(buf: &[u8]) -> Self { + Ptr::::from_bytes(buf).to_any() + } +} #[cfg(test)] mod tests { From 302a325728f731efcc6114bf88dc11fb3ce309c2 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:11:44 +0100 Subject: [PATCH 03/15] Bail-out early if reinterpreted pointer is null --- libcc2rs/src/rc.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 696c38ee..5122463d 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -402,13 +402,17 @@ impl Ptr { return self_any.downcast_ref::>().unwrap().clone(); } + if self.is_null() { + return Ptr::null(); + } + if U::byte_size() == 0 { panic!("cannot reinterpret_cast to zero-sized type"); } let src_byte_off = self.offset.wrapping_mul(T::byte_size()); let (alloc, abs_byte_off): (Rc, usize) = match &self.kind { - PtrKind::Null => return Ptr::null(), + PtrKind::Null => unreachable!(), PtrKind::StackSingle(weak) | PtrKind::HeapSingle(weak) => ( Rc::new(SingleOriginalAlloc { weak: weak.clone() }), src_byte_off, From 414fc4e5d0b3c512a4f5ba5f2be5d81c4fd4d4ad Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:12:23 +0100 Subject: [PATCH 04/15] Allow comparison-by-address of void pointers --- libcc2rs/src/rc.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 5122463d..191d96f3 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1076,7 +1076,10 @@ where } fn equals(&self, other: &dyn ErasedPtr) -> bool { - other.as_any().downcast_ref::>() == Some(self) + if let Some(o) = other.as_any().downcast_ref::>() { + return o == self; + } + self.as_bytes() == other.as_bytes() } fn is_null(&self) -> bool { From 500600a225bfc50b429134c0809771ac393f8b8f Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:17:07 +0100 Subject: [PATCH 05/15] Update tests --- .../out/refcount/opaque_forward_decl.rs | 1 + .../out/unsafe/opaque_forward_decl.rs | 1 + .../unit/out/refcount/opaque_forward_decl.rs | 1 + .../refcount/union_pointer_int_roundtrip.rs | 92 +++++++++++++++++++ tests/unit/out/unsafe/opaque_forward_decl.rs | 1 + .../out/unsafe/union_pointer_int_roundtrip.rs | 43 +++++++++ tests/unit/union_pointer_int_roundtrip.c | 27 ++++++ tests/unit/union_pointer_pun_address.c | 1 - tests/unit/union_pointer_pun_writethrough.c | 1 - tests/unit/union_tagged_many_arms.c | 1 - tests/unit/union_tagged_simple.c | 1 - tests/unit/union_void_ptr_sized_deref.c | 1 - 12 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 tests/unit/out/refcount/union_pointer_int_roundtrip.rs create mode 100644 tests/unit/out/unsafe/union_pointer_int_roundtrip.rs create mode 100644 tests/unit/union_pointer_int_roundtrip.c diff --git a/tests/multi-file/opaque_forward_decl/out/refcount/opaque_forward_decl.rs b/tests/multi-file/opaque_forward_decl/out/refcount/opaque_forward_decl.rs index 77d2b14b..0fbcf008 100644 --- a/tests/multi-file/opaque_forward_decl/out/refcount/opaque_forward_decl.rs +++ b/tests/multi-file/opaque_forward_decl/out/refcount/opaque_forward_decl.rs @@ -44,3 +44,4 @@ pub fn touch_0(c: Ptr) { (*(*(*c.borrow()).upgrade().deref()).p.borrow()).clone(); } pub struct opaque; +impl ByteRepr for opaque {} diff --git a/tests/multi-file/opaque_forward_decl/out/unsafe/opaque_forward_decl.rs b/tests/multi-file/opaque_forward_decl/out/unsafe/opaque_forward_decl.rs index 798b5256..aa1feb94 100644 --- a/tests/multi-file/opaque_forward_decl/out/unsafe/opaque_forward_decl.rs +++ b/tests/multi-file/opaque_forward_decl/out/unsafe/opaque_forward_decl.rs @@ -31,3 +31,4 @@ pub unsafe fn touch_0(mut c: *mut container) { &((*c).p); } pub struct opaque; +impl ByteRepr for opaque {} diff --git a/tests/unit/out/refcount/opaque_forward_decl.rs b/tests/unit/out/refcount/opaque_forward_decl.rs index 0403bbae..ec354de3 100644 --- a/tests/unit/out/refcount/opaque_forward_decl.rs +++ b/tests/unit/out/refcount/opaque_forward_decl.rs @@ -38,3 +38,4 @@ fn main_0() -> i32 { return ((*(*c.borrow()).x.borrow()) - 42); } pub struct opaque; +impl ByteRepr for opaque {} diff --git a/tests/unit/out/refcount/union_pointer_int_roundtrip.rs b/tests/unit/out/refcount/union_pointer_int_roundtrip.rs new file mode 100644 index 00000000..6e2bd896 --- /dev/null +++ b/tests/unit/out/refcount/union_pointer_int_roundtrip.rs @@ -0,0 +1,92 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let arr: Value> = Rc::new(RefCell::new(Box::new([10, 20, 30, 40]))); + pub struct anon_0 { + __bytes: Value>, + } + impl anon_0 { + pub fn p(&self) -> Ptr> { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn bits(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + } + impl Clone for anon_0 { + fn clone(&self) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(self.__bytes.borrow().clone())), + } + } + } + impl Default for anon_0 { + fn default() -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from([0u8; 8]))), + } + } + } + impl ByteRepr for anon_0 { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + buf.copy_from_slice(&self.__bytes.borrow()); + } + fn from_bytes(buf: &[u8]) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from(buf))), + } + } + }; + let u: Value = >::default(); + (*u.borrow_mut()) + .p() + .write(((arr.as_pointer() as Ptr).offset(1))); + let rhs_0 = ((((*u.borrow()).bits().read()) as u64) + .wrapping_add(((2_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + (*u.borrow_mut()).bits().write(rhs_0); + let q: Value> = Rc::new(RefCell::new(((*u.borrow()).p().read()).clone())); + assert!((((((*q.borrow()).read()) == 40) as i32) != 0)); + assert!( + ((({ + let _lhs = (*q.borrow()).clone(); + _lhs == ((arr.as_pointer() as Ptr).offset(3)) + }) as i32) + != 0) + ); + let rhs_0 = ((((*u.borrow()).bits().read()) as u64) + .wrapping_sub(((3_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + (*u.borrow_mut()).bits().write(rhs_0); + assert!( + ((({ + let _lhs = ((*u.borrow()).p().read()).clone(); + _lhs == ((arr.as_pointer() as Ptr).offset(0)) + }) as i32) + != 0) + ); + assert!(((((((*u.borrow()).p().read()).read()) == 10) as i32) != 0)); + (*u.borrow_mut()) + .p() + .write((arr.as_pointer() as Ptr).offset((4) as isize)); + assert!( + ((({ + let _lhs = ((*u.borrow()).p().read()).clone(); + _lhs == (arr.as_pointer() as Ptr).offset((4) as isize) + }) as i32) + != 0) + ); + return 0; +} diff --git a/tests/unit/out/unsafe/opaque_forward_decl.rs b/tests/unit/out/unsafe/opaque_forward_decl.rs index 966d4ee6..96bd6296 100644 --- a/tests/unit/out/unsafe/opaque_forward_decl.rs +++ b/tests/unit/out/unsafe/opaque_forward_decl.rs @@ -26,3 +26,4 @@ unsafe fn main_0() -> i32 { return ((c.x) - (42)); } pub struct opaque; +impl ByteRepr for opaque {} diff --git a/tests/unit/out/unsafe/union_pointer_int_roundtrip.rs b/tests/unit/out/unsafe/union_pointer_int_roundtrip.rs new file mode 100644 index 00000000..01c03227 --- /dev/null +++ b/tests/unit/out/unsafe/union_pointer_int_roundtrip.rs @@ -0,0 +1,43 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut arr: [i32; 4] = [10, 20, 30, 40]; + #[repr(C)] + #[derive(Copy, Clone)] + pub union anon_0 { + pub p: *mut i32, + pub bits: u64, + } + impl Default for anon_0 { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } + }; + let mut u: anon_0 = ::default(); + u.p = (&mut arr[(1) as usize] as *mut i32); + u.bits = ((u.bits as u64) + .wrapping_add(((2_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + let mut q: *mut i32 = u.p; + assert!(((((*q) == (40)) as i32) != 0)); + assert!(((((q) == (&mut arr[(3) as usize] as *mut i32)) as i32) != 0)); + u.bits = ((u.bits as u64) + .wrapping_sub(((3_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + assert!(((((u.p) == (&mut arr[(0) as usize] as *mut i32)) as i32) != 0)); + assert!(((((*u.p) == (10)) as i32) != 0)); + u.p = arr.as_mut_ptr().offset((4) as isize); + assert!(((((u.p) == (arr.as_mut_ptr().offset((4) as isize))) as i32) != 0)); + return 0; +} diff --git a/tests/unit/union_pointer_int_roundtrip.c b/tests/unit/union_pointer_int_roundtrip.c new file mode 100644 index 00000000..ef9f3cff --- /dev/null +++ b/tests/unit/union_pointer_int_roundtrip.c @@ -0,0 +1,27 @@ +#include +#include + +int main(void) { + int arr[4] = {10, 20, 30, 40}; + + union { + int *p; + uintptr_t bits; + } u; + + u.p = &arr[1]; + u.bits += 2 * sizeof(int); + int *q = u.p; + + assert(*q == 40); + assert(q == &arr[3]); + + u.bits -= 3 * sizeof(int); + assert(u.p == &arr[0]); + assert(*u.p == 10); + + u.p = arr + 4; + assert(u.p == arr + 4); + + return 0; +} diff --git a/tests/unit/union_pointer_pun_address.c b/tests/unit/union_pointer_pun_address.c index 75123e0c..163841d6 100644 --- a/tests/unit/union_pointer_pun_address.c +++ b/tests/unit/union_pointer_pun_address.c @@ -1,4 +1,3 @@ -// panic: refcount #include struct node_a { diff --git a/tests/unit/union_pointer_pun_writethrough.c b/tests/unit/union_pointer_pun_writethrough.c index 44bd842f..b03d57a0 100644 --- a/tests/unit/union_pointer_pun_writethrough.c +++ b/tests/unit/union_pointer_pun_writethrough.c @@ -1,4 +1,3 @@ -// panic: refcount #include int main(void) { diff --git a/tests/unit/union_tagged_many_arms.c b/tests/unit/union_tagged_many_arms.c index 7e83b42a..e3677004 100644 --- a/tests/unit/union_tagged_many_arms.c +++ b/tests/unit/union_tagged_many_arms.c @@ -1,4 +1,3 @@ -// panic: refcount #include #include diff --git a/tests/unit/union_tagged_simple.c b/tests/unit/union_tagged_simple.c index 3bc7daa1..6efa44de 100644 --- a/tests/unit/union_tagged_simple.c +++ b/tests/unit/union_tagged_simple.c @@ -1,4 +1,3 @@ -// panic: refcount #include typedef enum { diff --git a/tests/unit/union_void_ptr_sized_deref.c b/tests/unit/union_void_ptr_sized_deref.c index 26306517..586a608e 100644 --- a/tests/unit/union_void_ptr_sized_deref.c +++ b/tests/unit/union_void_ptr_sized_deref.c @@ -1,4 +1,3 @@ -// panic: refcount #include #include #include From ba3bdc7d96bc014f5467cc2c33c1fd171ea95c88 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:17:32 +0100 Subject: [PATCH 06/15] cargo fmt --- libcc2rs/src/rc.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 191d96f3..3a9ba16c 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1235,11 +1235,10 @@ impl RangeAllocator { } fn base_for(&mut self, real_addr: RealAddr, byte_len: ByteLen) -> SyntheticAddr { - if let Some(&(base, capacity)) = self.bases.get(&real_addr) { - if byte_len <= capacity { + if let Some(&(base, capacity)) = self.bases.get(&real_addr) + && byte_len <= capacity { return base; } - } let base = self.cursor; self.cursor = base + byte_len + 1; self.bases.insert(real_addr, (base, byte_len)); @@ -1270,8 +1269,7 @@ impl ByteRepr for Ptr { self.len() * T::byte_size(), ), }; - let base = - PTR_RANGE_ALLOC.with(|a| a.borrow_mut().base_for(self.kind.address(), byte_len)); + let base = PTR_RANGE_ALLOC.with(|a| a.borrow_mut().base_for(self.kind.address(), byte_len)); let rebased = Ptr { offset: 0, kind: self.kind.clone(), From 3288f90e8c681865c95c65dae16f1cbbd3679f3d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:25:05 +0100 Subject: [PATCH 07/15] cargo fmt --- libcc2rs/src/rc.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 3a9ba16c..aa42b0df 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1236,9 +1236,10 @@ impl RangeAllocator { fn base_for(&mut self, real_addr: RealAddr, byte_len: ByteLen) -> SyntheticAddr { if let Some(&(base, capacity)) = self.bases.get(&real_addr) - && byte_len <= capacity { - return base; - } + && byte_len <= capacity + { + return base; + } let base = self.cursor; self.cursor = base + byte_len + 1; self.bases.insert(real_addr, (base, byte_len)); From 32d9d9c95ceb0d97989f74512e85cf8220c4dda5 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:33:52 +0100 Subject: [PATCH 08/15] Add arrayEndA == objectStartB test --- .../refcount/struct_member_array_adjacency.rs | 52 +++++++++++++++++++ .../unsafe/struct_member_array_adjacency.rs | 32 ++++++++++++ tests/unit/struct_member_array_adjacency.c | 14 +++++ 3 files changed, 98 insertions(+) create mode 100644 tests/unit/out/refcount/struct_member_array_adjacency.rs create mode 100644 tests/unit/out/unsafe/struct_member_array_adjacency.rs create mode 100644 tests/unit/struct_member_array_adjacency.c diff --git a/tests/unit/out/refcount/struct_member_array_adjacency.rs b/tests/unit/out/refcount/struct_member_array_adjacency.rs new file mode 100644 index 00000000..cbb8d467 --- /dev/null +++ b/tests/unit/out/refcount/struct_member_array_adjacency.rs @@ -0,0 +1,52 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive()] +pub struct pair { + pub a: Value>, + pub b: Value>, +} +impl Default for pair { + fn default() -> Self { + pair { + a: Rc::new(RefCell::new( + (0..4).map(|_| ::default()).collect::>(), + )), + b: Rc::new(RefCell::new( + (0..4).map(|_| ::default()).collect::>(), + )), + } + } +} +impl ByteRepr for pair { + fn byte_size() -> usize { + 32 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.a.borrow()).to_bytes(&mut buf[0..16]); + (*self.b.borrow()).to_bytes(&mut buf[16..32]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + a: Rc::new(RefCell::new(>::from_bytes(&buf[0..16]))), + b: Rc::new(RefCell::new(>::from_bytes(&buf[16..32]))), + } + } +} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let s: Value = >::default(); + assert!( + (((((*s.borrow()).a.as_pointer() as Ptr::).offset((4) as isize) + == ((*s.borrow()).b.as_pointer() as Ptr::)) as i32) + != 0) + ); + return 0; +} diff --git a/tests/unit/out/unsafe/struct_member_array_adjacency.rs b/tests/unit/out/unsafe/struct_member_array_adjacency.rs new file mode 100644 index 00000000..c5a8a747 --- /dev/null +++ b/tests/unit/out/unsafe/struct_member_array_adjacency.rs @@ -0,0 +1,32 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pair { + pub a: [i32; 4], + pub b: [i32; 4], +} +impl Default for pair { + fn default() -> Self { + pair { + a: [0_i32; 4], + b: [0_i32; 4], + } + } +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut s: pair = ::default(); + assert!(((((s.a.as_mut_ptr().offset((4) as isize)) == (s.b.as_mut_ptr())) as i32) != 0)); + return 0; +} diff --git a/tests/unit/struct_member_array_adjacency.c b/tests/unit/struct_member_array_adjacency.c new file mode 100644 index 00000000..4206f0e8 --- /dev/null +++ b/tests/unit/struct_member_array_adjacency.c @@ -0,0 +1,14 @@ +// panic: refcount +#include + +struct pair { + int a[4]; + int b[4]; +}; + +int main(void) { + struct pair s; + + assert(s.a + 4 == s.b); + return 0; +} From bd4141415c0f82f9dd534abd7bd5ce7db4534949 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:34:15 +0100 Subject: [PATCH 09/15] Add null pointer serialization/deserialization test --- .../refcount/union_pointer_null_roundtrip.rs | 62 +++++++++++++++++++ .../unsafe/union_pointer_null_roundtrip.rs | 37 +++++++++++ tests/unit/union_pointer_null_roundtrip.c | 25 ++++++++ 3 files changed, 124 insertions(+) create mode 100644 tests/unit/out/refcount/union_pointer_null_roundtrip.rs create mode 100644 tests/unit/out/unsafe/union_pointer_null_roundtrip.rs create mode 100644 tests/unit/union_pointer_null_roundtrip.c diff --git a/tests/unit/out/refcount/union_pointer_null_roundtrip.rs b/tests/unit/out/refcount/union_pointer_null_roundtrip.rs new file mode 100644 index 00000000..20f8bdf0 --- /dev/null +++ b/tests/unit/out/refcount/union_pointer_null_roundtrip.rs @@ -0,0 +1,62 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + pub struct anon_0 { + __bytes: Value>, + } + impl anon_0 { + pub fn p(&self) -> Ptr> { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn bits(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + } + impl Clone for anon_0 { + fn clone(&self) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(self.__bytes.borrow().clone())), + } + } + } + impl Default for anon_0 { + fn default() -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from([0u8; 8]))), + } + } + } + impl ByteRepr for anon_0 { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + buf.copy_from_slice(&self.__bytes.borrow()); + } + fn from_bytes(buf: &[u8]) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from(buf))), + } + } + }; + let u: Value = >::default(); + (*u.borrow_mut()).bits().write(0_u64); + assert!((((((*u.borrow()).p().read()).is_null()) as i32) != 0)); + let x: Value = Rc::new(RefCell::new(5)); + (*u.borrow_mut()).p().write((x.as_pointer())); + assert!((((((*u.borrow()).bits().read()) != 0_u64) as i32) != 0)); + (*u.borrow_mut()).bits().write(0_u64); + assert!((((((*u.borrow()).p().read()).is_null()) as i32) != 0)); + (*u.borrow_mut()).p().write(Ptr::::null()); + assert!((((((*u.borrow()).bits().read()) == 0_u64) as i32) != 0)); + return 0; +} diff --git a/tests/unit/out/unsafe/union_pointer_null_roundtrip.rs b/tests/unit/out/unsafe/union_pointer_null_roundtrip.rs new file mode 100644 index 00000000..4f9c1ea1 --- /dev/null +++ b/tests/unit/out/unsafe/union_pointer_null_roundtrip.rs @@ -0,0 +1,37 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + #[repr(C)] + #[derive(Copy, Clone)] + pub union anon_0 { + pub p: *mut i32, + pub bits: u64, + } + impl Default for anon_0 { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } + }; + let mut u: anon_0 = ::default(); + u.bits = 0_u64; + assert!(((((u.p).is_null()) as i32) != 0)); + let mut x: i32 = 5; + u.p = (&mut x as *mut i32); + assert!(((((u.bits) != (0_u64)) as i32) != 0)); + u.bits = 0_u64; + assert!(((((u.p).is_null()) as i32) != 0)); + u.p = std::ptr::null_mut(); + assert!(((((u.bits) == (0_u64)) as i32) != 0)); + return 0; +} diff --git a/tests/unit/union_pointer_null_roundtrip.c b/tests/unit/union_pointer_null_roundtrip.c new file mode 100644 index 00000000..2114ecc7 --- /dev/null +++ b/tests/unit/union_pointer_null_roundtrip.c @@ -0,0 +1,25 @@ +#include +#include +#include + +int main(void) { + union { + int *p; + uintptr_t bits; + } u; + + u.bits = 0; + assert(u.p == NULL); + + int x = 5; + u.p = &x; + assert(u.bits != 0); + + u.bits = 0; + assert(u.p == NULL); + + u.p = NULL; + assert(u.bits == 0); + + return 0; +} From 43f686d136c5c8bd620164e2019b6e09d0019afb Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 16:51:46 +0100 Subject: [PATCH 10/15] Add ub tests for int to pointer conversion --- tests/ub/int_to_pointer_out_of_range.c | 19 ++++++ tests/ub/int_to_pointer_random.c | 16 +++++ .../refcount/int_to_pointer_out_of_range.rs | 64 +++++++++++++++++++ .../ub/out/refcount/int_to_pointer_random.rs | 59 +++++++++++++++++ .../out/unsafe/int_to_pointer_out_of_range.rs | 34 ++++++++++ tests/ub/out/unsafe/int_to_pointer_random.rs | 30 +++++++++ 6 files changed, 222 insertions(+) create mode 100644 tests/ub/int_to_pointer_out_of_range.c create mode 100644 tests/ub/int_to_pointer_random.c create mode 100644 tests/ub/out/refcount/int_to_pointer_out_of_range.rs create mode 100644 tests/ub/out/refcount/int_to_pointer_random.rs create mode 100644 tests/ub/out/unsafe/int_to_pointer_out_of_range.rs create mode 100644 tests/ub/out/unsafe/int_to_pointer_random.rs diff --git a/tests/ub/int_to_pointer_out_of_range.c b/tests/ub/int_to_pointer_out_of_range.c new file mode 100644 index 00000000..361f1a66 --- /dev/null +++ b/tests/ub/int_to_pointer_out_of_range.c @@ -0,0 +1,19 @@ +// panic-ub: refcount +// nondet-result: unsafe + +#include + +int main(void) { + int arr[4] = {1, 2, 3, 4}; + + union { + int *p; + uintptr_t bits; + } u; + + u.p = arr; + u.bits += 100 * sizeof(int); + int *p = u.p; + + return *p == 0 ? 0 : 1; +} diff --git a/tests/ub/int_to_pointer_random.c b/tests/ub/int_to_pointer_random.c new file mode 100644 index 00000000..9c345ef9 --- /dev/null +++ b/tests/ub/int_to_pointer_random.c @@ -0,0 +1,16 @@ +// panic-ub: refcount +// nondet-result: unsafe + +#include + +int main(void) { + union { + int *p; + uintptr_t bits; + } u; + + u.bits = 0xdeadbeef; + int *p = u.p; + + return *p == 0 ? 0 : 1; +} diff --git a/tests/ub/out/refcount/int_to_pointer_out_of_range.rs b/tests/ub/out/refcount/int_to_pointer_out_of_range.rs new file mode 100644 index 00000000..515c32e0 --- /dev/null +++ b/tests/ub/out/refcount/int_to_pointer_out_of_range.rs @@ -0,0 +1,64 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let arr: Value> = Rc::new(RefCell::new(Box::new([1, 2, 3, 4]))); + pub struct anon_0 { + __bytes: Value>, + } + impl anon_0 { + pub fn p(&self) -> Ptr> { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn bits(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + } + impl Clone for anon_0 { + fn clone(&self) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(self.__bytes.borrow().clone())), + } + } + } + impl Default for anon_0 { + fn default() -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from([0u8; 8]))), + } + } + } + impl ByteRepr for anon_0 { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + buf.copy_from_slice(&self.__bytes.borrow()); + } + fn from_bytes(buf: &[u8]) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from(buf))), + } + } + }; + let u: Value = >::default(); + (*u.borrow_mut()).p().write((arr.as_pointer() as Ptr)); + let rhs_0 = ((((*u.borrow()).bits().read()) as u64) + .wrapping_add(((100_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + (*u.borrow_mut()).bits().write(rhs_0); + let p: Value> = Rc::new(RefCell::new(((*u.borrow()).p().read()).clone())); + return if (((((*p.borrow()).read()) == 0) as i32) != 0) { + 0 + } else { + 1 + }; +} diff --git a/tests/ub/out/refcount/int_to_pointer_random.rs b/tests/ub/out/refcount/int_to_pointer_random.rs new file mode 100644 index 00000000..9ae7e358 --- /dev/null +++ b/tests/ub/out/refcount/int_to_pointer_random.rs @@ -0,0 +1,59 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + pub struct anon_0 { + __bytes: Value>, + } + impl anon_0 { + pub fn p(&self) -> Ptr> { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn bits(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + } + impl Clone for anon_0 { + fn clone(&self) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(self.__bytes.borrow().clone())), + } + } + } + impl Default for anon_0 { + fn default() -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from([0u8; 8]))), + } + } + } + impl ByteRepr for anon_0 { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + buf.copy_from_slice(&self.__bytes.borrow()); + } + fn from_bytes(buf: &[u8]) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from(buf))), + } + } + }; + let u: Value = >::default(); + (*u.borrow_mut()).bits().write(3735928559_u64); + let p: Value> = Rc::new(RefCell::new(((*u.borrow()).p().read()).clone())); + return if (((((*p.borrow()).read()) == 0) as i32) != 0) { + 0 + } else { + 1 + }; +} diff --git a/tests/ub/out/unsafe/int_to_pointer_out_of_range.rs b/tests/ub/out/unsafe/int_to_pointer_out_of_range.rs new file mode 100644 index 00000000..2ca6e907 --- /dev/null +++ b/tests/ub/out/unsafe/int_to_pointer_out_of_range.rs @@ -0,0 +1,34 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut arr: [i32; 4] = [1, 2, 3, 4]; + #[repr(C)] + #[derive(Copy, Clone)] + pub union anon_0 { + pub p: *mut i32, + pub bits: u64, + } + impl Default for anon_0 { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } + }; + let mut u: anon_0 = ::default(); + u.p = arr.as_mut_ptr(); + u.bits = ((u.bits as u64) + .wrapping_add(((100_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + let mut p: *mut i32 = u.p; + return if ((((*p) == (0)) as i32) != 0) { 0 } else { 1 }; +} diff --git a/tests/ub/out/unsafe/int_to_pointer_random.rs b/tests/ub/out/unsafe/int_to_pointer_random.rs new file mode 100644 index 00000000..56f52df5 --- /dev/null +++ b/tests/ub/out/unsafe/int_to_pointer_random.rs @@ -0,0 +1,30 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + #[repr(C)] + #[derive(Copy, Clone)] + pub union anon_0 { + pub p: *mut i32, + pub bits: u64, + } + impl Default for anon_0 { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } + }; + let mut u: anon_0 = ::default(); + u.bits = 3735928559_u64; + let mut p: *mut i32 = u.p; + return if ((((*p) == (0)) as i32) != 0) { 0 } else { 1 }; +} From 41fa40b96d4f169314c0427c8d7733074483d54e Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 18:18:27 +0100 Subject: [PATCH 11/15] Add is_dangling --- libcc2rs/src/fn_ptr.rs | 3 +++ libcc2rs/src/rc.rs | 11 +++++++++++ libcc2rs/src/reinterpret.rs | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/libcc2rs/src/fn_ptr.rs b/libcc2rs/src/fn_ptr.rs index e7152279..311e3e38 100644 --- a/libcc2rs/src/fn_ptr.rs +++ b/libcc2rs/src/fn_ptr.rs @@ -158,6 +158,9 @@ impl ErasedPtr for FnPtr { fn is_null(&self) -> bool { FnPtr::is_null(self) } + fn is_dangling(&self) -> bool { + false + } } impl FnPtr { diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index aa42b0df..c176a210 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1054,6 +1054,7 @@ pub(crate) trait ErasedPtr: std::any::Any { fn as_any(&self) -> &dyn std::any::Any; fn equals(&self, other: &dyn ErasedPtr) -> bool; fn is_null(&self) -> bool; + fn is_dangling(&self) -> bool; } impl PartialEq for dyn ErasedPtr { @@ -1085,6 +1086,16 @@ where fn is_null(&self) -> bool { Ptr::is_null(self) } + + fn is_dangling(&self) -> bool { + match &self.kind { + PtrKind::Null => false, + PtrKind::StackSingle(w) | PtrKind::HeapSingle(w) => w.strong_count() == 0, + PtrKind::Vec(w) => w.strong_count() == 0, + PtrKind::StackArray(w) | PtrKind::HeapArray(w) => w.strong_count() == 0, + PtrKind::Reinterpreted(data) => data.alloc.is_dangling(), + } + } } #[derive(Clone)] diff --git a/libcc2rs/src/reinterpret.rs b/libcc2rs/src/reinterpret.rs index b496f8b2..d9f814e6 100644 --- a/libcc2rs/src/reinterpret.rs +++ b/libcc2rs/src/reinterpret.rs @@ -100,6 +100,7 @@ pub trait OriginalAlloc { fn total_byte_len(&self) -> usize; // Stable address used for pointer equality across PtrKind variants. fn address(&self) -> usize; + fn is_dangling(&self) -> bool; } // Read bytes starting at `byte_offset` from a slice of S elements into `buf`. @@ -165,6 +166,10 @@ impl OriginalAlloc for SingleOriginalAlloc { fn address(&self) -> usize { self.weak.as_ptr() as usize } + + fn is_dangling(&self) -> bool { + self.weak.strong_count() == 0 + } } pub(crate) trait AsSlice { @@ -219,4 +224,8 @@ impl OriginalAlloc for SliceOriginalAlloc { fn address(&self) -> usize { self.weak.as_ptr() as usize } + + fn is_dangling(&self) -> bool { + self.weak.strong_count() == 0 + } } From c11115114b6def9197cf7f2f83ca2a96102af6f7 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 2 Jul 2026 18:27:12 +0100 Subject: [PATCH 12/15] Evict dead entries on PtrRegistry::put --- libcc2rs/src/rc.rs | 61 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index c176a210..a01c75fe 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1245,7 +1245,7 @@ impl RangeAllocator { } } - fn base_for(&mut self, real_addr: RealAddr, byte_len: ByteLen) -> SyntheticAddr { + fn get_synthetic_addr(&mut self, real_addr: RealAddr, byte_len: ByteLen) -> SyntheticAddr { if let Some(&(base, capacity)) = self.bases.get(&real_addr) && byte_len <= capacity { @@ -1258,10 +1258,50 @@ impl RangeAllocator { } } +struct PtrRegistry { + ranges: RangeAllocator, + entries: BTreeMap, + swept_len: usize, +} + +impl PtrRegistry { + fn new() -> Self { + Self { + ranges: RangeAllocator::new(), + entries: BTreeMap::new(), + swept_len: 0, + } + } + + fn put(&mut self, real_addr: RealAddr, byte_len: ByteLen, ptr: AnyPtr) -> SyntheticAddr { + self.evict_dead(); + let base = self.ranges.get_synthetic_addr(real_addr, byte_len); + self.entries.insert(base, (ptr, byte_len)); + base + } + + fn get(&self, addr: SyntheticAddr) -> Option<(SyntheticAddr, AnyPtr, ByteLen)> { + self.entries + .range(..=addr) + .next_back() + .map(|(base, (any, len))| (*base, any.clone(), *len)) + } + + fn evict_dead(&mut self) { + if self.entries.len() < 16.max(2 * self.swept_len) { + return; + } + self.entries.retain(|_, (any, _)| !any.ptr.is_dangling()); + let entries = &self.entries; + self.ranges + .bases + .retain(|_, &mut (base, _)| entries.contains_key(&base)); + self.swept_len = self.entries.len(); + } +} + thread_local! { - static PTR_RANGE_ALLOC: RefCell = RefCell::new(RangeAllocator::new()); - static PTR_REGISTRY: RefCell> = - const { RefCell::new(BTreeMap::new()) }; + static PTR_REGISTRY: RefCell = RefCell::new(PtrRegistry::new()); } impl ByteRepr for Ptr { @@ -1281,13 +1321,13 @@ impl ByteRepr for Ptr { self.len() * T::byte_size(), ), }; - let base = PTR_RANGE_ALLOC.with(|a| a.borrow_mut().base_for(self.kind.address(), byte_len)); let rebased = Ptr { offset: 0, kind: self.kind.clone(), }; - PTR_REGISTRY.with(|r| { - r.borrow_mut().insert(base, (rebased.to_any(), byte_len)); + let base = PTR_REGISTRY.with(|r| { + r.borrow_mut() + .put(self.kind.address(), byte_len, rebased.to_any()) }); base.wrapping_add(byte_off).to_bytes(buf); } @@ -1297,12 +1337,7 @@ impl ByteRepr for Ptr { if addr == 0 { return Ptr::null(); } - let entry = PTR_REGISTRY.with(|r| { - r.borrow() - .range(..=addr) - .next_back() - .map(|(base, (any, len))| (*base, any.clone(), *len)) - }); + let entry = PTR_REGISTRY.with(|r| r.borrow().get(addr)); let Some((base, any, byte_len)) = entry else { panic!("ub: cast of invalid address 0x{addr:x} to pointer"); }; From 49bbc5b5c3fffd28efa67246e570d26d48660c10 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Fri, 3 Jul 2026 10:26:55 +0100 Subject: [PATCH 13/15] Add pointer to struct serialization test In C and Rust refcount struct sizes are different. Make sure that the pointer serialization uses the C size. --- .../union_struct_pointer_roundtrip.rs | 112 ++++++++++++++++++ .../unsafe/union_struct_pointer_roundtrip.rs | 50 ++++++++ tests/unit/union_struct_pointer_roundtrip.c | 34 ++++++ 3 files changed, 196 insertions(+) create mode 100644 tests/unit/out/refcount/union_struct_pointer_roundtrip.rs create mode 100644 tests/unit/out/unsafe/union_struct_pointer_roundtrip.rs create mode 100644 tests/unit/union_struct_pointer_roundtrip.c diff --git a/tests/unit/out/refcount/union_struct_pointer_roundtrip.rs b/tests/unit/out/refcount/union_struct_pointer_roundtrip.rs new file mode 100644 index 00000000..6374de14 --- /dev/null +++ b/tests/unit/out/refcount/union_struct_pointer_roundtrip.rs @@ -0,0 +1,112 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Default)] +pub struct pair { + pub x: Value, + pub y: Value, +} +impl ByteRepr for pair { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.x.borrow()).to_bytes(&mut buf[0..4]); + (*self.y.borrow()).to_bytes(&mut buf[4..8]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + x: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + y: Rc::new(RefCell::new(::from_bytes(&buf[4..8]))), + } + } +} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let arr: Value> = Rc::new(RefCell::new( + (0..3).map(|_| ::default()).collect::>(), + )); + (*(*arr.borrow())[(0) as usize].x.borrow_mut()) = 10; + (*(*arr.borrow())[(1) as usize].x.borrow_mut()) = 20; + (*(*arr.borrow())[(2) as usize].x.borrow_mut()) = 30; + pub struct anon_0 { + __bytes: Value>, + } + impl anon_0 { + pub fn p(&self) -> Ptr> { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + pub fn bits(&self) -> Ptr { + (self.__bytes.as_pointer() as Ptr).reinterpret_cast() + } + } + impl Clone for anon_0 { + fn clone(&self) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(self.__bytes.borrow().clone())), + } + } + } + impl Default for anon_0 { + fn default() -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from([0u8; 8]))), + } + } + } + impl ByteRepr for anon_0 { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + buf.copy_from_slice(&self.__bytes.borrow()); + } + fn from_bytes(buf: &[u8]) -> Self { + anon_0 { + __bytes: Rc::new(RefCell::new(Box::from(buf))), + } + } + }; + let u: Value = >::default(); + (*u.borrow_mut()) + .p() + .write(((arr.as_pointer() as Ptr).offset(1))); + let q: Value> = Rc::new(RefCell::new(((*u.borrow()).p().read()).clone())); + assert!(((((*(*(*q.borrow()).upgrade().deref()).x.borrow()) == 20) as i32) != 0)); + assert!( + ((({ + let _lhs = (*q.borrow()).clone(); + _lhs == ((arr.as_pointer() as Ptr).offset(1)) + }) as i32) + != 0) + ); + let rhs_0 = ((((*u.borrow()).bits().read()) as u64).wrapping_add((8usize as u64))) as u64; + (*u.borrow_mut()).bits().write(rhs_0); + assert!(((((*(*((*u.borrow()).p().read()).upgrade().deref()).x.borrow()) == 30) as i32) != 0)); + assert!( + ((({ + let _lhs = ((*u.borrow()).p().read()).clone(); + _lhs == ((arr.as_pointer() as Ptr).offset(2)) + }) as i32) + != 0) + ); + let rhs_0 = ((((*u.borrow()).bits().read()) as u64) + .wrapping_sub(((2_usize).wrapping_mul((8usize as usize)) as u64))) as u64; + (*u.borrow_mut()).bits().write(rhs_0); + assert!(((((*(*((*u.borrow()).p().read()).upgrade().deref()).x.borrow()) == 10) as i32) != 0)); + assert!( + ((({ + let _lhs = ((*u.borrow()).p().read()).clone(); + _lhs == ((arr.as_pointer() as Ptr).offset(0)) + }) as i32) + != 0) + ); + return 0; +} diff --git a/tests/unit/out/unsafe/union_struct_pointer_roundtrip.rs b/tests/unit/out/unsafe/union_struct_pointer_roundtrip.rs new file mode 100644 index 00000000..c384ca9e --- /dev/null +++ b/tests/unit/out/unsafe/union_struct_pointer_roundtrip.rs @@ -0,0 +1,50 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct pair { + pub x: i32, + pub y: i32, +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut arr: [pair; 3] = [::default(); 3]; + arr[(0) as usize].x = 10; + arr[(1) as usize].x = 20; + arr[(2) as usize].x = 30; + #[repr(C)] + #[derive(Copy, Clone)] + pub union anon_0 { + pub p: *mut pair, + pub bits: u64, + } + impl Default for anon_0 { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } + }; + let mut u: anon_0 = ::default(); + u.p = (&mut arr[(1) as usize] as *mut pair); + let mut q: *mut pair = u.p; + assert!((((((*q).x) == (20)) as i32) != 0)); + assert!(((((q) == (&mut arr[(1) as usize] as *mut pair)) as i32) != 0)); + u.bits = ((u.bits as u64).wrapping_add((::std::mem::size_of::() as u64))) as u64; + assert!((((((*u.p).x) == (30)) as i32) != 0)); + assert!(((((u.p) == (&mut arr[(2) as usize] as *mut pair)) as i32) != 0)); + u.bits = ((u.bits as u64) + .wrapping_sub(((2_usize).wrapping_mul((::std::mem::size_of::() as usize)) as u64))) + as u64; + assert!((((((*u.p).x) == (10)) as i32) != 0)); + assert!(((((u.p) == (&mut arr[(0) as usize] as *mut pair)) as i32) != 0)); + return 0; +} diff --git a/tests/unit/union_struct_pointer_roundtrip.c b/tests/unit/union_struct_pointer_roundtrip.c new file mode 100644 index 00000000..d43f4ba8 --- /dev/null +++ b/tests/unit/union_struct_pointer_roundtrip.c @@ -0,0 +1,34 @@ +#include +#include + +struct pair { + int x; + int y; +}; + +int main(void) { + struct pair arr[3]; + arr[0].x = 10; + arr[1].x = 20; + arr[2].x = 30; + + union { + struct pair *p; + uintptr_t bits; + } u; + + u.p = &arr[1]; + struct pair *q = u.p; + assert(q->x == 20); + assert(q == &arr[1]); + + u.bits += sizeof(struct pair); + assert(u.p->x == 30); + assert(u.p == &arr[2]); + + u.bits -= 2 * sizeof(struct pair); + assert(u.p->x == 10); + assert(u.p == &arr[0]); + + return 0; +} From 299831f6497976fd687490c254bf089ee3a2460f Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Fri, 3 Jul 2026 22:19:58 +0100 Subject: [PATCH 14/15] Add c_byte_offset and c_byte_len --- libcc2rs/src/rc.rs | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index a01c75fe..7a1a4e69 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -438,6 +438,24 @@ impl Ptr { } } +impl Ptr { + #[inline] + fn c_byte_len(&self) -> usize { + match &self.kind { + PtrKind::Reinterpreted(data) => data.alloc.total_byte_len(), + _ => self.len().wrapping_mul(T::byte_size()), + } + } + + #[inline] + fn c_byte_offset(&self) -> usize { + match &self.kind { + PtrKind::Reinterpreted(_) => self.offset, + _ => self.offset.wrapping_mul(T::byte_size()), + } + } +} + impl Ptr { pub fn with_mut(&self, f: impl FnOnce(&mut T) -> R) -> R where @@ -1314,22 +1332,18 @@ impl ByteRepr for Ptr { 0usize.to_bytes(buf); return; } - let (byte_off, byte_len) = match &self.kind { - PtrKind::Reinterpreted(data) => (self.offset, data.alloc.total_byte_len()), - _ => ( - self.offset.wrapping_mul(T::byte_size()), - self.len() * T::byte_size(), - ), - }; - let rebased = Ptr { - offset: 0, - kind: self.kind.clone(), - }; let base = PTR_REGISTRY.with(|r| { - r.borrow_mut() - .put(self.kind.address(), byte_len, rebased.to_any()) + r.borrow_mut().put( + self.kind.address(), + self.c_byte_len(), + Ptr { + offset: 0, + kind: self.kind.clone(), + } + .to_any(), + ) }); - base.wrapping_add(byte_off).to_bytes(buf); + base.wrapping_add(self.c_byte_offset()).to_bytes(buf); } fn from_bytes(buf: &[u8]) -> Self { From 2b147de1e09c9cdd14f2787fb9d2c8d32d99145d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Fri, 3 Jul 2026 22:20:49 +0100 Subject: [PATCH 15/15] Rename swept_len to evicted_len --- libcc2rs/src/rc.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libcc2rs/src/rc.rs b/libcc2rs/src/rc.rs index 7a1a4e69..cd5f97f9 100644 --- a/libcc2rs/src/rc.rs +++ b/libcc2rs/src/rc.rs @@ -1279,7 +1279,7 @@ impl RangeAllocator { struct PtrRegistry { ranges: RangeAllocator, entries: BTreeMap, - swept_len: usize, + evicted_len: usize, } impl PtrRegistry { @@ -1287,7 +1287,7 @@ impl PtrRegistry { Self { ranges: RangeAllocator::new(), entries: BTreeMap::new(), - swept_len: 0, + evicted_len: 0, } } @@ -1306,7 +1306,7 @@ impl PtrRegistry { } fn evict_dead(&mut self) { - if self.entries.len() < 16.max(2 * self.swept_len) { + if self.entries.len() < 16.max(2 * self.evicted_len) { return; } self.entries.retain(|_, (any, _)| !any.ptr.is_dangling()); @@ -1314,7 +1314,7 @@ impl PtrRegistry { self.ranges .bases .retain(|_, &mut (base, _)| entries.contains_key(&base)); - self.swept_len = self.entries.len(); + self.evicted_len = self.entries.len(); } }