Describe the bug
In the Rust crate, ManagedTensor::from(&ndarray) stores arr.shape().as_ptr() — a pointer into the host ndarray's owned dimension storage — and .to_device() copies the DLTensor struct including that borrowed shape pointer. Index types that retain a non-owning device view of their build dataset (observed with brute_force::Index, which keeps _dataset) therefore alias the host array's dims. If the host Array2 is dropped (or moved out of scope) while the index is alive, the index's Drop reads the dangling shape pointer — observed as SIGABRT via multiply-overflow in dlpack.rs::dl_tensor_bytes.
Steps/Code to reproduce bug
let index = {
let dataset: ndarray::Array2<f32> = /* ... */;
let dev = ManagedTensor::from(&dataset).to_device(&res)?;
brute_force::Index::build(&res, ¶ms, &dev)?
// dataset dropped here; index._dataset.shape now dangles
};
// index.drop() -> dl_tensor_bytes reads garbage dims -> SIGABRT
Nothing in the type system prevents this; it compiles cleanly.
Expected behavior
Either the borrow is impossible to outlive, or the tensor owns its shape.
Recommended fix
Make ManagedTensor own its shape/strides storage: copy the dims into an internal Box<[i64]> at construction and point DLTensor.shape at that owned buffer (likewise strides). This is API-compatible, costs a few words per tensor, and removes the hazard for every index type at once. The stricter alternative — threading a lifetime parameter (ManagedTensor<'a> with PhantomData<&'a ()>) — is sound but viral across every index API signature, so the owned-shape approach seems the better fit. We hit this while adding serialize bindings (#2231) and worked around it test-side with drop ordering; happy to submit the owned-shape fix as a PR if the approach sounds right.
Environment details
Rust crate at main (78135be), conda libcuvs 26.06, CUDA 13.1, RTX 4000.
Describe the bug
In the Rust crate,
ManagedTensor::from(&ndarray)storesarr.shape().as_ptr()— a pointer into the host ndarray's owned dimension storage — and.to_device()copies theDLTensorstruct including that borrowed shape pointer. Index types that retain a non-owning device view of their build dataset (observed withbrute_force::Index, which keeps_dataset) therefore alias the host array's dims. If the hostArray2is dropped (or moved out of scope) while the index is alive, the index'sDropreads the dangling shape pointer — observed as SIGABRT via multiply-overflow indlpack.rs::dl_tensor_bytes.Steps/Code to reproduce bug
Nothing in the type system prevents this; it compiles cleanly.
Expected behavior
Either the borrow is impossible to outlive, or the tensor owns its shape.
Recommended fix
Make
ManagedTensorown its shape/strides storage: copy the dims into an internalBox<[i64]>at construction and pointDLTensor.shapeat that owned buffer (likewise strides). This is API-compatible, costs a few words per tensor, and removes the hazard for every index type at once. The stricter alternative — threading a lifetime parameter (ManagedTensor<'a>withPhantomData<&'a ()>) — is sound but viral across every index API signature, so the owned-shape approach seems the better fit. We hit this while adding serialize bindings (#2231) and worked around it test-side with drop ordering; happy to submit the owned-shape fix as a PR if the approach sounds right.Environment details
Rust crate at main (78135be), conda libcuvs 26.06, CUDA 13.1, RTX 4000.