Skip to content

Commit 6f563e9

Browse files
committed
Introduce CsMatBase::proper_indptr for ffi
This API is safer than the previously introduced IndPtrBase::to_proper_ffi, which could very easily lead to use after free in unsafe code, as pointed out by @mulimoen. This new API still can be misused, but that's a common pitfall for methods returning Cow. The documentation thus warns against this pitfall, and illustrates the proper usage.
1 parent a22c158 commit 6f563e9

File tree

4 files changed

+75
-35
lines changed

4 files changed

+75
-35
lines changed

sprs-benches/src/main.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,10 @@ fn eigen_prod(a: sprs::CsMatView<f64>, b: sprs::CsMatView<f64>) -> usize {
5252
assert!(b.is_csr());
5353
assert!(b.rows() <= isize::MAX as usize);
5454
assert!(b.indptr().nnz() <= isize::MAX as usize);
55-
let a_indptr = a.indptr();
56-
let (_a_indptr_proper, a_indptr_ptr) = a_indptr.to_proper_ffi();
55+
let a_indptr_proper = a.proper_indptr();
5756
let a_indices = a.indices().as_ptr() as *const isize;
5857
let a_data = a.data().as_ptr();
59-
let b_indptr = b.indptr();
60-
let (_b_indptr_proper, b_indptr_ptr) = b_indptr.to_proper_ffi();
58+
let b_indptr_proper = b.proper_indptr();
6159
let b_indices = b.indices().as_ptr() as *const isize;
6260
let b_data = b.data().as_ptr();
6361
// Safety: sprs guarantees the validity of these pointers, and our wrapping
@@ -73,10 +71,10 @@ fn eigen_prod(a: sprs::CsMatView<f64>, b: sprs::CsMatView<f64>) -> usize {
7371
a_rows,
7472
a_cols,
7573
b_cols,
76-
a_indptr_ptr as *const isize,
74+
a_indptr_proper.as_ptr() as *const isize,
7775
a_indices,
7876
a_data,
79-
b_indptr_ptr as *const isize,
77+
b_indptr_proper.as_ptr() as *const isize,
8078
b_indices,
8179
b_data,
8280
)

src/sparse/csmat.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,33 @@ where
810810
crate::IndPtrView::new_trusted(self.indptr.raw_storage())
811811
}
812812

