Skip to content

safety: introduce pointer types and their restrictions #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 31, 2025

Conversation

muzarski
Copy link
Collaborator

@muzarski muzarski commented Nov 26, 2024

This is a follow-up to: #207.

It defines a CassPtr type, wrapper over Option<NonNull<T>>. The pointer is parameterized by:

Lifetime

Thanks to that, we can distinguish between owned (should be freed) and borrowed pointers.

Properties

There are currently two properties:

  1. Ownership - it tells whether the pointer is mutable from Rust perspective. In other words, Exclusive = mutable pointer, Shared = immutable pointer.
  2. CMutability - the semantics of this one are very simple. It just tells whether the pointer is T* or const T* in C API definition. It's introduced so we can distinguish shared, logically mutable types (Mutability::Const and CMutability::CMut) from shared, logically immutable types (Mutability::Const and CMutability::CConst). This property will help to write safe unit tests where we play a role of C API user.

During review, please pay attention to that if some pointer was originally const T* (or *const T in Rust), then it is CConst after this PR. Analogously, if it was T* (or *mut T in Rust), then it should be CMut.

Pre-review checklist

  • I have split my patch into logically separate commits.
  • All commit messages clearly explain what they change and why.
  • PR description sums up the changes and reasons why they should be introduced.
  • I have implemented Rust unit tests for the features/changes introduced.
  • [ ] I have enabled appropriate tests in .github/workflows/build.yml in gtest_filter.
  • [ ] I have enabled appropriate tests in .github/workflows/cassandra.yml in gtest_filter.

@muzarski muzarski marked this pull request as draft November 26, 2024 15:47
@muzarski muzarski force-pushed the pointer-kinds branch 2 times, most recently from f433610 to 9dadad8 Compare November 26, 2024 17:35
@muzarski muzarski self-assigned this Nov 26, 2024
@muzarski muzarski marked this pull request as ready for review November 29, 2024 12:47
Copy link
Collaborator

@wprzytula wprzytula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is very impressive.
I couldn't check all the changes, there were too much of them; I just peeked through them. I trust your meticulousness :)

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 2, 2024

Rebased on master

@muzarski muzarski force-pushed the pointer-kinds branch 2 times, most recently from 98fbac9 to aa90f5a Compare December 2, 2024 19:30
@muzarski
Copy link
Collaborator Author

muzarski commented Dec 2, 2024

v1.1: Addressed @wprzytula comments

@muzarski muzarski requested a review from wprzytula December 2, 2024 19:32
@wprzytula
Copy link
Collaborator

Waiting with review for a rebase on #207.

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 4, 2024

Waiting with review for a rebase on #207.

Everything is up to date

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 4, 2024

