| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! `paimon_tantivy_buffer_t`: Rust-allocated byte buffer returned to C++. |
| //! |
| //! Contract: |
| //! - Buffer is allocated by Rust (as a `Box<[u8]>`) |
| //! - C++ reads `data[0..len]`, **must not** write past len |
| //! - C++ must call `paimon_tantivy_buffer_free()` exactly once per non-empty buffer |
| //! - Empty (len=0) buffer has null `data`; buffer_free accepts it as no-op |
| //! |
| //! This struct is #[repr(C)] so cbindgen generates a matching C struct. |
| |
| use std::ptr; |
| |
| #[repr(C)] |
| pub struct PaimonTantivyBuffer { |
| /// Pointer to `len` bytes. Null iff len == 0. |
| pub data: *mut u8, |
| /// Number of valid bytes. |
| pub len: usize, |
| /// Internal capacity hint for Rust-side reconstruction. C++ treats as opaque. |
| pub capacity: usize, |
| } |
| |
| impl PaimonTantivyBuffer { |
| /// Build a buffer from owned bytes; consumes the Vec. |
| pub(crate) fn from_vec(mut v: Vec<u8>) -> Self { |
| if v.is_empty() { |
| return Self::empty(); |
| } |
| v.shrink_to_fit(); |
| let len = v.len(); |
| let capacity = v.capacity(); |
| let data = v.as_mut_ptr(); |
| std::mem::forget(v); |
| Self { data, len, capacity } |
| } |
| |
| pub(crate) fn empty() -> Self { |
| Self { |
| data: ptr::null_mut(), |
| len: 0, |
| capacity: 0, |
| } |
| } |
| } |
| |
| /// Free a buffer returned by any Rust FFI function. Safe to call on an empty |
| /// buffer (len=0 / data=null). Must only be called once per buffer. |
| /// |
| /// SAFETY: `buf` must be either null, or point to a live `paimon_tantivy_buffer_t` |
| /// produced by this crate and not yet freed. |
| #[no_mangle] |
| pub unsafe extern "C" fn paimon_tantivy_buffer_free(buf: *mut PaimonTantivyBuffer) { |
| if buf.is_null() { |
| return; |
| } |
| let b = unsafe { &mut *buf }; |
| if b.len != 0 && !b.data.is_null() { |
| // Reconstruct the Vec<u8> and drop it |
| let v = unsafe { Vec::from_raw_parts(b.data, b.len, b.capacity) }; |
| drop(v); |
| } |
| b.data = ptr::null_mut(); |
| b.len = 0; |
| b.capacity = 0; |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn empty_has_null_data() { |
| let b = PaimonTantivyBuffer::empty(); |
| assert!(b.data.is_null()); |
| assert_eq!(b.len, 0); |
| } |
| |
| #[test] |
| fn from_vec_roundtrip() { |
| let src = vec![1u8, 2, 3, 4, 5]; |
| let src_clone = src.clone(); |
| let mut b = PaimonTantivyBuffer::from_vec(src); |
| assert_eq!(b.len, 5); |
| assert!(!b.data.is_null()); |
| let view: &[u8] = unsafe { std::slice::from_raw_parts(b.data, b.len) }; |
| assert_eq!(view, src_clone.as_slice()); |
| unsafe { paimon_tantivy_buffer_free(&mut b) }; |
| assert!(b.data.is_null()); |
| assert_eq!(b.len, 0); |
| } |
| |
| #[test] |
| fn free_null_is_noop() { |
| unsafe { paimon_tantivy_buffer_free(std::ptr::null_mut()) }; |
| } |
| |
| #[test] |
| fn free_empty_is_noop() { |
| let mut b = PaimonTantivyBuffer::empty(); |
| unsafe { paimon_tantivy_buffer_free(&mut b) }; |
| } |
| |
| #[test] |
| fn stress_alloc_free() { |
| // LSAN would catch any leak |
| for i in 0..5_000usize { |
| let mut b = PaimonTantivyBuffer::from_vec(vec![42u8; i.min(256)]); |
| unsafe { paimon_tantivy_buffer_free(&mut b) }; |
| } |
| } |
| } |