Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions cpp2rust/converter/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions libcc2rs/src/fn_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ impl<T: 'static> ErasedPtr for FnPtr<T> {
fn is_null(&self) -> bool {
FnPtr::is_null(self)
}
fn is_dangling(&self) -> bool {
false
}
}

impl<T: 'static> FnPtr<T> {
Expand Down
178 changes: 173 additions & 5 deletions libcc2rs/src/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -402,13 +402,17 @@ impl<T> Ptr<T> {
return self_any.downcast_ref::<Ptr<U>>().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<dyn OriginalAlloc>, 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,
Expand All @@ -434,6 +438,24 @@ impl<T> Ptr<T> {
}
}

impl<T: ByteRepr> Ptr<T> {
#[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<T> Ptr<T> {
pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R
where
Expand Down Expand Up @@ -1050,6 +1072,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 {
Expand All @@ -1072,12 +1095,25 @@ where
}

fn equals(&self, other: &dyn ErasedPtr) -> bool {
other.as_any().downcast_ref::<Ptr<T>>() == Some(self)
if let Some(o) = other.as_any().downcast_ref::<Ptr<T>>() {
return o == self;
}
self.as_bytes() == other.as_bytes()
}

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)]
Expand Down Expand Up @@ -1208,8 +1244,140 @@ impl<T: ?Sized> AsPointerDyn<T> for Rc<RefCell<T>> {
}
}

impl<T: 'static> ByteRepr for Ptr<T> {}
impl ByteRepr for AnyPtr {}
type RealAddr = usize;
type SyntheticAddr = usize;
type ByteLen = usize;

struct RangeAllocator {
cursor: SyntheticAddr,
bases: HashMap<RealAddr, (SyntheticAddr, ByteLen)>,
}

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 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
{
return base;
}
let base = self.cursor;
self.cursor = base + byte_len + 1;
self.bases.insert(real_addr, (base, byte_len));
base
}
}

struct PtrRegistry {
ranges: RangeAllocator,
entries: BTreeMap<SyntheticAddr, (AnyPtr, ByteLen)>,
evicted_len: usize,
}

impl PtrRegistry {
fn new() -> Self {
Self {
ranges: RangeAllocator::new(),
entries: BTreeMap::new(),
evicted_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.evicted_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.evicted_len = self.entries.len();
}
}

thread_local! {
static PTR_REGISTRY: RefCell<PtrRegistry> = RefCell::new(PtrRegistry::new());
}

impl<T: ByteRepr> ByteRepr for Ptr<T> {
fn byte_size() -> usize {
std::mem::size_of::<usize>()
}

fn to_bytes(&self, buf: &mut [u8]) {
if self.is_null() {
0usize.to_bytes(buf);
return;
}
let base = PTR_REGISTRY.with(|r| {
r.borrow_mut().put(
self.kind.address(),
self.c_byte_len(),
Ptr {
offset: 0,
kind: self.kind.clone(),
}
.to_any(),
)
});
base.wrapping_add(self.c_byte_offset()).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().get(addr));
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::<T>().offset(delta / elem_size)
}
}

impl ByteRepr for AnyPtr {
fn byte_size() -> usize {
std::mem::size_of::<usize>()
}

fn to_bytes(&self, buf: &mut [u8]) {
self.ptr.as_bytes().to_bytes(buf);
}

fn from_bytes(buf: &[u8]) -> Self {
Ptr::<u8>::from_bytes(buf).to_any()
}
}

#[cfg(test)]
mod tests {
Expand Down
9 changes: 9 additions & 0 deletions libcc2rs/src/reinterpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -165,6 +166,10 @@ impl<T: ByteRepr> OriginalAlloc for SingleOriginalAlloc<T> {
fn address(&self) -> usize {
self.weak.as_ptr() as usize
}

fn is_dangling(&self) -> bool {
self.weak.strong_count() == 0
}
}

pub(crate) trait AsSlice {
Expand Down Expand Up @@ -219,4 +224,8 @@ impl<T: AsSlice + 'static> OriginalAlloc for SliceOriginalAlloc<T> {
fn address(&self) -> usize {
self.weak.as_ptr() as usize
}

fn is_dangling(&self) -> bool {
self.weak.strong_count() == 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ pub fn touch_0(c: Ptr<container>) {
(*(*(*c.borrow()).upgrade().deref()).p.borrow()).clone();
}
pub struct opaque;
impl ByteRepr for opaque {}
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ pub unsafe fn touch_0(mut c: *mut container) {
&((*c).p);
}
pub struct opaque;
impl ByteRepr for opaque {}
19 changes: 19 additions & 0 deletions tests/ub/int_to_pointer_out_of_range.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// panic-ub: refcount
// nondet-result: unsafe

#include <stdint.h>

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;
}
16 changes: 16 additions & 0 deletions tests/ub/int_to_pointer_random.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// panic-ub: refcount
// nondet-result: unsafe

#include <stdint.h>

int main(void) {
union {
int *p;
uintptr_t bits;
} u;

u.bits = 0xdeadbeef;
int *p = u.p;

return *p == 0 ? 0 : 1;
}
64 changes: 64 additions & 0 deletions tests/ub/out/refcount/int_to_pointer_out_of_range.rs
Original file line number Diff line number Diff line change
@@ -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<Box<[i32]>> = Rc::new(RefCell::new(Box::new([1, 2, 3, 4])));
pub struct anon_0 {
__bytes: Value<Box<[u8]>>,
}
impl anon_0 {
pub fn p(&self) -> Ptr<Ptr<i32>> {
(self.__bytes.as_pointer() as Ptr<u8>).reinterpret_cast()
}
pub fn bits(&self) -> Ptr<u64> {
(self.__bytes.as_pointer() as Ptr<u8>).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<anon_0> = <Value<anon_0>>::default();
(*u.borrow_mut()).p().write((arr.as_pointer() as Ptr<i32>));
let rhs_0 = ((((*u.borrow()).bits().read()) as u64)
.wrapping_add(((100_usize).wrapping_mul((::std::mem::size_of::<i32>() as usize)) as u64)))
as u64;
(*u.borrow_mut()).bits().write(rhs_0);
let p: Value<Ptr<i32>> = Rc::new(RefCell::new(((*u.borrow()).p().read()).clone()));
return if (((((*p.borrow()).read()) == 0) as i32) != 0) {
0
} else {
1
};
}
Loading
Loading