Skip to content

Commit

Permalink
argconv: pointers
Browse files Browse the repository at this point in the history
This commit introduces a `CassPtr` type, generic over
pointer `Properties`. It allows specific pointer-to-reference conversions
based on the guarantees provided by the pointer type.

Existing `Ref/Box/Arc`FFIs are adjusted, so they now allow
interaction with new pointer type. You can say, that for a user of
`argconv` API, new type is opaque. The only way to operate on it is to
use the corresponding ffi API.
  • Loading branch information
muzarski committed Dec 2, 2024
1 parent e5efd2d commit 6e048c8
Show file tree
Hide file tree
Showing 21 changed files with 1,299 additions and 931 deletions.
330 changes: 283 additions & 47 deletions scylla-rust-wrapper/src/argconv.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::types::size_t;
use std::cmp::min;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::os::raw::c_char;
use std::ptr::NonNull;
use std::sync::Arc;

pub unsafe fn ptr_to_cstr(ptr: *const c_char) -> Option<&'static str> {
Expand Down Expand Up @@ -71,35 +73,269 @@ macro_rules! make_c_str {
#[cfg(test)]
pub(crate) use make_c_str;

/// Defines a pointer manipulation API for non-shared heap-allocated data.
mod sealed {
pub trait Sealed {}
}

pub trait Ownership: sealed::Sealed {}

pub struct Exclusive;
impl sealed::Sealed for Exclusive {}
impl Ownership for Exclusive {}

pub struct Shared;
impl sealed::Sealed for Shared {}
impl Ownership for Shared {}

pub struct Borrowed;
impl sealed::Sealed for Borrowed {}
impl Ownership for Borrowed {}

pub trait Mutability: sealed::Sealed {}

pub struct Const;
impl sealed::Sealed for Const {}
impl Mutability for Const {}

pub struct Mut;
impl sealed::Sealed for Mut {}
impl Mutability for Mut {}

pub trait Properties {
type Ownership: Ownership;
type Mutability: Mutability;
}

impl<O: Ownership, M: Mutability> Properties for (O, M) {
type Ownership = O;
type Mutability = M;
}

/// An exclusive pointer type, generic over mutability.
pub type CassExclusivePtr<T, M> = CassPtr<T, (Exclusive, M)>;

/// An exclusive const pointer. Should be used for Box-allocated objects
/// that need to be read-only.
pub type CassExclusiveConstPtr<T> = CassExclusivePtr<T, Const>;

/// An exclusive mutable pointer. Should be used for Box-allocated objects
/// that need to be mutable.
pub type CassExclusiveMutPtr<T> = CassExclusivePtr<T, Mut>;

/// A shared const pointer. Should be used for Arc-allocated objects.
///
/// Implement this trait for types that are allocated by the driver via [`Box::new`],
/// and then returned to the user as a pointer. The user is responsible for freeing
/// the memory associated with the pointer using corresponding driver's API function.
pub trait BoxFFI {
fn into_ptr(self: Box<Self>) -> *mut Self {
#[allow(clippy::disallowed_methods)]
Box::into_raw(self)
/// Notice that this type does not provide interior mutability itself.
/// It is the responsiblity of the user of this API, to provide soundness
/// in this matter (aliasing ^ mutability).
/// Take for example [`CassDataType`](crate::cass_types::CassDataType). It
/// holds underlying data in [`UnsafeCell`](std::cell::UnsafeCell) to provide
/// interior mutability.
pub type CassSharedPtr<T> = CassPtr<T, (Shared, Const)>;

/// A borrowed const pointer. Should be used for objects that reference
/// some field of already allocated object.
///
/// When operating on the pointer of this type, we do not make any assumptions
/// about the origin of the pointer, apart from the fact that it is obtained
/// from some valid reference.
///
/// In particular, it may be a reference obtained from `Box/Arc::as_ref()`.
/// Notice, however, that we do not allow to convert such pointer back to `Box/Arc`,
/// since we do not have a guarantee that corresponding memory was allocated certain way.
/// OTOH, we can safely convert the pointer to a valid reference (assuming user did not
/// provide a pointer pointing to some garbage memory).
pub type CassBorrowedPtr<T> = CassPtr<T, (Borrowed, Const)>;

// repr(transparent), so the struct has the same layout as underlying Option<NonNull<T>>.
// Thanks to https://doc.rust-lang.org/std/option/#representation optimization,
// we are guaranteed, that for T: Sized, our struct has the same layout
// and function call ABI as simply NonNull<T>.
#[repr(transparent)]
pub struct CassPtr<T: Sized, P: Properties> {
ptr: Option<NonNull<T>>,
_phantom: PhantomData<P>,
}

impl<T: Sized, P: Properties> Copy for CassPtr<T, P> {}

impl<T: Sized, P: Properties> Clone for CassPtr<T, P> {
fn clone(&self) -> Self {
*self
}
unsafe fn from_ptr(ptr: *mut Self) -> Box<Self> {
#[allow(clippy::disallowed_methods)]
Box::from_raw(ptr)
}

/// Methods for any pointer kind.
///
/// Notice that there are no constructors except from null(), which is trivial.
/// Thanks to that, we can make some assumptions and guarantees based on
/// the origin of the pointer. For these, see `SAFETY` comments.
impl<T: Sized, P: Properties> CassPtr<T, P> {
fn null() -> Self {
CassPtr {
ptr: None,
_phantom: PhantomData,
}
}
unsafe fn as_maybe_ref<'a>(ptr: *const Self) -> Option<&'a Self> {

fn is_null(&self) -> bool {
self.ptr.is_none()
}

/// Converts a pointer to an optional valid reference.
fn into_ref<'a>(self) -> Option<&'a T> {
// SAFETY: We know that our pointers either come from a valid allocation (Box or Arc),
// or were created based on the reference to the field of some allocated object.
// Thus, if the pointer is non-null, we are guaranteed that it's valid.
unsafe { self.ptr.map(|p| p.as_ref()) }
}
}

/// Methods for any exclusive pointers (no matter the mutability).
impl<T, M: Mutability> CassPtr<T, (Exclusive, M)> {
/// Creates a pointer based on a VALID Box allocation.
/// This is the only way to obtain such pointer.
fn from_box(b: Box<T>) -> Self {
#[allow(clippy::disallowed_methods)]
ptr.as_ref()
let ptr = Box::into_raw(b);
CassPtr {
ptr: NonNull::new(ptr),
_phantom: PhantomData,
}
}

/// Converts the pointer back to the owned Box (if not null).
fn into_box(self) -> Option<Box<T>> {
// SAFETY: We are guaranteed that if ptr is Some, then it was obtained
// from a valid Box allocation (see new() implementation).
unsafe {
self.ptr.map(|p| {
#[allow(clippy::disallowed_methods)]
Box::from_raw(p.as_ptr())
})
}
}
}

/// Methods for exclusive mutable pointers.
impl<T> CassPtr<T, (Exclusive, Mut)> {
fn null_mut() -> Self {
CassPtr {
ptr: None,
_phantom: PhantomData,
}
}
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {

/// Converts a pointer to an optional valid, and mutable reference.
fn into_mut_ref<'a>(self) -> Option<&'a mut T> {
// SAFETY: We are guaranteed that if ptr is Some, then it was obtained
// from a valid Box allocation (see new() implementation).
unsafe { self.ptr.map(|mut p| p.as_mut()) }
}
}

/// Define this conversion for test purposes.
/// User receives a mutable exclusive pointer from constructor,
/// but following function calls need a const exclusive pointer.
#[cfg(test)]
impl<T> CassPtr<T, (Exclusive, Mut)> {
pub fn into_const(self) -> CassPtr<T, (Exclusive, Const)> {
CassPtr {
ptr: self.ptr,
_phantom: PhantomData,
}
}
}

/// Methods for Shared pointers.
impl<T: Sized> CassSharedPtr<T> {
/// Creates a pointer based on a VALID Arc allocation.
fn from_arc(a: Arc<T>) -> Self {
#[allow(clippy::disallowed_methods)]
ptr.as_ref().unwrap()
let ptr = Arc::into_raw(a);
CassPtr {
ptr: NonNull::new(ptr as *mut T),
_phantom: PhantomData,
}
}
unsafe fn as_mut_ref<'a>(ptr: *mut Self) -> &'a mut Self {

/// Creates a pointer which borrows from a VALID Arc allocation.
fn from_ref(a: &Arc<T>) -> Self {
#[allow(clippy::disallowed_methods)]
ptr.as_mut().unwrap()
let ptr = Arc::as_ptr(a);
CassPtr {
ptr: NonNull::new(ptr as *mut T),
_phantom: PhantomData,
}
}

/// Converts the pointer back to the Arc.
fn into_arc(self) -> Option<Arc<T>> {
// SAFETY: The pointer can only be obtained via new() or from_ref(),
// both of which accept an Arc.
// It means that the pointer comes from a valid allocation.
unsafe {
self.ptr.map(|p| {
#[allow(clippy::disallowed_methods)]
Arc::from_raw(p.as_ptr())
})
}
}

/// Converts the pointer to an Arc, by increasing its reference count.
fn clone_arced(self) -> Option<Arc<T>> {
// SAFETY: The pointer can only be obtained via new() or from_ref(),
// both of which accept an Arc.
// It means that the pointer comes from a valid allocation.
unsafe {
self.ptr.map(|p| {
let ptr = p.as_ptr();
#[allow(clippy::disallowed_methods)]
Arc::increment_strong_count(ptr);
#[allow(clippy::disallowed_methods)]
Arc::from_raw(ptr)
})
}
}
}

/// Methods for borrowed pointers.
impl<T: Sized> CassPtr<T, (Borrowed, Const)> {
fn from_ref(r: &T) -> Self {
Self {
ptr: Some(NonNull::from(r)),
_phantom: PhantomData,
}
}
}

/// Defines a pointer manipulation API for non-shared heap-allocated data.
///
/// Implement this trait for types that are allocated by the driver via [`Box::new`],
/// and then returned to the user as a pointer. The user is responsible for freeing
/// the memory associated with the pointer using corresponding driver's API function.
pub trait BoxFFI: Sized {
fn into_ptr<M: Mutability>(self: Box<Self>) -> CassExclusivePtr<Self, M> {
CassExclusivePtr::from_box(self)
}
fn from_ptr<M: Mutability>(ptr: CassExclusivePtr<Self, M>) -> Option<Box<Self>> {
ptr.into_box()
}
fn as_ref<'a, M: Mutability>(ptr: CassExclusivePtr<Self, M>) -> Option<&'a Self> {
ptr.into_ref()
}
fn as_mut_ref<'a>(ptr: CassExclusiveMutPtr<Self>) -> Option<&'a mut Self> {
ptr.into_mut_ref()
}
unsafe fn free(ptr: *mut Self) {
fn free(ptr: CassExclusiveMutPtr<Self>) {
std::mem::drop(BoxFFI::from_ptr(ptr));
}
#[cfg(test)]
fn null() -> CassExclusiveConstPtr<Self> {
CassExclusiveConstPtr::null()
}
fn null_mut() -> CassExclusiveMutPtr<Self> {
CassExclusiveMutPtr::null_mut()
}
}

/// Defines a pointer manipulation API for shared heap-allocated data.
Expand All @@ -108,36 +344,31 @@ pub trait BoxFFI {
/// The data should be allocated via [`Arc::new`], and then returned to the user as a pointer.
/// The user is responsible for freeing the memory associated
/// with the pointer using corresponding driver's API function.
pub trait ArcFFI {
fn as_ptr(self: &Arc<Self>) -> *const Self {
#[allow(clippy::disallowed_methods)]
Arc::as_ptr(self)
pub trait ArcFFI: Sized {
fn as_ptr(self: &Arc<Self>) -> CassSharedPtr<Self> {
CassSharedPtr::from_ref(self)
}
fn into_ptr(self: Arc<Self>) -> *const Self {
#[allow(clippy::disallowed_methods)]
Arc::into_raw(self)
fn into_ptr(self: Arc<Self>) -> CassSharedPtr<Self> {
CassSharedPtr::from_arc(self)
}
unsafe fn from_ptr(ptr: *const Self) -> Arc<Self> {
#[allow(clippy::disallowed_methods)]
Arc::from_raw(ptr)
fn from_ptr(ptr: CassSharedPtr<Self>) -> Option<Arc<Self>> {
ptr.into_arc()
}
unsafe fn cloned_from_ptr(ptr: *const Self) -> Arc<Self> {
#[allow(clippy::disallowed_methods)]
Arc::increment_strong_count(ptr);
#[allow(clippy::disallowed_methods)]
Arc::from_raw(ptr)
fn cloned_from_ptr(ptr: CassSharedPtr<Self>) -> Option<Arc<Self>> {
ptr.clone_arced()
}
unsafe fn as_maybe_ref<'a>(ptr: *const Self) -> Option<&'a Self> {
#[allow(clippy::disallowed_methods)]
ptr.as_ref()
}
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {
#[allow(clippy::disallowed_methods)]
ptr.as_ref().unwrap()
fn as_ref<'a>(ptr: CassSharedPtr<Self>) -> Option<&'a Self> {
ptr.into_ref()
}
unsafe fn free(ptr: *const Self) {
fn free(ptr: CassSharedPtr<Self>) {
std::mem::drop(ArcFFI::from_ptr(ptr));
}
fn null() -> CassSharedPtr<Self> {
CassSharedPtr::null()
}
fn is_null(ptr: CassSharedPtr<Self>) -> bool {
ptr.is_null()
}
}

/// Defines a pointer manipulation API for data owned by some other object.
Expand All @@ -148,12 +379,17 @@ pub trait ArcFFI {
/// For example: lifetime of CassRow is bound by the lifetime of CassResult.
/// There is no API function that frees the CassRow. It should be automatically
/// freed when user calls cass_result_free.
pub trait RefFFI {
fn as_ptr(&self) -> *const Self {
self as *const Self
pub trait RefFFI: Sized {
fn as_ptr(&self) -> CassBorrowedPtr<Self> {
CassBorrowedPtr::from_ref(self)
}
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {
#[allow(clippy::disallowed_methods)]
ptr.as_ref().unwrap()
fn as_ref<'a>(ptr: CassBorrowedPtr<Self>) -> Option<&'a Self> {
ptr.into_ref()
}
fn null() -> CassBorrowedPtr<Self> {
CassBorrowedPtr::null()
}
fn is_null(ptr: CassBorrowedPtr<Self>) -> bool {
ptr.is_null()
}
}
Loading

0 comments on commit 6e048c8

Please sign in to comment.