813+
/// Get an indptr representation suitable for ffi, cloning if necessary to
814+
/// get a compatible representation.
815+
///
816+
/// # Warning
817+
///
818+
/// For ffi usage, one needs to call `Cow::as_ptr`, but it's important
819+
/// to keep the `Cow` alive during the lifetime of the pointer. Example
820+
/// of a correct and incorrect ffi usage:
821+
///
822+
/// ```rust
823+
/// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
824+
/// let mid = mat.view().middle_outer_views(1, 2);
825+
/// let ptr = {
826+
/// let indptr_proper = mid.proper_indptr();
827+
/// println!(
828+
/// "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
829+
/// indptr_proper.as_ptr()
830+
/// );
831+
/// indptr_proper.as_ptr()
832+
/// };
833+
/// // This line is UB.
834+
/// // println!("ptr deref: {}", *ptr);
835+
/// ```
836+
pub fn proper_indptr(&self) -> std::borrow::Cow<[Iptr]> {
837+
self.indptr.to_proper()
838+
}
839+
813840
/// The inner dimension location for each non-zero value. See
814841
/// the documentation of indptr() for more explanations.
815842
pub fn indices(&self) -> &[I] {

src/sparse/indptr.rs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,46 @@ where
149149

150150
/// Returns a proper indptr representation, cloning if we do not have
151151
/// a proper indptr.
152+
///
153+
/// # Warning
154+
///
155+
/// For ffi usage, one needs to call `Cow::as_ptr`, but it's important
156+
/// to keep the `Cow` alive during the lifetime of the pointer. Example
157+
/// of a correct and incorrect ffi usage:
158+
///
159+
/// ```rust
160+
/// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
161+
/// let mid = mat.view().middle_outer_views(1, 2);
162+
/// let ptr = {
163+
/// let indptr = mid.indptr(); // needed for lifetime
164+
/// let indptr_proper = indptr.to_proper();
165+
/// println!(
166+
/// "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
167+
/// indptr_proper.as_ptr()
168+
/// );
169+
/// indptr_proper.as_ptr()
170+
/// };
171+
/// // This line is UB.
172+
/// // println!("ptr deref: {}", *ptr);
173+
/// ```
174+
///
175+
/// It is much easier to directly use the `proper_indptr` method of
176+
/// `CsMatBase` directly:
177+
///
178+
/// ```rust
179+
/// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
180+
/// let mid = mat.view().middle_outer_views(1, 2);
181+
/// let ptr = {
182+
/// let indptr_proper = mid.proper_indptr();
183+
/// println!(
184+
/// "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
185+
/// indptr_proper.as_ptr()
186+
/// );
187+
/// indptr_proper.as_ptr()
188+
/// };
189+
/// // This line is UB.
190+
/// // println!("ptr deref: {}", *ptr);
191+
/// ```
152192
pub fn to_proper(&self) -> std::borrow::Cow<[Iptr]> {
153193
if self.is_proper() {
154194
std::borrow::Cow::Borrowed(&self.storage[..])
@@ -159,31 +199,6 @@ where
159199
}
160200
}
161201

162-
/// Returns a proper indptr representation, cloning if we do not have
163-
/// a proper indptr, and a pointer to this proper representation for use
164-
/// in ffi. The returned pointer has thus the same lifetime as the
165-
/// proper representation.
166-
///
167-
/// # Example
168-
///
169-
/// ```rust
170-
/// let mat: sprs::CsMat<f64> = sprs::CsMat::eye(5);
171-
/// let mid = mat.view().middle_outer_views(1, 2);
172-
/// let (_indptr_proper_owned, ptr) = mid.indptr().to_proper_ffi();
173-
/// println!(
174-
/// "ptr {:?} is valid as long as _indptr_proper_owned is in scope",
175-
/// ptr
176-
/// );
177-
/// unsafe {
178-
/// assert_eq!(*ptr, 0)
179-
/// };
180-
/// ```
181-
pub fn to_proper_ffi(&self) -> (std::borrow::Cow<[Iptr]>, *const Iptr) {
182-
let proper = self.to_proper();
183-
let ptr = proper.as_ptr();
184-
(proper, ptr)
185-
}
186-
187202
fn offset(&self) -> Iptr {
188203
let zero = Iptr::zero();
189204
self.storage.get(0).cloned().unwrap_or(zero)

suitesparse_bindings/sprs_suitesparse_camd/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ where
3838
mat.to_other_types();
3939
let mut perm: Vec<SuiteSparseInt> = vec![0; n];
4040
let camd_res = unsafe {
41-
let (_indptr_proper, indptr_ptr) = mat.indptr().to_proper_ffi();
41+
let indptr_proper = mat.proper_indptr();
4242
camd_order(
4343
n as SuiteSparseInt,
44-
indptr_ptr,
44+
indptr_proper.as_ptr(),
4545
mat.indices().as_ptr(),
4646
perm.as_mut_ptr(),
4747
control.as_mut_ptr(),
@@ -57,10 +57,10 @@ where
5757
mat.to_other_types();
5858
let mut perm: Vec<SuiteSparseLong> = vec![0; n];
5959
let camd_res = unsafe {
60-
let (_indptr_proper, indptr_ptr) = mat.indptr().to_proper_ffi();
60+
let indptr_proper = mat.proper_indptr();
6161
camd_l_order(
6262
n as SuiteSparseLong,
63-
indptr_ptr,
63+
indptr_proper.as_ptr(),
6464
mat.indices().as_ptr(),
6565
perm.as_mut_ptr(),
6666
control.as_mut_ptr(),

0 commit comments

Comments
 (0)