blob: 2d96e89deeeee986a1fc9def6a836794158758c5 [file]
// 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) };
}
}
}