Skip to content

Commit 6e048c8

Browse files
committed
argconv: pointers
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.
1 parent e5efd2d commit 6e048c8

21 files changed

+1299
-931
lines changed

scylla-rust-wrapper/src/argconv.rs

Lines changed: 283 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::types::size_t;
22
use std::cmp::min;
33
use std::ffi::CStr;
4+
use std::marker::PhantomData;
45
use std::os::raw::c_char;
6+
use std::ptr::NonNull;
57
use std::sync::Arc;
68

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

74-
/// Defines a pointer manipulation API for non-shared heap-allocated data.
76+
mod sealed {
77+
pub trait Sealed {}
78+
}
79+
80+
pub trait Ownership: sealed::Sealed {}
81+
82+
pub struct Exclusive;
83+
impl sealed::Sealed for Exclusive {}
84+
impl Ownership for Exclusive {}
85+
86+
pub struct Shared;
87+
impl sealed::Sealed for Shared {}
88+
impl Ownership for Shared {}
89+
90+
pub struct Borrowed;
91+
impl sealed::Sealed for Borrowed {}
92+
impl Ownership for Borrowed {}
93+
94+
pub trait Mutability: sealed::Sealed {}
95+
96+
pub struct Const;
97+
impl sealed::Sealed for Const {}
98+
impl Mutability for Const {}
99+
100+
pub struct Mut;
101+
impl sealed::Sealed for Mut {}
102+
impl Mutability for Mut {}
103+
104+
pub trait Properties {
105+
type Ownership: Ownership;
106+
type Mutability: Mutability;
107+
}
108+
109+
impl<O: Ownership, M: Mutability> Properties for (O, M) {
110+
type Ownership = O;
111+
type Mutability = M;
112+
}
113+
114+
/// An exclusive pointer type, generic over mutability.
115+
pub type CassExclusivePtr<T, M> = CassPtr<T, (Exclusive, M)>;
116+
117+
/// An exclusive const pointer. Should be used for Box-allocated objects
118+
/// that need to be read-only.
119+
pub type CassExclusiveConstPtr<T> = CassExclusivePtr<T, Const>;
120+
121+
/// An exclusive mutable pointer. Should be used for Box-allocated objects
122+
/// that need to be mutable.
123+
pub type CassExclusiveMutPtr<T> = CassExclusivePtr<T, Mut>;
124+
125+
/// A shared const pointer. Should be used for Arc-allocated objects.
75126
///
76-
/// Implement this trait for types that are allocated by the driver via [`Box::new`],
77-
/// and then returned to the user as a pointer. The user is responsible for freeing
78-
/// the memory associated with the pointer using corresponding driver's API function.
79-
pub trait BoxFFI {
80-
fn into_ptr(self: Box<Self>) -> *mut Self {
81-
#[allow(clippy::disallowed_methods)]
82-
Box::into_raw(self)
127+
/// Notice that this type does not provide interior mutability itself.
128+
/// It is the responsiblity of the user of this API, to provide soundness
129+
/// in this matter (aliasing ^ mutability).
130+
/// Take for example [`CassDataType`](crate::cass_types::CassDataType). It
131+
/// holds underlying data in [`UnsafeCell`](std::cell::UnsafeCell) to provide
132+
/// interior mutability.
133+
pub type CassSharedPtr<T> = CassPtr<T, (Shared, Const)>;
134+
135+
/// A borrowed const pointer. Should be used for objects that reference
136+
/// some field of already allocated object.
137+
///
138+
/// When operating on the pointer of this type, we do not make any assumptions
139+
/// about the origin of the pointer, apart from the fact that it is obtained
140+
/// from some valid reference.
141+
///
142+
/// In particular, it may be a reference obtained from `Box/Arc::as_ref()`.
143+
/// Notice, however, that we do not allow to convert such pointer back to `Box/Arc`,
144+
/// since we do not have a guarantee that corresponding memory was allocated certain way.
145+
/// OTOH, we can safely convert the pointer to a valid reference (assuming user did not
146+
/// provide a pointer pointing to some garbage memory).
147+
pub type CassBorrowedPtr<T> = CassPtr<T, (Borrowed, Const)>;
148+
149+
// repr(transparent), so the struct has the same layout as underlying Option<NonNull<T>>.
150+
// Thanks to https://doc.rust-lang.org/std/option/#representation optimization,
151+
// we are guaranteed, that for T: Sized, our struct has the same layout
152+
// and function call ABI as simply NonNull<T>.
153+
#[repr(transparent)]
154+
pub struct CassPtr<T: Sized, P: Properties> {
155+
ptr: Option<NonNull<T>>,
156+
_phantom: PhantomData<P>,
157+
}
158+
159+
impl<T: Sized, P: Properties> Copy for CassPtr<T, P> {}
160+
161+
impl<T: Sized, P: Properties> Clone for CassPtr<T, P> {
162+
fn clone(&self) -> Self {
163+
*self
83164
}
84-
unsafe fn from_ptr(ptr: *mut Self) -> Box<Self> {
85-
#[allow(clippy::disallowed_methods)]
86-
Box::from_raw(ptr)
165+
}
166+
167+
/// Methods for any pointer kind.
168+
///
169+
/// Notice that there are no constructors except from null(), which is trivial.
170+
/// Thanks to that, we can make some assumptions and guarantees based on
171+
/// the origin of the pointer. For these, see `SAFETY` comments.
172+
impl<T: Sized, P: Properties> CassPtr<T, P> {
173+
fn null() -> Self {
174+
CassPtr {
175+
ptr: None,
176+
_phantom: PhantomData,
177+
}
87178
}
88-
unsafe fn as_maybe_ref<'a>(ptr: *const Self) -> Option<&'a Self> {
179+
180+
fn is_null(&self) -> bool {
181+
self.ptr.is_none()
182+
}
183+
184+
/// Converts a pointer to an optional valid reference.
185+
fn into_ref<'a>(self) -> Option<&'a T> {
186+
// SAFETY: We know that our pointers either come from a valid allocation (Box or Arc),
187+
// or were created based on the reference to the field of some allocated object.
188+
// Thus, if the pointer is non-null, we are guaranteed that it's valid.
189+
unsafe { self.ptr.map(|p| p.as_ref()) }
190+
}
191+
}
192+
193+
/// Methods for any exclusive pointers (no matter the mutability).
194+
impl<T, M: Mutability> CassPtr<T, (Exclusive, M)> {
195+
/// Creates a pointer based on a VALID Box allocation.
196+
/// This is the only way to obtain such pointer.
197+
fn from_box(b: Box<T>) -> Self {
89198
#[allow(clippy::disallowed_methods)]
90-
ptr.as_ref()
199+
let ptr = Box::into_raw(b);
200+
CassPtr {
201+
ptr: NonNull::new(ptr),
202+
_phantom: PhantomData,
203+
}
204+
}
205+
206+
/// Converts the pointer back to the owned Box (if not null).
207+
fn into_box(self) -> Option<Box<T>> {
208+
// SAFETY: We are guaranteed that if ptr is Some, then it was obtained
209+
// from a valid Box allocation (see new() implementation).
210+
unsafe {
211+
self.ptr.map(|p| {
212+
#[allow(clippy::disallowed_methods)]
213+
Box::from_raw(p.as_ptr())
214+
})
215+
}
216+
}
217+
}
218+
219+
/// Methods for exclusive mutable pointers.
220+
impl<T> CassPtr<T, (Exclusive, Mut)> {
221+
fn null_mut() -> Self {
222+
CassPtr {
223+
ptr: None,
224+
_phantom: PhantomData,
225+
}
91226
}
92-
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {
227+
228+
/// Converts a pointer to an optional valid, and mutable reference.
229+
fn into_mut_ref<'a>(self) -> Option<&'a mut T> {
230+
// SAFETY: We are guaranteed that if ptr is Some, then it was obtained
231+
// from a valid Box allocation (see new() implementation).
232+
unsafe { self.ptr.map(|mut p| p.as_mut()) }
233+
}
234+
}
235+
236+
/// Define this conversion for test purposes.
237+
/// User receives a mutable exclusive pointer from constructor,
238+
/// but following function calls need a const exclusive pointer.
239+
#[cfg(test)]
240+
impl<T> CassPtr<T, (Exclusive, Mut)> {
241+
pub fn into_const(self) -> CassPtr<T, (Exclusive, Const)> {
242+
CassPtr {
243+
ptr: self.ptr,
244+
_phantom: PhantomData,
245+
}
246+
}
247+
}
248+
249+
/// Methods for Shared pointers.
250+
impl<T: Sized> CassSharedPtr<T> {
251+
/// Creates a pointer based on a VALID Arc allocation.
252+
fn from_arc(a: Arc<T>) -> Self {
93253
#[allow(clippy::disallowed_methods)]
94-
ptr.as_ref().unwrap()
254+
let ptr = Arc::into_raw(a);
255+
CassPtr {
256+
ptr: NonNull::new(ptr as *mut T),
257+
_phantom: PhantomData,
258+
}
95259
}
96-
unsafe fn as_mut_ref<'a>(ptr: *mut Self) -> &'a mut Self {
260+
261+
/// Creates a pointer which borrows from a VALID Arc allocation.
262+
fn from_ref(a: &Arc<T>) -> Self {
97263
#[allow(clippy::disallowed_methods)]
98-
ptr.as_mut().unwrap()
264+
let ptr = Arc::as_ptr(a);
265+
CassPtr {
266+
ptr: NonNull::new(ptr as *mut T),
267+
_phantom: PhantomData,
268+
}
269+
}
270+
271+
/// Converts the pointer back to the Arc.
272+
fn into_arc(self) -> Option<Arc<T>> {
273+
// SAFETY: The pointer can only be obtained via new() or from_ref(),
274+
// both of which accept an Arc.
275+
// It means that the pointer comes from a valid allocation.
276+
unsafe {
277+
self.ptr.map(|p| {
278+
#[allow(clippy::disallowed_methods)]
279+
Arc::from_raw(p.as_ptr())
280+
})
281+
}
282+
}
283+
284+
/// Converts the pointer to an Arc, by increasing its reference count.
285+
fn clone_arced(self) -> Option<Arc<T>> {
286+
// SAFETY: The pointer can only be obtained via new() or from_ref(),
287+
// both of which accept an Arc.
288+
// It means that the pointer comes from a valid allocation.
289+
unsafe {
290+
self.ptr.map(|p| {
291+
let ptr = p.as_ptr();
292+
#[allow(clippy::disallowed_methods)]
293+
Arc::increment_strong_count(ptr);
294+
#[allow(clippy::disallowed_methods)]
295+
Arc::from_raw(ptr)
296+
})
297+
}
298+
}
299+
}
300+
301+
/// Methods for borrowed pointers.
302+
impl<T: Sized> CassPtr<T, (Borrowed, Const)> {
303+
fn from_ref(r: &T) -> Self {
304+
Self {
305+
ptr: Some(NonNull::from(r)),
306+
_phantom: PhantomData,
307+
}
308+
}
309+
}
310+
311+
/// Defines a pointer manipulation API for non-shared heap-allocated data.
312+
///
313+
/// Implement this trait for types that are allocated by the driver via [`Box::new`],
314+
/// and then returned to the user as a pointer. The user is responsible for freeing
315+
/// the memory associated with the pointer using corresponding driver's API function.
316+
pub trait BoxFFI: Sized {
317+
fn into_ptr<M: Mutability>(self: Box<Self>) -> CassExclusivePtr<Self, M> {
318+
CassExclusivePtr::from_box(self)
319+
}
320+
fn from_ptr<M: Mutability>(ptr: CassExclusivePtr<Self, M>) -> Option<Box<Self>> {
321+
ptr.into_box()
322+
}
323+
fn as_ref<'a, M: Mutability>(ptr: CassExclusivePtr<Self, M>) -> Option<&'a Self> {
324+
ptr.into_ref()
325+
}
326+
fn as_mut_ref<'a>(ptr: CassExclusiveMutPtr<Self>) -> Option<&'a mut Self> {
327+
ptr.into_mut_ref()
99328
}
100-
unsafe fn free(ptr: *mut Self) {
329+
fn free(ptr: CassExclusiveMutPtr<Self>) {
101330
std::mem::drop(BoxFFI::from_ptr(ptr));
102331
}
332+
#[cfg(test)]
333+
fn null() -> CassExclusiveConstPtr<Self> {
334+
CassExclusiveConstPtr::null()
335+
}
336+
fn null_mut() -> CassExclusiveMutPtr<Self> {
337+
CassExclusiveMutPtr::null_mut()
338+
}
103339
}
104340

105341
/// Defines a pointer manipulation API for shared heap-allocated data.
@@ -108,36 +344,31 @@ pub trait BoxFFI {
108344
/// The data should be allocated via [`Arc::new`], and then returned to the user as a pointer.
109345
/// The user is responsible for freeing the memory associated
110346
/// with the pointer using corresponding driver's API function.
111-
pub trait ArcFFI {
112-
fn as_ptr(self: &Arc<Self>) -> *const Self {
113-
#[allow(clippy::disallowed_methods)]
114-
Arc::as_ptr(self)
347+
pub trait ArcFFI: Sized {
348+
fn as_ptr(self: &Arc<Self>) -> CassSharedPtr<Self> {
349+
CassSharedPtr::from_ref(self)
115350
}
116-
fn into_ptr(self: Arc<Self>) -> *const Self {
117-
#[allow(clippy::disallowed_methods)]
118-
Arc::into_raw(self)
351+
fn into_ptr(self: Arc<Self>) -> CassSharedPtr<Self> {
352+
CassSharedPtr::from_arc(self)
119353
}
120-
unsafe fn from_ptr(ptr: *const Self) -> Arc<Self> {
121-
#[allow(clippy::disallowed_methods)]
122-
Arc::from_raw(ptr)
354+
fn from_ptr(ptr: CassSharedPtr<Self>) -> Option<Arc<Self>> {
355+
ptr.into_arc()
123356
}
124-
unsafe fn cloned_from_ptr(ptr: *const Self) -> Arc<Self> {
125-
#[allow(clippy::disallowed_methods)]
126-
Arc::increment_strong_count(ptr);
127-
#[allow(clippy::disallowed_methods)]
128-
Arc::from_raw(ptr)
357+
fn cloned_from_ptr(ptr: CassSharedPtr<Self>) -> Option<Arc<Self>> {
358+
ptr.clone_arced()
129359
}
130-
unsafe fn as_maybe_ref<'a>(ptr: *const Self) -> Option<&'a Self> {
131-
#[allow(clippy::disallowed_methods)]
132-
ptr.as_ref()
133-
}
134-
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {
135-
#[allow(clippy::disallowed_methods)]
136-
ptr.as_ref().unwrap()
360+
fn as_ref<'a>(ptr: CassSharedPtr<Self>) -> Option<&'a Self> {
361+
ptr.into_ref()
137362
}
138-
unsafe fn free(ptr: *const Self) {
363+
fn free(ptr: CassSharedPtr<Self>) {
139364
std::mem::drop(ArcFFI::from_ptr(ptr));
140365
}
366+
fn null() -> CassSharedPtr<Self> {
367+
CassSharedPtr::null()
368+
}
369+
fn is_null(ptr: CassSharedPtr<Self>) -> bool {
370+
ptr.is_null()
371+
}
141372
}
142373

143374
/// Defines a pointer manipulation API for data owned by some other object.
@@ -148,12 +379,17 @@ pub trait ArcFFI {
148379
/// For example: lifetime of CassRow is bound by the lifetime of CassResult.
149380
/// There is no API function that frees the CassRow. It should be automatically
150381
/// freed when user calls cass_result_free.
151-
pub trait RefFFI {
152-
fn as_ptr(&self) -> *const Self {
153-
self as *const Self
382+
pub trait RefFFI: Sized {
383+
fn as_ptr(&self) -> CassBorrowedPtr<Self> {
384+
CassBorrowedPtr::from_ref(self)
154385
}
155-
unsafe fn as_ref<'a>(ptr: *const Self) -> &'a Self {
156-
#[allow(clippy::disallowed_methods)]
157-
ptr.as_ref().unwrap()
386+
fn as_ref<'a>(ptr: CassBorrowedPtr<Self>) -> Option<&'a Self> {
387+
ptr.into_ref()
388+
}
389+
fn null() -> CassBorrowedPtr<Self> {
390+
CassBorrowedPtr::null()
391+
}
392+
fn is_null(ptr: CassBorrowedPtr<Self>) -> bool {
393+
ptr.is_null()
158394
}
159395
}

0 commit comments

Comments
 (0)