v2: Improved pointer API based on @wprzytula suggestions. Thank you!

  • removed Copy and Clone implementations for CassPtr (currently, it's only defined for test purposes. I need to think how to address it some other way)
  • Left CassPtr::into_ref() as is. As discussed in the comment, it is required to "erase" the lifetime of the returned reference. This method consumes the pointer.
  • Introduced CassPtr::as_ref() method. It accepts a reference to the pointer. The lifetime of reference is inherited from pointer's lifetime.
  • Changed CassExclusiveMutPtr::into_mut_ref() to [...]::as_mut_ref(). It accepts &mut CassExclusiveMutPtr. The lifetime of returned mutable reference is inherited from mutable borrow of the pointer. This is really cool, because this ensures aliasing ^ mutability at borrow-checker level.
  • CassPtr::is_null() now borrows the pointer, instead of consuming it
  • CassFuture::set_callback(), and CassFutureCallback now expect a raw pointer. Safe CassPtr::as_raw() method is introduced for this use-case

I still need to think how to address the issue in tests. It's not possible to reuse the pointer, which is not Copy, while passing it to multiple API functions that consume the pointer.

@muzarski muzarski requested a review from wprzytula December 4, 2024 18:37
@wprzytula
Copy link
Collaborator

I still need to think how to address the issue in tests. It's not possible to reuse the pointer, which is not Copy, while passing it to multiple API functions that consume the pointer.

You could have an unsafe clone() method for tests only, then this would not break soundness guarantees of safe code.

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 4, 2024

I still need to think how to address the issue in tests. It's not possible to reuse the pointer, which is not Copy, while passing it to multiple API functions that consume the pointer.

You could have an unsafe clone() method for tests only, then this would not break soundness guarantees of safe code.

Applied the suggestion. At first, I did not understand how is this different from implementing Copy and Clone for test only. But this part: "this would not break soundness guarantees of safe code" convinced me.

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 9, 2024

Rebased on master

@muzarski
Copy link
Collaborator Author

muzarski commented Dec 9, 2024

v2.1: improved docstrings as suggested by @wprzytula

@muzarski muzarski force-pushed the pointer-kinds branch 2 times, most recently from 59cfb50 to ce3896f Compare March 14, 2025 12:38
Before this commit, the pointer was obtained from a valid
reference &CassFuture, which is totally fine.

However, I want to reduce the ways one can obtain such pointer.
For ArcFFI (shared pointers), I want them to be obtainable only in two ways:
- `ArcFFI::as_ptr()` which accepts an &Arc
- from the user, as a function parameter

This way, we are guaranteed that the pointer comes from a valid Arc allocation
(unless user provided pointer to some garbage, but there is no much we can do about it).
If we assume that user provides a pointer returned from some prior call to
API, we are guaranteed that it is valid, and comes from an Arc allocation (or is null).

I don't want to allow ArcFFI api to create a pointer from a refernce, to prevent
creating a pointer, from example from stack allocated object:
```
let future = CassFuture { ... };
let future_ptr = ArcFFI::as_ptr(&future);
```

Since some methods started accepting a pointer, they are marked as unsafe, because
they assume that pointer is valid. We will be able to remove unsafes once
CassPtr is introduced - the type will give enough guarantees for these methods
to be safe.

In this PR, there was a question regarding CassFuture::set_callback:

Q: Here self_ptr should refer to the same CassFuture as self, right?
Can you instead make the receiver of this function an arc? self: &Arc<CassFuture>

A:
I don't see a good reason behind it. We get a pointer in cass_future_set_callback,
then we would need to convert it to Arc (increase a refcount), just to then convert it
back to pointer (decrease refcount) and pass the ponter to callback.
@muzarski muzarski requested review from Lorak-mmk and wprzytula March 17, 2025 12:15
@muzarski muzarski marked this pull request as ready for review March 17, 2025 12:16
@muzarski
Copy link
Collaborator Author

v4:

  • Introduced CMutability property - it simply tells whether the pointer was T* or const T* - as defined in C API. It tells about logical mutability of the pointer.
  • Rebased on main
  • Updated cover letter

@muzarski muzarski mentioned this pull request Mar 18, 2025
3 tasks
@muzarski
Copy link
Collaborator Author

v4.1: addressed @wprzytula comments

@muzarski muzarski requested a review from wprzytula March 21, 2025 14:12
Copy link
Collaborator

@Lorak-mmk Lorak-mmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Really great work! I no longer see any big problems, only left some minor comments.

I'm not a fan of the workaround in cass_future_set_callback, but I don't have any good ideas on how to improve it.

Comment on lines 76 to 108
mod sealed {
pub trait Sealed {}
}

/// A trait representing (Rust) mutability of the pointer.
///
/// Pointer can either be [`Const`] or [`Mut`].
///
/// ## Const pointers
/// Const pointers can only be converted to **immutable** Rust referential types.
/// There is no way to obtain a mutable reference from such pointer.
///
/// In some cases, we need to be able to mutate the data behind a shared pointer.
/// There is an example of such use case - namely [`crate::cass_types::CassDataType`].
/// argconv API does not provide a way to mutate such pointer - one can only convert the pointer
/// to [`Arc`] or &. It is the API user's responsibility to implement sound interior mutability
/// pattern in such case. This is what we currently do - CassDataType wraps CassDataTypeInner
/// inside an `UnsafeCell` to implement interior mutability for the type.
/// Other example is [`crate::future::CassFuture`] which uses Mutex.
///
/// ## Mut pointers
/// Mut pointers can be converted to both immutable and mutable Rust referential types.
pub trait Mutability: sealed::Sealed {}

/// Represents immutable pointer.
pub struct Const;
impl sealed::Sealed for Const {}
impl Mutability for Const {}

/// Represents mutable pointer.
pub struct Mut;
impl sealed::Sealed for Mut {}
impl Mutability for Mut {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutability and CMutability are quite similar names. The names of specific structs are also similar.

WDYT about
Mutability -> Ownership
Const -> Shared
Mut -> Exclusive

I think it better reflects what this property is about, and reflects the imo better way to think about rust references.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely agree with Exclusive and Shared names. Not sure about Ownership. For me, Ownership property is currently represented via lifetimes (Owned vs Borrowed).

OTOH, Mutability name is not perfect either. I'll try to come up with some reasonable name. If I fail to do so, I'll apply your suggestion and rename the trait to Ownership.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't come up with a better name. Renamed the trait and corresponding struct as you suggested.

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.
Disallow implementing two different APIs for specific type.
@muzarski
Copy link
Collaborator Author

v4.2: Addressed @Lorak-mmk comments.

@muzarski muzarski requested a review from Lorak-mmk March 30, 2025 21:14
@wprzytula
Copy link
Collaborator

Hmm, why did cassandra tests fail?

@wprzytula
Copy link
Collaborator

The cover letter needs to be adjusted for the recent Mutability -> Ownership change.

@muzarski
Copy link
Collaborator Author

Hmm, why did cassandra tests fail?

Due to the recent change on scylla-ccm's master. I'll be investivating this, but the failure is not related to this PR.

@muzarski
Copy link
Collaborator Author

muzarski commented Mar 31, 2025

Hmm, why did cassandra tests fail?

Due to the recent change on scylla-ccm's master. I'll be investivating this, but the failure is not related to this PR.

@wprzytula @Lorak-mmk I opened an issue on scylla-ccm: scylladb/scylla-ccm#646. As a temporary fix, should I set the ccm version in CI to the version before the change that introduced the bug?

@Lorak-mmk
Copy link
Collaborator

Hmm, why did cassandra tests fail?

Due to the recent change on scylla-ccm's master. I'll be investivating this, but the failure is not related to this PR.

@wprzytula @Lorak-mmk I opened an issue on scylla-ccm: scylladb/scylla-ccm#646. As a temporary fix, should I set the ccm version in CI to the version before the change that introduced the bug?

Yes

@muzarski
Copy link
Collaborator Author

CI passed - I am merging

@muzarski muzarski merged commit 283fd5f into scylladb:master Mar 31, 2025
11 checks passed
@muzarski muzarski deleted the pointer-kinds branch March 31, 2025 12:48
@muzarski muzarski mentioned this pull request Apr 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants